From beded4ad661d86103669c1bd1101a3b287e41a70 Mon Sep 17 00:00:00 2001 From: boomzero Date: Sun, 23 Aug 2026 10:54:24 +0800 Subject: [PATCH 01/12] =?UTF-8?q?fix:=20=E5=9C=A8=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E6=98=BE=E7=A4=BA=E5=B9=B6=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 评测队列繁忙时 XMOJ 会启用 vcode.php 图片验证码,但提交界面是脚本自己 渲染的,服务端的验证码字段在替换 DOM 时被丢弃,提交因此静默失败。 - 替换页面前先检测服务端是否渲染了验证码字段,若有则在提交按钮上方 显示验证码图片与输入框,点击图片可更换 - 提交时附带 vcode 参数;队列不繁忙时 submit.php 会忽略该字段,因此 无条件携带是安全的 - submitpage.php 在队列 >10 时显示验证码,submit.php 在 >50 时才校验, 两者可能不一致:识别到"验证码错误"时展开验证码区域并允许重新提交 - 验证码为空时在客户端拦截。提交空验证码会让服务端标记 vfail,此后 验证码会从 4 位数字变成 8 位字母数字,直到会话结束 - 新增 AutoCaptcha 开关,通过 captchaSolve 自动识别 4 位数字验证码; 识别结果非 4 位数字时不填入,8 位字母验证码不送识别,均可手动填写 Closes #420 Co-Authored-By: Claude Opus 5 --- XMOJ.user.js | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 2 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 3cb45de7..0738983b 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -29,6 +29,7 @@ // @connect api.xmoj-bbs.tech // @connect api.xmoj-bbs.me // @connect api.xmoj-script.uk +// @connect captcha.xmoj-script.uk // @connect challenges.cloudflare.com // @connect cppinsights.io // @connect cdnjs.cloudflare.com @@ -551,6 +552,12 @@ let _earlyObs = null; })(); const CaptchaSiteKey = "0x4AAAAAAALBT58IhyDViNmv"; +// XMOJ turns on its own image captcha (vcode.php) whenever the judge queue is busy. submitpage.php +// renders the field at >10 pending solutions but submit.php only enforces it at >50, so the two can +// disagree; see GetCaptchaParameter below for how that gap is handled. +// Recognises the 4 digit variant only: https://github.com/boomzero/captchaSolve +// workers.dev is unreachable from mainland China, so this has to stay on a custom domain. +const CaptchaSolverURL = "https://captcha.xmoj-script.uk/"; // 0.53.0 leaks its minified helper variables (m, r, o, ...) into the global scope from every // chunk file, so whichever chunk happens to be evaluated last clobbers the others and the // editor randomly fails to load (microsoft/monaco-editor#5015). 0.52.2 ships a single bundle @@ -2855,6 +2862,8 @@ async function main() { }, {"ID": "DownloadPlayback", "Type": "A", "Name": "回放视频增加下载功能"}, { "ID": "ImproveACRate", "Type": "A", "Name": "自动提交已AC题目以提高AC率" }, {"ID": "AutoO2", "Type": "F", "Name": "代码提交界面自动选择O2优化"}, { + "ID": "AutoCaptcha", "Type": "A", "Name": "自动识别提交界面的验证码(识别失败时仍可手动填写)" + }, { "ID": "Beautify", "Type": "F", "Name": "美化界面", "Children": [{ "ID": "NewTopBar", "Type": "F", "Name": "使用新的顶部导航栏" }, { @@ -4084,6 +4093,13 @@ async function main() { } } else if (location.pathname == "/submitpage.php") { document.title = "提交代码: " + (SearchParams.get("id") != null ? "题目" + Number(SearchParams.get("id")) : "比赛" + Number(SearchParams.get("cid"))); + // submitpage.php only renders the vcode field while the judge queue is busy, and the + // custom page below throws the server markup away, so read it before that happens. + // The queue is idle almost all of the time, so the only other way to reach this state + // is to flood the judge; localStorage UserScript-ForceCaptcha=true forces it instead. + const NativeCaptchaShown = document.querySelector("input[name='vcode']") != null || + document.querySelector("img#vcode") != null || + localStorage.getItem("UserScript-ForceCaptcha") === "true"; document.querySelector("body > div > div.mt-3").innerHTML = `

Loading...

@@ -4092,6 +4108,12 @@ async function main() {

+