mirror of
https://github.com/OLmatter/glm-coding-helper.git
synced 2026-10-07 12:57:52 +08:00
Tune rush release timing
This commit is contained in:
@@ -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 反馈的冲刺模式提前发请求问题。开启冲刺模式后,目标时间窗口外不再自动点击订阅;如果验证码被提前打开,也不会在窗口外自动点击“确定”。
|
||||
|
||||
@@ -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`。
|
||||
- **核心原则**:先把流程跑稳,再去追求更快。窗口不要开太多,请求不要堆太密,能稳定跑完一整条链路通常比盲目并发更重要。
|
||||
|
||||
|
||||
+67
-15
@@ -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 @@
|
||||
<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>
|
||||
<span title="开启后,脚本只会在目标时间前最后 10 秒内自动点击订阅,并把验证码确定卡到目标时间附近。目标窗口外不自动发起真实抢购请求。" 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>
|
||||
<span title="开启后,脚本只会在目标时间前最后 10 秒内自动点击订阅,并根据实测 RTT 保守提前释放验证码确定。目标窗口外不自动发起真实抢购请求。" 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="display:flex;align-items:center;gap:6px;padding-left:26px">
|
||||
<span style="font-size:13px;color:#888">目标时间</span>
|
||||
@@ -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 = (() => {
|
||||
|
||||
Reference in New Issue
Block a user