From 86b64c4248a8a9d5b50215b2681e1668bd5342ad Mon Sep 17 00:00:00 2001 From: ephron Date: Sat, 18 Apr 2026 22:22:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AF=84=E5=AE=A1=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=9AA1-A5/B1-B7/C1-C6/D1-D4=20=E5=85=A8=E9=83=A82?= =?UTF-8?q?2=E9=A1=B9=E4=BF=AE=E6=94=B9=E6=94=B6=E5=B0=BE=20-=20=E6=8D=A1?= =?UTF-8?q?=E6=BC=8F=E6=A8=A1=E5=BC=8F=E7=A7=BB=E5=85=A5=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=E5=86=85=E9=83=A8=EF=BC=88=E9=9D=9E=E6=AD=BB=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=89=20-=20rAF/interval=20=E5=8F=8C=E9=87=8D=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E6=94=B9=20else=20if=20=E4=BA=92=E6=96=A5=20-=20?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=86=97=E4=BD=99=20'555'=20=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E5=92=8C=E9=87=8D=E5=A4=8D=20EXPIRE=20-=20=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E9=94=99=E8=AF=AF=E8=AE=A1=E6=95=B0=E5=9F=BA=E5=87=86?= =?UTF-8?q?=E4=BB=8E=20batchSize=20=E6=94=B9=E4=B8=BA=20failedResults.leng?= =?UTF-8?q?th=20-=20Fetch=20=E6=8B=A6=E6=88=AA=E5=8A=A0=20needsPatch=20?= =?UTF-8?q?=E5=BF=AB=E9=80=9F=E7=9F=AD=E8=B7=AF=20-=20=E8=B7=A8=E5=A4=A9?= =?UTF-8?q?=E9=A1=BA=E5=BB=B6=E9=80=BB=E8=BE=91=EF=BC=88=E8=B6=85=E8=BF=87?= =?UTF-8?q?30=E7=A7=92=E7=A7=BB=E8=87=B3=E6=98=8E=E5=A4=A9=EF=BC=89=20-=20?= =?UTF-8?q?rAF=20=E5=8F=AF=E9=80=9A=E8=BF=87=20=5FglmRafCancelled=20?= =?UTF-8?q?=E4=B8=AD=E6=96=AD=20-=20setState=20=E6=94=B9=E7=94=A8=20Object?= =?UTF-8?q?.assign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- glm-rush-v4.user.js | 78 +++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/glm-rush-v4.user.js b/glm-rush-v4.user.js index 97b0248..26796ed 100644 --- a/glm-rush-v4.user.js +++ b/glm-rush-v4.user.js @@ -91,7 +91,7 @@ }; function setState(patch) { - state = { ...state, ...patch }; + Object.assign(state, patch); refreshUI(); } @@ -338,7 +338,8 @@ 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) { @@ -354,8 +355,8 @@ return { ok: false }; } - // 只有 429(限流)才退避,555 和 EXPIRE 无延迟立即重试 - if (reasons.every(r => r === 'EXPIRE' || r === '系统繁忙' || r === '555')) continue; + // 只有 429(限流)才退避,EXPIRE 和系统繁忙无延迟立即重试 + if (reasons.every(r => r === 'EXPIRE' || r === '系统繁忙')) continue; // 限流检测 (独立计数) if (reasons.some(r => r.includes('429') || r.includes('限流'))) { @@ -367,9 +368,6 @@ throttleCount = 0; } - // EXPIRE → 立即重试不等待 - if (reasons.every(r => r === 'EXPIRE')) continue; - // 前20秒全速冲,之后才考虑降速 const elapsedSec = (performance.now() - state.stats.startTime) / 1000; @@ -398,25 +396,29 @@ // 自适应延迟 const d = getDelay(roundNum); if (d > 0) await sleep(d); + + // 超过 maxRetry 且在5分钟内 → 切捡漏模式 + if (totalAttempt >= CFG.maxRetry) { + const elapsedSec2 = (performance.now() - state.stats.startTime) / 1000; + if (elapsedSec2 < 300) { + log('进入捡漏模式,降速等待退票...'); + CFG._savedConcurrency = CFG.concurrency; + CFG._savedMaxRetry = CFG.maxRetry; + CFG.concurrency = 2; + CFG.slowDelay = 3000; + CFG.maxRetry = totalAttempt + 200; + continue; + } else { + setState({ status: 'failed' }); + CFG.concurrency = CFG._savedConcurrency ?? CFG.concurrency; + CFG.maxRetry = CFG._savedMaxRetry ?? CFG.maxRetry; + log(`达到上限 ${CFG._savedMaxRetry ?? CFG.maxRetry} 次`); + return { ok: false }; + } + } } - if (!stopRequested) { - const elapsed = (performance.now() - state.stats.startTime) / 1000; - if (elapsed < 300) { - log('进入捡漏模式(10:00-10:05),降速等待退票...'); - CFG._savedConcurrency = CFG.concurrency; - CFG.concurrency = 2; - CFG.slowDelay = 3000; - // 继续循环不走 else - } else { - setState({ status: 'failed' }); - CFG.concurrency = CFG._savedConcurrency ?? CFG.concurrency; - } - } else { - CFG.concurrency = CFG._savedConcurrency ?? CFG.concurrency; - } return { ok: false }; - })(); try { return await _retryLock; } finally { _retryLock = null; } @@ -486,19 +488,20 @@ const ct = resp.headers.get('content-type') || ''; if (ct.includes('application/json')) { const text = await resp.text(); - if (/"isSoldOut":true|"soldOut":true|"isServerBusy":true/.test(text)) { - const patched = 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'); - return new Response(patched, { + const needsPatch = /"isSoldOut":true|"soldOut":true|"isServerBusy":true|"stock":0/.test(text); + if (!needsPatch) { + return new Response(text, { status: resp.status, statusText: resp.statusText, headers: resp.headers, }); } - return new Response(text, { + const patched = 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'); + return new Response(patched, { status: resp.status, statusText: resp.statusText, headers: resp.headers, @@ -786,6 +789,7 @@ function stopAll() { stopRequested = true; + if (window._glmRafCancelled) window._glmRafCancelled(); _activeControllers.forEach(ac => { try { ac.abort(); } catch {} }); _activeControllers = []; setState({ proactive: false, status: 'idle', count: 0 }); @@ -836,9 +840,12 @@ log(`已过${CFG.rushTime} ${passedSec.toFixed(0)}秒, 立即开抢!`); startProactive(); } else { + // 超过30秒,顺延至明天 log(`今天${CFG.rushTime}已过, 明天自动抢购`); + target.setDate(target.getDate() + 1); + log(`顺延至明天 ${CFG.rushTime}`); } - return; + // 如果是顺延情况,继续往下设定定时 } // 未到时间 → 自动设定定时 @@ -869,16 +876,17 @@ // 精确等待: 用 setInterval 10ms 检查, 到时间立即启动 const preAdvanceMs = (CFG.preAdvanceSec || 0) * 1000; + let rafCancelled = false; + window._glmRafCancelled = () => { rafCancelled = true; }; const checkInterval = setInterval(() => { const remaining = target.getTime() - getServerNow(); if (remaining <= preAdvanceMs) { clearInterval(checkInterval); startProactive(); - } - // 额外:在剩余1秒内用 requestAnimationFrame 做最后精确对齐 - if (remaining > 0 && remaining <= 1000) { + } else if (remaining > 0 && remaining <= 1000) { clearInterval(checkInterval); function rafWait() { + if (rafCancelled || stopRequested) return; if (target.getTime() - getServerNow() <= preAdvanceMs) { startProactive(); } else {