AtCoderの利用規約について
本手法は、ユーザーがボタンを押したタイミングで通常のWebブラウザでの手動提出と全く同じ単一のPOSTリクエストを発行するのみであり、連続投稿や高負荷なスクレイピングを行わないため、上記禁止事項に該当しないはずです。
Cloudflare Turnstileについて
本手法は、外部サービスを使ってCAPTCHAをバイパス・解読(破壊)するものではありません。
ユーザー自身の正規ブラウザ上で発行される正規のTurnstileトークンをそのまま利用/認証し、拡張機能のDOM変更によって生じる一時的な描画不具合のみを補正する仕組みです。
1. はじめに
AtCoderなどの競技プログラミングにおいて、問題文の閲覧・コード記述・テスト実行・提出までのプロセスを効率化するツールはこれまでにも多数開発されてきました。
しかし、従来のCLIツールやブラウザ拡張機能の多くは、運用上以下のような課題を抱えていました。
-
ユーザーによるセッションCookieの手動取得・設定が必要
- 開発者ツール(DevTools)から
RECRUIT_TABB_IDなどのCookie文字列を手動コピーし、設定ファイルへ保存する必要があった。 - 初心者にとって導入のハードルが高く、使用するスクリプトによってCookieの期限切れのたびに再設定する手間が発生していた。
- 開発者ツール(DevTools)から
-
Cloudflare Turnstile(bot対策)導入による自動提出のブロック
- 近年AtCoderに導入された Cloudflare Turnstile により、ブラウザ外部からの単純なHTTPリクエスト(curlやPythonのrequests等)が拒否されるケースが増加した。
本記事では、セッションCookieの手動取得ではなく、Chrome拡張機能(Manifest V3) を活用して、「Cookie手動登録不要」「ユーザーの正規ブラウザ環境のまま」「Turnstileの描画破損を自動補正して安全に提出する」 仕組みと、その技術的裏側について解説します。
2. なぜ「Cookieの手動登録」が不要になるのか?
答えはシンプルで、「ユーザーがAtCoderを開いているブラウザコンテキスト内で提出処理を行うから」 です。
従来のCLIツールは「ブラウザ外部」からリクエストを送るためセッションCookieの渡し込みが必須でした。しかし、Chrome拡張機能の Content Script(https://atcoder.jp/* 上で動作)であれば、ブラウザがすでに保持しているログイン済みのCookieコンテキストがそのままHTTPリクエストに適用されます。
実際のCSRFトークン取得処理
提出に必要な CSRF トークンも、ログイン中の画面DOMまたは同一セッション内での fetch によりシームレスに取得しています。
// 実際のソースコード: src/content/submitter.js より一部抜粋
fetchFreshCsrfToken(contestId) {
return fetch(`/contests/${contestId}/submit`)
.then((res) => {
if (!res.ok) throw new Error('CSRFトークンの取得に失敗しました。');
return res.text();
})
.then((html) => {
if (html.includes('/login') || html.includes('ログイン') || html.includes('Sign In')) {
throw new Error('AtCoderにログインしていません。AtCoderのサイトでログインしてください。');
}
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const tokenInput = doc.querySelector('input[name="csrf_token"]');
if (tokenInput && tokenInput.value) {
return tokenInput.value;
}
throw new Error('CSRFトークンが見つかりません。');
});
}
🔗 ソースコード参照:
src/content/submitter.js
3. 直面した壁:DOMの再構築(Reparenting)による Turnstile の白画面化
AtCoderの問題ページ上に「分割エディタ(Split View)」を動的に埋め込む際、問題文などの既存DOM要素を拡張機能側の自作コンテナへ移動(Reparenting)させる必要がありました。
しかし、ここで重大な問題が発生しました。
⚠️ 「DOM要素を別容器へ移動させた瞬間、Cloudflare Turnstileの
iframeが壊れて白画面(ブランク)になる」
Cloudflare Turnstile はセキュリティ上、DOMツリー内での親要素の変更や再配置が行われると iframe 内のスクリプト実行状態が破壊され、トークンが生成されなくなってしまう仕様になっていたのです。
4. 解決策:world: "MAIN" 注入による Turnstile の自動復元
Chrome拡張機能の Content Script はデフォルトで Isolated World(隔離環境) で動作するため、Webページ側の window.turnstile オブジェクトに直接アクセスしてメソッドを呼び出すことができません。
そこで、Manifest V3から利用可能になった world: "MAIN" を活用し、ページのメイン実行環境(Main World)にスクリプトを注入して Turnstile を安全にリセット・再描画させる設計を採用しました。
① manifest.json での Main World 宣言
// manifest.json の設定抜粋
{
"matches": ["https://atcoder.jp/contests/*/tasks/*"],
"js": ["src/content/turnstile-kick.js"],
"world": "MAIN",
"run_at": "document_start"
}
🔗 ソースコード参照:
manifest.json
② Main World 内での Turnstile 自動復元ロジック
DOMの移動(Reparenting)が発生したタイミングで、Main World側から window.turnstile.reset() および window.turnstile.render() を実行し、壊れた iframe を除去して正常な状態へ再構築します。
// 実際のソースコード: src/content/turnstile-kick.js 全体より抜粋
(function () {
'use strict';
// Content Script(Isolated World)からのカスタムイベントを検知してリセット
document.addEventListener('atcoder-workspace-reset-turnstile', () => {
const container = document.querySelector(
'.cf-challenge[data-sitekey], .cf-turnstile[data-sitekey]'
);
if (container && window.turnstile && typeof window.turnstile.reset === 'function') {
try {
window.turnstile.reset(container);
delete container.dataset.turnstileStatus;
console.log('[AtCoder Workspace] Turnstile reset via custom event');
} catch (e) {
console.warn('[AtCoder Workspace] Turnstile reset via event failed:', e);
}
}
});
function tryRender() {
if (!window.turnstile) return false;
const mainContainer = document.getElementById('main-container');
const wrapper = document.getElementById('atcoder-workspace-wrapper');
if (mainContainer && !wrapper) return false;
const container = document.querySelector(
'.cf-challenge[data-sitekey], .cf-turnstile[data-sitekey]'
);
if (!container) return false;
// iframeが存在するがステータス未設定の場合、DOM Reparentingで白画面化しているためリセット
if (container.querySelector('iframe')) {
if (
container.dataset.turnstileStatus === 'force-rendered' ||
container.dataset.turnstileStatus === 'auto-rendered'
) {
return true;
}
try {
if (window.turnstile.reset) {
window.turnstile.reset(container);
console.log('[AtCoder Workspace] Turnstile reset due to DOM reparenting');
}
const iframe = container.querySelector('iframe');
if (iframe) iframe.remove();
} catch (e) {
console.warn('[AtCoder Workspace] Turnstile reset failed:', e);
}
}
try {
const sitekey = container.getAttribute('data-sitekey');
if (typeof window.turnstile.render === 'function') {
window.turnstile.render(container, { sitekey: sitekey });
container.dataset.turnstileStatus = 'force-rendered';
console.log('[AtCoder Workspace] Turnstile render triggered via MAIN world injection');
return true;
}
return false;
} catch (e) {
console.warn('[AtCoder Workspace] Turnstile render error:', e);
return false;
}
}
// レンダリング成功まで定期ポーリング
let attempts = 0;
const maxAttempts = 60;
const interval = setInterval(() => {
attempts++;
if (tryRender()) clearInterval(interval);
if (attempts >= maxAttempts) clearInterval(interval);
}, 500);
})();
🔗 ソースコード参照:
src/content/turnstile-kick.js
これにより、ユーザーは何の意識もしなくても、通常アクセスと同等に正しい Turnstile トークン(cf-turnstile-response)が生成され、バックグラウンドでの即時提出が完結します。
5. まとめ
今回の設計アプローチにより、以下のメリットを同時に実現することができました。
- Cookieの手動コピー・設定管理:一切不要
- 有料のCAPTCHAバイパスサービス:不要
- Turnstileによる提出失敗・エラー:完全自動補正
[宣伝]オープンソース Chrome拡張機能「AtCoder Workspace」
今回解説した技術をベースに、環境構築不要・タブ移動ゼロでAtCoderの問題画面上に「問題閲覧 + Monaco Editor + 自動テスト + ポップアップ提出」を完結させるオールインワン開発環境 AtCoder Workspace を公開しています。今度の精進・コンテストに試してみませんか?
コードもすべてオープンソース(MITライセンス)で公開していますので、AtCoderでの毎日の精進や開発の参考にぜひ覗いてみてください!