-
Notifications
You must be signed in to change notification settings - Fork 6
fix: 比赛结束后回退提交不再被静默丢弃 #1021
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: 比赛结束后回退提交不再被静默丢弃 #1021
Changes from all commits
586d916
e32a469
af887e0
ab272ee
ad0eb05
157e9b2
514bd86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,6 @@ | ||||||||||||||
| // ==UserScript== | ||||||||||||||
| // @name XMOJ | ||||||||||||||
| // @version 3.6.4 | ||||||||||||||
| // @version 3.6.5 | ||||||||||||||
| // @description XMOJ增强脚本 | ||||||||||||||
| // @author @XMOJ-Script-dev, @langningchen and the community | ||||||||||||||
| // @namespace https://github/langningchen | ||||||||||||||
|
|
@@ -4473,6 +4473,122 @@ async function main() { | |||||||||||||
| RefreshCaptcha(""); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const ShowSubmitStatus = (Message) => { | ||||||||||||||
| ErrorElement.style.display = "block"; | ||||||||||||||
| ErrorMessage.style.color = "red"; | ||||||||||||||
| try { _xmoj_disposeErrorMessageEditors(); } catch (e) { | ||||||||||||||
| console.error(e); | ||||||||||||||
| if (UtilityEnabled("DebugMode")) { | ||||||||||||||
| SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| ErrorMessage.innerText = Message; | ||||||||||||||
| console.log(Message); | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| // Credit: https://github.com/boomzero/quicksubmit/blob/main/index.ts | ||||||||||||||
| // Also licensed under GPL-3.0 | ||||||||||||||
| // The contest is over, so submit.php refuses the cid+pid submission. Look up the | ||||||||||||||
| // real problem number on the contest page and submit to that problem instead. | ||||||||||||||
| // Returns {Success, Message}; Success means a submission record was created. | ||||||||||||||
| async function SubmitToEndedContestProblem(Source, O2Switch, ReportStatus) { | ||||||||||||||
| const ContestID = new URL(location.href).searchParams.get("cid"); | ||||||||||||||
| const ProblemNumber = new URL(location.href).searchParams.get("pid"); | ||||||||||||||
| // A rejected fetch here would unwind all the way out of the click handler, which | ||||||||||||||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 有效,已在 157e9b2 修掉。 确认了一遍控制流:整条链上唯一的 try/catch 是包 除了你指的 contest.php,循环里 submit.php 的那次 fetch 是同一个问题,所以两处一起包了 try/catch。contest.php 失败复用原来的"无法读取比赛页面"提示(对用户来说是同一种情况);submit.php 失败返回"网络错误,请稍后重试"。 回归脚本加了三条:contest.php fetch reject、contest.php 另外提一下,外层 |
||||||||||||||
| // has no catch, leaving 提交 stuck on 正在提交... with the error box still hidden. | ||||||||||||||
| let ContestResponse = undefined; | ||||||||||||||
| let ContestPage = ""; | ||||||||||||||
| try { | ||||||||||||||
| ContestResponse = await fetch("https://www.xmoj.tech/contest.php?cid=" + ContestID); | ||||||||||||||
| ContestPage = await ContestResponse.text(); | ||||||||||||||
| } catch (e) { | ||||||||||||||
| console.error(e); | ||||||||||||||
| return {Success: false, Message: "无法读取比赛页面,未能找到原题题号!"}; | ||||||||||||||
| } | ||||||||||||||
| if (ContestResponse.status !== 200 || ContestPage.indexOf("比赛尚未开始或私有,不能查看题目。") !== -1) { | ||||||||||||||
| console.error("Failed to get contest page!"); | ||||||||||||||
| return {Success: false, Message: "无法读取比赛页面,未能找到原题题号!"}; | ||||||||||||||
| } | ||||||||||||||
| let RealPID = undefined; | ||||||||||||||
| try { | ||||||||||||||
| const ContestDocument = new DOMParser().parseFromString(ContestPage, "text/html"); | ||||||||||||||
| const ProblemTable = ContestDocument.querySelector("#problemset > tbody"); | ||||||||||||||
| if (ProblemTable === null) { | ||||||||||||||
| console.error("Failed to find the problem list of the contest!"); | ||||||||||||||
| return {Success: false, Message: "无法解析比赛题目列表,未能找到原题题号!"}; | ||||||||||||||
| } | ||||||||||||||
| const ContestProblems = []; | ||||||||||||||
| for (let i = 0; i < ProblemTable.rows.length; i++) { | ||||||||||||||
| // The 题号 cell is padded with newlines and tabs; a fixed substring(2, 6) | ||||||||||||||
| // truncates any problem number that is not exactly four digits. | ||||||||||||||
| const ProblemNumberMatch = ProblemTable.rows[i].children[1].textContent.match(/\d+/); | ||||||||||||||
| ContestProblems.push(ProblemNumberMatch === null ? "" : ProblemNumberMatch[0]); | ||||||||||||||
| } | ||||||||||||||
| RealPID = ContestProblems[ProblemNumber]; | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: For malformed Prompt for AI agents
Suggested change
|
||||||||||||||
| if (UtilityEnabled("DebugMode")) { | ||||||||||||||
| console.log("Contest Problems:", ContestProblems); | ||||||||||||||
| console.log("Real PID:", RealPID); | ||||||||||||||
| } | ||||||||||||||
| } catch (e) { | ||||||||||||||
| console.error(e); | ||||||||||||||
| return {Success: false, Message: "无法解析比赛题目列表,未能找到原题题号!"}; | ||||||||||||||
| } | ||||||||||||||
| if (RealPID === undefined || RealPID === "") { | ||||||||||||||
| return {Success: false, Message: "无法确定原题题号,请手动前往原题提交!"}; | ||||||||||||||
| } | ||||||||||||||
| // XMOJ rejects anything submitted within a few seconds of the previous submission | ||||||||||||||
| // with 请勿重复提交, so wait the cooldown out instead of silently dropping the code. | ||||||||||||||
| for (let Attempt = 0; Attempt < 5; Attempt++) { | ||||||||||||||
| // The captcha field stays editable while we fetch contest.php and while we wait | ||||||||||||||
| // out a cooldown, so re-check it before every POST rather than trusting the | ||||||||||||||
| // check the 提交 handler did. Sending a blank answer would burn the session. | ||||||||||||||
| // CaptchaIsMissing() already shows its own message and restores the button. | ||||||||||||||
| if (CaptchaIsMissing()) return {Success: false, Handled: true, Message: ""}; | ||||||||||||||
| ReportStatus("比赛已结束, 正在尝试向题目 " + RealPID + " 提交"); | ||||||||||||||
| let SubmitPage = ""; | ||||||||||||||
| try { | ||||||||||||||
| const SubmitResponse = await fetch("https://www.xmoj.tech/submit.php", { | ||||||||||||||
| "headers": { | ||||||||||||||
| "content-type": "application/x-www-form-urlencoded" | ||||||||||||||
| }, | ||||||||||||||
| "referrer": location.href, | ||||||||||||||
| "method": "POST", | ||||||||||||||
| "body": "id=" + RealPID + "&language=1&" + "source=" + encodeURIComponent(Source) + O2Switch + GetCaptchaParameter() | ||||||||||||||
| }); | ||||||||||||||
| if (SubmitResponse.redirected) { | ||||||||||||||
| location.href = SubmitResponse.url; | ||||||||||||||
| return {Success: true, Message: ""}; | ||||||||||||||
| } | ||||||||||||||
| SubmitPage = await SubmitResponse.text(); | ||||||||||||||
| } catch (e) { | ||||||||||||||
| console.error(e); | ||||||||||||||
| return {Success: false, Message: "向题目 " + RealPID + " 提交失败!网络错误,请稍后重试!"}; | ||||||||||||||
| } | ||||||||||||||
| if (UtilityEnabled("DebugMode")) { | ||||||||||||||
| console.log("Direct submission response:", SubmitPage); | ||||||||||||||
| } | ||||||||||||||
| // Retrying cannot help here: the answer that was sent has already been spent. | ||||||||||||||
| if (SubmitPage.indexOf("验证码错误") !== -1) { | ||||||||||||||
| await RefreshCaptcha(""); | ||||||||||||||
| document.querySelector("#vcode").focus(); | ||||||||||||||
| return {Success: false, Message: "验证码错误!请填写上方的验证码后重新提交。"}; | ||||||||||||||
| } | ||||||||||||||
| if (SubmitPage.indexOf("请勿重复提交") === -1) { | ||||||||||||||
| let ServerMessage = ""; | ||||||||||||||
| try { | ||||||||||||||
| const MessageElement = new DOMParser().parseFromString(SubmitPage, "text/html").querySelector(".jumbotron"); | ||||||||||||||
| if (MessageElement !== null) ServerMessage = MessageElement.textContent.trim(); | ||||||||||||||
| } catch (e) { | ||||||||||||||
| console.error(e); | ||||||||||||||
| } | ||||||||||||||
| return {Success: false, Message: "向题目 " + RealPID + " 提交失败!" + (ServerMessage === "" ? "请关闭脚本后重试!" : ServerMessage)}; | ||||||||||||||
| } | ||||||||||||||
| ReportStatus("提交过于频繁, 3 秒后重新尝试向题目 " + RealPID + " 提交"); | ||||||||||||||
| await new Promise((Resolve) => setTimeout(Resolve, 3000)); | ||||||||||||||
|
Comment on lines
+4586
to
+4587
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: After the fifth cooldown response, this code displays a retry message and sleeps for three seconds, then returns without another submission. Guard the status update and delay with Prompt for AI agents
Suggested change
|
||||||||||||||
| } | ||||||||||||||
| return {Success: false, Message: "向题目 " + RealPID + " 提交失败!提交过于频繁,请稍后手动重试!"}; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| PassCheck.addEventListener("click", async () => { | ||||||||||||||
| // This is the request that actually reaches submit.php, so the captcha is checked | ||||||||||||||
| // here as well as in the 提交 handler above. | ||||||||||||||
|
|
@@ -4515,76 +4631,18 @@ async function main() { | |||||||||||||
| document.querySelector("#vcode").focus(); | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
| if (text.indexOf("没有这个比赛!") !== -1 && new URL(location.href).searchParams.get("pid") !== null) { | ||||||||||||||
| // Credit: https://github.com/boomzero/quicksubmit/blob/main/index.ts | ||||||||||||||
| // Also licensed under GPL-3.0 | ||||||||||||||
| const contestReq = await fetch("https://www.xmoj.tech/contest.php?cid=" + new URL(location.href).searchParams.get("cid")); | ||||||||||||||
| const res = await contestReq.text(); | ||||||||||||||
| if ( | ||||||||||||||
| contestReq.status !== 200 || | ||||||||||||||
| res.indexOf("比赛尚未开始或私有,不能查看题目。") !== -1 | ||||||||||||||
| ) { | ||||||||||||||
| console.error(`Failed to get contest page!`); | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
| const parser = new DOMParser(); | ||||||||||||||
| const dom = parser.parseFromString(res, "text/html"); | ||||||||||||||
| const contestProblems = []; | ||||||||||||||
| const rows = (dom.querySelector( | ||||||||||||||
| "#problemset > tbody", | ||||||||||||||
| )).rows; | ||||||||||||||
| for (let i = 0; i < rows.length; i++) { | ||||||||||||||
| contestProblems.push( | ||||||||||||||
| rows[i].children[1].textContent.substring(2, 6).replaceAll( | ||||||||||||||
| "\t", | ||||||||||||||
| "", | ||||||||||||||
| ), | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
| rPID = contestProblems[new URL(location.href).searchParams.get("pid")]; | ||||||||||||||
| if (UtilityEnabled("DebugMode")) { | ||||||||||||||
| console.log("Contest Problems:", contestProblems); | ||||||||||||||
| console.log("Real PID:", rPID); | ||||||||||||||
| } | ||||||||||||||
| ErrorElement.style.display = "block"; | ||||||||||||||
| ErrorMessage.style.color = "red"; | ||||||||||||||
| try { _xmoj_disposeErrorMessageEditors(); } catch (e) { | ||||||||||||||
| console.error(e); | ||||||||||||||
| if (UtilityEnabled("DebugMode")) { | ||||||||||||||
| SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| ErrorMessage.innerText = "比赛已结束, 正在尝试向题目 " + rPID + " 提交"; | ||||||||||||||
| console.log("比赛已结束, 正在尝试向题目 " + rPID + " 提交"); | ||||||||||||||
| let o2Switch = "&enable_O2=on"; | ||||||||||||||
| if (!document.querySelector("#enable_O2").checked) o2Switch = ""; | ||||||||||||||
| await fetch("https://www.xmoj.tech/submit.php", { | ||||||||||||||
| "headers": { | ||||||||||||||
| "content-type": "application/x-www-form-urlencoded" | ||||||||||||||
| }, | ||||||||||||||
| "referrer": location.href, | ||||||||||||||
| "method": "POST", | ||||||||||||||
| "body": "id=" + rPID + "&language=1&" + "source=" + encodeURIComponent(CodeMirrorElement.getValue()) + o2Switch + GetCaptchaParameter() | ||||||||||||||
| }).then(async (Response) => { | ||||||||||||||
| if (Response.redirected) { | ||||||||||||||
| location.href = Response.url; | ||||||||||||||
| } | ||||||||||||||
| console.log(await Response.text()); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| } | ||||||||||||||
| if (UtilityEnabled("DebugMode")) { | ||||||||||||||
| console.log("Submission failed! Response:", text); | ||||||||||||||
| } | ||||||||||||||
| ErrorElement.style.display = "block"; | ||||||||||||||
| ErrorMessage.style.color = "red"; | ||||||||||||||
| try { _xmoj_disposeErrorMessageEditors(); } catch (e) { | ||||||||||||||
| console.error(e); | ||||||||||||||
| if (UtilityEnabled("DebugMode")) { | ||||||||||||||
| SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); | ||||||||||||||
| let FailMessage = "提交失败!请关闭脚本后重试!"; | ||||||||||||||
| if (text.indexOf("没有这个比赛!") !== -1 && SearchParams.get("pid") !== null) { | ||||||||||||||
| const FallbackResult = await SubmitToEndedContestProblem(CodeMirrorElement.getValue(), o2Switch, ShowSubmitStatus); | ||||||||||||||
| if (FallbackResult.Success || FallbackResult.Handled) { | ||||||||||||||
| return; | ||||||||||||||
| } | ||||||||||||||
| FailMessage = FallbackResult.Message; | ||||||||||||||
| } | ||||||||||||||
| ErrorMessage.innerText = "提交失败!请关闭脚本后重试!"; | ||||||||||||||
| ShowSubmitStatus(FailMessage); | ||||||||||||||
| Submit.disabled = false; | ||||||||||||||
| Submit.value = "提交"; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: This PR manually bumps
@versionto 3.6.5, but repository automation (Update/UpdateVersion.jsvia the UpdateVersion workflow) is supposed to be the only writer of version numbers on PRs todev, and it re-derives the patch frompackage.json. A manual bump can conflict with that automation (double-bump or enforced re-sync), and the version is enforced to stay consistent acrosspackage.json,XMOJ.user.js, andUpdate.json. Recommend leaving the version line untouched and letting the workflow bump it.Prompt for AI agents