Skip to content

sync: dev to extern-contrib - #1022

Open
github-actions[bot] wants to merge 79 commits into
extern-contribfrom
dev
Open

github-actions[bot] wants to merge 79 commits into
extern-contribfrom
dev

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

sync-branches: New code has just landed in dev, so let's bring extern-contrib up to speed!

Summary by Sourcery

Synchronize the external-contrib branch with dev by improving captcha recovery and making ended-contest fallback submissions reliable.

New Features:

  • Automatically refresh unreadable captcha images up to five times when auto-solving is enabled.

Bug Fixes:

  • Improve submissions for ended contests by retrying cooldown responses, reporting server errors, and refreshing the captcha after captcha failures.
  • Prevent stale captcha answers from being entered while a replacement image is loading.

Enhancements:

  • Centralize ended-contest fallback submission handling and user-facing submission status reporting.

Build:

  • Bump the userscript and package versions to 3.7.0.

Chores:

  • Update release metadata in Update.json for the synchronized versions.

Summary by cubic

Syncs extern-contrib with dev, bumping XMOJ.user.js and package.json to 3.7.1. Brings the captcha auto-refresh, reliable ended-contest fallback submissions, and compatibility with the new /web/contest/ app.

New Features

  • The /web/contest/ app now gets the same theme, top navbar, problem switcher, and other enhancements as the legacy pages; old contest URLs redirect to the matching /web pages.
  • Adds unit tests and a Playwright-based harness that runs the script against the live site, wired into a Test CI workflow.

Bug Fixes

  • Fallback submissions after a contest ends wait out 请勿重复提交 cooldowns (3 seconds apart, up to 5 attempts) instead of being silently dropped; other failures surface the server's message, and captcha errors refresh the captcha and stop retrying.
  • Pages with missing DOM elements (403/404, multi-subtask test reports) no longer throw TypeErrors; user-info and AC-rate features skip invalid states instead of submitting empty data.
  • Reduces load-time flashing and navbar layout shift, and fixes the empty scores on status.php.

Written for commit 97546b4. Summary will update on new commits.

Review in cubic

boomzero and others added 8 commits September 19, 2026 08:23
回退路径本身可以解析出真实题号,问题在 POST 之后:内层 fetch 只把响应
console.log 掉,从不判断成功与否,随后外层无条件覆盖成"提交失败"。日志里
那次回退实际收到的是 XMOJ 的提交冷却页(`请勿重复提交`,HUSTOJ 的
$OJ_SUBMIT_COOLDOWN_TIME 默认 5 秒,XMOJ 渲染成页面而非 302),响应被丢掉,
所以状态里没有任何提交记录。

- 把回退逻辑抽成 SubmitToEndedContestProblem,返回 {Success, Message}
- 只把 redirected 当成功信号,成功后 return,不再被外层覆盖
- 遇到 `请勿重复提交` 等冷却过去后重试(3 秒一次,最多 5 次)
- 其余失败从响应的 .jumbotron 取服务端原文,不再显示通用报错
- 遇到 `验证码错误` 立即刷新验证码并停止重试(答案已被消耗)
- 题号改用 /\d+/ 提取,原 substring(2, 6) 会把 5 位题号截成 4 位
- 补齐缺失的 GetCaptchaParameter(),并在解析失败时恢复提交按钮

Closes #1017

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
冷却等待期间验证码输入框和刷新按钮仍可交互。如果用户在这 3 秒里清空了
输入框或刷新了图片,下一次重试会用空的 GetCaptchaParameter() 发出请求,
而空答案会让服务端把本 session 的 4 位验证码换成 8 位。

在循环每次 POST 之前调用 CaptchaIsMissing()(它自己会提示并恢复按钮),
并用 Handled 标志让外层直接返回,不覆盖它设置的提示。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
contest.php 或 submit.php 的 fetch 被 reject(网络错误)时,异常会一路穿出
SubmitToEndedContestProblem、穿出 .then 回调、穿出 PassCheck 的 async 监听器
——整条链上没有任何 catch。结果 ShowSubmitStatus 和恢复按钮的两行都不会执行,
提交按钮永远停在"正在提交...",而错误框在监听器开头已经被设成 display: none,
用户什么提示都看不到。这正是本 PR 声称要修掉的那个症状。

把两处网络请求都包进 try/catch,失败时返回错误信息交给外层显示。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ack-1017

