From ba08aac5b19f2ae394137a43c1dbc9de1c26e38e Mon Sep 17 00:00:00 2001 From: boomzero Date: Sat, 19 Sep 2026 08:52:32 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E4=B8=8D=E5=87=BA=E6=97=B6=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=8D=A2=E4=B8=80=E5=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 --- XMOJ.user.js | 79 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 1f59a730..da150b80 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -576,6 +576,8 @@ const CaptchaGlyphs = { // generated and 40 live captchas, 4 is the point where every wrong answer turns into a decline: it // still fills in about three quarters of them and never once guessed wrong. const CaptchaMinMargin = 4; +// With about three quarters of challenges read, five images leave well under a 1% chance of giving up. +const CaptchaMaxAttempts = 5; // Settings that start off rather than on. Both UtilityEnabled and the settings list seed missing // values, so they have to agree or whichever runs first decides the default. const DefaultOffSettings = ["DebugMode", "SuperDebug", "ReplaceXM"]; @@ -4395,40 +4397,51 @@ async function main() { document.querySelector("#CaptchaElement").style.display = "block"; CaptchaInput.value = ""; SetCaptchaStatus(StatusMessage || ""); - let ImageBlob; - try { - const CaptchaResponse = await fetch("https://www.xmoj.tech/vcode.php?" + Math.random(), {cache: "no-store"}); - ImageBlob = await CaptchaResponse.blob(); - } catch (e) { - console.error(e); - SetCaptchaStatus("验证码加载失败,请点击图片重试"); - return; - } - if (RequestID !== CaptchaRequestID) return; - if (CaptchaObjectURL !== null) URL.revokeObjectURL(CaptchaObjectURL); - CaptchaObjectURL = URL.createObjectURL(ImageBlob); - document.querySelector("#CaptchaImage").src = CaptchaObjectURL; - if (!UtilityEnabled("AutoCaptcha")) return; - let CaptchaLength = 4; - try { - CaptchaLength = await GetCaptchaLength(ImageBlob); - } catch (e) { - console.error(e); - } - if (CaptchaLength !== 4) { - SetCaptchaStatus("本次为 " + CaptchaLength + " 位字母验证码,请手动输入"); - return; - } - const Answer = await SolveCaptcha(ImageBlob); - if (RequestID !== CaptchaRequestID) return; - if (Answer === null) { - SetCaptchaStatus("这张看不太准,请手动输入,或点击图片换一张"); - return; + // Only a submitted answer counts against the session, so fetching another image is + // free: when the solver declines one it is cheaper to ask for a fresh challenge than + // to make the user type it. Each fetch replaces the answer the session expects, so + // this stops as soon as the user starts typing against the image on screen. + for (let Attempt = 1; Attempt <= CaptchaMaxAttempts; Attempt++) { + if (Attempt > 1) { + await new Promise((Resolve) => setTimeout(Resolve, 300)); + if (RequestID !== CaptchaRequestID || CaptchaInput.value !== "") return; + SetCaptchaStatus("这张看不太准,正在自动换一张(" + Attempt + "/" + CaptchaMaxAttempts + ")"); + } + let ImageBlob; + try { + const CaptchaResponse = await fetch("https://www.xmoj.tech/vcode.php?" + Math.random(), {cache: "no-store"}); + ImageBlob = await CaptchaResponse.blob(); + } catch (e) { + console.error(e); + if (RequestID === CaptchaRequestID) SetCaptchaStatus("验证码加载失败,请点击图片重试"); + return; + } + if (RequestID !== CaptchaRequestID) return; + if (CaptchaObjectURL !== null) URL.revokeObjectURL(CaptchaObjectURL); + CaptchaObjectURL = URL.createObjectURL(ImageBlob); + document.querySelector("#CaptchaImage").src = CaptchaObjectURL; + if (!UtilityEnabled("AutoCaptcha")) return; + let CaptchaLength = 4; + try { + CaptchaLength = await GetCaptchaLength(ImageBlob); + } catch (e) { + console.error(e); + } + if (CaptchaLength !== 4) { + SetCaptchaStatus("本次为 " + CaptchaLength + " 位字母验证码,请手动输入"); + return; + } + const Answer = await SolveCaptcha(ImageBlob); + if (RequestID !== CaptchaRequestID) return; + // Never overwrite what the user has already started typing. + if (CaptchaInput.value !== "") return; + if (Answer !== null) { + CaptchaInput.value = Answer; + SetCaptchaStatus("已自动识别,若与图片不符请手动修改"); + return; + } } - // Never overwrite what the user has already started typing. - if (CaptchaInput.value !== "") return; - CaptchaInput.value = Answer; - SetCaptchaStatus("已自动识别,若与图片不符请手动修改"); + SetCaptchaStatus("连续几张都看不太准,请手动输入,或点击图片换一张"); }; document.querySelector("#CaptchaImage").addEventListener("click", () => { RefreshCaptcha(""); From c2b21da5057743e167929384ef7223bad8fdc813 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 19 Sep 2026 00:52:58 +0000 Subject: [PATCH 2/6] 3.6.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c50f9fb0..e6af0b78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmoj-script", - "version": "3.6.5", + "version": "3.6.6", "description": "an improvement script for xmoj.tech", "main": "AddonScript.js", "scripts": { From 05e1956415377c48a066bafbb0edb5c3384f1837 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 19 Sep 2026 00:53:04 +0000 Subject: [PATCH 3/6] Update version info to 3.6.6 --- Update.json | 11 +++++++++++ XMOJ.user.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Update.json b/Update.json index 942f091a..f6875e83 100644 --- a/Update.json +++ b/Update.json @@ -3738,6 +3738,17 @@ } ], "Notes": "修复比赛结束后自动向原题提交的回退功能。此前回退提交的响应被丢弃,遇到提交冷却时会静默失败并显示\"提交失败\",状态页里也没有任何提交记录。" + }, + "3.6.6": { + "UpdateDate": 1789779179257, + "Prerelease": true, + "UpdateContents": [ + { + "PR": 1023, + "Description": "feat: 验证码识别不出时自动换一张" + } + ], + "Notes": "自动识别验证码时,若当前这张看不准,脚本会自动换一张重试(最多 5 张),无需手动点击图片。" } } } \ No newline at end of file diff --git a/XMOJ.user.js b/XMOJ.user.js index da150b80..6ec9bc7c 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.6.5 +// @version 3.6.6 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen From 2544311ca83a25484727b86a472b7e5db77106dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 19 Sep 2026 00:55:01 +0000 Subject: [PATCH 4/6] Update time and description of 3.6.6 --- Update.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Update.json b/Update.json index f6875e83..f85af226 100644 --- a/Update.json +++ b/Update.json @@ -3740,7 +3740,7 @@ "Notes": "修复比赛结束后自动向原题提交的回退功能。此前回退提交的响应被丢弃,遇到提交冷却时会静默失败并显示\"提交失败\",状态页里也没有任何提交记录。" }, "3.6.6": { - "UpdateDate": 1789779179257, + "UpdateDate": 1789779296094, "Prerelease": true, "UpdateContents": [ { From 62e9c7f142fc2088b1136e0ab48b81c800dffc0d Mon Sep 17 00:00:00 2001 From: boomzero Date: Sat, 19 Sep 2026 08:55:17 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=E6=8D=A2=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E6=9C=9F=E9=97=B4=E9=94=81=E5=AE=9A=E8=BE=93=E5=85=A5=E6=A1=86?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E6=8C=89=E6=97=A7=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E5=A1=AB=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 --- XMOJ.user.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 6ec9bc7c..2f74ae62 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -4400,26 +4400,36 @@ async function main() { // Only a submitted answer counts against the session, so fetching another image is // free: when the solver declines one it is cheaper to ask for a fresh challenge than // to make the user type it. Each fetch replaces the answer the session expects, so - // this stops as soon as the user starts typing against the image on screen. + // this stops if the user starts typing against the image on screen before the next one. for (let Attempt = 1; Attempt <= CaptchaMaxAttempts; Attempt++) { if (Attempt > 1) { await new Promise((Resolve) => setTimeout(Resolve, 300)); if (RequestID !== CaptchaRequestID || CaptchaInput.value !== "") return; SetCaptchaStatus("这张看不太准,正在自动换一张(" + Attempt + "/" + CaptchaMaxAttempts + ")"); } + // From the moment vcode.php is requested the image on screen no longer matches the + // session, so anything typed from it would be a guaranteed wrong answer. The box is + // locked until the new image replaces it. A newer call that takes over leaves the + // lock to be released by that call rather than by this one. + CaptchaInput.readOnly = true; let ImageBlob; try { const CaptchaResponse = await fetch("https://www.xmoj.tech/vcode.php?" + Math.random(), {cache: "no-store"}); ImageBlob = await CaptchaResponse.blob(); } catch (e) { console.error(e); - if (RequestID === CaptchaRequestID) SetCaptchaStatus("验证码加载失败,请点击图片重试"); + if (RequestID === CaptchaRequestID) { + CaptchaInput.readOnly = false; + SetCaptchaStatus("验证码加载失败,请点击图片重试"); + } return; } if (RequestID !== CaptchaRequestID) return; if (CaptchaObjectURL !== null) URL.revokeObjectURL(CaptchaObjectURL); CaptchaObjectURL = URL.createObjectURL(ImageBlob); document.querySelector("#CaptchaImage").src = CaptchaObjectURL; + CaptchaInput.value = ""; + CaptchaInput.readOnly = false; if (!UtilityEnabled("AutoCaptcha")) return; let CaptchaLength = 4; try { From abff30484f56188c5c0c767c518a99dcb4d53984 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 19 Sep 2026 00:55:54 +0000 Subject: [PATCH 6/6] Update time and description of 3.6.6 --- Update.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Update.json b/Update.json index f85af226..37752240 100644 --- a/Update.json +++ b/Update.json @@ -3740,7 +3740,7 @@ "Notes": "修复比赛结束后自动向原题提交的回退功能。此前回退提交的响应被丢弃,遇到提交冷却时会静默失败并显示\"提交失败\",状态页里也没有任何提交记录。" }, "3.6.6": { - "UpdateDate": 1789779296094, + "UpdateDate": 1789779349314, "Prerelease": true, "UpdateContents": [ {