v23.5: add configurable first-click delay (random range) before clicking captcha

OCR is now so fast (v6 tens of ms) that the first character click can land
before the captcha DOM finishes rendering/animating -> first click misses.
Previously the inter-click delay only applied between characters; the first
click had zero wait. Add a configurable pre-first-click random delay (default
150-300ms): new CAPTCHA_FIRST_CLICK_DELAY_MIN_MS/MAX_MS config, exposed in the
config panel under '验证码点字延时策略' as '首击前延时', passed through
getCaptchaDirectDelayConfig + normalize, slept at the start of
clickCaptchaDirectCoords. Set to 0 to disable.
This commit is contained in:
OLmatter
2026-06-23 21:30:09 +08:00
parent d0f14e8d29
commit 9de06c1890
2 changed files with 37 additions and 1 deletions
+1
View File
@@ -2,6 +2,7 @@
## 2026-06-23
- 发布用户脚本 v23.5:新增「首击前延时」配置。PP-OCRv6 识别太快(几十 ms),识别完瞬间点第一个字时验证码 DOM 可能还没完全渲染/动画到位,导致第一击落空。之前点击间延时只加在字与字之间,第一击前零等待。现在加可配置的首击前随机延时(默认 150-300ms),在配置面板「验证码点字延时策略」下方新增「首击前延时」最小/最大输入框,设为 0 则不等。
- 发布用户脚本 v23.4:彻底简化 WAITING 超时逻辑,删除所有验证码状态判断(captchaSeen/计数器/PS.inProgress 分支),兜底超时从 30 秒压到 10 秒。之前的多层判断有遗漏,少数场景会误判"验证码识别中"然后耐心等到 20-30 秒。现在回到最原始逻辑:点击后只看有没有结果(preview 响应/弹窗),10 秒没结果立即重试点订阅。10 秒兜底:正常识别(含老 CPU)5-8 秒够,超 10 秒就是卡住了。
- 发布用户脚本 v23.3:修复自动点击订阅后干等到 15 秒超时的问题。之前点击订阅后,若合成 click 事件没触发智谱前端弹验证码(按钮 DOM 已就绪但前端状态机还没准备好),主流程会一直停在 WAITING 干等到 `MODAL_WAIT=15000` 超时,浪费抢购黄金窗口。现在用"验证码 iframe 是否已拿到新 prompt+背景图"作为信号:iframe 脚本每次拿到新验证码(prompt 或背景图变化)时让 GM 计数器 `glm_captcha_seen_seq` 自增,主流程点击时记下基线,WAITING 阶段比对——计数器增长了说明验证码已弹出,耐心等识别;1.5 秒还没增长说明点击没触发验证码,立即回 IDLE 重试点订阅。计数器只增不减、无残留、无时序错乱。
- 修复 PS 5.1 中文系统下 `.ps1` 脚本 ParserError("Missing expression after ','"):所有 `.ps1` 加 UTF-8 BOM,避免 PS 5.1 按 GBK 解析 UTF-8 中文字节导致语法解析崩溃。
+36 -1
View File
@@ -1,7 +1,7 @@
// ==UserScript==
// @name 智谱 GLM Coding Plan 抢购助手 + 本地 OCR 自动验证码
// @namespace http://tampermonkey.net/
// @version 23.4
// @version 23.5
// @description GLM Coding Rush / 智谱 GLM Coding Plan 抢购助手,一键抢购油猴脚本 / Tampermonkey userscript,配合本地 CPU/GPU OCR 自动识别中文点选验证码并点击,支持多窗口并发、限流重试和支付页安全保护
// @author mumumi
// @include https://*bigmodel.cn/glm-coding*
@@ -609,6 +609,9 @@
CAPTCHA_CLICK_DELAY_MIN_MS: 250,
CAPTCHA_CLICK_DELAY_MAX_MS: 400,
CAPTCHA_CLICK_DELAY_JITTER_PERCENT: 20,
// 首击前延迟(验证码 DOM 渲染/动画需要时间,识别太快会导致第一击落空)。随机区间。
CAPTCHA_FIRST_CLICK_DELAY_MIN_MS: 150,
CAPTCHA_FIRST_CLICK_DELAY_MAX_MS: 300,
RUSH_ENABLED : false,
RUSH_TARGET_HOUR : 10,
RUSH_TARGET_MIN : 0,
@@ -1105,6 +1108,9 @@
const delayMin = Number.isFinite(parseInt(CFG.CAPTCHA_CLICK_DELAY_MIN_MS, 10)) ? parseInt(CFG.CAPTCHA_CLICK_DELAY_MIN_MS, 10) : 250;
const delayMaxRaw = Number.isFinite(parseInt(CFG.CAPTCHA_CLICK_DELAY_MAX_MS, 10)) ? parseInt(CFG.CAPTCHA_CLICK_DELAY_MAX_MS, 10) : 400;
const delayMax = Math.max(delayMin, delayMaxRaw);
const fcMin = Number.isFinite(parseInt(CFG.CAPTCHA_FIRST_CLICK_DELAY_MIN_MS, 10)) ? parseInt(CFG.CAPTCHA_FIRST_CLICK_DELAY_MIN_MS, 10) : 150;
const fcMaxRaw = Number.isFinite(parseInt(CFG.CAPTCHA_FIRST_CLICK_DELAY_MAX_MS, 10)) ? parseInt(CFG.CAPTCHA_FIRST_CLICK_DELAY_MAX_MS, 10) : 300;
const fcMax = Math.max(fcMin, fcMaxRaw);
if (!document.getElementById('glm-tf-s')) {
const s = document.createElement('style'); s.id = 'glm-tf-s';
s.textContent = '.tf-item{padding:6px 10px;margin-bottom:4px;border-radius:4px;cursor:pointer;font-size:13px;color:#333;border:1px solid transparent;transition:all .15s}.tf-item:hover{background:#f5f5f5}.tf-item.active{background:#e6f7ff;border-color:#91d5ff;color:#1890ff;font-weight:700}.tf-btn{padding:4px 8px;font-size:10px;cursor:pointer;border:1px solid #d9d9d9;border-radius:4px;background:#fff;color:#555;height:28px;transition:.2s}.tf-btn:hover{border-color:#40a9ff;color:#40a9ff}';
@@ -1156,6 +1162,18 @@
<input type="number" id="glm-cdmax" value="${delayMax}" min="1" max="10000" style="width:72px;padding:3px 6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;text-align:center">
<span style="font-size:13px;color:#888">ms</span>
</div>
<div style="display:flex;align-items:center;gap:8px;margin-top:8px">
<span style="font-size:13px;color:#666">首击前延时</span>
<span title="识别完到点第一个字之间的等待。验证码 DOM 渲染/动画需要时间,OCR 太快会导致第一击落空。设为 0 则不等。随机区间。" style="cursor:help;color:#999;font-size:14px;border:1px solid #ccc;border-radius:50%;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;line-height:1">?</span>
</div>
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
<span style="font-size:13px;color:#888">最小</span>
<input type="number" id="glm-fcdmin" value="${fcMin}" min="0" max="10000" style="width:72px;padding:3px 6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;text-align:center">
<span style="font-size:13px;color:#888">ms</span>
<span style="font-size:13px;color:#888">最大</span>
<input type="number" id="glm-fcdmax" value="${fcMax}" min="0" max="10000" style="width:72px;padding:3px 6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;text-align:center">
<span style="font-size:13px;color:#888">ms</span>
</div>
</div>
<div style="border-top:1px dashed #eee;padding-top:12px;margin-top:4px"></div>
<label style="display:flex;align-items:center;cursor:pointer">
@@ -1228,6 +1246,8 @@
CAPTCHA_CLICK_DELAY_MIN_MS: parseInt(panel.querySelector('#glm-cdmin').value, 10),
CAPTCHA_CLICK_DELAY_MAX_MS: parseInt(panel.querySelector('#glm-cdmax').value, 10),
CAPTCHA_CLICK_DELAY_JITTER_PERCENT: CFG.CAPTCHA_CLICK_DELAY_JITTER_PERCENT,
CAPTCHA_FIRST_CLICK_DELAY_MIN_MS: parseInt(panel.querySelector('#glm-fcdmin').value, 10),
CAPTCHA_FIRST_CLICK_DELAY_MAX_MS: parseInt(panel.querySelector('#glm-fcdmax').value, 10),
RUSH_ENABLED: panel.querySelector('#glm-re').checked,
RUSH_TARGET_HOUR: parseInt(panel.querySelector('#glm-rh').value, 10),
RUSH_TARGET_MIN: parseInt(panel.querySelector('#glm-rm').value, 10),
@@ -1598,6 +1618,8 @@
maxGapMs: clampCaptchaInt(config.maxGapMs, 1, 10000, 500),
jitterPercent: clampCaptchaInt(config.jitterPercent, 0, 1000, 20),
stepDelays: stepDelays && stepDelays.length ? stepDelays : null,
firstClickMinMs: clampCaptchaInt(config.firstClickMinMs, 0, 10000, 150),
firstClickMaxMs: clampCaptchaInt(config.firstClickMaxMs, 0, 10000, 300),
};
}
function getCaptchaDirectDelayConfig() {
@@ -1607,6 +1629,8 @@
minGapMs: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_MIN_MS,
maxGapMs: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_MAX_MS,
jitterPercent: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_JITTER_PERCENT,
firstClickMinMs: Number.isFinite(parseInt(CAPTCHA_CFG.CAPTCHA_FIRST_CLICK_DELAY_MIN_MS, 10)) ? parseInt(CAPTCHA_CFG.CAPTCHA_FIRST_CLICK_DELAY_MIN_MS, 10) : 150,
firstClickMaxMs: Number.isFinite(parseInt(CAPTCHA_CFG.CAPTCHA_FIRST_CLICK_DELAY_MAX_MS, 10)) ? parseInt(CAPTCHA_CFG.CAPTCHA_FIRST_CLICK_DELAY_MAX_MS, 10) : 300,
});
var override = readCaptchaDirectDelayOverride();
if (!override) return baseConfig;
@@ -1617,6 +1641,8 @@
maxGapMs: override.maxGapMs != null ? override.maxGapMs : baseConfig.maxGapMs,
jitterPercent: override.jitterPercent != null ? override.jitterPercent : baseConfig.jitterPercent,
stepDelays: Array.isArray(override.stepDelays) ? override.stepDelays : baseConfig.stepDelays,
firstClickMinMs: override.firstClickMinMs != null ? override.firstClickMinMs : baseConfig.firstClickMinMs,
firstClickMaxMs: override.firstClickMaxMs != null ? override.firstClickMaxMs : baseConfig.firstClickMaxMs,
});
}
function getWindowIndex() {
@@ -2292,6 +2318,15 @@
var rect = bgEl.getBoundingClientRect();
var plan = buildCaptchaDirectClickPlan(rect, result, schedule);
console.log('[captcha-direct-page] click result:', JSON.stringify(result).substring(0, 260));
// 首击前延迟:验证码 DOM 渲染/动画需要时间,OCR 太快(v6 几十 ms)会导致第一击落空。
// 用随机区间,避免固定值被识别为机器。
var fcMin = Math.min(schedule.firstClickMinMs || 0, schedule.firstClickMaxMs || 0);
var fcMax = Math.max(schedule.firstClickMinMs || 0, schedule.firstClickMaxMs || 0);
if (fcMax > 0) {
var firstDelay = randCaptchaInt(fcMin, fcMax);
console.log('[captcha-direct-page] first-click delay: ' + firstDelay + 'ms');
await sleepCaptchaDirectClickGap(firstDelay);
}
for (var i = 0; i < plan.steps.length; i++) {
await runCaptchaDirectClickStep(bgEl, plan.steps[i], i < plan.steps.length - 1);
}