Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Update.json
Original file line number Diff line number Diff line change
Expand Up @@ -3738,6 +3738,17 @@
}
],
"Notes": "修复比赛结束后自动向原题提交的回退功能。此前回退提交的响应被丢弃,遇到提交冷却时会静默失败并显示\"提交失败\",状态页里也没有任何提交记录。"
},
"3.6.6": {
"UpdateDate": 1789779349314,
"Prerelease": true,
"UpdateContents": [
{
"PR": 1023,
"Description": "feat: 验证码识别不出时自动换一张"
}
],
"Notes": "自动识别验证码时,若当前这张看不准,脚本会自动换一张重试(最多 5 张),无需手动点击图片。"
}
}
}
91 changes: 57 additions & 34 deletions XMOJ.user.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"];
Expand Down Expand Up @@ -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);
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prevent typing while a retry replaces the challenge

When a user starts manually entering the currently displayed captcha after the pre-fetch check on line 4407 but before the retry fetch and solver finish, vcode.php has already replaced the session's expected answer and the image is replaced on line 4422. This late check then returns while preserving the answer typed from the old image, so the next submission is guaranteed to fail and may escalate the session to the harder captcha. Disable manual entry during an in-flight automatic refresh or otherwise clear/reconcile input when the fetched challenge replaces the displayed one.

Useful? React with 👍 / 👎.

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("");
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down