From 2d0834e4aeeaaa87e3786c12ad1038ae2e39c014 Mon Sep 17 00:00:00 2001 From: OLmatter Date: Sun, 21 Jun 2026 03:48:35 +0800 Subject: [PATCH] Tune rush release timing --- CHANGELOG.md | 5 +++ README.md | 2 +- glm-coding-helper.user.js | 82 ++++++++++++++++++++++++++++++++------- 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec1138b..22c5175 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # 修复历史 +## 2026-06-21 + +- 发布用户脚本 v22.4:冲刺模式默认目标从 `09:59:58` 改为 `10:00:00`,并把旧默认配置自动迁移到 10 点整。 +- 冲刺模式释放验证码“确定”时不再使用固定 40ms 提前量;页面会用 3 次 BigModel same-origin 请求估算 RTT,默认按 `RTT/2 + 20ms` 保守提前,并限制在 40-180ms。用户手动设置 `RUSH_RELEASE_ADVANCE_MS > 0` 时仍优先使用手动值。 + ## 2026-06-15 - 发布用户脚本 v22.3:修复 issue #24 反馈的冲刺模式提前发请求问题。开启冲刺模式后,目标时间窗口外不再自动点击订阅;如果验证码被提前打开,也不会在窗口外自动点击“确定”。 diff --git a/README.md b/README.md index 68b7a70..321363c 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ https://www.bigmodel.cn/glm-coding - **窗口数量**:最好只开 **1-2 个窗口**,**推荐最多 2 个**。近期很多 `500` 反馈,最后排查下来并不是单纯页面慢,而是**并发太高、请求太密**导致的 RPM 风控。 - **不要过早放弃**:目前社区和实测里,**到上午 11:00 之前都仍然有抢到的记录**。如果 10 点整这一波没中,不代表当天彻底结束;只要后端、脚本和支付流程都还正常,建议继续坚持抢。 - **无痕模式**:如果之前抢过且账号疑似被风控盯上,建议试试 Chrome / Edge 的**无痕模式窗口**(`Ctrl+Shift+N`)。无痕窗口没有历史 Cookie / 缓存 / Service Worker / 本地存储,可能减少隐形风控标记。注意要在 Tampermonkey 扩展详情里允许脚本在无痕模式运行。 -- **Rush mode**:`Rush mode(定时确认)` 开启后,脚本会继续扫描页面,但只在目标时间前最后 10 秒内自动点击订阅,并把验证码“确定”卡到目标时间附近;目标窗口外不自动发起真实抢购请求。 +- **Rush mode**:`Rush mode(定时确认)` 默认目标是 `10:00:00`。开启后,脚本会继续扫描页面,但只在目标时间前最后 10 秒内自动点击订阅,并根据实测 RTT 保守提前释放验证码“确定”(默认自动估算,通常几十到一百多毫秒);目标窗口外不自动发起真实抢购请求。 - **验证码随机延时**:如果近期限流更重、风控更频繁,可以把配置面板里的验证码点字随机延时区间整体调大一点。当前默认是 `250-400ms`;更保守可以试 `300-450ms`,再重一点可以试 `350-500ms`。 - **核心原则**:先把流程跑稳,再去追求更快。窗口不要开太多,请求不要堆太密,能稳定跑完一整条链路通常比盲目并发更重要。 diff --git a/glm-coding-helper.user.js b/glm-coding-helper.user.js index eed3123..5656ba0 100644 --- a/glm-coding-helper.user.js +++ b/glm-coding-helper.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name 智谱 GLM Coding Plan 抢购助手 + 本地 OCR 自动验证码 // @namespace http://tampermonkey.net/ -// @version 22.3 +// @version 22.4 // @description GLM Coding Rush / 智谱 GLM Coding Plan 抢购助手,一键抢购油猴脚本 / Tampermonkey userscript,配合本地 CPU/GPU OCR 自动识别中文点选验证码并点击,支持多窗口并发、限流重试和支付页安全保护 // @author mumumi // @include https://*bigmodel.cn/glm-coding* @@ -266,10 +266,20 @@ document.documentElement.dataset.glmHelper = '1'; // ── 最早读配置(document-start 时还没有主流程)────────────────────────── const EARLY_STORAGE_KEY = 'glm_coding_config_v5'; - const SAFE_DEFAULTS_VERSION = 2; + const SAFE_DEFAULTS_VERSION = 3; const _ec = (() => { try { return JSON.parse(GM_getValue(EARLY_STORAGE_KEY, '{}')); } catch { return {}; } })(); if (_ec.SAFE_DEFAULTS_VERSION !== SAFE_DEFAULTS_VERSION) { _ec.AUTO_CLOSE_INVALID = false; + const oldRushTarget = (_ec.RUSH_TARGET_HOUR == null && _ec.RUSH_TARGET_MIN == null && _ec.RUSH_TARGET_SEC == null) || + (Number(_ec.RUSH_TARGET_HOUR) === 9 && Number(_ec.RUSH_TARGET_MIN) === 59 && Number(_ec.RUSH_TARGET_SEC) === 58); + if (oldRushTarget) { + _ec.RUSH_TARGET_HOUR = 10; + _ec.RUSH_TARGET_MIN = 0; + _ec.RUSH_TARGET_SEC = 0; + } + if (_ec.RUSH_RELEASE_ADVANCE_MS == null || Number(_ec.RUSH_RELEASE_ADVANCE_MS) === 40) { + _ec.RUSH_RELEASE_ADVANCE_MS = 0; + } _ec.SAFE_DEFAULTS_VERSION = SAFE_DEFAULTS_VERSION; GM_setValue(EARLY_STORAGE_KEY, JSON.stringify(_ec)); } @@ -514,21 +524,26 @@ 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_TARGET_HOUR : 10, + RUSH_TARGET_MIN : 0, + RUSH_TARGET_SEC : 0, RUSH_HOLD_WINDOW_MS : 10000, - RUSH_RELEASE_ADVANCE_MS: 40, + RUSH_RELEASE_ADVANCE_MS: 0, }; 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(); + const RUSH_LATENCY_KEY = 'glm_rush_latency_v1'; + function rushNumber(value, fallback) { + const n = parseInt(value, 10); + return Number.isFinite(n) ? n : fallback; + } function getRushTargetTimestamp(now = Date.now()) { const target = new Date(now); target.setHours( - parseInt(CFG.RUSH_TARGET_HOUR, 10) || 9, - parseInt(CFG.RUSH_TARGET_MIN, 10) || 59, - parseInt(CFG.RUSH_TARGET_SEC, 10) || 58, + rushNumber(CFG.RUSH_TARGET_HOUR, 10), + rushNumber(CFG.RUSH_TARGET_MIN, 0), + rushNumber(CFG.RUSH_TARGET_SEC, 0), 0 ); return target.getTime(); @@ -543,6 +558,36 @@ const holdWindow = Math.max(0, parseInt(CFG.RUSH_HOLD_WINDOW_MS, 10) || 10000); return remaining <= holdWindow; } + function medianRush(values) { + const sorted = values.filter(Number.isFinite).sort((a, b) => a - b); + if (!sorted.length) return null; + return sorted[Math.floor(sorted.length / 2)]; + } + function computeRushReleaseAdvance(rttMs) { + const measured = Number.isFinite(rttMs) ? rttMs : 120; + return Math.max(40, Math.min(180, Math.round(measured / 2 + 20))); + } + async function calibrateRushLatency() { + if (!CFG.RUSH_ENABLED) return; + const samples = []; + const url = GLM_CODING_URL() + '&rush_probe=' + Date.now(); + for (let i = 0; i < 3; i++) { + try { + const t0 = performance.now(); + await fetch(url + '_' + i, { method: 'GET', credentials: 'include', cache: 'no-store' }); + samples.push(performance.now() - t0); + } catch (e) {} + if (i < 2) await new Promise(r => setTimeout(r, 160)); + } + const rtt = medianRush(samples); + if (rtt == null) return; + const manualAdvance = Math.max(0, parseInt(CFG.RUSH_RELEASE_ADVANCE_MS, 10) || 0); + const autoAdvance = computeRushReleaseAdvance(rtt); + const advance = manualAdvance > 0 ? manualAdvance : autoAdvance; + const payload = { rttMs: Math.round(rtt), advanceMs: advance, at: Date.now(), source: manualAdvance > 0 ? 'manual' : 'auto' }; + GM_setValue(RUSH_LATENCY_KEY, JSON.stringify(payload)); + console.log('[GLM] rush latency calibrated:', payload); + } GM_registerMenuCommand('⚙️ 打开配置面板', openConfigPanel); GM_registerMenuCommand('🗑️ 清除今日套餐状态缓存', () => { localStorage.removeItem(_dsKey); alert('今日状态已清除,即将刷新。'); location.reload(); }); GM_registerMenuCommand('🚀 一键多开窗口', openMultipleWindows); @@ -916,7 +961,7 @@
目标时间 @@ -961,6 +1006,7 @@ 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), + RUSH_RELEASE_ADVANCE_MS: CFG.RUSH_RELEASE_ADVANCE_MS, SAFE_DEFAULTS_VERSION, }); ov.remove(); alert('已保存,即将刷新。'); location.reload(); @@ -1202,6 +1248,7 @@ return true; } if (checkLogin()) { + calibrateRushLatency(); setInterval(tick, CFG.CHECK_INTERVAL); const _startDOM = () => { setInterval(forceEnableButtons, 500); @@ -1226,19 +1273,24 @@ const ph = parseInt(cfg.RUSH_TARGET_HOUR, 10); const pm = parseInt(cfg.RUSH_TARGET_MIN, 10); const ps = parseInt(cfg.RUSH_TARGET_SEC, 10); + const manualAdvance = Math.max(0, parseInt(cfg.RUSH_RELEASE_ADVANCE_MS, 10) || 0); + const latencyRaw = GM_getValue('glm_rush_latency_v1', '{}'); + const latency = JSON.parse(latencyRaw || '{}'); + const latencyFresh = latency && Number.isFinite(Number(latency.advanceMs)) && Date.now() - Number(latency.at || 0) < 10 * 60 * 1000; + const releaseAdvanceMs = manualAdvance > 0 ? manualAdvance : (latencyFresh ? Math.max(40, Math.min(180, Number(latency.advanceMs))) : 80); return { enabled: cfg.RUSH_ENABLED === true, - targetHour: Number.isFinite(ph) ? ph : 9, - targetMin: Number.isFinite(pm) ? pm : 59, - targetSec: Number.isFinite(ps) ? ps : 58, + targetHour: Number.isFinite(ph) ? ph : 10, + targetMin: Number.isFinite(pm) ? pm : 0, + targetSec: Number.isFinite(ps) ? ps : 0, holdWindowMs: Math.max(0, parseInt(cfg.RUSH_HOLD_WINDOW_MS, 10) || 10000), - releaseAdvanceMs: Math.max(0, parseInt(cfg.RUSH_RELEASE_ADVANCE_MS, 10) || 40), + releaseAdvanceMs: releaseAdvanceMs, 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 }; + return { enabled: false, targetHour: 10, targetMin: 0, targetSec: 0, holdWindowMs: 10000, releaseAdvanceMs: 80, staggerMs: 2000, pollInterval: 50, pollTimeout: 20000 }; } })(); const CAPTCHA_CFG = (() => {