Refine rush and captcha timing flow

This commit is contained in:
OLmatter
2026-06-16 04:08:15 +08:00
parent 6f00ad245f
commit 15313a9ab6
3 changed files with 483 additions and 142 deletions
+58
View File
@@ -175,6 +175,64 @@ https://www.bigmodel.cn/glm-coding
默认配置比较保守:脚本会帮你识别并点选验证码文字,但不会替你按验证码“确定”。
## Rush 与验证码延时策略
### Rush mode(冲刺模式)怎么工作
`Rush mode(定时确认)` 只控制**最后的验证码“确定”按钮**,不控制前面的扫描、订阅点击、验证码识别和点字。
实际行为是:
1. 平时照常全自动扫描、点击订阅、识别验证码、点选验证码文字。
2. 只有在接近目标时间时,脚本才会进入 `rush hold`。
3. `rush hold` 期间,前面的验证码文字照常点完,但**最后的“确定”会暂时按住不发**。
4. 到达目标时间附近后,再自动释放“确定”按钮。
5. 这一波过后,脚本会继续恢复正常全自动,不会停机。
也就是说,`rush` 不是“到 10 点才开始工作”,而是:
- 平时一直真实抢、真实识别、真实提交
- 只在最后几秒卡住“确认提交”
- 过点后继续自动抢
### 最近容易限流 / 风控时怎么调
如果你最近明显更容易遇到:
- `555`
- `429`
- “系统繁忙”
- 限流弹窗更频繁
可以先把验证码点字区间**整体调慢一点**,减少过于机械、过于密集的节奏。
建议按下面顺序试:
1. 默认先用 `250-400ms`
2. 如果还是容易被限流,试 `300-450ms`
3. 如果风控很重,再试 `350-500ms`
建议原则:
- 先小幅上调,不要一口气拉太大
- 先保证流程稳定,再去追求更快
- 多窗口数量不要太激进,窗口越多,请求越密,越容易撞 RPM 风控
不建议的做法:
- 一上来就把窗口开很多
- 同时把点击节奏调得很快
- 指望靠“高并发 + 高密度点击”硬撞过风控
近期更稳的思路通常是:
- 少开窗口
- 放慢验证码点字节奏
- 让整条链路稳定跑完
- 不要把请求密度堆得太夸张
如果你只是想先保守一点,直接把区间改成 `300-450ms` 就是一个比较稳的起点。
## 验证码识别说明
当前验证码流程是:
+424 -141
View File
@@ -1,7 +1,7 @@
// ==UserScript==
// ==UserScript==
// @name 智谱 GLM Coding Plan 抢购助手 + 本地 OCR 自动验证码
// @namespace http://tampermonkey.net/
// @version 8.19
// @version 22.2
// @description GLM Coding Rush / 智谱 GLM Coding Plan 抢购助手,一键抢购油猴脚本 / Tampermonkey userscript,配合本地 CPU/GPU OCR 自动识别中文点选验证码并点击,支持多窗口并发、限流重试和支付页安全保护
// @author mumumi
// @include https://*bigmodel.cn/glm-coding*
@@ -508,6 +508,17 @@
AUTO_CLICK_SUB : true,
AUTO_CAPTCHA_CLICK : true,
AUTO_CAPTCHA_CONFIRM: false,
CAPTCHA_CLICK_DELAY_MODE : 'range',
CAPTCHA_CLICK_DELAY_MS : 325,
CAPTCHA_CLICK_DELAY_MIN_MS: 250,
CAPTCHA_CLICK_DELAY_MAX_MS: 400,
CAPTCHA_CLICK_DELAY_JITTER_PERCENT: 20,
RUSH_ENABLED : false,
RUSH_TARGET_HOUR : 9,
RUSH_TARGET_MIN : 59,
RUSH_TARGET_SEC : 58,
RUSH_HOLD_WINDOW_MS : 10000,
RUSH_RELEASE_ADVANCE_MS: 40,
};
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)); }
@@ -579,14 +590,13 @@
setTimeout(() => setBar('📭 今日缓存显示全售罄,仍会重新扫描确认。', '#434343'), 800);
}
// ── 状态机变量 ────────────────────────────────────────────────────────────
let state = 'SCANNING'; // SCANNING | TASK_UNIT | SLEEPING | DONE
let state = 'SCANNING'; // SCANNING | TASK_UNIT | DONE
// SCANNING / TASK_UNIT
let qIdx = 0, sweepRestocks = [], lastTabSwitch = 0, sweepBusyCount = 0, emptySweepCount = 0;
const soldOutHits = Object.create(null);
let taskTarget = null, taskPhase = 'IDLE', taskClickTime = 0, taskRLCount = 0;
let lastCloseReason = '';
let sleepUntil = 0;
const MAX_RL = 3, MODAL_WAIT = 15000, EMPTY_SWEEP_CONFIRM = 3, EMPTY_SWEEP_RETRY_MS = 180000, SOLD_OUT_CONFIRM = 2;
const MAX_RL = 3, MODAL_WAIT = 15000, EMPTY_SWEEP_CONFIRM = 3, SOLD_OUT_CONFIRM = 2;
// ── 工具函数 ──────────────────────────────────────────────────────────────
function parseRestock(text) {
const m = (text || '').match(/0?(\d{1,2})月0?(\d{1,2})日\s*(\d{1,2}):0?(\d{1,2})/);
@@ -610,6 +620,17 @@
if (ms > 10000) return 3000;
return 0;
}
function clampInt(value, min, max, fallback) {
const n = parseInt(value, 10);
if (!Number.isFinite(n)) return fallback;
return Math.max(min, Math.min(max, n));
}
function randInt(min, max) {
min = Math.floor(Number(min) || 0);
max = Math.floor(Number(max) || 0);
if (max < min) { const t = min; min = max; max = t; }
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function isRealBizId(id) { return id && !id.startsWith('debug-'); }
// ── v8.19: 黄金时间判断(9:30-11:00)────────────────────────────────────
function isGoldenTime() {
@@ -622,7 +643,7 @@
return time >= start && time <= end;
}
// ── DOM 访问 ──────────────────────────────────────────────────────────────
const tabEl = n => document.querySelectorAll('#switchTabBox .switch-tab-item')[n];
const tabEl = n => document.querySelectorAll('#switchTabBox .switch-tab-item')[n - 1];
const btnEl = n => document.querySelector(`.glm-coding-package-list > div:nth-child(${n}) > div > .package-card-btn-box > button`);
const canBuy = b => b && !b.disabled && !b.classList.contains('is-disabled') && !b.classList.contains('disabled') && !/售罄|补货|暂时/.test(b.innerText || '');
const isSoldOut = b => /售罄|补货|暂时/.test(b?.innerText || '');
@@ -695,8 +716,12 @@
function checkPayDialog() {
const dlg = getPayDialog();
if (!dlg) return 'keep';
// 绝对安全锁:本次会话中只要成功过一次,永不关闭
if (everSucceeded) return 'keep';
if (window.__glmRushConfirmed) {
window.__glmRushDialogSeen = 1;
return 'keep';
}
// 只有当前拿到有效 bizId,才锁住支付弹窗,避免历史成功状态误判
if (everSucceeded && PS.bizId) return 'keep';
// 接口还没返回
if (PS.inProgress) return 'keep';
// ── 情况 A:接口 555 系统繁忙 → 关弹窗试下一个
@@ -812,6 +837,9 @@
}
function openConfigPanel() {
document.getElementById('glm-cfg-ov')?.remove();
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);
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}';
@@ -850,6 +878,33 @@
<span style="font-size:14px;color:#555">自动点击验证码确定</span>
<span title="默认关闭。开启后点完验证码文字会自动点确定;关闭后需要你手动点确定。" style="margin-left:6px;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>
</label>
<div style="padding-left:26px;display:flex;flex-direction:column;gap:10px">
<div style="display:flex;align-items:center;gap:8px">
<span style="font-size:13px;color:#666">验证码点字延时策略</span>
<span title="每次点字都会在最小值和最大值之间随机取一个延时。" 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-cdmin" value="${delayMin}" 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>
<span style="font-size:13px;color:#888">最大延时</span>
<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>
<div style="border-top:1px dashed #eee;padding-top:12px;margin-top:4px"></div>
<label style="display:flex;align-items:center;cursor:pointer">
<input type="checkbox" id="glm-re" ${CFG.RUSH_ENABLED ? 'checked' : ''} style="margin-right:8px">
<span style="font-size:14px;color:#555">冲刺模式(定时确认)</span>
</label>
<div style="display:flex;align-items:center;gap:6px;padding-left:26px">
<span style="font-size:13px;color:#888">目标时间</span>
<input type="number" id="glm-rh" value="${CFG.RUSH_TARGET_HOUR}" min="0" max="23" style="width:52px;padding:3px 6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;text-align:center">
<span style="font-size:14px;color:#888">:</span>
<input type="number" id="glm-rm" value="${CFG.RUSH_TARGET_MIN}" min="0" max="59" style="width:52px;padding:3px 6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;text-align:center">
<span style="font-size:14px;color:#888">:</span>
<input type="number" id="glm-rs" value="${CFG.RUSH_TARGET_SEC}" min="0" max="59" style="width:52px;padding:3px 6px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;text-align:center">
</div>
</div>
<div style="display:flex;justify-content:space-between;gap:10px">
<button id="glm-multi" style="padding:8px 16px;border:1px solid #52c41a;background:#f6ffed;color:#52c41a;border-radius:6px;cursor:pointer;font-weight:600">🚀 一键多开</button>
@@ -876,6 +931,15 @@
AUTO_CLICK_SUB : panel.querySelector('#glm-acs').checked,
AUTO_CAPTCHA_CLICK: panel.querySelector('#glm-acc').checked,
AUTO_CAPTCHA_CONFIRM: panel.querySelector('#glm-acf').checked,
CAPTCHA_CLICK_DELAY_MODE: 'range',
CAPTCHA_CLICK_DELAY_MS: Math.round((parseInt(panel.querySelector('#glm-cdmin').value, 10) + parseInt(panel.querySelector('#glm-cdmax').value, 10)) / 2),
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,
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),
RUSH_TARGET_SEC: parseInt(panel.querySelector('#glm-rs').value, 10),
SAFE_DEFAULTS_VERSION,
});
ov.remove(); alert('已保存,即将刷新。'); location.reload();
@@ -887,22 +951,12 @@
// ═══════════════════════════════════════════════════════════════════════════
function tick() {
if (state === 'DONE') return;
if (ensureDiscountEntry()) return;
if (state === 'SLEEPING') {
const rem = sleepUntil - Date.now();
if (rem <= 0) {
// v8.0: 黄金时间禁止刷新
if (isGoldenTime()) {
setBar('🔥 黄金时间!取消刷新,继续高频监控!', '#ff4d4f');
state = 'SCANNING'; qIdx = 0; sweepRestocks = [];
} else {
location.replace(GLM_CODING_URL());
}
} else {
setBar(`💤 休眠中,<b>${fmt(rem)}</b> 后刷新`, '#434343');
}
return;
if (window.__glmRushConfirmed && window.__glmRushDialogSeen && !getPayDialog()) {
window.__glmRushConfirmed = 0;
window.__glmRushDialogSeen = 0;
console.log('[GLM] rush lock cleared');
}
if (ensureDiscountEntry()) return;
if (state === 'TASK_UNIT') { doTaskUnit(); return; }
doScan();
}
@@ -939,14 +993,10 @@
}
function onSweepDone() {
if (sweepBusyCount >= scanQueue.length) {
setBar('⚡ 所有套餐系统繁忙(batch-preview 555),刷新页面重试...', '#d46b08');
setBar('⚠️ 所有套餐当前都在系统繁忙,刷新页面重试...', '#d46b08');
setTimeout(() => location.replace(GLM_CODING_URL()), 1500);
return;
}
if (!sweepRestocks.length && isGoldenTime()) {
setBar(`🔥 黄金时间!系统繁忙中,持续高频监控!`, '#ff4d4f');
qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; emptySweepCount = 0; return;
}
if (!sweepRestocks.length) {
emptySweepCount++;
qIdx = 0; sweepRestocks = []; sweepBusyCount = 0;
@@ -954,28 +1004,22 @@
setBar(`📭 暂未发现可买/补货时间,继续确认 ${emptySweepCount}/${EMPTY_SWEEP_CONFIRM}...`, '#434343');
return;
}
state = 'SLEEPING';
sleepUntil = Date.now() + EMPTY_SWEEP_RETRY_MS;
setBar(`📭 连续 ${EMPTY_SWEEP_CONFIRM} 轮未发现库存,${fmt(EMPTY_SWEEP_RETRY_MS)} 后重新扫描。脚本未停止。`, '#434343');
emptySweepCount = 0;
setBar('📭 连续多轮未发现库存,继续高频扫描...', '#434343');
return;
}
emptySweepCount = 0;
sweepRestocks.sort((a, b) => a.msUntil - b.msUntil);
const nearest = sweepRestocks[0];
const sleep = calcSleepMs(nearest.msUntil);
// ── v8.19: 黄金时间(9:30-11:00)禁止刷新页面 ─────────────────────────
if (isGoldenTime()) {
setBar(`🔥 黄金时间!补货倒计时 <b>${fmt(nearest.msUntil)}</b>,禁止刷新,高频监控!`, '#ff4d4f');
qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; return;
}
qIdx = 0; sweepRestocks = []; sweepBusyCount = 0;
if (sleep === 0) {
setBar(`⚡ 补货倒计时 <b>${fmt(nearest.msUntil)}</b>,高频监控!`, '#d4380d');
qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; return;
return;
}
if (CFG.SMART_REFRESH) {
state = 'SLEEPING'; sleepUntil = Date.now() + sleep;
setBar(`💤 补货还需 <b>${fmt(nearest.msUntil)}</b>,<b>${fmt(sleep)}</b> 后刷新`, '#434343');
} else { qIdx = 0; sweepRestocks = []; }
setBar(`🕙 补货还需 <b>${fmt(nearest.msUntil)}</b>,持续扫描中`, '#434343');
}
}
function doTaskUnit() {
const { tab, pkg } = taskTarget;
@@ -1070,9 +1114,9 @@
return;
}
const prices = readDialogPrices();
if (everSucceeded || prices?.any) {
if (everSucceeded && PS.bizId || prices?.any) {
state = 'DONE';
if (everSucceeded) showPayAlarm();
if (everSucceeded && PS.bizId) showPayAlarm();
setBar('💳 <b>支付弹窗已出现!请立即扫码支付!</b> 脚本已停止。', '#16a34a');
} else {
setBar(`🔄 ${TABS_MAP[tab]}·${PKGS_MAP[pkg]} 弹窗等待确认...`, '#1677ff');
@@ -1149,23 +1193,114 @@
'use strict';
if (window.__glmCaptchaPromptBridge === 1) return;
window.__glmCaptchaPromptBridge = 1;
const RUSH_CONFIG = {
enabled: true,
targetHour: 10,
targetMin: 0,
targetSec: 0,
staggerMs: 2000,
pollInterval: 50,
pollTimeout: 20000,
};
const RUSH_CFG = (() => {
try {
const raw = GM_getValue('glm_coding_config_v5', '{}');
const cfg = JSON.parse(raw || '{}');
const ph = parseInt(cfg.RUSH_TARGET_HOUR, 10);
const pm = parseInt(cfg.RUSH_TARGET_MIN, 10);
const ps = parseInt(cfg.RUSH_TARGET_SEC, 10);
return {
enabled: cfg.RUSH_ENABLED === true,
targetHour: Number.isFinite(ph) ? ph : 9,
targetMin: Number.isFinite(pm) ? pm : 59,
targetSec: Number.isFinite(ps) ? ps : 58,
holdWindowMs: Math.max(0, parseInt(cfg.RUSH_HOLD_WINDOW_MS, 10) || 10000),
releaseAdvanceMs: Math.max(0, parseInt(cfg.RUSH_RELEASE_ADVANCE_MS, 10) || 40),
staggerMs: 2000,
pollInterval: 50,
pollTimeout: 20000,
};
} catch {
return { enabled: false, targetHour: 9, targetMin: 59, targetSec: 58, holdWindowMs: 10000, releaseAdvanceMs: 40, staggerMs: 2000, pollInterval: 50, pollTimeout: 20000 };
}
})();
const CAPTCHA_CFG = (() => {
try {
const raw = GM_getValue('glm_coding_config_v5', '{}');
return { AUTO_CAPTCHA_CLICK: true, AUTO_CAPTCHA_CONFIRM: false, ...JSON.parse(raw || '{}') };
return {
AUTO_CAPTCHA_CLICK: true,
AUTO_CAPTCHA_CONFIRM: false,
CAPTCHA_CLICK_DELAY_MODE: 'fixed',
CAPTCHA_CLICK_DELAY_MS: 325,
CAPTCHA_CLICK_DELAY_MIN_MS: 250,
CAPTCHA_CLICK_DELAY_MAX_MS: 400,
CAPTCHA_CLICK_DELAY_JITTER_PERCENT: 20,
...JSON.parse(raw || '{}')
};
} catch {
return { AUTO_CAPTCHA_CLICK: true, AUTO_CAPTCHA_CONFIRM: false };
return {
AUTO_CAPTCHA_CLICK: true,
AUTO_CAPTCHA_CONFIRM: false,
CAPTCHA_CLICK_DELAY_MODE: 'fixed',
CAPTCHA_CLICK_DELAY_MS: 325,
CAPTCHA_CLICK_DELAY_MIN_MS: 250,
CAPTCHA_CLICK_DELAY_MAX_MS: 400,
CAPTCHA_CLICK_DELAY_JITTER_PERCENT: 20
};
}
})();
function clampCaptchaInt(value, min, max, fallback) {
var n = parseInt(value, 10);
if (!Number.isFinite(n)) return fallback;
return Math.max(min, Math.min(max, n));
}
function randCaptchaInt(min, max) {
min = Math.floor(Number(min) || 0);
max = Math.floor(Number(max) || 0);
if (max < min) { var t = min; min = max; max = t; }
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function normalizeCaptchaDirectDelayMode(mode) {
mode = String(mode || 'fixed').trim().toLowerCase();
return (mode === 'range' || mode === 'jitter') ? mode : 'fixed';
}
function readCaptchaDirectDelayOverride() {
try {
var raw = window.__glmCaptchaDelayExperiment;
if (!raw) return null;
if (typeof raw === 'string') return JSON.parse(raw);
if (typeof raw === 'object') return raw;
} catch {}
return null;
}
function normalizeCaptchaDirectDelayConfig(config) {
config = config || {};
var stepDelays = Array.isArray(config.stepDelays)
? config.stepDelays.map(function (value) {
return clampCaptchaInt(value, 1, 10000, 220);
}).filter(function (value) {
return Number.isFinite(value) && value > 0;
})
: null;
return {
delayMode: normalizeCaptchaDirectDelayMode(config.delayMode),
gapMs: clampCaptchaInt(config.gapMs, 1, 10000, 220),
minGapMs: clampCaptchaInt(config.minGapMs, 1, 10000, 200),
maxGapMs: clampCaptchaInt(config.maxGapMs, 1, 10000, 500),
jitterPercent: clampCaptchaInt(config.jitterPercent, 0, 1000, 20),
stepDelays: stepDelays && stepDelays.length ? stepDelays : null,
};
}
function getCaptchaDirectDelayConfig() {
var baseConfig = normalizeCaptchaDirectDelayConfig({
delayMode: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_MODE,
gapMs: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_MS,
minGapMs: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_MIN_MS,
maxGapMs: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_MAX_MS,
jitterPercent: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_JITTER_PERCENT,
});
var override = readCaptchaDirectDelayOverride();
if (!override) return baseConfig;
return normalizeCaptchaDirectDelayConfig({
delayMode: override.delayMode != null ? override.delayMode : baseConfig.delayMode,
gapMs: override.gapMs != null ? override.gapMs : baseConfig.gapMs,
minGapMs: override.minGapMs != null ? override.minGapMs : baseConfig.minGapMs,
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,
});
}
function getWindowIndex() {
const params = new URLSearchParams(location.search);
return parseInt(params.get('wi') || '0', 10);
@@ -1173,13 +1308,25 @@
function getTargetTimestamp() {
const now = new Date();
const target = new Date(now.getFullYear(), now.getMonth(), now.getDate(),
RUSH_CONFIG.targetHour, RUSH_CONFIG.targetMin, RUSH_CONFIG.targetSec, 0);
const offset = getWindowIndex() * RUSH_CONFIG.staggerMs;
RUSH_CFG.targetHour, RUSH_CFG.targetMin, RUSH_CFG.targetSec, 0);
const offset = getWindowIndex() * RUSH_CFG.staggerMs;
return target.getTime() + offset;
}
let lastCaptchaText = '';
let captchaSent = false;
let rushState = 'idle';
const captchaSession = {
lastText: '',
sent: false,
state: 'idle',
};
function getCaptchaLastText() { return captchaSession.lastText; }
function setCaptchaLastText(text) { captchaSession.lastText = text || ''; }
function isCaptchaSent() { return captchaSession.sent === true; }
function setCaptchaSent(sent) { captchaSession.sent = sent === true; }
function getCaptchaState() { return captchaSession.state; }
function setCaptchaState(state) { captchaSession.state = state; }
function resetCaptchaSession() {
setCaptchaSent(false);
setCaptchaLastText('');
}
function serverRequest(method, path, data) {
function doFetch() {
return fetch('http://localhost:8888' + path, {
@@ -1213,14 +1360,14 @@
return new Promise((resolve, reject) => {
const start = Date.now();
function poll() {
if (Date.now() - start > RUSH_CONFIG.pollTimeout) {
if (Date.now() - start > RUSH_CFG.pollTimeout) {
reject(new Error('poll timeout'));
return;
}
serverRequest('POST', '/result', { ts: ts }).then(d => {
if (d.has_result) resolve(d.result);
else setTimeout(poll, RUSH_CONFIG.pollInterval);
}).catch(() => setTimeout(poll, RUSH_CONFIG.pollInterval));
else setTimeout(poll, RUSH_CFG.pollInterval);
}).catch(() => setTimeout(poll, RUSH_CFG.pollInterval));
}
poll();
});
@@ -1239,12 +1386,26 @@
setTimeout(check, Math.max(0, remaining - 5000));
});
}
function shouldHoldRushConfirm(nowTs) {
if (!RUSH_CFG.enabled) return false;
const targetTs = getTargetTimestamp();
const remaining = targetTs - nowTs;
return remaining > RUSH_CFG.releaseAdvanceMs && remaining <= RUSH_CFG.holdWindowMs;
}
function waitForRushRelease() {
return new Promise(resolve => {
function check() {
const remaining = getTargetTimestamp() - Date.now();
if (remaining <= RUSH_CFG.releaseAdvanceMs) resolve();
else setTimeout(check, remaining > 2000 ? 20 : 5);
}
check();
});
}
function findAndClickConfirm() {
var selectors = [
'.tencent-captcha-dy__btn-confirm',
'.tencent-captcha-dy__footer .btn',
'.pay-dialog button.el-button--primary',
'.el-dialog__wrapper:not([style*="display: none"]) .el-button--primary',
'[class*="captcha"] [class*="confirm"]',
'[class*="captcha"] [class*="submit"]',
];
@@ -1270,7 +1431,7 @@
async function handleCaptchaRush(chars) {
var winIdx = getWindowIndex();
var payloadText = chars.join('');
rushState = 'solving';
setCaptchaState('solving');
rushStatus('\uD83D\uDD10 [#' + winIdx + '] \u9A8C\u8BC1\u7801\u8BC6\u522B\u4E2D: ' + payloadText + '...', '#1890ff');
try {
var sendRes = await serverRequest('POST', '/captcha', { text: payloadText, ts: Date.now() });
@@ -1280,9 +1441,8 @@
rushStatus('\u23F3 [#' + winIdx + '] \u7B49\u5F85\u8BC6\u522B...', '#faad14');
var result = await pollResult(ts);
if (!result || !result.result || !result.result.success) {
rushState = 'idle';
captchaSent = false;
lastCaptchaText = '';
setCaptchaState('idle');
resetCaptchaSession();
rushStatus('\u274C [#' + winIdx + '] \u8BC6\u522B\u5931\u8D25: ' + (result && result.result ? result.result.error : '?') + ' \u2192 \u5FEB\u901F\u91CD\u8BD5', '#ff4d4f');
return;
}
@@ -1318,7 +1478,7 @@
}
}
await new Promise(function(r) { setTimeout(r, 500); });
rushState = 'idle';
setCaptchaState('idle');
(async function() {
var isRushMode = isGoldenTime();
if (isRushMode) {
@@ -1328,20 +1488,19 @@
await new Promise(function(r) { setTimeout(r, 300); });
rushStatus('\uD83D\uDE80 [#' + winIdx + '] \u81EA\u52A8\u786E\u8BA4...', '#237804');
}
rushState = 'confirming';
setCaptchaState('confirming');
var clicked = findAndClickConfirm();
if (clicked) {
rushState = 'confirmed';
setCaptchaState('confirmed');
rushStatus('\uD83C\uDFAF [#' + winIdx + '] \u5DF2\u70B9\u786E\u8BA4!' + (isRushMode ? ' (\u5361\u70B0)' : ''), '#237804');
} else {
rushState = 'idle';
setCaptchaState('idle');
rushStatus('\u26A0\uFE0F [#' + winIdx + '] \u672A\u627E\u5230\u786E\u8BA4\u6309\u94AE!', '#faad14');
}
})();
} catch (e) {
rushState = 'idle';
captchaSent = false;
lastCaptchaText = '';
setCaptchaState('idle');
resetCaptchaSession();
console.error('[captcha-rush] error:', e);
rushStatus('\u274C [#' + getWindowIndex() + '] \u5F02\u5E38: ' + e.message + ' \u2192 \u5FEB\u901F\u91CD\u8BD5', '#ff4d4f');
}
@@ -1693,85 +1852,209 @@
}
return doFetch().catch(function() { return doGM(); });
}
async function handleCaptchaDirectInPage(chars) {
function resolveCaptchaDirectTarget(bgEl, bgUrl) {
bgEl = bgEl || findCaptchaBgElementDirect();
if (!bgEl) throw new Error('no captcha background element');
bgUrl = bgUrl || captchaBgUrlFrom(bgEl);
if (!bgUrl) throw new Error('no captcha background url');
return { bgEl: bgEl, bgUrl: bgUrl };
}
async function requestCaptchaDirectResult(payloadText, bgUrl) {
console.log('[captcha-direct-page] bg:', bgUrl.substring(0, 120));
var image = await fetchCaptchaImageDirect(bgUrl);
var resp = await serverRequest('POST', '/captcha_direct', {
image: image,
text: payloadText,
remark: payloadText,
ts: Date.now(),
source: 'glm-coding-helper-page-direct'
});
var result = resp && resp.result;
if (!result || !result.success || !Array.isArray(result.click_coords)) {
throw new Error('bad direct result: ' + JSON.stringify(resp).substring(0, 180));
}
return result;
}
function normalizeCaptchaDirectCoord(coord, rect) {
var nx = Number(coord.nx);
var ny = Number(coord.ny);
if (!Number.isFinite(nx) && Number.isFinite(Number(coord.rel_x))) nx = Number(coord.rel_x) / rect.width;
if (!Number.isFinite(ny) && Number.isFinite(Number(coord.rel_y))) ny = Number(coord.rel_y) / rect.height;
if (!Number.isFinite(nx) || !Number.isFinite(ny)) return null;
return { nx: nx, ny: ny };
}
function createCaptchaDirectClickSchedule() {
return {
...getCaptchaDirectDelayConfig(),
afterClicksMs: 350,
};
}
function buildCaptchaDirectFixedDelayPlan(result, schedule) {
var delays = [];
for (var i = 0; i < result.click_coords.length; i++) {
delays.push(schedule.gapMs);
}
return delays;
}
function buildCaptchaDirectRangeDelayPlan(result, schedule) {
var minGap = Math.min(schedule.minGapMs, schedule.maxGapMs);
var maxGap = Math.max(schedule.minGapMs, schedule.maxGapMs);
var delays = [];
for (var i = 0; i < result.click_coords.length; i++) {
delays.push(randCaptchaInt(minGap, maxGap));
}
return delays;
}
function buildCaptchaDirectJitterDelayPlan(result, schedule) {
var baseGap = schedule.gapMs;
var jitterPercent = Math.max(0, schedule.jitterPercent);
var jitterDelta = Math.round(baseGap * jitterPercent / 100);
var minGap = Math.max(1, baseGap - jitterDelta);
var maxGap = Math.max(minGap, baseGap + jitterDelta);
var delays = [];
for (var i = 0; i < result.click_coords.length; i++) {
delays.push(randCaptchaInt(minGap, maxGap));
}
return delays;
}
function buildCaptchaDirectStepDelayPlan(result, schedule) {
var delays = [];
var stepDelays = Array.isArray(schedule.stepDelays) ? schedule.stepDelays : [];
for (var i = 0; i < result.click_coords.length; i++) {
delays.push(stepDelays[i] != null ? stepDelays[i] : schedule.gapMs);
}
return delays;
}
function resolveCaptchaDirectDelayPlanBuilder(schedule) {
if (Array.isArray(schedule.stepDelays) && schedule.stepDelays.length) return buildCaptchaDirectStepDelayPlan;
if (schedule.delayMode === 'range') return buildCaptchaDirectRangeDelayPlan;
if (schedule.delayMode === 'jitter') return buildCaptchaDirectJitterDelayPlan;
return buildCaptchaDirectFixedDelayPlan;
}
function buildCaptchaDirectDelayPlan(result, schedule) {
return resolveCaptchaDirectDelayPlanBuilder(schedule)(result, schedule);
}
function buildCaptchaDirectClickPlan(rect, result, schedule) {
var delays = buildCaptchaDirectDelayPlan(result, schedule);
var steps = [];
for (var i = 0; i < result.click_coords.length; i++) {
var coord = result.click_coords[i];
var normalized = normalizeCaptchaDirectCoord(coord, rect);
if (!normalized) continue;
steps.push({
x: normalized.nx * rect.width,
y: normalized.ny * rect.height,
label: coord.char || String(i + 1),
delayMs: delays[i],
});
}
return {
steps: steps,
afterClicksMs: schedule.afterClicksMs,
};
}
async function sleepCaptchaDirectClickGap(delayMs) {
await new Promise(function(r) { setTimeout(r, delayMs); });
}
async function sleepCaptchaDirectAfterClicks(plan) {
await new Promise(function(r) { setTimeout(r, plan.afterClicksMs); });
}
async function runCaptchaDirectClickStep(bgEl, step, shouldWaitAfter) {
dispatchClickAt(bgEl, step.x, step.y, step.label);
if (shouldWaitAfter) {
await sleepCaptchaDirectClickGap(step.delayMs);
}
}
async function clickCaptchaDirectCoords(bgEl, result) {
var schedule = createCaptchaDirectClickSchedule();
var rect = bgEl.getBoundingClientRect();
var plan = buildCaptchaDirectClickPlan(rect, result, schedule);
console.log('[captcha-direct-page] click result:', JSON.stringify(result).substring(0, 260));
for (var i = 0; i < plan.steps.length; i++) {
await runCaptchaDirectClickStep(bgEl, plan.steps[i], i < plan.steps.length - 1);
}
await sleepCaptchaDirectAfterClicks(plan);
}
async function finishCaptchaDirectConfirm() {
if (RUSH_CFG.enabled) {
var nowTs = Date.now();
if (shouldHoldRushConfirm(nowTs)) {
var targetTs = getTargetTimestamp();
console.log('[captcha-rush] hold confirm for ' + Math.max(0, targetTs - nowTs).toFixed(0) + 'ms');
await waitForRushRelease();
console.log('[captcha-rush] release confirm');
}
var clicked = findAndClickConfirm();
if (clicked) window.__glmRushConfirmed = Date.now();
} else if (CAPTCHA_CFG.AUTO_CAPTCHA_CONFIRM) {
findAndClickConfirm();
} else {
console.log('[captcha-direct-page] captcha confirm is disabled; waiting for manual confirm');
}
}
async function handleCaptchaDirectInPage(chars, bgEl, bgUrl) {
if (!CAPTCHA_CFG.AUTO_CAPTCHA_CLICK) {
console.log('[captcha-direct-page] auto captcha click disabled');
return;
}
if (rushState === 'solving') return;
rushState = 'solving';
if (getCaptchaState() === 'solving') return;
setCaptchaState('solving');
var payloadText = chars.join('');
try {
var bgEl = findCaptchaBgElementDirect();
if (!bgEl) throw new Error('no captcha background element');
var bgUrl = captchaBgUrlFrom(bgEl);
if (!bgUrl) throw new Error('no captcha background url');
console.log('[captcha-direct-page] bg:', bgUrl.substring(0, 120));
var image = await fetchCaptchaImageDirect(bgUrl);
var resp = await serverRequest('POST', '/captcha_direct', {
image: image,
text: payloadText,
remark: payloadText,
ts: Date.now(),
source: 'glm-coding-helper-page-direct'
});
var result = resp && resp.result;
if (!result || !result.success || !Array.isArray(result.click_coords)) {
throw new Error('bad direct result: ' + JSON.stringify(resp).substring(0, 180));
}
var rect = bgEl.getBoundingClientRect();
console.log('[captcha-direct-page] click result:', JSON.stringify(result).substring(0, 260));
for (var i = 0; i < result.click_coords.length; i++) {
var c = result.click_coords[i];
var nx = Number(c.nx);
var ny = Number(c.ny);
if (!Number.isFinite(nx) && Number.isFinite(Number(c.rel_x))) nx = Number(c.rel_x) / rect.width;
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));
await new Promise(function(r) { setTimeout(r, 220); });
}
await new Promise(function(r) { setTimeout(r, 350); });
if (CAPTCHA_CFG.AUTO_CAPTCHA_CONFIRM) {
findAndClickConfirm();
} else {
console.log('[captcha-direct-page] captcha confirm is disabled; waiting for manual confirm');
}
var target = resolveCaptchaDirectTarget(bgEl, bgUrl);
var result = await requestCaptchaDirectResult(payloadText, target.bgUrl);
await clickCaptchaDirectCoords(target.bgEl, result);
await finishCaptchaDirectConfirm();
} catch (e) {
console.error('[captcha-direct-page] error:', e);
captchaSent = false;
lastCaptchaText = '';
resetCaptchaSession();
} finally {
rushState = 'idle';
setCaptchaState('idle');
}
}
function collectCaptchaChallenge() {
var found = findCaptchaPromptElement();
if (!found) {
setCaptchaSent(false);
return null;
}
var bgEl = findCaptchaBgElementDirect();
var bgUrl = captchaBgUrlFrom(bgEl);
if (!bgEl || !bgUrl) {
setCaptchaSent(false);
return null;
}
var payloadText = found.chars.join('');
if (!payloadText) {
setCaptchaSent(false);
return null;
}
return { found: found, bgEl: bgEl, bgUrl: bgUrl, payloadText: payloadText };
}
function syncCaptchaChallengeText(challenge) {
if (challenge.payloadText !== getCaptchaLastText()) {
setCaptchaLastText(challenge.payloadText);
setCaptchaSent(false);
console.log('[captcha] sel:', challenge.found.selector);
console.log('[captcha] raw:', challenge.found.text);
console.log('[captcha] prompt:', challenge.payloadText);
}
}
function tryStartCaptchaDirectSolve(challenge) {
if (isCaptchaSent()) return;
setCaptchaSent(true);
console.log('[captcha] page direct solver:', challenge.payloadText);
handleCaptchaDirectInPage(challenge.found.chars, challenge.bgEl, challenge.bgUrl).catch(function(e) {
console.error('[captcha-direct-page] unhandled:', e);
});
}
async function checkCaptchaPrompt() {
if (rushState === 'solving') return;
var found = findCaptchaPromptElement();
if (!found) { captchaSent = false; return; }
var payloadText = found.chars.join('');
if (!payloadText) { captchaSent = false; return; }
if (payloadText !== lastCaptchaText) {
lastCaptchaText = payloadText;
captchaSent = false;
console.log('[captcha] sel:', found.selector);
console.log('[captcha] raw:', found.text);
console.log('[captcha] prompt:', payloadText);
}
if (!captchaSent) {
captchaSent = true;
console.log('[captcha] page direct solver:', payloadText);
handleCaptchaDirectInPage(found.chars).catch(function(e) { console.error('[captcha-direct-page] unhandled:', e); });
return;
if (RUSH_CONFIG.enabled) {
handleCaptchaRush(found.chars).catch(function(e) { console.error('[captcha-rush] unhandled:', e); });
} else {
serverRequest('POST', '/captcha', { text: payloadText, ts: Date.now() })
.then(function() { console.log('[captcha] legacy ok'); })
.catch(function(e) { console.log('[captcha] legacy err:', e); });
}
}
if (getCaptchaState() === 'solving') return;
var challenge = collectCaptchaChallenge();
if (!challenge) return;
syncCaptchaChallengeText(challenge);
tryStartCaptchaDirectSolve(challenge);
}
setInterval(checkCaptchaPrompt, 50);
console.log('[captcha] bridge v2 started | rush=' + RUSH_CONFIG.enabled + ' | wi=' + getWindowIndex() + ' | target=10:' + String(RUSH_CONFIG.targetSec).padStart(2,'0') + '+' + (getWindowIndex() * RUSH_CONFIG.staggerMs / 1000) + 's');
console.log('[captcha] bridge v2 started | rush=' + RUSH_CFG.enabled + ' | wi=' + getWindowIndex() + ' | target=' + String(RUSH_CFG.targetHour).padStart(2,'0') + ':' + String(RUSH_CFG.targetMin).padStart(2,'0') + ':' + String(RUSH_CFG.targetSec).padStart(2,'0') + '+' + (getWindowIndex() * RUSH_CFG.staggerMs / 1000) + 's');
})();
+1 -1
View File
@@ -106,7 +106,7 @@ Recommended:
Manual:
- one-click-start.cmd installs the CPU backend environment on first run.
- start-backend-pipeline-gui.cmd launches the pipeline backend with a Tk GUI window.
- start-backend.cmd starts CPU backend after environment exists.
- scripts\start_backend.ps1 starts the backend after environment exists.
"@
Set-Content -LiteralPath (Join-Path $OnlineDir "ONLINE_INSTALLER_README.txt") -Value $OnlineGuide -Encoding UTF8
New-Zip -PackageDir $OnlineDir -ZipPath (Join-Path $OutRoot "$OnlineName.zip")