fix: 比赛结束后回退提交不再被静默丢弃
@sourcery-ai

sourcery-ai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Version 3.6.5 improves submission of problems from ended contests by extracting the fallback into a resilient, retrying workflow, centralizing status/error handling, and updating release metadata.

Sequence diagram for ended-contest problem submission

sequenceDiagram
    actor User
    participant SubmitHandler
    participant XMOJ as XMOJ
    participant ContestPage
    participant SubmitPage

    User->>SubmitHandler: click PassCheck
    SubmitHandler->>XMOJ: POST submit.php
    XMOJ-->>SubmitHandler: 没有这个比赛!
    SubmitHandler->>ContestPage: fetch contest.php?cid=ContestID
    ContestPage-->>SubmitHandler: contest HTML
    SubmitHandler->>SubmitHandler: parse RealPID
    loop up to 5 attempts
        SubmitHandler->>SubmitHandler: CaptchaIsMissing()
        SubmitHandler->>SubmitPage: POST submit.php with RealPID
        alt redirected
            SubmitPage-->>SubmitHandler: redirect response
            SubmitHandler->>XMOJ: navigate to result URL
        else 请勿重复提交
            SubmitPage-->>SubmitHandler: cooldown response
            SubmitHandler->>SubmitHandler: wait 3 seconds
        else submission result
            SubmitPage-->>SubmitHandler: result HTML
        end
    end
    SubmitHandler->>SubmitHandler: ShowSubmitStatus(Message)
Loading

Flow diagram for resilient ended-contest submission fallback

flowchart TD
    A[Initial submission fails with 没有这个比赛] --> B[Fetch contest.php using cid]
    B --> C{Contest page valid?}
    C -- No --> D[Show failure status]
    C -- Yes --> E[Parse contest problem list and resolve RealPID]
    E --> F{RealPID found?}
    F -- No --> D
    F -- Yes --> G[Check captcha]
    G --> H[POST submission to submit.php for RealPID]
    H --> I{Response}
    I -- Redirected --> J[Navigate to submission result]
    I -- 验证码错误 --> K[RefreshCaptcha and show captcha error]
    I -- 请勿重复提交 --> L{Attempts remain?}
    L -- Yes --> M[Wait 3 seconds and retry]
    M --> G
    L -- No --> D
    I -- Other failure --> D
Loading

File-Level Changes

Change Details Files
Extract and harden ended-contest submission into a reusable fallback workflow.
  • Fetch and parse the contest page to map contest positions to real problem IDs, including variable-length IDs.
  • Handle contest-page, parsing, network, captcha, and server errors with user-visible messages.
  • Retry rate-limited submissions with a three-second cooldown and re-check the captcha before each attempt.
  • Preserve redirect-based success handling and report submission progress through a shared status helper.
XMOJ.user.js
Refactor normal submission failure handling to use the new fallback and shared status display.
  • Invoke the ended-contest fallback when submit.php reports that the contest no longer exists.
  • Restore submit controls consistently for fallback failures and cancellation paths.
  • Centralize error-message rendering and editor cleanup.
XMOJ.user.js
Bump the script/package release version and add update metadata.
  • Update the userscript and npm package versions from 3.6.4 to 3.6.5.
  • Add the new update metadata entry.
XMOJ.user.js
package.json
Update.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Deploying xmoj-script-dev-channel with  Cloudflare Pages  Cloudflare Pages

Latest commit: 97546b4
Status: ✅  Deploy successful!
Preview URL: https://69416af5.xmoj-script-dev-channel.pages.dev

View logs

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="XMOJ.user.js" line_range="4639" />
<code_context>
-                                        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;
</code_context>
<issue_to_address>
**issue (bug_risk):** The fetch that submits the original contest request is still outside any try/catch. When this submit.php request rejects because of a network failure, the PassCheck async listener rejects before it can restore the submit button or show an error, so the button remains stuck on “正在提交...” with no visible message.

**Triggers:** When the initial contest submission request fails at the network level before returning a Response.

**Suggested fix:** Wrap the PassCheck submit fetch and response-processing chain in try/catch, and restore `Submit.disabled`/`Submit.value` while displaying a network-error message in the catch path.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and when the contest-to-problem mapping or retry logic is wrong, this code can POST the user's source to the wrong problem or create duplicate submissions, and those external submission records are not undone by reverting the script. The impact is bounded to the affected submissions and does not involve money or access control.

