fix(userscript): v8.21.2 inline jitter logic, kill captcha-send loop

Root cause: v8.21 added a top-level `function jitterDelay()` helper.
In some Tampermonkey/cache scenarios the helper is undefined inside
the `handleCaptchaDirectInPage` async closure, throwing
`ReferenceError: jitterDelay is not defined`. The catch block then
resets `captchaSent = false` and `lastCaptchaText = ''`, so the
`setInterval(checkCaptchaPrompt, 50)` loop resends the same captcha
to the backend every 50ms ("dead loop sending captcha").

Fix: inline the ±20% jitter math at both call sites
(line 1814 and 1117), drop the helper entirely. Also added
`Number(...) || default` guards so a stale `CFG.CAPTCHA_CLICK_DELAY`
or `CFG.RL_RETRY_DELAY` of NaN/null/undefined falls back to the
DEF value rather than making `setTimeout(NaN)` fire instantly.

Side effect that confirmed the bug: the captcha bg element lost
its layout during the storm, so `rect.width = 0` and
`nx * rect.width = 0`, but `rect.left` was ~-9992 from a stale
frame ref — hence the marker spamming `(-9992, -10029)`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
OLmatter
2026-06-15 23:12:47 +08:00
co-authored by Claude Opus 4.7
parent 66a9ea559e
commit cd64ff6bab
3 changed files with 17 additions and 15 deletions
@@ -522,11 +522,6 @@
function loadCfg() { try { const s = GM_getValue(STORAGE_KEY, null); return s ? { ...DEF, ...JSON.parse(s) } : { ...DEF }; } catch { return { ...DEF }; } }
function saveCfg(c) { GM_setValue(STORAGE_KEY, JSON.stringify(c)); }
const CFG = loadCfg();
// v8.21: 延迟统一加 ±20% 随机抖动(经典/高级模式都有,防 RPM 风控)
function jitterDelay(base) {
const delta = base * 0.2;
return Math.max(1, Math.round(base + (Math.random() * 2 - 1) * delta));
}
GM_registerMenuCommand('⚙️ 打开配置面板', openConfigPanel);
GM_registerMenuCommand('🗑️ 清除今日套餐状态缓存', () => { localStorage.removeItem(_dsKey); alert('今日状态已清除,即将刷新。'); location.reload(); });
GM_registerMenuCommand('🚀 一键多开窗口', openMultipleWindows);
@@ -1114,7 +1109,9 @@
setBar(`⚠️ 限流 ${taskRLCount}/${MAX_RL},自动关闭后重试...`, '#d46b08');
// v8.21: 高级模式可调限流重试间隔(带 ±20% 抖动防风控)
// 期间用 WAITING_RL 状态占位,避免下一轮 tick 重复关弹窗
const _rlDelay = jitterDelay(CFG.RL_RETRY_DELAY);
var _rld = Number(CFG.RL_RETRY_DELAY) || 1000;
var _rldDelta = _rld * 0.2;
var _rlDelay = Math.max(1, Math.round(_rld + (Math.random() * 2 - 1) * _rldDelta));
taskPhase = 'WAITING_RL';
setTimeout(() => { taskPhase = 'IDLE'; }, _rlDelay);
return;
@@ -1811,7 +1808,9 @@
if (!Number.isFinite(ny) && Number.isFinite(Number(c.rel_y))) ny = Number(c.rel_y) / rect.height;
if (!Number.isFinite(nx) || !Number.isFinite(ny)) continue;
dispatchClickAt(bgEl, nx * rect.width, ny * rect.height, c.char || String(i + 1));
var _clickDelay = jitterDelay(CFG.CAPTCHA_CLICK_DELAY);
var _ccd = Number(CFG.CAPTCHA_CLICK_DELAY) || 220;
var _cdDelta = _ccd * 0.2;
var _clickDelay = Math.max(1, Math.round(_ccd + (Math.random() * 2 - 1) * _cdDelta));
await new Promise(function(r) { setTimeout(r, _clickDelay); });
}
await new Promise(function(r) { setTimeout(r, 350); });