Compare commits
No commits in common. "57f57f931f0651e73d23af51f1d5fc92faa37347" and "543480a4fe0b5a4e2c38f3baedbd935797113dcd" have entirely different histories.
57f57f931f
...
543480a4fe
4 changed files with 53 additions and 42 deletions
|
|
@ -54,7 +54,6 @@ function M.submit(opts)
|
||||||
logger.log('Source file not found', { level = vim.log.levels.ERROR })
|
logger.log('Source file not found', { level = vim.log.levels.ERROR })
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
source_file = vim.fn.fnamemodify(source_file, ':p')
|
|
||||||
|
|
||||||
prompt_credentials(platform, function(creds)
|
prompt_credentials(platform, function(creds)
|
||||||
vim.cmd.update()
|
vim.cmd.update()
|
||||||
|
|
|
||||||
|
|
@ -221,17 +221,25 @@ async def _save_kattis_cookies(client: httpx.AsyncClient) -> None:
|
||||||
_COOKIE_PATH.write_text(json.dumps(cookies))
|
_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(
|
async def _do_kattis_login(
|
||||||
client: httpx.AsyncClient, username: str, password: str
|
client: httpx.AsyncClient, username: str, password: str
|
||||||
) -> bool:
|
) -> bool:
|
||||||
client.cookies.clear()
|
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
f"{BASE_URL}/login",
|
f"{BASE_URL}/login/email",
|
||||||
data={"user": username, "password": password, "script": "true"},
|
data={"user": username, "password": password, "script": "true"},
|
||||||
headers=HEADERS,
|
headers=HEADERS,
|
||||||
timeout=HTTP_TIMEOUT,
|
timeout=HTTP_TIMEOUT,
|
||||||
)
|
)
|
||||||
return r.status_code == 200
|
return r.status_code == 200 and "login failed" not in r.text.lower()
|
||||||
|
|
||||||
|
|
||||||
class KattisScraper(BaseScraper):
|
class KattisScraper(BaseScraper):
|
||||||
|
|
@ -322,7 +330,9 @@ class KattisScraper(BaseScraper):
|
||||||
|
|
||||||
async with httpx.AsyncClient(follow_redirects=True) as client:
|
async with httpx.AsyncClient(follow_redirects=True) as client:
|
||||||
await _load_kattis_cookies(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)
|
print(json.dumps({"status": "logging_in"}), flush=True)
|
||||||
ok = await _do_kattis_login(client, username, password)
|
ok = await _do_kattis_login(client, username, password)
|
||||||
if not ok:
|
if not ok:
|
||||||
|
|
@ -341,35 +351,18 @@ class KattisScraper(BaseScraper):
|
||||||
}
|
}
|
||||||
if contest_id != problem_id:
|
if contest_id != problem_id:
|
||||||
data["contest"] = contest_id
|
data["contest"] = contest_id
|
||||||
|
try:
|
||||||
async def _do_submit() -> httpx.Response:
|
r = await client.post(
|
||||||
return await client.post(
|
|
||||||
f"{BASE_URL}/submit",
|
f"{BASE_URL}/submit",
|
||||||
data=data,
|
data=data,
|
||||||
files={"sub_file[]": (f"solution.{ext}", source, "text/plain")},
|
files={"sub_file[]": (f"solution.{ext}", source, "text/plain")},
|
||||||
headers=HEADERS,
|
headers=HEADERS,
|
||||||
timeout=HTTP_TIMEOUT,
|
timeout=HTTP_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
|
||||||
r = await _do_submit()
|
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return self._submit_error(f"Submit request failed: {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_m = re.search(r"Submission ID:\s*(\d+)", r.text, re.IGNORECASE)
|
||||||
sid = sid_m.group(1) if sid_m else ""
|
sid = sid_m.group(1) if sid_m else ""
|
||||||
return SubmitResult(
|
return SubmitResult(
|
||||||
|
|
@ -383,10 +376,21 @@ class KattisScraper(BaseScraper):
|
||||||
return self._login_error("Missing username or password")
|
return self._login_error("Missing username or password")
|
||||||
|
|
||||||
async with httpx.AsyncClient(follow_redirects=True) as client:
|
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)
|
print(json.dumps({"status": "logging_in"}), flush=True)
|
||||||
ok = await _do_kattis_login(client, username, password)
|
ok = await _do_kattis_login(client, username, password)
|
||||||
if not ok:
|
if not ok:
|
||||||
return self._login_error("Login failed (bad credentials?)")
|
return self._login_error("Login failed (bad credentials?)")
|
||||||
|
|
||||||
await _save_kattis_cookies(client)
|
await _save_kattis_cookies(client)
|
||||||
return LoginResult(
|
return LoginResult(
|
||||||
success=True,
|
success=True,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ LANGUAGE_IDS = {
|
||||||
"python": "python",
|
"python": "python",
|
||||||
},
|
},
|
||||||
"kattis": {
|
"kattis": {
|
||||||
"cpp": "C++",
|
"cpp": "C++17",
|
||||||
"python": "Python 3",
|
"python": "Python 3",
|
||||||
},
|
},
|
||||||
"codechef": {
|
"codechef": {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ CONNECTIONS = 4
|
||||||
|
|
||||||
_COOKIE_PATH = Path.home() / ".cache" / "cp-nvim" / "usaco-cookies.json"
|
_COOKIE_PATH = Path.home() / ".cache" / "cp-nvim" / "usaco-cookies.json"
|
||||||
_LOGIN_PATH = "/current/tpcm/login-session.php"
|
_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]] = {
|
_LANG_KEYWORDS: dict[str, list[str]] = {
|
||||||
"cpp": ["c++17", "c++ 17", "g++17", "c++", "cpp"],
|
"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:
|
def _pick_lang_option(select_body: str, language_id: str) -> str | None:
|
||||||
keywords = _LANG_KEYWORDS.get(language_id.lower(), [language_id.lower()])
|
keywords = _LANG_KEYWORDS.get(language_id.lower(), [language_id.lower()])
|
||||||
options = [
|
for m in re.finditer(
|
||||||
(m.group(1), m.group(2).strip().lower())
|
r'<option\b[^>]*\bvalue=["\']([^"\']*)["\'][^>]*>([^<]+)',
|
||||||
for m in re.finditer(
|
select_body,
|
||||||
r'<option\b[^>]*\bvalue=["\']([^"\']*)["\'][^>]*>([^<]+)',
|
re.IGNORECASE,
|
||||||
select_body,
|
):
|
||||||
re.IGNORECASE,
|
val, text = m.group(1), m.group(2).strip().lower()
|
||||||
)
|
for kw in keywords:
|
||||||
]
|
|
||||||
for kw in keywords:
|
|
||||||
for val, text in options:
|
|
||||||
if kw in text:
|
if kw in text:
|
||||||
return val
|
return val
|
||||||
return None
|
return None
|
||||||
|
|
@ -168,7 +165,7 @@ def _parse_submit_form(
|
||||||
re.DOTALL | re.IGNORECASE,
|
re.DOTALL | re.IGNORECASE,
|
||||||
):
|
):
|
||||||
action, body = form_m.group(1), form_m.group(2)
|
action, body = form_m.group(1), form_m.group(2)
|
||||||
if "sourcefile" not in body.lower():
|
if "sub_file" not in body.lower():
|
||||||
continue
|
continue
|
||||||
if action.startswith("http"):
|
if action.startswith("http"):
|
||||||
form_action = action
|
form_action = action
|
||||||
|
|
@ -185,7 +182,7 @@ def _parse_submit_form(
|
||||||
name_m = re.search(r'\bname=["\']([^"\']+)["\']', tag, re.IGNORECASE)
|
name_m = re.search(r'\bname=["\']([^"\']+)["\']', tag, re.IGNORECASE)
|
||||||
val_m = re.search(r'\bvalue=["\']([^"\']*)["\']', tag, re.IGNORECASE)
|
val_m = re.search(r'\bvalue=["\']([^"\']*)["\']', tag, re.IGNORECASE)
|
||||||
if name_m and val_m:
|
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(
|
for sel_m in re.finditer(
|
||||||
r'<select\b[^>]*\bname=["\']([^"\']+)["\'][^>]*>(.*?)</select>',
|
r'<select\b[^>]*\bname=["\']([^"\']+)["\'][^>]*>(.*?)</select>',
|
||||||
body,
|
body,
|
||||||
|
|
@ -234,15 +231,16 @@ async def _do_usaco_login(
|
||||||
) -> bool:
|
) -> bool:
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
f"{_AUTH_BASE}{_LOGIN_PATH}",
|
f"{_AUTH_BASE}{_LOGIN_PATH}",
|
||||||
data={"uname": username, "password": password},
|
data={"user": username, "password": password},
|
||||||
headers=HEADERS,
|
headers=HEADERS,
|
||||||
timeout=HTTP_TIMEOUT,
|
timeout=HTTP_TIMEOUT,
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
try:
|
try:
|
||||||
return r.json().get("code") == 1
|
data = r.json()
|
||||||
|
return bool(data.get("success") or data.get("status") == "success")
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return r.status_code == 200 and "error" not in r.text.lower()
|
||||||
|
|
||||||
|
|
||||||
class USACOScraper(BaseScraper):
|
class USACOScraper(BaseScraper):
|
||||||
|
|
@ -455,7 +453,7 @@ class USACOScraper(BaseScraper):
|
||||||
r = await client.post(
|
r = await client.post(
|
||||||
form_url,
|
form_url,
|
||||||
data=data,
|
data=data,
|
||||||
files={"sourcefile": (f"solution.{ext}", source, "text/plain")},
|
files={"sub_file[]": (f"solution.{ext}", source, "text/plain")},
|
||||||
headers=HEADERS,
|
headers=HEADERS,
|
||||||
timeout=HTTP_TIMEOUT,
|
timeout=HTTP_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
@ -479,6 +477,16 @@ class USACOScraper(BaseScraper):
|
||||||
return self._login_error("Missing username or password")
|
return self._login_error("Missing username or password")
|
||||||
|
|
||||||
async with httpx.AsyncClient(follow_redirects=True) as client:
|
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)
|
print(json.dumps({"status": "logging_in"}), flush=True)
|
||||||
try:
|
try:
|
||||||
ok = await _do_usaco_login(client, username, password)
|
ok = await _do_usaco_login(client, username, password)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue