fix: 修复已结束比赛提交回退功能失效 #1017 - #1018
Conversation
Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com>
(cherry picked from commit c7137ff)
Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com> (cherry picked from commit 07d7590) Update feature.yml Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com> (cherry picked from commit 1a99430) Update docs.yml Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com> (cherry picked from commit 6017bcf)
Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>
…ns[bot] This prevents infinite loops where the bot commits version updates, which triggers the workflow again, causing another commit. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The last-commit-author guard now only exits for non-edited events, so PR title/body changes still update Update.json metadata even when the branch tip is a github-actions[bot] commit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Exclude all bot actors (not just github-actions[bot]) from triggering the UpdateVersion workflow, preventing loops from AI code review bots. Allow edited events through the script-level guard so PR title/body changes still update Update.json metadata. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update to release 3.2.2
Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>
Removed Google Ads script from the HTML file. Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>
Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>
新增回退提交后判断结果并阻断外层错误,外层处理会继续执行并覆盖为“提交失败”状态。 Signed-off-by: Feng Jiajun <kp6phnt44s@privaterelay.appleid.com>
Reviewer's GuideRestores and hardens the fallback submission mechanism for finished contests by making problem ID parsing more robust, improving error handling and user feedback, and short‑circuiting outer error logic when the fallback submission succeeds or fails explicitly. Flow diagram for robust contest problem ID resolutionflowchart TD
A[Finished contest submission fails] --> B{Contest error detected?}
B -- No --> C[Continue normal failure handling]
B -- Yes --> D[Fetch contest page]
D --> E{Contest page available?}
E -- No --> F[Show contest information error]
E -- Yes --> G[Read problem row number]
G --> H[Extract first numeric sequence with /\\d+/]
H --> I{Real PID resolved?}
I -- No --> J[Show PID parsing error]
I -- Yes --> K[POST code to submit.php with real PID]
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="XMOJ.user.js" line_range="4273-4275" />
<code_context>
+ return;
+ } else {
+ const retryText = await retryResp.text();
+ if (retryText.indexOf("提交成功") !== -1 ||
+ retryText.indexOf("Solution") !== -1 ||
+ retryText.indexOf("status.php") !== -1) {
+ ErrorMessage.style.color = "green";
+ ErrorMessage.innerText = "✅ 回退提交成功!请查看状态。";
</code_context>
<issue_to_address>
**issue (bug_risk):** The fallback treats any non-redirect response containing `status.php` as a successful submission, even when the response is an error page that includes the site's normal status-page navigation link. A failed fallback therefore displays “回退提交成功” and disables further submission attempts.
**Triggers:** When `submit.php` returns an HTML failure page without a redirect.
**Suggested fix:** Require a specific success response or successful submission status, and check `retryResp.ok` before reporting success instead of matching the generic `status.php` string.
```suggestion
if (retryResp.ok && (retryText.indexOf("提交成功") !== -1 ||
retryText.indexOf("Solution") !== -1)) {
```
</issue_to_address>
### Comment 2
<location path="XMOJ.user.js" line_range="4218" />
<code_context>
} else {
const text = await Response.text();
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
</code_context>
<issue_to_address>
**issue (bug_risk):** Fallback is still triggered only when the original response contains the exact string `没有这个比赛!`; responses containing the newly described `比赛已结束` message, or another equivalent finished-contest message, bypass the fallback and are reported as ordinary submission failures.
**Triggers:** When the server reports a finished contest using `比赛已结束` without also including `没有这个比赛!`.
**Suggested fix:** Detect the complete set of finished/non-submit-able contest messages, including `比赛已结束`, before entering the fallback path.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 2 findings to address first, and if the contest-page parsing selects the wrong problem ID or the success detection is mistaken, the script can submit the user's code to an unintended problem and leave a persistent submission record that reverting cannot remove. The impact is bounded to the affected submission, but it is not fully reversible.
Blocking findings: XMOJ.user.js:4275, XMOJ.user.js:4218
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| @@ -4216,78 +4216,77 @@ async function main() { | |||
| } else { | |||
| const text = await Response.text(); | |||
| if (text.indexOf("没有这个比赛!") !== -1 && new URL(location.href).searchParams.get("pid") !== null) { | |||
There was a problem hiding this comment.
issue (bug_risk): Fallback is still triggered only when the original response contains the exact string 没有这个比赛!; responses containing the newly described 比赛已结束 message, or another equivalent finished-contest message, bypass the fallback and are reported as ordinary submission failures.
Triggers: When the server reports a finished contest using 比赛已结束 without also including 没有这个比赛!.
Suggested fix: Detect the complete set of finished/non-submit-able contest messages, including 比赛已结束, before entering the fallback path.
There was a problem hiding this comment.
All reported issues were addressed across 1 file
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
感谢你的第一个 PR!诊断方向是对的——核心问题确实是回退提交后没有 return,导致外层无条件覆盖成"提交失败"。另外用正则提取题号也比原来的 substring(2, 6) 更稳:原写法遇到 5 位题号会截断(18650 → 1865),会静默提交到错题,这个改进很有价值。
有几点需要处理。
1. 目标分支
外部贡献者的 PR 需要基于并指向 extern-contrib,不能直接指向 master。这是流程要求,和代码质量无关。
2. 阻塞项:成功判定会误报
if (retryText.indexOf("提交成功") !== -1 ||
retryText.indexOf("Solution") !== -1 ||
retryText.indexOf("status.php") !== -1) {status.php 出现在每个页面的导航栏里:
<li ><a href="status.php">状态</a></li>而真正提交成功时 submit.php 会 302 到 status.php,在上面的 if (retryResp.redirected) 分支就已经 return 了。也就是说这段 else 只有在失败时才会走到,而它必定命中 status.php —— 结果是每次失败都会显示 "✅ 回退提交成功!请查看状态。" 并把按钮置成"已提交"。这比原来的通用报错更糟,用户会以为代码已经交上去了。
建议只把 redirected 当成功信号,其余一律按失败处理,并从响应的 .jumbotron 里取服务端的真实提示显示给用户。
3. #1017 里"没有提交记录"的真正原因
这部分从 issue 里看不出来,我补充一下。XMOJ 基于 HUSTOJ,trunk/web/submit.php 里有一段提交冷却:
if (!isset($OJ_SUBMIT_COOLDOWN_TIME)) $OJ_SUBMIT_COOLDOWN_TIME = 5;
$time_point = date("Y-m-d H:i:s", time() - $OJ_SUBMIT_COOLDOWN_TIME);
$sql = "SELECT `in_date`,solution_id FROM `solution` WHERE `user_id`=? AND in_date>? ORDER BY `in_date` DESC LIMIT 1";上游命中时是 302,但 XMOJ 改成了直接渲染页面,.jumbotron 里是 请勿重复提交。issue 附的日志里,回退提交拿到的正是这个页面——所以 Response.redirected 是 false,响应体被丢掉,最终没有产生任何提交记录。
顺序上比赛校验(没有这个比赛!)在冷却检查之前,所以第一次 cid+pid 的失败提交本身不会写 solution 表,冷却是被用户前一次提交触发的。
要真正修好 #1017,回退提交遇到 请勿重复提交 时需要等冷却过去再重试(比如 3 秒一次、最多 5 次),期间在 ErrorMessage 里提示进度。
4. 一些小问题
- 删掉了 quicksubmit 的
// Credit:注释,那是 GPL-3.0 的署名要求,需要保留 - 删掉了所有
_xmoj_disposeErrorMessageEditors()调用,它负责在覆盖innerText前销毁错误框里的 Monaco 编辑器和 model,去掉会泄漏 retryText.substring(0, 200)会把<!DOCTYPE html>...原样显示给用户,建议换成解析出来的服务端提示- 进行中的提示用了
yellow,浅色主题下白底黄字看不清(原来用的是 red) - 缩进被打乱成了 4/8/36 空格混排,麻烦按原文件风格调回来
第 1、2 点处理掉就可以合了。第 3 点的冷却重试如果你不想写,我也可以补一个 commit 上去。再次感谢!
这条 review 是 LLM 帮我整理的,我不想手写这么长的东西。有说得不对的地方直接指出来。
|
补充更正:上面第 1 点我最初写成了 |
|
HUSTOJ 对提交有 5s 的 rate limit |
|
cnm点错了 |
|
算了先不squash了 |
|
本次修改仅限于 PassCheck 监听器。 |
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="XMOJ.user.js">
<violation number="1" location="XMOJ.user.js:4237">
P2: When the server reports a finished contest with `比赛已结束` or equivalent wording, this exact-match guard skips rollback. Match the supported finished-contest phrases before falling through to generic failure handling.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| const text = await response.text(); | ||
|
|
||
| // 回退处理:比赛已结束,尝试直接提交到真实题目 | ||
| if (text.indexOf("没有这个比赛!") !== -1 && new URL(location.href).searchParams.get("pid") !== null) { |
There was a problem hiding this comment.
P2: When the server reports a finished contest with 比赛已结束 or equivalent wording, this exact-match guard skips rollback. Match the supported finished-contest phrases before falling through to generic failure handling.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At XMOJ.user.js, line 4237:
<comment>When the server reports a finished contest with `比赛已结束` or equivalent wording, this exact-match guard skips rollback. Match the supported finished-contest phrases before falling through to generic failure handling.</comment>
<file context>
@@ -4211,116 +4211,127 @@ async function main() {
+ const text = await response.text();
+
+ // 回退处理:比赛已结束,尝试直接提交到真实题目
+ if (text.indexOf("没有这个比赛!") !== -1 && new URL(location.href).searchParams.get("pid") !== null) {
+ // Credit: https://github.com/boomzero/quicksubmit/blob/main/index.ts (GPL-3.0)
+ const cid = new URL(location.href).searchParams.get("cid");
</file context>
| if (text.indexOf("没有这个比赛!") !== -1 && new URL(location.href).searchParams.get("pid") !== null) { | |
| if ((text.indexOf("没有这个比赛!") !== -1 || text.indexOf("比赛已结束") !== -1 || text.indexOf("比赛已经结束") !== -1) && new URL(location.href).searchParams.get("pid") !== null) { |
There was a problem hiding this comment.
@INFP-coder I have started the AI code review. It will take a few minutes to complete.
|
分支和那几个小的都改好了, if (retryText.indexOf("提交成功") !== -1 ||
retryText.indexOf("Solution") !== -1 ||
retryText.indexOf("status.php") !== -1) {留着这条,你新写的 jumbotron 提取分支永远走不到。每个页面导航栏都有 真提交成功是 302,上面 另外冷却重试还没加。#1017 里"状态里没有提交记录"就是它引起的,不加的话原来的现象还在,只是从"提交失败"变成误报"提交成功"。要我补一个 commit 上去吗? 同上,LLM 帮我整理的。 |
There was a problem hiding this comment.
1 issue found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="XMOJ.user.js">
<violation number="1" location="XMOJ.user.js:4237">
P3: The entire new block is indented at 4 spaces while the surrounding code of the PassCheck `click` handler is nested at 24 spaces. This drops the added code out of the visual nesting level, making the handler body read as if it were separate top-level code and hurting maintainability. The repo convention is to preserve original formatting (CLAUDE.md: "Do NOT run code formatters - maintain original formatting"). Re-indent the block to match the surrounding level.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| const text = await response.text(); | ||
|
|
||
| // 回退处理:比赛已结束或不存在,尝试直接提交到真实题目 | ||
| if ((text.indexOf("没有这个比赛!") !== -1 || text.indexOf("比赛已结束") !== -1) && new URL(location.href).searchParams.get("pid") !== null) { |
There was a problem hiding this comment.
P3: The entire new block is indented at 4 spaces while the surrounding code of the PassCheck click handler is nested at 24 spaces. This drops the added code out of the visual nesting level, making the handler body read as if it were separate top-level code and hurting maintainability. The repo convention is to preserve original formatting (CLAUDE.md: "Do NOT run code formatters - maintain original formatting"). Re-indent the block to match the surrounding level.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At XMOJ.user.js, line 4237:
<comment>The entire new block is indented at 4 spaces while the surrounding code of the PassCheck `click` handler is nested at 24 spaces. This drops the added code out of the visual nesting level, making the handler body read as if it were separate top-level code and hurting maintainability. The repo convention is to preserve original formatting (CLAUDE.md: "Do NOT run code formatters - maintain original formatting"). Re-indent the block to match the surrounding level.</comment>
<file context>
@@ -4211,98 +4211,143 @@ async function main() {
+ const text = await response.text();
+
+ // 回退处理:比赛已结束或不存在,尝试直接提交到真实题目
+if ((text.indexOf("没有这个比赛!") !== -1 || text.indexOf("比赛已结束") !== -1) && new URL(location.href).searchParams.get("pid") !== null) {
+ // Credit: https://github.com/boomzero/quicksubmit/blob/main/index.ts (GPL-3.0)
+ const cid = new URL(location.href).searchParams.get("cid");
</file context>
There was a problem hiding this comment.
1 existing issue remains and 1 new issue found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="XMOJ.user.js">
<violation number="1" location="XMOJ.user.js:4312">
P2: After a non-redirected fallback submission succeeds, `PassCheck` remains visible and clickable, so users can submit the same code repeatedly despite the success state. Hide or disable `PassCheck` when marking the fallback submission successful.</violation>
</file>
Requires human review: Auto-approval blocked because this review re-detected 3 unresolved issues already reported by Cubic.
Re-trigger cubic
| ErrorElement.style.display = "block"; | ||
| ErrorMessage.style.color = "green"; | ||
| ErrorMessage.innerText = "✅ 回退提交成功!请查看状态。"; | ||
| Submit.disabled = true; |
There was a problem hiding this comment.
P2: After a non-redirected fallback submission succeeds, PassCheck remains visible and clickable, so users can submit the same code repeatedly despite the success state. Hide or disable PassCheck when marking the fallback submission successful.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At XMOJ.user.js, line 4312:
<comment>After a non-redirected fallback submission succeeds, `PassCheck` remains visible and clickable, so users can submit the same code repeatedly despite the success state. Hide or disable `PassCheck` when marking the fallback submission successful.</comment>
<file context>
@@ -4211,98 +4211,143 @@ async function main() {
+ ErrorElement.style.display = "block";
+ ErrorMessage.style.color = "green";
+ ErrorMessage.innerText = "✅ 回退提交成功!请查看状态。";
+ Submit.disabled = true;
+ Submit.value = "已提交";
+ return;
</file context>
| Submit.disabled = true; | |
| Submit.disabled = true; | |
| PassCheck.style.display = "none"; |
|
成功判定改对了, 不过现在这版有个更要命的问题:文件语法都过不了,整个脚本会加载失败。 4327–4329 连着三个 顺手再提几个:
冷却重试还是没加,所以 #1017 的现象目前还在——只是从误报"提交成功"变成了如实显示"❌ 回退提交失败:请勿重复提交"。这一步比之前对,但功能还没通。要我补一个 commit 上去吗? 推之前跑一下 同上,LLM 帮我整理的。 |
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="XMOJ.user.js">
<violation number="1" location="XMOJ.user.js:4316">
P0: The extra closing brace makes `XMOJ.user.js` syntactically invalid, so the userscript cannot load at all. Remove the obsolete brace left behind by deleting the nested success check.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| } else { | ||
| const textOnly = retryText.replace(/<[^>]*>/g, "").trim(); | ||
| errorMsg = textOnly.substring(0, 100) || errorMsg; | ||
| } |
There was a problem hiding this comment.
P0: The extra closing brace makes XMOJ.user.js syntactically invalid, so the userscript cannot load at all. Remove the obsolete brace left behind by deleting the nested success check.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At XMOJ.user.js, line 4316:
<comment>The extra closing brace makes `XMOJ.user.js` syntactically invalid, so the userscript cannot load at all. Remove the obsolete brace left behind by deleting the nested success check.</comment>
<file context>
@@ -4295,45 +4295,35 @@ if ((text.indexOf("没有这个比赛!") !== -1 || text.indexOf("比赛已结
- return;
+ const textOnly = retryText.replace(/<[^>]*>/g, "").trim();
+ errorMsg = textOnly.substring(0, 100) || errorMsg;
+ }
+ } catch (_) {
+ // 解析失败则使用默认信息
</file context>
|
冲突正好落在你改的那个 1. 处理器开头新增的验证码检查 PassCheck.addEventListener("click", async () => {
if (CaptchaIsMissing()) return;
...2. 回退提交的 body 要接上 extern-contrib 上现在是这样的: "body": "id=" + rPID + "&language=1&" + "source=" + encodeURIComponent(CodeMirrorElement.getValue()) + o2Switch + GetCaptchaParameter()你这版回退提交没带 vcode。评测队列忙、验证码出来的时候,回退提交会因为缺 vcode 直接失败——而且按 #1019 注释里的说法,空答案会让服务端把这个 session 的 4 位验证码换成 8 位,代价不小。 另外前面那个语法错误(4329 行多的 同上,LLM 帮我整理的。 |

新增回退提交后判断结果并阻断外层错误,外层处理会继续执行并覆盖为“提交失败”状态。
What does this PR aim to accomplish?:
修复已结束比赛的提交回退功能失效问题。当用户从已结束比赛的提交页(/submitpage.php?cid=&pid=)提交代码时,脚本原本应自动解析该比赛中的真实题目号,并向题目页面直接提交(回退),但此功能现已失效,导致用户显示“提交失败”且无任何回退记录。
本次 PR 旨在恢复此回退功能确保在各种比赛题目编号格式下均能正确解析真实 PID,同时优化错误处理,防止成功回退后被外层逻辑错误覆盖为“提交失败”。
How does this PR accomplish the above?:
改进真实题目号解析
原先依赖 substring(2, 6) 截取题号列,过于脆弱(当题号列为纯数字或格式变化时失败)。现改用 正则表达式 /\d+/ 从题号列文本中提取第一个数字,无论格式是“1”、“Problem 1”还是“问题 1”均可正确提取。
增加回退提交后的状态判断
回退提交后,检查响应是否发生重定向(Response.redirected),或响应文本中是否包含“提交成功”、“Solution”、“status.php”等成功标识。若成功,显示绿色成功提示,并 立即 return,阻止外层错误覆盖;若失败,显示具体错误信息并保留重试机会。
完善错误提示与用户体验
区分“回退中”、“回退成功”、“回退失败”三种状态,使用不同颜色(黄/绿/红)反馈,让用户清晰知晓当前进展。若解析真实 PID 失败,给出明确错误提示,引导用户手动提交。
错误检测范围扩展
除 “没有这个比赛!” 外,增加对 “比赛已结束” 等常见提示的检测(通过 text.indexOf 涵盖),但主要仍以 text.indexOf("没有这个比赛!") 作为回退触发点。
By submitting this pull request, I confirm the following:
git rebase)Testing:
/submitpage.php?cid=123&pid=0).Fixed Screenshot:
Summary by Sourcery
恢复已结束比赛的提交回退流程并改进其结果处理与用户反馈。
Bug Fixes:
Enhancements:
Chores:
Summary by cubic
修复已结束比赛的提交回退被误判为失败。旧行为:题号解析脆弱且回退成功会被外层覆盖为失败;新行为:仅以服务端重定向判定成功,命中后立即短路并提示。
.jumbotron或纯文本前 100 字作为错误信息,允许重试。Written for commit 9f07ee2. Summary will update on new commits.