Blocking findings: XMOJ.user.js:4639


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread XMOJ.user.js
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The fetch that submits the original contest request is still outside any try/catch. When this submit.php request rejects because of a network failure, the PassCheck async listener rejects before it can restore the submit button or show an error, so the button remains stuck on “正在提交...” with no visible message.

Triggers: When the initial contest submission request fails at the network level before returning a Response.

Suggested fix: Wrap the PassCheck submit fetch and response-processing chain in try/catch, and restore Submit.disabled/Submit.value while displaying a network-error message in the catch path.

@hendragon-bot hendragon-bot Bot added the user-script This issue or pull request is related to the main user script label Sep 19, 2026
github-actions Bot and others added 10 commits September 19, 2026 01:07
在每个对 querySelector 结果取属性/调用方法的地方先判断是否为 null,
避免在 403/404 等页面结构不同时抛出 TypeError。
- 语句:前面加 if (xxx.querySelector(...) != null)
- 条件:在条件中加 xxx.querySelector(...) != null &&
- 变量声明/赋值:为 null 时使用空值([] / "" / 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
- 各页面在所需容器缺失时(如403/404页面)直接跳过处理,
  避免后续代码使用未生成的元素(排名表头、CodeMirror 文本框等)
- RefreshCaptcha: #vcode 缺失时直接返回
- 修改个人信息: 表单元素缺失时不提交,避免用空值覆盖用户信息
- 自动提交/刷AC率/获取数据: 找不到提交记录时跳过,避免提交错误代码或无限轮询
- 恢复发帖板块 ID 的原写法(前面已有 null 检查)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
- GetUserInfo: #statics 表格缺失时返回默认值且不写入缓存,避免索引空数组
- ImproveACRate: 没有解析到已通过的题目时保持按钮禁用,避免用无效题号提交

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
claude and others added 27 commits September 27, 2026 11:15
- 页面隐藏期间改为隐藏 body 而不是把根元素设为透明:根元素透明时主题背景
  也会被隐藏,深色模式下会露出浏览器默认的白色背景
- 画布背景和 color-scheme 使用当前皮肤的背景变量(ThemeCanvasCSS),
  旧版页面和新版页面共用
- 页面在导航栏转换完成、顶部导航栏应用后才显示(RevealPage),
  不再先显示未转换的导航栏;1.5 秒后无论如何都会显示
- 新版顶部导航栏(NavbarStyler)在导航栏转换后立即应用,
  不再等到第一次定时刷新,避免约 1 秒后内容整体下移

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
保底:main() 开始后 1.5 秒,且最迟 DOMContentLoaded 后 4 秒。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
黑白皮肤的表头使用反色背景,但全局链接颜色与表头背景相同(对比度 1:1),
排名表等表头中的题号链接因此不可见。表头中的链接改用反色,深色和浅色主题下
对比度分别为 13.8:1 和 21:1。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
- reinfo.php:遍历所有结果分组,逐层检查结构,跳过分组标题等没有测试点结构的元素,
  修复多 subtask 时 “Cannot read properties of undefined” 的错误
- package.json 的 test 脚本改为运行 tests/contest-web.test.cjs,新增 Test 工作流在
  PR 和推送时运行
- 单元测试截取代码时要求标记唯一且顺序正确,否则直接失败;新增 NewBootstrap 开启、
  MonochromeUI 关闭时的测试

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
与讨论区等其他渲染后端 Markdown 的地方保持一致(Sourcery 安全检查)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
公告内容由维护者控制,按原设计直接渲染 Markdown。撤销 99af565。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
The load-time hide used visibility: hidden on the body, and innerText
skips invisible text, so the score captured before the reveal was empty.
Hide with opacity instead and read the score with textContent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
…king

- Problem page buttons are full size and outlined, as on the legacy page.
- With RemoveUseless, hide the repeated buttons below the statement, as
  the legacy page does.
- Sample input/output uses the legacy layout: side by side cards with the
  copy button on the right; file names are no longer oversized headings.
- The early block now also removes old stylesheet links that were parsed
  before it ran. They set the root font size to 10px/14px until
  DOMContentLoaded, so the navbar shrank and grew back on reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

GitHub-related size/XXL user-script This issue or pull request is related to the main user script

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants