feat: v4.4 北京时间同步+全自动抢购+极速模式+抢到停止+浏览器通知

- 同步服务器时间(Date头+worldtimeapi备用), 精确到毫秒
- 定时前3秒自动预热
- 极速模式: 前5秒10路并发, 之后5路
- 请求指纹随机化(X-Request-Id/Timestamp/Accept-Language)
- 抢到后不再重复抢购
- 成功后浏览器通知
- 面板实时倒计时显示
This commit is contained in:
qtaxm
2026-04-08 11:59:52 +08:00
parent 47a7ae227c
commit 1fbac26f15
2 changed files with 225 additions and 95 deletions

View File

@@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name 智谱 GLM Coding 抢购助手 v4.0 // @name 智谱 GLM Coding 抢购助手 v4.0
// @namespace http://tampermonkey.net/ // @namespace http://tampermonkey.net/
// @version 4.3 // @version 4.4
// @description 并发重试 + 自适应间隔 + 反检测 + check校验 + 弹窗恢复 + 定时触发 + 配置持久化 // @description 并发重试 + 自适应间隔 + 反检测 + check校验 + 弹窗恢复 + 定时触发 + 配置持久化
// @author Assistant // @author Assistant
// @match *://www.bigmodel.cn/* // @match *://www.bigmodel.cn/*
@@ -17,11 +17,13 @@
// 配置 (localStorage 持久化) // 配置 (localStorage 持久化)
// ═══════════════════════════════════════════ // ═══════════════════════════════════════════
const DEFAULT_CFG = { const DEFAULT_CFG = {
concurrency: 3, // 并发路数 concurrency: 5, // 并发路数 (普通模式)
turboConcurrency: 10, // 极速模式并发路数
turboSec: 5, // 极速模式持续秒数
maxRetry: 2000, // 最大重试次数 maxRetry: 2000, // 最大重试次数
burstCount: 10, // 前N次零延迟爆发 burstCount: 20, // 前N次零延迟爆发
fastDelay: 50, // 爆发后的快速间隔 fastDelay: 30, // 爆发后的快速间隔
slowDelay: 150, // 后期随机间隔中值 slowDelay: 100, // 后期随机间隔中值
jitter: 0.3, // 间隔随机抖动 ±30% jitter: 0.3, // 间隔随机抖动 ±30%
recoveryMax: 3, // 弹窗恢复最大次数 recoveryMax: 3, // 弹窗恢复最大次数
logMax: 100, // 日志条数上限 logMax: 100, // 日志条数上限
@@ -140,7 +142,15 @@
async function singleAttempt(url, opts, attemptNum) { async function singleAttempt(url, opts, attemptNum) {
try { try {
const resp = await _fetch(url, { ...opts, credentials: 'include' }); // 请求指纹随机化 — 每次请求看起来不一样,降低被识别为脚本的概率
const randHeaders = { ...opts.headers };
randHeaders['X-Request-Id'] = Math.random().toString(36).slice(2, 15);
randHeaders['X-Timestamp'] = String(Date.now());
// 随机 Accept-Language 权重,让每次请求指纹不同
const q = (0.5 + Math.random() * 0.5).toFixed(1);
randHeaders['Accept-Language'] = `zh-CN,zh;q=${q},en;q=${(q * 0.7).toFixed(1)}`;
const resp = await _fetch(url, { ...opts, headers: randHeaders, credentials: 'include' });
// HTTP 状态码检测 // HTTP 状态码检测
if (resp.status === 401 || resp.status === 403) { if (resp.status === 401 || resp.status === 403) {
@@ -205,7 +215,11 @@
let consecutiveSoldOut = 0; let consecutiveSoldOut = 0;
while (totalAttempt < CFG.maxRetry && !stopRequested) { while (totalAttempt < CFG.maxRetry && !stopRequested) {
const batchSize = Math.min(CFG.concurrency, CFG.maxRetry - totalAttempt); // 极速模式: 前N秒用更高并发
const elapsedMs = performance.now() - state.stats.startTime;
const isTurbo = elapsedMs < CFG.turboSec * 1000;
const curConcurrency = isTurbo ? CFG.turboConcurrency : CFG.concurrency;
const batchSize = Math.min(curConcurrency, CFG.maxRetry - totalAttempt);
const controllers = []; const controllers = [];
const promises = []; const promises = [];
@@ -347,6 +361,12 @@
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {} try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
log('捕获 preview (Fetch)'); log('捕获 preview (Fetch)');
// 已经成功过 → 直接返回缓存,不再重试
if (state.status === 'success' && state.lastSuccess) {
log('已抢到, 返回成功响应');
return new Response(state.lastSuccess.text, { status: 200, headers: { 'Content-Type': 'application/json' } });
}
if (state.cache) { if (state.cache) {
log('返回缓存响应'); log('返回缓存响应');
const c = state.cache; const c = state.cache;
@@ -404,6 +424,13 @@
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {} try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
log('捕获 preview (XHR)'); log('捕获 preview (XHR)');
// 已经成功过 → 直接返回缓存
if (state.status === 'success' && state.lastSuccess) {
log('已抢到, 返回成功响应 (XHR)');
fakeXHR(self, state.lastSuccess.text);
return;
}
if (state.cache) { if (state.cache) {
log('返回缓存响应 (XHR)'); log('返回缓存响应 (XHR)');
const c = state.cache; setState({ cache: null }); const c = state.cache; setState({ cache: null });
@@ -596,11 +623,15 @@
async function startProactive() { async function startProactive() {
if (!state.captured) { if (!state.captured) {
log('请先手动点一次购买按钮'); log('请先手动点一次购买按钮');
alert('请先手动点一次购买按钮,让脚本捕获请求参数'); alert('请先手动点一次购买/订阅按钮,让脚本捕获请求参数');
return;
}
if (state.status === 'success') {
log('已经抢到了, 不重复抢购');
return; return;
} }
setState({ proactive: true }); setState({ proactive: true });
log('主动抢购启动 (并发=' + CFG.concurrency + ')'); log(`极速抢购启动! 前${CFG.turboSec}${CFG.turboConcurrency}路并发, 之后${CFG.concurrency}`);
const { url, method, body, headers } = state.captured; const { url, method, body, headers } = state.captured;
const result = await retry(url, { method, body, headers }); const result = await retry(url, { method, body, headers });
@@ -608,11 +639,13 @@
if (result.ok) { if (result.ok) {
setState({ cache: { text: result.text, data: result.data } }); setState({ cache: { text: result.text, data: result.data } });
log('主动模式成功!'); log('抢购成功! 触发支付...');
// 自动通知
try { new Notification('GLM 抢购成功!', { body: `bizId=${state.bizId}` }); } catch {}
const errDlg = findErrorDialog(); const errDlg = findErrorDialog();
if (errDlg) { dismissDialog(errDlg); await sleep(300); } if (errDlg) { dismissDialog(errDlg); await sleep(300); }
const btn = findBuyButton(); const btn = findBuyButton();
if (btn) { btn.click(); log('已点击购买按钮'); } if (btn) { btn.click(); log('已自动点击购买按钮'); }
else { alert('已获取到商品! 请立即点击购买按钮!'); } else { alert('已获取到商品! 请立即点击购买按钮!'); }
} }
} }
@@ -620,40 +653,90 @@
function stopAll() { function stopAll() {
stopRequested = true; stopRequested = true;
setState({ proactive: false, status: 'idle', count: 0 }); setState({ proactive: false, status: 'idle', count: 0 });
if (state.timerId) { clearTimeout(state.timerId); setState({ timerId: null }); } if (state.timerId) { clearInterval(state.timerId); setState({ timerId: null }); }
log('已停止'); log('已停止');
} }
// 高精度定时 // ═══════════════════════════════════════════
function scheduleAt(timeStr) { // 北京时间同步 + 自动定时
if (state.timerId) { clearTimeout(state.timerId); setState({ timerId: null }); } // ═══════════════════════════════════════════
const parts = timeStr.split(':').map(Number); let serverTimeOffset = 0; // 本地时间与服务器时间的差值(ms)
const now = new Date();
const target = new Date(now.getFullYear(), now.getMonth(), now.getDate(), parts[0], parts[1], parts[2] || 0);
if (target <= now) { log('目标时间已过'); return; }
const ms = target - now;
log(`定时: ${timeStr} (${Math.ceil(ms / 1000)}秒后)`);
// 提前500ms开始用rAF精确等待 async function syncServerTime() {
const earlyMs = Math.max(0, ms - 500); // 用服务器响应头的 Date 字段同步时间
const tid = setTimeout(() => { try {
const targetTs = performance.now() + 500; const t0 = Date.now();
function tick() { const resp = await _fetch(location.origin + '/api/biz/pay/check?bizId=sync', { credentials: 'include' }).catch(() => null);
if (performance.now() >= targetTs) { const t1 = Date.now();
log('时间到! 启动抢购!'); const rtt = t1 - t0;
setState({ timerId: null });
if (state.captured) startProactive(); if (resp && resp.headers.get('date')) {
else { const serverTime = new Date(resp.headers.get('date')).getTime();
const btn = findBuyButton(); // 服务器时间 ≈ 发送时间 + RTT/2
if (btn) btn.click(); serverTimeOffset = serverTime - (t0 + rtt / 2);
else alert('定时到了! 请手动点击购买!'); const localNow = new Date(Date.now() + serverTimeOffset);
} log(`时间同步: 服务器偏差 ${serverTimeOffset > 0 ? '+' : ''}${serverTimeOffset}ms (RTT=${rtt}ms)`);
log(`北京时间: ${localNow.toLocaleTimeString('zh-CN', { hour12: false })}`);
return; return;
} }
requestAnimationFrame(tick); } catch {}
// 备用: 用 worldtimeapi
try {
const resp = await fetch('https://worldtimeapi.org/api/timezone/Asia/Shanghai');
const data = await resp.json();
const serverTime = new Date(data.datetime).getTime();
serverTimeOffset = serverTime - Date.now();
log(`时间同步(备用): 偏差 ${serverTimeOffset > 0 ? '+' : ''}${serverTimeOffset}ms`);
} catch {
log('时间同步失败, 使用本地时钟');
serverTimeOffset = 0;
} }
requestAnimationFrame(tick); }
}, earlyMs);
function getServerNow() {
return Date.now() + serverTimeOffset;
}
// 自动定时: 同步时间后自动等待到 10:00:00
function scheduleAt(timeStr) {
if (state.timerId) { clearInterval(state.timerId); setState({ timerId: null }); }
const parts = timeStr.split(':').map(Number);
if (parts.length < 2 || parts[0] > 23 || parts[1] > 59) { log('时间格式错误'); return; }
const now = new Date(getServerNow());
const target = new Date(now.getFullYear(), now.getMonth(), now.getDate(), parts[0], parts[1], parts[2] || 0);
if (target.getTime() <= getServerNow()) { log('目标时间已过'); return; }
const ms = target.getTime() - getServerNow();
log(`定时: ${timeStr} (${Math.ceil(ms / 1000)}秒后, 北京时间)`);
// 提前3秒自动预热
if (ms > 4000) {
setTimeout(() => {
log('定时前3秒, 自动预热...');
preheat();
}, Math.max(0, ms - 3000));
}
// 精确等待: 用 setInterval 10ms 检查, 到时间立即启动
const tid = setInterval(() => {
const remaining = target.getTime() - getServerNow();
// 更新面板倒计时
if (remaining > 0 && remaining < 60000) {
const sec = (remaining / 1000).toFixed(1);
const timerEl = _shadowRef?.getElementById('timer-info');
if (timerEl) timerEl.textContent = `-${sec}s`;
}
if (remaining <= 0) {
clearInterval(tid);
setState({ timerId: null });
const timerEl = _shadowRef?.getElementById('timer-info');
if (timerEl) timerEl.textContent = '';
log('时间到! 自动启动抢购!');
startProactive();
}
}, 10);
setState({ timerId: tid }); setState({ timerId: tid });
} }
@@ -661,9 +744,19 @@
// 预热 // 预热
async function preheat() { async function preheat() {
try { try {
await _fetch(location.origin + '/api/biz/pay/check?bizId=preheat', { credentials: 'include' }); log('TCP预热中...');
log('TCP预热完成'); // 连发3次预热请求确保连接池暖好
} catch {} 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 请求不产生副作用)
await _fetch(location.origin + CFG.PREVIEW, {
method: 'HEAD',
credentials: 'include',
}).catch(() => {});
log('预热完成 (4次连接已建立)');
} catch { log('预热部分失败,不影响使用'); }
} }
// ═══════════════════════════════════════════ // ═══════════════════════════════════════════
@@ -727,7 +820,7 @@
.keys{font-size:10px;color:#636e72;text-align:center;margin-top:6px} .keys{font-size:10px;color:#636e72;text-align:center;margin-top:6px}
</style> </style>
<div class="panel"> <div class="panel">
<div class="hd" id="drag"><b>GLM v4.3</b><button class="mn" id="min">-</button></div> <div class="hd" id="drag"><b>GLM v4.4</b><button class="mn" id="min">-</button></div>
<div class="bd" id="bd"> <div class="bd" id="bd">
<div class="st st-idle" id="st">等待中</div> <div class="st st-idle" id="st">等待中</div>
<div class="cap" id="cap">${state.captured ? '已恢复上次捕获的请求' : '请先点一次购买按钮'}</div> <div class="cap" id="cap">${state.captured ? '已恢复上次捕获的请求' : '请先点一次购买按钮'}</div>
@@ -737,7 +830,8 @@
<div><div class="v" id="s-err">0</div>错误</div> <div><div class="v" id="s-err">0</div>错误</div>
</div> </div>
<div class="row"> <div class="row">
<span>并发</span><input type="number" id="i-conc" value="${CFG.concurrency}" min="1" max="10" step="1"> <span>并发</span><input type="number" id="i-conc" value="${CFG.concurrency}" min="1" max="20" step="1">
<span>极速</span><input type="number" id="i-turbo" value="${CFG.turboConcurrency}" min="1" max="20" step="1">
<span>上限</span><input type="number" id="i-max" value="${CFG.maxRetry}" min="10" max="9999" step="50"> <span>上限</span><input type="number" id="i-max" value="${CFG.maxRetry}" min="10" max="9999" step="50">
</div> </div>
<div class="row"> <div class="row">
@@ -762,8 +856,9 @@
$('b-stop').onclick = stopAll; $('b-stop').onclick = stopAll;
$('b-heat').onclick = preheat; $('b-heat').onclick = preheat;
$('b-time').onclick = () => { const v = $('i-time').value; if (v) scheduleAt(v); }; $('b-time').onclick = () => { const v = $('i-time').value; if (v) scheduleAt(v); };
$('i-conc').onchange = function() { CFG.concurrency = Math.max(1, +this.value || 3); saveCfg(CFG); }; $('i-conc').onchange = function() { CFG.concurrency = Math.max(1, +this.value || 5); saveCfg(CFG); };
$('i-max').onchange = function() { CFG.maxRetry = Math.max(10, +this.value || 500); saveCfg(CFG); }; $('i-turbo').onchange = function() { CFG.turboConcurrency = Math.max(1, +this.value || 10); saveCfg(CFG); };
$('i-max').onchange = function() { CFG.maxRetry = Math.max(10, +this.value || 2000); saveCfg(CFG); };
$('min').onclick = function() { $('min').onclick = function() {
const bd = $('bd'); const bd = $('bd');
const hidden = bd.style.display === 'none'; const hidden = bd.style.display === 'none';
@@ -786,9 +881,17 @@
// 闭包引用供 refreshUI 使用 // 闭包引用供 refreshUI 使用
_shadowRef = shadow; _shadowRef = shadow;
log('v4.0 已加载 (并发重试+反检测+高精度定时)'); log('v4.4 已加载 (极速并发+时间同步+全自动抢购)');
if (state.captured) log('已恢复上次捕获的请求参数'); if (state.captured) log('已恢复上次捕获的请求参数, 可直接设定时间');
setupDialogWatcher(); setupDialogWatcher();
// 自动同步服务器时间
syncServerTime();
// 请求通知权限
if (Notification && Notification.permission === 'default') {
Notification.requestPermission();
}
} }
// ═══════════════════════════════════════════ // ═══════════════════════════════════════════
@@ -808,8 +911,9 @@
const stEl = $('st'); const stEl = $('st');
if (stEl) { if (stEl) {
stEl.className = 'st st-' + state.status; stEl.className = 'st st-' + state.status;
const isTurbo = state.stats.startTime && (performance.now() - state.stats.startTime) < CFG.turboSec * 1000;
stEl.textContent = state.status === 'idle' ? '等待中' stEl.textContent = state.status === 'idle' ? '等待中'
: state.status === 'retrying' ? `重试中... ${state.count}/${CFG.maxRetry}` : state.status === 'retrying' ? `${isTurbo ? '⚡极速' : ''}重试中... ${state.count}/${CFG.maxRetry}`
: state.status === 'success' ? `成功! bizId=${state.bizId}` : state.status === 'success' ? `成功! bizId=${state.bizId}`
: `失败 (${state.count}次)`; : `失败 (${state.count}次)`;
} }

102
inject.js
View File

@@ -5,11 +5,13 @@
// 配置 (localStorage 持久化) // 配置 (localStorage 持久化)
// ═══════════════════════════════════════════ // ═══════════════════════════════════════════
const DEFAULT_CFG = { const DEFAULT_CFG = {
concurrency: 3, // 并发路数 concurrency: 5, // 并发路数 (普通模式)
turboConcurrency: 10, // 极速模式并发路数
turboSec: 5, // 极速模式持续秒数
maxRetry: 2000, // 最大重试次数 maxRetry: 2000, // 最大重试次数
burstCount: 10, // 前N次零延迟爆发 burstCount: 20, // 前N次零延迟爆发
fastDelay: 50, // 爆发后的快速间隔 fastDelay: 30, // 爆发后的快速间隔
slowDelay: 150, // 后期随机间隔中值 slowDelay: 100, // 后期随机间隔中值
jitter: 0.3, // 间隔随机抖动 ±30% jitter: 0.3, // 间隔随机抖动 ±30%
recoveryMax: 3, // 弹窗恢复最大次数 recoveryMax: 3, // 弹窗恢复最大次数
logMax: 100, // 日志条数上限 logMax: 100, // 日志条数上限
@@ -128,7 +130,15 @@
async function singleAttempt(url, opts, attemptNum) { async function singleAttempt(url, opts, attemptNum) {
try { try {
const resp = await _fetch(url, { ...opts, credentials: 'include' }); // 请求指纹随机化 — 每次请求看起来不一样,降低被识别为脚本的概率
const randHeaders = { ...opts.headers };
randHeaders['X-Request-Id'] = Math.random().toString(36).slice(2, 15);
randHeaders['X-Timestamp'] = String(Date.now());
// 随机 Accept-Language 权重,让每次请求指纹不同
const q = (0.5 + Math.random() * 0.5).toFixed(1);
randHeaders['Accept-Language'] = `zh-CN,zh;q=${q},en;q=${(q * 0.7).toFixed(1)}`;
const resp = await _fetch(url, { ...opts, headers: randHeaders, credentials: 'include' });
// HTTP 状态码检测 // HTTP 状态码检测
if (resp.status === 401 || resp.status === 403) { if (resp.status === 401 || resp.status === 403) {
@@ -191,10 +201,13 @@
let consecutiveErrors = 0; let consecutiveErrors = 0;
let throttleCount = 0; let throttleCount = 0;
let consecutiveSoldOut = 0; let consecutiveSoldOut = 0;
let consecutiveBusy = 0;
while (totalAttempt < CFG.maxRetry && !stopRequested) { while (totalAttempt < CFG.maxRetry && !stopRequested) {
const batchSize = Math.min(CFG.concurrency, CFG.maxRetry - totalAttempt); // 极速模式: 前N秒用更高并发
const elapsedMs = performance.now() - state.stats.startTime;
const isTurbo = elapsedMs < CFG.turboSec * 1000;
const curConcurrency = isTurbo ? CFG.turboConcurrency : CFG.concurrency;
const batchSize = Math.min(curConcurrency, CFG.maxRetry - totalAttempt);
const controllers = []; const controllers = [];
const promises = []; const promises = [];
@@ -276,42 +289,29 @@
// EXPIRE → 立即重试不等待 // EXPIRE → 立即重试不等待
if (reasons.every(r => r === 'EXPIRE')) continue; if (reasons.every(r => r === 'EXPIRE')) continue;
// 连续售罄检测 — 非抢购时间不要浪费重试 // 前20秒全速冲之后才考虑降速
const elapsedSec = (performance.now() - state.stats.startTime) / 1000;
if (elapsedSec > 20) {
// 超过20秒 — 检测是否该降速
const soldOutCount = reasons.filter(r => r === '售罄').length; const soldOutCount = reasons.filter(r => r === '售罄').length;
const busyCount = reasons.filter(r => r === '系统繁忙').length;
if (soldOutCount === batchSize) { if (soldOutCount === batchSize) {
consecutiveSoldOut++; consecutiveSoldOut++;
} else { } else {
consecutiveSoldOut = 0; consecutiveSoldOut = 0;
} }
// 连续全部返回系统繁忙 → 可能不在抢购时间 // 连续10轮全售罄 → 可能已经抢完了
if (busyCount === batchSize) { if (consecutiveSoldOut >= 10) {
consecutiveBusy++; if (consecutiveSoldOut === 10) log('连续售罄, 可能已抢完, 降速 (2s)...');
} else {
consecutiveBusy = 0;
}
// 连续5轮全售罄 → 放慢重试 (2秒一次)
if (consecutiveSoldOut >= 5) {
if (consecutiveSoldOut === 5) log('连续售罄, 降速重试 (2s间隔)...');
await sleep(2000); await sleep(2000);
continue; continue;
} }
// 连续5轮全系统繁忙 → 可能不在抢购窗口, 放慢到5秒
if (consecutiveBusy >= 5) {
if (consecutiveBusy === 5) log('连续系统繁忙, 可能未到抢购时间, 降速 (5s间隔)...');
await sleep(5000);
// 每30轮检查一次是否恢复
if (consecutiveBusy % 30 === 0) {
log(`仍在等待... 已重试${totalAttempt}次 (系统繁忙)`);
}
continue;
} }
// 日志 (前5次 + 每20次) // 日志 (前5次 + 每20次)
if (totalAttempt <= 5 * CFG.concurrency || totalAttempt % (20 * CFG.concurrency) === 0) { if (totalAttempt <= 5 * CFG.concurrency || totalAttempt % (20 * CFG.concurrency) === 0) {
log(`#${totalAttempt} ${reasons[0]}`); const sec = elapsedSec.toFixed(0);
log(`#${totalAttempt} ${reasons[0]} (${sec}s)`);
} }
// 自适应延迟 // 自适应延迟
@@ -349,6 +349,12 @@
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {} try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
log('捕获 preview (Fetch)'); log('捕获 preview (Fetch)');
// 已经成功过 → 直接返回缓存,不再重试
if (state.status === 'success' && state.lastSuccess) {
log('已抢到, 返回成功响应');
return new Response(state.lastSuccess.text, { status: 200, headers: { 'Content-Type': 'application/json' } });
}
if (state.cache) { if (state.cache) {
log('返回缓存响应'); log('返回缓存响应');
const c = state.cache; const c = state.cache;
@@ -406,6 +412,13 @@
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {} try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
log('捕获 preview (XHR)'); log('捕获 preview (XHR)');
// 已经成功过 → 直接返回缓存
if (state.status === 'success' && state.lastSuccess) {
log('已抢到, 返回成功响应 (XHR)');
fakeXHR(self, state.lastSuccess.text);
return;
}
if (state.cache) { if (state.cache) {
log('返回缓存响应 (XHR)'); log('返回缓存响应 (XHR)');
const c = state.cache; setState({ cache: null }); const c = state.cache; setState({ cache: null });
@@ -663,9 +676,19 @@
// 预热 // 预热
async function preheat() { async function preheat() {
try { try {
await _fetch(location.origin + '/api/biz/pay/check?bizId=preheat', { credentials: 'include' }); log('TCP预热中...');
log('TCP预热完成'); // 连发3次预热请求确保连接池暖好
} catch {} 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 请求不产生副作用)
await _fetch(location.origin + CFG.PREVIEW, {
method: 'HEAD',
credentials: 'include',
}).catch(() => {});
log('预热完成 (4次连接已建立)');
} catch { log('预热部分失败,不影响使用'); }
} }
// ═══════════════════════════════════════════ // ═══════════════════════════════════════════
@@ -729,7 +752,7 @@
.keys{font-size:10px;color:#636e72;text-align:center;margin-top:6px} .keys{font-size:10px;color:#636e72;text-align:center;margin-top:6px}
</style> </style>
<div class="panel"> <div class="panel">
<div class="hd" id="drag"><b>GLM v4.2</b><button class="mn" id="min">-</button></div> <div class="hd" id="drag"><b>GLM v4.4</b><button class="mn" id="min">-</button></div>
<div class="bd" id="bd"> <div class="bd" id="bd">
<div class="st st-idle" id="st">等待中</div> <div class="st st-idle" id="st">等待中</div>
<div class="cap" id="cap">${state.captured ? '已恢复上次捕获的请求' : '请先点一次购买按钮'}</div> <div class="cap" id="cap">${state.captured ? '已恢复上次捕获的请求' : '请先点一次购买按钮'}</div>
@@ -739,7 +762,8 @@
<div><div class="v" id="s-err">0</div>错误</div> <div><div class="v" id="s-err">0</div>错误</div>
</div> </div>
<div class="row"> <div class="row">
<span>并发</span><input type="number" id="i-conc" value="${CFG.concurrency}" min="1" max="10" step="1"> <span>并发</span><input type="number" id="i-conc" value="${CFG.concurrency}" min="1" max="20" step="1">
<span>极速</span><input type="number" id="i-turbo" value="${CFG.turboConcurrency}" min="1" max="20" step="1">
<span>上限</span><input type="number" id="i-max" value="${CFG.maxRetry}" min="10" max="9999" step="50"> <span>上限</span><input type="number" id="i-max" value="${CFG.maxRetry}" min="10" max="9999" step="50">
</div> </div>
<div class="row"> <div class="row">
@@ -764,8 +788,9 @@
$('b-stop').onclick = stopAll; $('b-stop').onclick = stopAll;
$('b-heat').onclick = preheat; $('b-heat').onclick = preheat;
$('b-time').onclick = () => { const v = $('i-time').value; if (v) scheduleAt(v); }; $('b-time').onclick = () => { const v = $('i-time').value; if (v) scheduleAt(v); };
$('i-conc').onchange = function() { CFG.concurrency = Math.max(1, +this.value || 3); saveCfg(CFG); }; $('i-conc').onchange = function() { CFG.concurrency = Math.max(1, +this.value || 5); saveCfg(CFG); };
$('i-max').onchange = function() { CFG.maxRetry = Math.max(10, +this.value || 500); saveCfg(CFG); }; $('i-turbo').onchange = function() { CFG.turboConcurrency = Math.max(1, +this.value || 10); saveCfg(CFG); };
$('i-max').onchange = function() { CFG.maxRetry = Math.max(10, +this.value || 2000); saveCfg(CFG); };
$('min').onclick = function() { $('min').onclick = function() {
const bd = $('bd'); const bd = $('bd');
const hidden = bd.style.display === 'none'; const hidden = bd.style.display === 'none';
@@ -810,8 +835,9 @@
const stEl = $('st'); const stEl = $('st');
if (stEl) { if (stEl) {
stEl.className = 'st st-' + state.status; stEl.className = 'st st-' + state.status;
const isTurbo = state.stats.startTime && (performance.now() - state.stats.startTime) < CFG.turboSec * 1000;
stEl.textContent = state.status === 'idle' ? '等待中' stEl.textContent = state.status === 'idle' ? '等待中'
: state.status === 'retrying' ? `重试中... ${state.count}/${CFG.maxRetry}` : state.status === 'retrying' ? `${isTurbo ? '⚡极速' : ''}重试中... ${state.count}/${CFG.maxRetry}`
: state.status === 'success' ? `成功! bizId=${state.bizId}` : state.status === 'success' ? `成功! bizId=${state.bizId}`
: `失败 (${state.count}次)`; : `失败 (${state.count}次)`;
} }