diff --git a/lua/cp/submit.lua b/lua/cp/submit.lua index 50d884f..77e3d66 100644 --- a/lua/cp/submit.lua +++ b/lua/cp/submit.lua @@ -54,7 +54,6 @@ function M.submit(opts) logger.log('Source file not found', { level = vim.log.levels.ERROR }) return end - source_file = vim.fn.fnamemodify(source_file, ':p') prompt_credentials(platform, function(creds) vim.cmd.update() diff --git a/scrapers/kattis.py b/scrapers/kattis.py index 734705e..43ce1f3 100644 --- a/scrapers/kattis.py +++ b/scrapers/kattis.py @@ -221,17 +221,25 @@ 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", + f"{BASE_URL}/login/email", data={"user": username, "password": password, "script": "true"}, headers=HEADERS, timeout=HTTP_TIMEOUT, ) - return r.status_code == 200 + return r.status_code == 200 and "login failed" not in r.text.lower() class KattisScraper(BaseScraper): @@ -322,7 +330,9 @@ class KattisScraper(BaseScraper): async with httpx.AsyncClient(follow_redirects=True) as client: await _load_kattis_cookies(client) - if not client.cookies: + print(json.dumps({"status": "checking_login"}), flush=True) + logged_in = bool(client.cookies) and await _check_kattis_login(client) + if not logged_in: print(json.dumps({"status": "logging_in"}), flush=True) ok = await _do_kattis_login(client, username, password) if not ok: @@ -341,35 +351,18 @@ class KattisScraper(BaseScraper): } if contest_id != problem_id: data["contest"] = contest_id - - async def _do_submit() -> httpx.Response: - return await client.post( + try: + r = 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( @@ -383,10 +376,21 @@ 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, diff --git a/scrapers/language_ids.py b/scrapers/language_ids.py index 1abfcc6..6870aa3 100644 --- a/scrapers/language_ids.py +++ b/scrapers/language_ids.py @@ -16,7 +16,7 @@ LANGUAGE_IDS = { "python": "python", }, "kattis": { - "cpp": "C++", + "cpp": "C++17", "python": "Python 3", }, "codechef": { diff --git a/scrapers/usaco.py b/scrapers/usaco.py index 074cbf9..73ec6b1 100644 --- a/scrapers/usaco.py +++ b/scrapers/usaco.py @@ -29,7 +29,7 @@ CONNECTIONS = 4 _COOKIE_PATH = Path.home() / ".cache" / "cp-nvim" / "usaco-cookies.json" _LOGIN_PATH = "/current/tpcm/login-session.php" -_SUBMIT_PATH = "/current/tpcm/submit-solution.php" +_SUBMIT_PATH = "/current/tpcm/submitproblem.php" _LANG_KEYWORDS: dict[str, list[str]] = { "cpp": ["c++17", "c++ 17", "g++17", "c++", "cpp"], @@ -141,16 +141,13 @@ def _parse_problem_page(html: str) -> dict[str, Any]: def _pick_lang_option(select_body: str, language_id: str) -> str | None: keywords = _LANG_KEYWORDS.get(language_id.lower(), [language_id.lower()]) - options = [ - (m.group(1), m.group(2).strip().lower()) - for m in re.finditer( - r']*\bvalue=["\']([^"\']*)["\'][^>]*>([^<]+)', - select_body, - re.IGNORECASE, - ) - ] - for kw in keywords: - for val, text in options: + for m in re.finditer( + r']*\bvalue=["\']([^"\']*)["\'][^>]*>([^<]+)', + select_body, + re.IGNORECASE, + ): + val, text = m.group(1), m.group(2).strip().lower() + for kw in keywords: if kw in text: return val return None @@ -168,7 +165,7 @@ def _parse_submit_form( re.DOTALL | re.IGNORECASE, ): action, body = form_m.group(1), form_m.group(2) - if "sourcefile" not in body.lower(): + if "sub_file" not in body.lower(): continue if action.startswith("http"): form_action = action @@ -185,7 +182,7 @@ def _parse_submit_form( name_m = re.search(r'\bname=["\']([^"\']+)["\']', tag, re.IGNORECASE) val_m = re.search(r'\bvalue=["\']([^"\']*)["\']', tag, re.IGNORECASE) if name_m and val_m: - hidden[name_m.group(1)] = val_m.group(1) + hidden[name_m.group(1)] = val_m.group(2) for sel_m in re.finditer( r']*\bname=["\']([^"\']+)["\'][^>]*>(.*?)', body, @@ -234,15 +231,16 @@ async def _do_usaco_login( ) -> bool: r = await client.post( f"{_AUTH_BASE}{_LOGIN_PATH}", - data={"uname": username, "password": password}, + data={"user": username, "password": password}, headers=HEADERS, timeout=HTTP_TIMEOUT, ) r.raise_for_status() try: - return r.json().get("code") == 1 + data = r.json() + return bool(data.get("success") or data.get("status") == "success") except Exception: - return False + return r.status_code == 200 and "error" not in r.text.lower() class USACOScraper(BaseScraper): @@ -455,7 +453,7 @@ class USACOScraper(BaseScraper): r = await client.post( form_url, data=data, - files={"sourcefile": (f"solution.{ext}", source, "text/plain")}, + files={"sub_file[]": (f"solution.{ext}", source, "text/plain")}, headers=HEADERS, timeout=HTTP_TIMEOUT, ) @@ -479,6 +477,16 @@ class USACOScraper(BaseScraper): return self._login_error("Missing username or password") async with httpx.AsyncClient(follow_redirects=True) as client: + await _load_usaco_cookies(client) + if client.cookies: + print(json.dumps({"status": "checking_login"}), flush=True) + if await _check_usaco_login(client, username): + return LoginResult( + success=True, + error="", + credentials={"username": username, "password": password}, + ) + print(json.dumps({"status": "logging_in"}), flush=True) try: ok = await _do_usaco_login(client, username, password)