feat: v4.4 北京时间同步+全自动抢购+极速模式+抢到停止+浏览器通知
- 同步服务器时间(Date头+worldtimeapi备用), 精确到毫秒 - 定时前3秒自动预热 - 极速模式: 前5秒10路并发, 之后5路 - 请求指纹随机化(X-Request-Id/Timestamp/Accept-Language) - 抢到后不再重复抢购 - 成功后浏览器通知 - 面板实时倒计时显示
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name 智谱 GLM Coding 抢购助手 v4.0
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 4.3
|
||||
// @version 4.4
|
||||
// @description 并发重试 + 自适应间隔 + 反检测 + check校验 + 弹窗恢复 + 定时触发 + 配置持久化
|
||||
// @author Assistant
|
||||
// @match *://www.bigmodel.cn/*
|
||||
@@ -17,11 +17,13 @@
|
||||
// 配置 (localStorage 持久化)
|
||||
// ═══════════════════════════════════════════
|
||||
const DEFAULT_CFG = {
|
||||
concurrency: 3, // 并发路数
|
||||
concurrency: 5, // 并发路数 (普通模式)
|
||||
turboConcurrency: 10, // 极速模式并发路数
|
||||
turboSec: 5, // 极速模式持续秒数
|
||||
maxRetry: 2000, // 最大重试次数
|
||||
burstCount: 10, // 前N次零延迟爆发
|
||||
fastDelay: 50, // 爆发后的快速间隔
|
||||
slowDelay: 150, // 后期随机间隔中值
|
||||
burstCount: 20, // 前N次零延迟爆发
|
||||
fastDelay: 30, // 爆发后的快速间隔
|
||||
slowDelay: 100, // 后期随机间隔中值
|
||||
jitter: 0.3, // 间隔随机抖动 ±30%
|
||||
recoveryMax: 3, // 弹窗恢复最大次数
|
||||
logMax: 100, // 日志条数上限
|
||||
@@ -140,7 +142,15 @@
|
||||
|
||||
async function singleAttempt(url, opts, attemptNum) {
|
||||
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 状态码检测
|
||||
if (resp.status === 401 || resp.status === 403) {
|
||||
@@ -205,7 +215,11 @@
|
||||
let consecutiveSoldOut = 0;
|
||||
|
||||
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 promises = [];
|
||||
|
||||
@@ -347,6 +361,12 @@
|
||||
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
|
||||
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) {
|
||||
log('返回缓存响应');
|
||||
const c = state.cache;
|
||||
@@ -404,6 +424,13 @@
|
||||
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
|
||||
log('捕获 preview (XHR)');
|
||||
|
||||
// 已经成功过 → 直接返回缓存
|
||||
if (state.status === 'success' && state.lastSuccess) {
|
||||
log('已抢到, 返回成功响应 (XHR)');
|
||||
fakeXHR(self, state.lastSuccess.text);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.cache) {
|
||||
log('返回缓存响应 (XHR)');
|
||||
const c = state.cache; setState({ cache: null });
|
||||
@@ -596,11 +623,15 @@
|
||||
async function startProactive() {
|
||||
if (!state.captured) {
|
||||
log('请先手动点一次购买按钮');
|
||||
alert('请先手动点一次购买按钮,让脚本捕获请求参数');
|
||||
alert('请先手动点一次购买/订阅按钮,让脚本捕获请求参数');
|
||||
return;
|
||||
}
|
||||
if (state.status === 'success') {
|
||||
log('已经抢到了, 不重复抢购');
|
||||
return;
|
||||
}
|
||||
setState({ proactive: true });
|
||||
log('主动抢购启动 (并发=' + CFG.concurrency + ')');
|
||||
log(`极速抢购启动! 前${CFG.turboSec}秒${CFG.turboConcurrency}路并发, 之后${CFG.concurrency}路`);
|
||||
|
||||
const { url, method, body, headers } = state.captured;
|
||||
const result = await retry(url, { method, body, headers });
|
||||
@@ -608,11 +639,13 @@
|
||||
|
||||
if (result.ok) {
|
||||
setState({ cache: { text: result.text, data: result.data } });
|
||||
log('主动模式成功!');
|
||||
log('抢购成功! 触发支付...');
|
||||
// 自动通知
|
||||
try { new Notification('GLM 抢购成功!', { body: `bizId=${state.bizId}` }); } catch {}
|
||||
const errDlg = findErrorDialog();
|
||||
if (errDlg) { dismissDialog(errDlg); await sleep(300); }
|
||||
const btn = findBuyButton();
|
||||
if (btn) { btn.click(); log('已点击购买按钮'); }
|
||||
if (btn) { btn.click(); log('已自动点击购买按钮'); }
|
||||
else { alert('已获取到商品! 请立即点击购买按钮!'); }
|
||||
}
|
||||
}
|
||||
@@ -620,40 +653,90 @@
|
||||
function stopAll() {
|
||||
stopRequested = true;
|
||||
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('已停止');
|
||||
}
|
||||
|
||||
// 高精度定时
|
||||
function scheduleAt(timeStr) {
|
||||
if (state.timerId) { clearTimeout(state.timerId); setState({ timerId: null }); }
|
||||
const parts = timeStr.split(':').map(Number);
|
||||
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)}秒后)`);
|
||||
// ═══════════════════════════════════════════
|
||||
// 北京时间同步 + 自动定时
|
||||
// ═══════════════════════════════════════════
|
||||
let serverTimeOffset = 0; // 本地时间与服务器时间的差值(ms)
|
||||
|
||||
// 提前500ms开始用rAF精确等待
|
||||
const earlyMs = Math.max(0, ms - 500);
|
||||
const tid = setTimeout(() => {
|
||||
const targetTs = performance.now() + 500;
|
||||
function tick() {
|
||||
if (performance.now() >= targetTs) {
|
||||
log('时间到! 启动抢购!');
|
||||
setState({ timerId: null });
|
||||
if (state.captured) startProactive();
|
||||
else {
|
||||
const btn = findBuyButton();
|
||||
if (btn) btn.click();
|
||||
else alert('定时到了! 请手动点击购买!');
|
||||
}
|
||||
async function syncServerTime() {
|
||||
// 用服务器响应头的 Date 字段同步时间
|
||||
try {
|
||||
const t0 = Date.now();
|
||||
const resp = await _fetch(location.origin + '/api/biz/pay/check?bizId=sync', { credentials: 'include' }).catch(() => null);
|
||||
const t1 = Date.now();
|
||||
const rtt = t1 - t0;
|
||||
|
||||
if (resp && resp.headers.get('date')) {
|
||||
const serverTime = new Date(resp.headers.get('date')).getTime();
|
||||
// 服务器时间 ≈ 发送时间 + RTT/2
|
||||
serverTimeOffset = serverTime - (t0 + rtt / 2);
|
||||
const localNow = new Date(Date.now() + serverTimeOffset);
|
||||
log(`时间同步: 服务器偏差 ${serverTimeOffset > 0 ? '+' : ''}${serverTimeOffset}ms (RTT=${rtt}ms)`);
|
||||
log(`北京时间: ${localNow.toLocaleTimeString('zh-CN', { hour12: false })}`);
|
||||
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 });
|
||||
}
|
||||
@@ -661,9 +744,19 @@
|
||||
// 预热
|
||||
async function preheat() {
|
||||
try {
|
||||
await _fetch(location.origin + '/api/biz/pay/check?bizId=preheat', { credentials: 'include' });
|
||||
log('TCP预热完成');
|
||||
} catch {}
|
||||
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 请求不产生副作用)
|
||||
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}
|
||||
</style>
|
||||
<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="st st-idle" id="st">等待中</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="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">
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -762,8 +856,9 @@
|
||||
$('b-stop').onclick = stopAll;
|
||||
$('b-heat').onclick = preheat;
|
||||
$('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-max').onchange = function() { CFG.maxRetry = Math.max(10, +this.value || 500); saveCfg(CFG); };
|
||||
$('i-conc').onchange = function() { CFG.concurrency = Math.max(1, +this.value || 5); 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() {
|
||||
const bd = $('bd');
|
||||
const hidden = bd.style.display === 'none';
|
||||
@@ -786,9 +881,17 @@
|
||||
// 闭包引用供 refreshUI 使用
|
||||
_shadowRef = shadow;
|
||||
|
||||
log('v4.0 已加载 (并发重试+反检测+高精度定时)');
|
||||
if (state.captured) log('已恢复上次捕获的请求参数');
|
||||
log('v4.4 已加载 (极速并发+时间同步+全自动抢购)');
|
||||
if (state.captured) log('已恢复上次捕获的请求参数, 可直接设定时间');
|
||||
setupDialogWatcher();
|
||||
|
||||
// 自动同步服务器时间
|
||||
syncServerTime();
|
||||
|
||||
// 请求通知权限
|
||||
if (Notification && Notification.permission === 'default') {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
@@ -808,8 +911,9 @@
|
||||
const stEl = $('st');
|
||||
if (stEl) {
|
||||
stEl.className = 'st st-' + state.status;
|
||||
const isTurbo = state.stats.startTime && (performance.now() - state.stats.startTime) < CFG.turboSec * 1000;
|
||||
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.count}次)`;
|
||||
}
|
||||
|
||||
102
inject.js
102
inject.js
@@ -5,11 +5,13 @@
|
||||
// 配置 (localStorage 持久化)
|
||||
// ═══════════════════════════════════════════
|
||||
const DEFAULT_CFG = {
|
||||
concurrency: 3, // 并发路数
|
||||
concurrency: 5, // 并发路数 (普通模式)
|
||||
turboConcurrency: 10, // 极速模式并发路数
|
||||
turboSec: 5, // 极速模式持续秒数
|
||||
maxRetry: 2000, // 最大重试次数
|
||||
burstCount: 10, // 前N次零延迟爆发
|
||||
fastDelay: 50, // 爆发后的快速间隔
|
||||
slowDelay: 150, // 后期随机间隔中值
|
||||
burstCount: 20, // 前N次零延迟爆发
|
||||
fastDelay: 30, // 爆发后的快速间隔
|
||||
slowDelay: 100, // 后期随机间隔中值
|
||||
jitter: 0.3, // 间隔随机抖动 ±30%
|
||||
recoveryMax: 3, // 弹窗恢复最大次数
|
||||
logMax: 100, // 日志条数上限
|
||||
@@ -128,7 +130,15 @@
|
||||
|
||||
async function singleAttempt(url, opts, attemptNum) {
|
||||
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 状态码检测
|
||||
if (resp.status === 401 || resp.status === 403) {
|
||||
@@ -191,10 +201,13 @@
|
||||
let consecutiveErrors = 0;
|
||||
let throttleCount = 0;
|
||||
let consecutiveSoldOut = 0;
|
||||
let consecutiveBusy = 0;
|
||||
|
||||
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 promises = [];
|
||||
|
||||
@@ -276,42 +289,29 @@
|
||||
// EXPIRE → 立即重试不等待
|
||||
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 busyCount = reasons.filter(r => r === '系统繁忙').length;
|
||||
if (soldOutCount === batchSize) {
|
||||
consecutiveSoldOut++;
|
||||
} else {
|
||||
consecutiveSoldOut = 0;
|
||||
}
|
||||
// 连续全部返回系统繁忙 → 可能不在抢购时间
|
||||
if (busyCount === batchSize) {
|
||||
consecutiveBusy++;
|
||||
} else {
|
||||
consecutiveBusy = 0;
|
||||
}
|
||||
|
||||
// 连续5轮全售罄 → 放慢重试 (2秒一次)
|
||||
if (consecutiveSoldOut >= 5) {
|
||||
if (consecutiveSoldOut === 5) log('连续售罄, 降速重试 (2s间隔)...');
|
||||
// 连续10轮全售罄 → 可能已经抢完了
|
||||
if (consecutiveSoldOut >= 10) {
|
||||
if (consecutiveSoldOut === 10) log('连续售罄, 可能已抢完, 降速 (2s)...');
|
||||
await sleep(2000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 连续5轮全系统繁忙 → 可能不在抢购窗口, 放慢到5秒
|
||||
if (consecutiveBusy >= 5) {
|
||||
if (consecutiveBusy === 5) log('连续系统繁忙, 可能未到抢购时间, 降速 (5s间隔)...');
|
||||
await sleep(5000);
|
||||
// 每30轮检查一次是否恢复
|
||||
if (consecutiveBusy % 30 === 0) {
|
||||
log(`仍在等待... 已重试${totalAttempt}次 (系统繁忙)`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 日志 (前5次 + 每20次)
|
||||
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 {}
|
||||
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) {
|
||||
log('返回缓存响应');
|
||||
const c = state.cache;
|
||||
@@ -406,6 +412,13 @@
|
||||
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
|
||||
log('捕获 preview (XHR)');
|
||||
|
||||
// 已经成功过 → 直接返回缓存
|
||||
if (state.status === 'success' && state.lastSuccess) {
|
||||
log('已抢到, 返回成功响应 (XHR)');
|
||||
fakeXHR(self, state.lastSuccess.text);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.cache) {
|
||||
log('返回缓存响应 (XHR)');
|
||||
const c = state.cache; setState({ cache: null });
|
||||
@@ -663,9 +676,19 @@
|
||||
// 预热
|
||||
async function preheat() {
|
||||
try {
|
||||
await _fetch(location.origin + '/api/biz/pay/check?bizId=preheat', { credentials: 'include' });
|
||||
log('TCP预热完成');
|
||||
} catch {}
|
||||
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 请求不产生副作用)
|
||||
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}
|
||||
</style>
|
||||
<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="st st-idle" id="st">等待中</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="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">
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -764,8 +788,9 @@
|
||||
$('b-stop').onclick = stopAll;
|
||||
$('b-heat').onclick = preheat;
|
||||
$('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-max').onchange = function() { CFG.maxRetry = Math.max(10, +this.value || 500); saveCfg(CFG); };
|
||||
$('i-conc').onchange = function() { CFG.concurrency = Math.max(1, +this.value || 5); 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() {
|
||||
const bd = $('bd');
|
||||
const hidden = bd.style.display === 'none';
|
||||
@@ -810,8 +835,9 @@
|
||||
const stEl = $('st');
|
||||
if (stEl) {
|
||||
stEl.className = 'st st-' + state.status;
|
||||
const isTurbo = state.stats.startTime && (performance.now() - state.stats.startTime) < CFG.turboSec * 1000;
|
||||
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.count}次)`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user