fix(submit): harden atcoder and codeforces submit flow
Problem: AtCoder file upload always used a `.cpp` temp file regardless of language. CF submit used `solve_cloudflare=True` causing a spurious "No Cloudflare challenge found" error, and `_wait_for_gate_reload` in `login_action` was dead code. Stale or expired cookies caused silent auth failures with no recovery. The `uv.spawn` ndjson path for submit had no overall timeout, so a hung browser process would live forever. Solution: Replace AtCoder's temp file with `page.set_input_files` using an in-memory buffer and correct extension via `_LANGUAGE_ID_EXTENSION`. Replace CF's temp-file/fallback dance with a direct `textarea[name="source"]` fill and set `solve_cloudflare=False` on the submit fetch. Add a fast-path that skips the homepage login check when cookies exist, with automatic stale-cookie recovery via `_retried` flag on redirect-to-login detection. Remove `_wait_for_gate_reload`. Fix `_ensure_browser` to propagate install errors instead of swallowing them. Add a 120s kill timer to the ndjson `uv.spawn` submit path.
This commit is contained in:
parent
6fcb5d1bbc
commit
6923301562
3 changed files with 105 additions and 99 deletions
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
|
@ -21,10 +19,8 @@ from .models import (
|
|||
TestCase,
|
||||
)
|
||||
from .timeouts import (
|
||||
BROWSER_ELEMENT_WAIT,
|
||||
BROWSER_NAV_TIMEOUT,
|
||||
BROWSER_SESSION_TIMEOUT,
|
||||
BROWSER_SETTLE_DELAY,
|
||||
HTTP_TIMEOUT,
|
||||
)
|
||||
|
||||
|
|
@ -307,24 +303,13 @@ class CodeforcesScraper(BaseScraper):
|
|||
)
|
||||
|
||||
|
||||
def _wait_for_gate_reload(page, wait_selector: str) -> None:
|
||||
from .atcoder import _solve_turnstile
|
||||
|
||||
if "Verification" not in page.title():
|
||||
return
|
||||
_solve_turnstile(page)
|
||||
page.wait_for_function(
|
||||
f"() => !!document.querySelector('{wait_selector}')",
|
||||
timeout=BROWSER_ELEMENT_WAIT,
|
||||
)
|
||||
|
||||
|
||||
def _submit_headless(
|
||||
contest_id: str,
|
||||
problem_id: str,
|
||||
source_code: str,
|
||||
language_id: str,
|
||||
credentials: dict[str, str],
|
||||
_retried: bool = False,
|
||||
) -> SubmitResult:
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -349,9 +334,10 @@ def _submit_headless(
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
logged_in = False
|
||||
logged_in = cookie_cache.exists() and not _retried
|
||||
login_error: str | None = None
|
||||
submit_error: str | None = None
|
||||
needs_relogin = False
|
||||
|
||||
def check_login(page):
|
||||
nonlocal logged_in
|
||||
|
|
@ -362,10 +348,6 @@ def _submit_headless(
|
|||
|
||||
def login_action(page):
|
||||
nonlocal login_error
|
||||
try:
|
||||
_wait_for_gate_reload(page, "#enterForm")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
page.fill(
|
||||
'input[name="handleOrEmail"]',
|
||||
|
|
@ -383,28 +365,21 @@ def _submit_headless(
|
|||
login_error = str(e)
|
||||
|
||||
def submit_action(page):
|
||||
nonlocal submit_error
|
||||
nonlocal submit_error, needs_relogin
|
||||
if "/enter" in page.url or "/login" in page.url:
|
||||
needs_relogin = True
|
||||
return
|
||||
try:
|
||||
_solve_turnstile(page)
|
||||
except Exception:
|
||||
pass
|
||||
tmp_path: str | None = None
|
||||
try:
|
||||
page.select_option(
|
||||
'select[name="submittedProblemIndex"]',
|
||||
problem_id.upper(),
|
||||
)
|
||||
page.select_option('select[name="programTypeId"]', language_id)
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w", suffix=".cpp", delete=False, prefix="cf_"
|
||||
) as tf:
|
||||
tf.write(source_code)
|
||||
tmp_path = tf.name
|
||||
try:
|
||||
page.set_input_files('input[name="sourceFile"]', tmp_path)
|
||||
page.wait_for_timeout(BROWSER_SETTLE_DELAY)
|
||||
except Exception:
|
||||
page.fill('textarea[name="source"]', source_code)
|
||||
page.fill('textarea[name="source"]', source_code)
|
||||
page.locator("form.submit-form input.submit").click(no_wait_after=True)
|
||||
try:
|
||||
page.wait_for_url(
|
||||
|
|
@ -419,26 +394,21 @@ def _submit_headless(
|
|||
submit_error = "Submit failed: page did not navigate"
|
||||
except Exception as e:
|
||||
submit_error = str(e)
|
||||
finally:
|
||||
if tmp_path:
|
||||
try:
|
||||
os.unlink(tmp_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
with StealthySession(
|
||||
headless=True,
|
||||
timeout=BROWSER_SESSION_TIMEOUT,
|
||||
google_search=False,
|
||||
cookies=saved_cookies,
|
||||
cookies=saved_cookies if (cookie_cache.exists() and not _retried) else [],
|
||||
) as session:
|
||||
print(json.dumps({"status": "checking_login"}), flush=True)
|
||||
session.fetch(
|
||||
f"{BASE_URL}/",
|
||||
page_action=check_login,
|
||||
network_idle=True,
|
||||
)
|
||||
if not (cookie_cache.exists() and not _retried):
|
||||
print(json.dumps({"status": "checking_login"}), flush=True)
|
||||
session.fetch(
|
||||
f"{BASE_URL}/",
|
||||
page_action=check_login,
|
||||
network_idle=True,
|
||||
)
|
||||
|
||||
if not logged_in:
|
||||
print(json.dumps({"status": "logging_in"}), flush=True)
|
||||
|
|
@ -456,7 +426,7 @@ def _submit_headless(
|
|||
session.fetch(
|
||||
f"{BASE_URL}/contest/{contest_id}/submit",
|
||||
page_action=submit_action,
|
||||
solve_cloudflare=True,
|
||||
solve_cloudflare=False,
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
@ -466,15 +436,21 @@ def _submit_headless(
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
if submit_error:
|
||||
return SubmitResult(success=False, error=submit_error)
|
||||
|
||||
return SubmitResult(
|
||||
success=True,
|
||||
error="",
|
||||
submission_id="",
|
||||
verdict="submitted",
|
||||
if needs_relogin and not _retried:
|
||||
cookie_cache.unlink(missing_ok=True)
|
||||
return _submit_headless(
|
||||
contest_id, problem_id, source_code, language_id, credentials, _retried=True
|
||||
)
|
||||
|
||||
if submit_error:
|
||||
return SubmitResult(success=False, error=submit_error)
|
||||
|
||||
return SubmitResult(
|
||||
success=True,
|
||||
error="",
|
||||
submission_id="",
|
||||
verdict="submitted",
|
||||
)
|
||||
except Exception as e:
|
||||
return SubmitResult(success=False, error=str(e))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue