fix(scrapers): fix Kattis and USACO login and submit (#330)
## Problem Kattis and USACO login and submit were broken in multiple ways discovered during manual end-to-end testing. Neither platform could successfully authenticate or submit through the plugin. ## Solution **Kattis:** switch login from `POST /login/email` (requires CSRF fetch) to `POST /login` with `script=true` (200 = success, 403 = bad credentials); remove `_check_kattis_login` entirely since Kattis blocks all GET requests from httpx; add submit retry on `"Request validation failed"` to handle expired sessions; fix language ID `"C++17"` → `"C++"`. **USACO:** fix login field `user` → `uname`; fix success check to `code==1`; fix submit endpoint to `submit-solution.php`, file field to `sourcefile`, hidden field extraction off-by-one (`group(2)` → `group(1)`); fix `_pick_lang_option` loop order (keywords outer, options inner) so specific keywords like `"c++17"` match before broad ones like `"c++"`. **`submit.lua`:** absolutize source file path via `fnamemodify(..., ':p')` before passing to the scraper — Python is spawned with `cwd=plugin_path` so relative paths silently fail with `FileNotFoundError`. **Both platforms:** remove cookie fast path from `login` subcommand so credentials are always validated, preventing stale cookies from masking wrong credentials.
This commit is contained in:
parent
543480a4fe
commit
b6d3df03e3
4 changed files with 42 additions and 53 deletions
|
|
@ -221,25 +221,17 @@ async def _save_kattis_cookies(client: httpx.AsyncClient) -> None:
|
|||
_COOKIE_PATH.write_text(json.dumps(cookies))
|
||||
|
||||
|
||||
async def _check_kattis_login(client: httpx.AsyncClient) -> bool:
|
||||
try:
|
||||
r = await client.get(BASE_URL + "/", headers=HEADERS, timeout=HTTP_TIMEOUT)
|
||||
text = r.text.lower()
|
||||
return "sign out" in text or "logout" in text or "my profile" in text
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
async def _do_kattis_login(
|
||||
client: httpx.AsyncClient, username: str, password: str
|
||||
) -> bool:
|
||||
client.cookies.clear()
|
||||
r = await client.post(
|
||||
f"{BASE_URL}/login/email",
|
||||
f"{BASE_URL}/login",
|
||||
data={"user": username, "password": password, "script": "true"},
|
||||
headers=HEADERS,
|
||||
timeout=HTTP_TIMEOUT,
|
||||
)
|
||||
return r.status_code == 200 and "login failed" not in r.text.lower()
|
||||
return r.status_code == 200
|
||||
|
||||
|
||||
class KattisScraper(BaseScraper):
|
||||
|
|
@ -330,9 +322,7 @@ class KattisScraper(BaseScraper):
|
|||
|
||||
async with httpx.AsyncClient(follow_redirects=True) as client:
|
||||
await _load_kattis_cookies(client)
|
||||
print(json.dumps({"status": "checking_login"}), flush=True)
|
||||
logged_in = bool(client.cookies) and await _check_kattis_login(client)
|
||||
if not logged_in:
|
||||
if not client.cookies:
|
||||
print(json.dumps({"status": "logging_in"}), flush=True)
|
||||
ok = await _do_kattis_login(client, username, password)
|
||||
if not ok:
|
||||
|
|
@ -351,18 +341,35 @@ class KattisScraper(BaseScraper):
|
|||
}
|
||||
if contest_id != problem_id:
|
||||
data["contest"] = contest_id
|
||||
try:
|
||||
r = await client.post(
|
||||
|
||||
async def _do_submit() -> httpx.Response:
|
||||
return await client.post(
|
||||
f"{BASE_URL}/submit",
|
||||
data=data,
|
||||
files={"sub_file[]": (f"solution.{ext}", source, "text/plain")},
|
||||
headers=HEADERS,
|
||||
timeout=HTTP_TIMEOUT,
|
||||
)
|
||||
|
||||
try:
|
||||
r = await _do_submit()
|
||||
r.raise_for_status()
|
||||
except Exception as e:
|
||||
return self._submit_error(f"Submit request failed: {e}")
|
||||
|
||||
if r.text == "Request validation failed":
|
||||
_COOKIE_PATH.unlink(missing_ok=True)
|
||||
print(json.dumps({"status": "logging_in"}), flush=True)
|
||||
ok = await _do_kattis_login(client, username, password)
|
||||
if not ok:
|
||||
return self._submit_error("Login failed (bad credentials?)")
|
||||
await _save_kattis_cookies(client)
|
||||
try:
|
||||
r = await _do_submit()
|
||||
r.raise_for_status()
|
||||
except Exception as e:
|
||||
return self._submit_error(f"Submit request failed: {e}")
|
||||
|
||||
sid_m = re.search(r"Submission ID:\s*(\d+)", r.text, re.IGNORECASE)
|
||||
sid = sid_m.group(1) if sid_m else ""
|
||||
return SubmitResult(
|
||||
|
|
@ -376,21 +383,10 @@ 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:
|
||||
return self._login_error("Login failed (bad credentials?)")
|
||||
|
||||
await _save_kattis_cookies(client)
|
||||
return LoginResult(
|
||||
success=True,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue