fix atcoder :CP logins
propagate scraper error codes
This commit is contained in:
Barrett Ruth 2026-03-04 12:47:48 -05:00 committed by GitHub
parent baaaa95b27
commit 18a60da2d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 68 additions and 21 deletions

View file

@ -5,19 +5,19 @@ local logger = require('cp.log')
local utils = require('cp.utils') local utils = require('cp.utils')
local function syshandle(result) local function syshandle(result)
local ok, data = pcall(vim.json.decode, result.stdout or '')
if ok then
return { success = true, data = data }
end
if result.code ~= 0 then if result.code ~= 0 then
local msg = 'Scraper failed: ' .. (result.stderr or 'Unknown error') local msg = 'Scraper failed: ' .. (result.stderr or 'Unknown error')
return { success = false, error = msg } return { success = false, error = msg }
end end
local ok, data = pcall(vim.json.decode, result.stdout) local msg = 'Failed to parse scraper output: ' .. tostring(data)
if not ok then logger.log(msg, vim.log.levels.ERROR)
local msg = 'Failed to parse scraper output: ' .. tostring(data) return { success = false, error = msg }
logger.log(msg, vim.log.levels.ERROR)
return { success = false, error = msg }
end
return { success = true, data = data }
end end
---@param env_map table<string, string> ---@param env_map table<string, string>

View file

@ -2,6 +2,7 @@
import asyncio import asyncio
import json import json
import os
import re import re
import sys import sys
import time import time
@ -15,6 +16,7 @@ from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry from urllib3.util.retry import Retry
from .base import BaseScraper, extract_precision from .base import BaseScraper, extract_precision
from .language_ids import get_language_id
from .models import ( from .models import (
CombinedTest, CombinedTest,
ContestListResult, ContestListResult,
@ -378,10 +380,12 @@ class AtcoderScraper(BaseScraper):
credentials: dict[str, str], credentials: dict[str, str],
) -> SubmitResult: ) -> SubmitResult:
def _submit_sync() -> SubmitResult: def _submit_sync() -> SubmitResult:
from curl_cffi import requests as curl_requests
try: try:
login_page = _session.get( session = curl_requests.Session(impersonate="chrome")
f"{BASE_URL}/login", headers=HEADERS, timeout=TIMEOUT_SECONDS
) login_page = session.get(f"{BASE_URL}/login", timeout=TIMEOUT_SECONDS)
login_page.raise_for_status() login_page.raise_for_status()
soup = BeautifulSoup(login_page.text, "html.parser") soup = BeautifulSoup(login_page.text, "html.parser")
csrf_input = soup.find("input", {"name": "csrf_token"}) csrf_input = soup.find("input", {"name": "csrf_token"})
@ -391,21 +395,29 @@ class AtcoderScraper(BaseScraper):
) )
csrf_token = csrf_input.get("value", "") or "" # type: ignore[union-attr] csrf_token = csrf_input.get("value", "") or "" # type: ignore[union-attr]
login_resp = _session.post( login_resp = session.post(
f"{BASE_URL}/login", f"{BASE_URL}/login",
data={ data={
"username": credentials.get("username", ""), "username": credentials.get("username", ""),
"password": credentials.get("password", ""), "password": credentials.get("password", ""),
"csrf_token": csrf_token, "csrf_token": csrf_token,
}, },
headers=HEADERS,
timeout=TIMEOUT_SECONDS, timeout=TIMEOUT_SECONDS,
allow_redirects=False,
) )
login_resp.raise_for_status() if login_resp.status_code in (301, 302):
location = login_resp.headers.get("Location", "")
if "/login" in location:
return SubmitResult(
success=False,
error="Login failed: incorrect username or password",
)
session.get(BASE_URL + location, timeout=TIMEOUT_SECONDS)
else:
login_resp.raise_for_status()
submit_page = _session.get( submit_page = session.get(
f"{BASE_URL}/contests/{contest_id}/submit", f"{BASE_URL}/contests/{contest_id}/submit",
headers=HEADERS,
timeout=TIMEOUT_SECONDS, timeout=TIMEOUT_SECONDS,
) )
submit_page.raise_for_status() submit_page.raise_for_status()
@ -418,7 +430,7 @@ class AtcoderScraper(BaseScraper):
csrf_token = csrf_input.get("value", "") or "" # type: ignore[union-attr] csrf_token = csrf_input.get("value", "") or "" # type: ignore[union-attr]
task_screen_name = f"{contest_id}_{problem_id}" task_screen_name = f"{contest_id}_{problem_id}"
submit_resp = _session.post( submit_resp = session.post(
f"{BASE_URL}/contests/{contest_id}/submit", f"{BASE_URL}/contests/{contest_id}/submit",
data={ data={
"data.TaskScreenName": task_screen_name, "data.TaskScreenName": task_screen_name,
@ -426,13 +438,26 @@ class AtcoderScraper(BaseScraper):
"sourceCode": source_code, "sourceCode": source_code,
"csrf_token": csrf_token, "csrf_token": csrf_token,
}, },
headers=HEADERS,
timeout=TIMEOUT_SECONDS, timeout=TIMEOUT_SECONDS,
allow_redirects=False,
) )
if submit_resp.status_code in (301, 302):
location = submit_resp.headers.get("Location", "")
if "/submissions/me" in location:
return SubmitResult(
success=True,
error="",
submission_id="",
verdict="submitted",
)
return SubmitResult(
success=False,
error=f"Submit may have failed: redirected to {location}",
)
submit_resp.raise_for_status() submit_resp.raise_for_status()
return SubmitResult( return SubmitResult(
success=True, error="", submission_id="", verdict="submitted" success=False,
error="Unexpected response from submit (expected redirect)",
) )
except Exception as e: except Exception as e:
return SubmitResult(success=False, error=str(e)) return SubmitResult(success=False, error=str(e))
@ -495,9 +520,31 @@ async def main_async() -> int:
print(contest_result.model_dump_json()) print(contest_result.model_dump_json())
return 0 if contest_result.success else 1 return 0 if contest_result.success else 1
if mode == "submit":
if len(sys.argv) != 5:
print(
SubmitResult(
success=False,
error="Usage: atcoder.py submit <contest_id> <problem_id> <language>",
).model_dump_json()
)
return 1
source_code = sys.stdin.read()
creds_raw = os.environ.get("CP_CREDENTIALS", "{}")
try:
credentials = json.loads(creds_raw)
except json.JSONDecodeError:
credentials = {}
language_id = get_language_id("atcoder", sys.argv[4]) or sys.argv[4]
submit_result = await scraper.submit(
sys.argv[2], sys.argv[3], source_code, language_id, credentials
)
print(submit_result.model_dump_json())
return 0 if submit_result.success else 1
result = MetadataResult( result = MetadataResult(
success=False, success=False,
error="Unknown mode. Use 'metadata <contest_id>', 'tests <contest_id>', or 'contests'", error="Unknown mode. Use 'metadata <contest_id>', 'tests <contest_id>', 'contests', or 'submit <contest_id> <problem_id> <language>'",
url="", url="",
) )
print(result.model_dump_json()) print(result.model_dump_json())