diff --git a/Update.json b/Update.json index 942f091a..37752240 100644 --- a/Update.json +++ b/Update.json @@ -3738,6 +3738,17 @@ } ], "Notes": "修复比赛结束后自动向原题提交的回退功能。此前回退提交的响应被丢弃,遇到提交冷却时会静默失败并显示\"提交失败\",状态页里也没有任何提交记录。" + }, + "3.6.6": { + "UpdateDate": 1789779349314, + "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 1f59a730..2f74ae62 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 @@ -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,61 @@ 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 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) { + 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 { + 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(""); 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": {