fix(scrapers): browser login fast paths and AtCoder submit rewrite

Problem: CF and AtCoder always did a full browser login on every
`login` invocation, even with valid cookies. AtCoder submit never
persisted cookies, re-logging in on every submit. CF's cookie guard
used `X-User-Handle` (no longer set by CF — now `X-User-Sha1`),
so cookies were never saved. CF `login_action` was missing
`wait_for_selector` for the form that appears after the Cloudflare
gate reloads. AtCoder submit injected source via CodeMirror which
doesn't exist on AtCoder (it uses ACE editor).

Solution: Added cookie fast paths to CF and AtCoder login — emit
`checking_login` and return early if the existing session is valid.
`checking_login` is only emitted when cookies actually exist; fresh
starts go straight to `logging_in`. Fixed CF cookie guard to
`X-User-Sha1` and added `wait_for_selector` for the login form.
Rewrote AtCoder submit to use `set_input_files` on the real source
file path, with `wait_for_function` on `#plain-textarea` to confirm
the ACE editor populated before clicking submit.
This commit is contained in:
Barrett Ruth 2026-03-07 15:31:25 -05:00
parent f5992fa9b5
commit 13438beca7
Signed by: barrett
GPG key ID: A6C96C9349D2FC81
4 changed files with 727 additions and 128 deletions

View file

@ -219,6 +219,14 @@ async def _save_kattis_cookies(client: httpx.AsyncClient) -> None:
save_platform_cookies("kattis", cookies)
async def _check_kattis_login(client: httpx.AsyncClient) -> bool:
try:
r = await client.get(BASE_URL, headers=HEADERS, timeout=HTTP_TIMEOUT)
return bool(r.headers.get("x-username"))
except Exception:
return False
async def _do_kattis_login(
client: httpx.AsyncClient, username: str, password: str
) -> bool:
@ -323,9 +331,10 @@ class KattisScraper(BaseScraper):
return self._submit_error("Missing credentials. Use :CP kattis login")
async with httpx.AsyncClient(follow_redirects=True) as client:
print(json.dumps({"status": "checking_login"}), flush=True)
await _load_kattis_cookies(client)
if not client.cookies:
if client.cookies:
print(json.dumps({"status": "checking_login"}), flush=True)
else:
print(json.dumps({"status": "logging_in"}), flush=True)
ok = await _do_kattis_login(client, username, password)
if not ok:
@ -393,6 +402,16 @@ class KattisScraper(BaseScraper):
return self._login_error("Missing username or password")
async with httpx.AsyncClient(follow_redirects=True) as client:
await _load_kattis_cookies(client)
if client.cookies:
print(json.dumps({"status": "checking_login"}), flush=True)
if await _check_kattis_login(client):
return LoginResult(
success=True,
error="",
credentials={"username": username, "password": password},
)
print(json.dumps({"status": "logging_in"}), flush=True)
ok = await _do_kattis_login(client, username, password)
if not ok: