From a0fb927f788127a702e1fe9e45c5e726cbccd5d1 Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Sat, 18 Apr 2026 23:24:22 +0800 Subject: [PATCH] feat: apply all A/B/C/D modifications to v4.6 --- glm-rush-v4.user.js | 165 ++++++++++++++++++++++++++++++-------------- 1 file changed, 114 insertions(+), 51 deletions(-) diff --git a/glm-rush-v4.user.js b/glm-rush-v4.user.js index b168f2a..06b7808 100644 --- a/glm-rush-v4.user.js +++ b/glm-rush-v4.user.js @@ -11,6 +11,7 @@ // ==/UserScript== (function () { + const VERSION = '4.6'; 'use strict'; // ═══════════════════════════════════════════ @@ -68,11 +69,19 @@ // 恢复上次捕获的请求 try { - const saved = sessionStorage.getItem('glm_rush_captured'); - if (saved) state.captured = JSON.parse(saved); + const raw = sessionStorage.getItem('glm_rush_captured'); + if (raw) { + const parsed = JSON.parse(raw); + if (parsed.__v !== 1) { + sessionStorage.removeItem('glm_rush_captured'); + } else { + state.captured = parsed; + } + } } catch {} let stopRequested = false; + let _activeControllers = []; let recovering = false; let recoveryAttempts = 0; let _shadowRef = null; @@ -85,9 +94,9 @@ const rand = (min, max) => min + Math.random() * (max - min); const jitteredDelay = base => Math.round(base * (1 + (Math.random() * 2 - 1) * CFG.jitter)); - function getDelay(attempt) { - if (attempt <= CFG.burstCount) return 0; - if (attempt <= 50) return jitteredDelay(CFG.fastDelay); + function getDelay(round) { + if (round <= CFG.burstCount) return 0; + if (round <= 50) return jitteredDelay(CFG.fastDelay); return jitteredDelay(CFG.slowDelay); } @@ -116,6 +125,8 @@ function patchSoldOut(obj, visited = new WeakSet()) { if (!obj || typeof obj !== 'object' || visited.has(obj)) return; + if (obj.__ob__ !== undefined) return; + if (obj.__v_isVNode || obj.__v_isRef) return; visited.add(obj); if (obj.isSoldOut === true) obj.isSoldOut = false; if (obj.soldOut === true) obj.soldOut = false; @@ -128,6 +139,17 @@ } } + // ═══════════════════════════════════════════ + // Vue 2/3 兼容获取根实例 + // ═══════════════════════════════════════════ + function getVueRoot(selector = '#app') { + const el = document.querySelector(selector); + if (!el) return null; + if (el.__vue__) return el.__vue__; + if (el.__vue_app__) return el.__vue_app__._instance?.proxy; + return null; + } + // 全局 patch: 页面加载时也需要解除售罄状态,否则按钮不可点击 JSON.parse = function (text, reviver) { const result = _parse(text, reviver); @@ -163,8 +185,17 @@ } const text = await resp.text(); + // 全局 patch: 对所有 JSON 响应移除售罄/繁忙标记,只在命中时替换 + const needsPatch = /"isSoldOut":true|"soldOut":true|"isServerBusy":true|"stock":0/.test(text); + const patchedText = needsPatch + ? text + .replace(/"isSoldOut":true/g, '"isSoldOut":false') + .replace(/"soldOut":true/g, '"soldOut":false') + .replace(/"isServerBusy":true/g, '"isServerBusy":false') + .replace(/"stock":0/g, '"stock":999') + : text; let data; - try { data = _parse(text); } catch { data = null; } + try { data = _parse(patchedText); } catch { data = null; } if (data && data.code === 200 && data.data && data.data.bizId) { const bizId = data.data.bizId; @@ -180,9 +211,17 @@ if (checkData && checkData.data === 'EXPIRE') { return { ok: false, reason: 'EXPIRE', attempt: attemptNum }; } + // 新增:金额为0 = 空单,继续重试 + const payData = checkData?.data; + if (payData) { + const amount = payData.amount ?? payData.totalAmount ?? payData.payAmount ?? 0; + if (amount === 0) { + return { ok: false, reason: '空单(金额为0)', attempt: attemptNum }; + } + } // 通过! - return { ok: true, text, data, bizId, status: resp.status, attempt: attemptNum }; + return { ok: true, text: patchedText, data, bizId, status: resp.status, attempt: attemptNum }; } catch (e) { return { ok: false, reason: `check异常: ${e.message}`, attempt: attemptNum }; } @@ -223,6 +262,7 @@ const curConcurrency = isTurbo ? CFG.turboConcurrency : CFG.concurrency; const batchSize = Math.min(curConcurrency, CFG.maxRetry - totalAttempt); const controllers = []; + _activeControllers = controllers; const promises = []; for (let j = 0; j < batchSize; j++) { @@ -253,7 +293,10 @@ }); // 收集失败原因 (用于日志) - const results = await Promise.all(promises.map(p => p.catch(() => ({ ok: false, reason: '已取消' })))); + const settled = await Promise.allSettled(promises); + const failedResults = settled + .filter(r => r.status === 'fulfilled' && !r.value.ok && r.value.reason !== '已取消') + .map(r => r.value); if (winner) { setState({ @@ -269,12 +312,12 @@ } // 统计错误 - const failedResults = results.filter(r => !r.ok); const reasons = failedResults.map(r => r.reason || '未知'); setState({ stats: { ...state.stats, errors: state.stats.errors + failedResults.length } }); const networkErrors = reasons.filter(r => r.startsWith('网络')).length; - consecutiveErrors = networkErrors === batchSize ? consecutiveErrors + 1 : 0; + consecutiveErrors = (networkErrors > 0 && networkErrors === failedResults.length) + ? consecutiveErrors + 1 : 0; // 连续网络错误 → 暂停 if (consecutiveErrors >= 3) { @@ -293,23 +336,23 @@ // 限流检测 (独立计数) if (reasons.some(r => r.includes('429') || r.includes('限流'))) { throttleCount++; - const backoff = Math.min(2000 * (2 ** Math.min(throttleCount, 4)), 16000); + const backoff = Math.min(1000 * (2 ** Math.min(throttleCount, 3)), 8000); log(`限流, 退避${backoff}ms...`, 'warn'); await sleep(backoff); } else { throttleCount = 0; } - // EXPIRE → 立即重试不等待 - if (reasons.every(r => r === 'EXPIRE')) continue; + // 只有 EXPIRE 和系统繁忙(555)无延迟立即重试 + if (reasons.every(r => r === 'EXPIRE' || r === '系统繁忙')) continue; // 前20秒全速冲,之后才考虑降速 const elapsedSec = (performance.now() - state.stats.startTime) / 1000; if (elapsedSec > 20) { // 超过20秒 — 检测是否该降速 - const soldOutCount = reasons.filter(r => r === '售罄').length; - if (soldOutCount === batchSize) { + const soldOutRatio = reasons.filter(r => r === '售罄').length / batchSize; + if (soldOutRatio >= 0.6) { consecutiveSoldOut++; } else { consecutiveSoldOut = 0; @@ -359,6 +402,7 @@ method: init?.method || 'POST', body: init?.body, headers: extractHeaders(init?.headers), + __v: 1, }; setState({ captured }); try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {} @@ -430,7 +474,7 @@ if (typeof url === 'string' && url.includes(CFG.PREVIEW)) { const self = this; - const captured = { url, method: this._m, body, headers: this._h || {} }; + const captured = { url, method: this._m, body, headers: this._h || {}, __v: 1 }; setState({ captured }); try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {} @@ -546,7 +590,7 @@ // 策略2: 缓存响应 + 重新点购买按钮 setState({ cache: state.lastSuccess }); - const btn = findBuyButton(); + const btn = await waitForBuyButton(); if (btn) { btn.click(); log('已重新点击购买按钮 (策略2)'); @@ -622,16 +666,17 @@ // ═══════════════════════════════════════════ // 主动抢购 & 定时 // ═══════════════════════════════════════════ - function findBuyButton() { - // 优先找 buy-btn 类的按钮(特惠订阅/订阅升级) - for (const el of document.querySelectorAll('button.buy-btn')) { - const t = el.textContent.trim(); - if (el.offsetParent !== null) return el; - } - // 降级:通用匹配,排除导航按钮 - for (const el of document.querySelectorAll('button, [role="button"]')) { - const t = el.textContent.trim(); - if (/购买|抢购|下单|特惠/.test(t) && t.length < 15 && el.offsetParent !== null) return el; + async function waitForBuyButton(timeout = 8000) { + const start = Date.now(); + while (Date.now() - start < timeout) { + for (const el of document.querySelectorAll('button.buy-btn, button[class*="subscribe"], button[class*="buy"]')) { + if (el.offsetParent !== null) return el; + } + for (const el of document.querySelectorAll('button')) { + const t = el.textContent?.trim(); + if (/^特惠订购$|^立即订购$|^立即购买$/.test(t) && el.offsetParent !== null) return el; + } + await sleep(200); } return null; } @@ -659,8 +704,12 @@ // 自动通知 try { new Notification('GLM 抢购成功!', { body: `bizId=${state.bizId}` }); } catch {} const errDlg = findErrorDialog(); - if (errDlg) { dismissDialog(errDlg); await sleep(300); } - const btn = findBuyButton(); + if (errDlg) { + dismissDialog(errDlg); + if (errDlg.offsetParent !== null) recoveryAttempts = 0; + await sleep(300); + } + const btn = await waitForBuyButton(); if (btn) { btn.click(); log('已自动点击购买按钮'); } else { alert('已获取到商品! 请立即点击购买按钮!'); } @@ -759,15 +808,15 @@ const ms = target.getTime() - getServerNow(); log(`定时: ${timeStr} (${Math.ceil(ms / 1000)}秒后, 北京时间)`); - // 提前3秒自动预热 - if (ms > 4000) { - setTimeout(() => { - log('定时前3秒, 自动预热...'); - preheat(); - }, Math.max(0, ms - 3000)); + // 提前5分钟自动预热 + if (ms > 310_000) { + setTimeout(preheat, Math.max(0, ms - 300_000)); } - // 精确等待: 用 setInterval 10ms 检查, 到时间立即启动 + // 精确等待: 用 setInterval 10ms 检查, 剩余1秒切换 rAF + const preAdvanceMs = (CFG.preAdvanceSec || 0) * 1000; + let rafCancelled = false; + window._glmRafCancelled = () => { rafCancelled = true; }; const tid = setInterval(() => { const remaining = target.getTime() - getServerNow(); // 更新面板倒计时 @@ -776,13 +825,26 @@ const timerEl = _shadowRef?.getElementById('timer-info'); if (timerEl) timerEl.textContent = `-${sec}s`; } - if (remaining <= 0) { + if (remaining <= preAdvanceMs) { clearInterval(tid); setState({ timerId: null }); const timerEl = _shadowRef?.getElementById('timer-info'); if (timerEl) timerEl.textContent = ''; - log('时间到! 自动启动抢购!'); + log(`提前 ${CFG.preAdvanceSec}s 触发!`); startProactive(); + } else if (remaining > 0 && remaining <= 1000) { + clearInterval(tid); + function rafWait() { + if (rafCancelled || stopRequested) return; + if (target.getTime() - getServerNow() <= preAdvanceMs) { + const timerEl = _shadowRef?.getElementById('timer-info'); + if (timerEl) timerEl.textContent = ''; + startProactive(); + } else { + requestAnimationFrame(rafWait); + } + } + requestAnimationFrame(rafWait); } }, 10); @@ -791,20 +853,20 @@ // 预热 async function preheat() { - try { - log('TCP预热中...'); - // 连发3次预热请求,确保连接池暖好 - for (let i = 0; i < 3; i++) { - await _fetch(location.origin + '/api/biz/pay/check?bizId=preheat_' + i, { credentials: 'include' }).catch(() => {}); - await sleep(200); - } - // 也预热 preview 的 DNS + TCP (用 HEAD 请求不产生副作用) + log('开始预热连接...'); + let ok = 0; + for (let i = 0; i < 5; i++) { + const r = await _fetch(location.origin + '/favicon.ico', { method: 'HEAD' }).catch(() => null); + if (r) ok++; await _fetch(location.origin + CFG.PREVIEW, { - method: 'HEAD', + method: 'POST', credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + body: '{}', }).catch(() => {}); - log('预热完成 (4次连接已建立)'); - } catch { log('预热部分失败,不影响使用'); } + await sleep(300); + } + log(`预热完成:${ok}/5 连接建立成功`); } // ═══════════════════════════════════════════ @@ -925,7 +987,7 @@ .keys{font-size:10px;color:#636e72;text-align:center;margin-top:6px}
-
GLM v4.6
+
GLM v${VERSION}
等待中
${state.captured ? '已恢复上次捕获的请求' : '请先点一次购买按钮'}
@@ -944,6 +1006,7 @@
+
⚠️ 每次确认放票时间
@@ -1072,7 +1135,7 @@ // ═══════════════════════════════════════════ // 启动 // ═══════════════════════════════════════════ - console.log('[GLM] v4.0 已注入'); + console.log(`[GLM] v${VERSION} 已注入`); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createPanel); } else {