fix: v4.4 点击购买只捕获参数不触发抢购, 自动定时10点

- 捕获时放行原始请求, 不进入重试
- 只有主动模式/定时触发才启动重试引擎
- 过了10点不到30秒直接开抢, 超过30秒等明天
- 测试通过: 捕获正常/面板正常/支付弹窗正常
This commit is contained in:
qtaxm
2026-04-08 12:19:55 +08:00
parent 1397dce059
commit eccba9325a
2 changed files with 192 additions and 71 deletions

View File

@@ -352,6 +352,7 @@
const url = typeof input === 'string' ? input : input?.url; const url = typeof input === 'string' ? input : input?.url;
if (url && url.includes(CFG.PREVIEW)) { if (url && url.includes(CFG.PREVIEW)) {
// 捕获请求参数
const captured = { const captured = {
url, url,
method: init?.method || 'POST', method: init?.method || 'POST',
@@ -360,17 +361,14 @@
}; };
setState({ captured }); setState({ captured });
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {} try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
log('捕获 preview (Fetch)');
// 自动设定抢购定时(如果还没设定且未在抢购中) // 已经成功过 → 直接返回缓存
autoScheduleIfNeeded();
// 已经成功过 → 直接返回缓存,不再重试
if (state.status === 'success' && state.lastSuccess) { if (state.status === 'success' && state.lastSuccess) {
log('已抢到, 返回成功响应'); log('已抢到, 返回成功响应');
return new Response(state.lastSuccess.text, { status: 200, headers: { 'Content-Type': 'application/json' } }); 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;
@@ -379,18 +377,26 @@
return new Response(c.text, { status: 200, headers: { 'Content-Type': 'application/json' } }); return new Response(c.text, { status: 200, headers: { 'Content-Type': 'application/json' } });
} }
// 主动模式/正在抢购 → 进入重试引擎
if (state.proactive || state.status === 'retrying') {
log('抢购中, 启动重试...');
const result = await retry(url, { const result = await retry(url, {
method: init?.method || 'POST', method: init?.method || 'POST',
body: init?.body, body: init?.body,
headers: extractHeaders(init?.headers), headers: extractHeaders(init?.headers),
}); });
if (result.ok) { if (result.ok) {
return new Response(result.text, { status: result.status, headers: { 'Content-Type': 'application/json' } }); return new Response(result.text, { status: result.status, headers: { 'Content-Type': 'application/json' } });
} }
return _fetch.apply(this, [input, init]); return _fetch.apply(this, [input, init]);
} }
// 普通捕获 → 只记录参数,放行原始请求,自动设定定时
log('已捕获请求参数, 等待抢购时间...');
autoScheduleIfNeeded();
return _fetch.apply(this, [input, init]);
}
if (url && url.includes(CFG.CHECK) && url.includes('bizId=null')) { if (url && url.includes(CFG.CHECK) && url.includes('bizId=null')) {
log('拦截 check(bizId=null)'); log('拦截 check(bizId=null)');
return new Response('{"code":-1,"msg":"等待有效bizId"}', { return new Response('{"code":-1,"msg":"等待有效bizId"}', {
@@ -426,10 +432,6 @@
const captured = { url, method: this._m, body, headers: this._h || {} }; const captured = { url, method: this._m, body, headers: this._h || {} };
setState({ captured }); setState({ captured });
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {} try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
log('捕获 preview (XHR)');
// 自动设定抢购定时
autoScheduleIfNeeded();
// 已经成功过 → 直接返回缓存 // 已经成功过 → 直接返回缓存
if (state.status === 'success' && state.lastSuccess) { if (state.status === 'success' && state.lastSuccess) {
@@ -446,12 +448,21 @@
return; return;
} }
// 主动模式/正在抢购 → 重试
if (state.proactive || state.status === 'retrying') {
log('抢购中, 启动重试 (XHR)...');
retry(url, { method: this._m, body, headers: this._h || {} }).then(result => { retry(url, { method: this._m, body, headers: this._h || {} }).then(result => {
fakeXHR(self, result.ok ? result.text : '{"code":-1,"msg":"重试失败"}'); fakeXHR(self, result.ok ? result.text : '{"code":-1,"msg":"重试失败"}');
}); });
return; return;
} }
// 普通捕获 → 放行原始请求,自动设定定时
log('已捕获请求参数, 等待抢购时间...');
autoScheduleIfNeeded();
return _xhrSend.call(this, body);
}
if (typeof url === 'string' && url.includes(CFG.CHECK) && url.includes('bizId=null')) { if (typeof url === 'string' && url.includes(CFG.CHECK) && url.includes('bizId=null')) {
fakeXHR(this, '{"code":-1,"msg":"等待有效bizId"}'); fakeXHR(this, '{"code":-1,"msg":"等待有效bizId"}');
return; return;

182
inject.js
View File

@@ -15,6 +15,7 @@
jitter: 0.3, // 间隔随机抖动 ±30% jitter: 0.3, // 间隔随机抖动 ±30%
recoveryMax: 3, // 弹窗恢复最大次数 recoveryMax: 3, // 弹窗恢复最大次数
logMax: 100, // 日志条数上限 logMax: 100, // 日志条数上限
rushTime: '10:00:00', // 每天抢购时间 (北京时间)
PREVIEW: '/api/biz/pay/preview', PREVIEW: '/api/biz/pay/preview',
CHECK: '/api/biz/pay/check', CHECK: '/api/biz/pay/check',
}; };
@@ -339,6 +340,7 @@
const url = typeof input === 'string' ? input : input?.url; const url = typeof input === 'string' ? input : input?.url;
if (url && url.includes(CFG.PREVIEW)) { if (url && url.includes(CFG.PREVIEW)) {
// 捕获请求参数
const captured = { const captured = {
url, url,
method: init?.method || 'POST', method: init?.method || 'POST',
@@ -347,14 +349,14 @@
}; };
setState({ captured }); setState({ captured });
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {} try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
log('捕获 preview (Fetch)');
// 已经成功过 → 直接返回缓存,不再重试 // 已经成功过 → 直接返回缓存
if (state.status === 'success' && state.lastSuccess) { if (state.status === 'success' && state.lastSuccess) {
log('已抢到, 返回成功响应'); log('已抢到, 返回成功响应');
return new Response(state.lastSuccess.text, { status: 200, headers: { 'Content-Type': 'application/json' } }); 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;
@@ -363,18 +365,26 @@
return new Response(c.text, { status: 200, headers: { 'Content-Type': 'application/json' } }); return new Response(c.text, { status: 200, headers: { 'Content-Type': 'application/json' } });
} }
// 主动模式/正在抢购 → 进入重试引擎
if (state.proactive || state.status === 'retrying') {
log('抢购中, 启动重试...');
const result = await retry(url, { const result = await retry(url, {
method: init?.method || 'POST', method: init?.method || 'POST',
body: init?.body, body: init?.body,
headers: extractHeaders(init?.headers), headers: extractHeaders(init?.headers),
}); });
if (result.ok) { if (result.ok) {
return new Response(result.text, { status: result.status, headers: { 'Content-Type': 'application/json' } }); return new Response(result.text, { status: result.status, headers: { 'Content-Type': 'application/json' } });
} }
return _fetch.apply(this, [input, init]); return _fetch.apply(this, [input, init]);
} }
// 普通捕获 → 只记录参数,放行原始请求,自动设定定时
log('已捕获请求参数, 等待抢购时间...');
autoScheduleIfNeeded();
return _fetch.apply(this, [input, init]);
}
if (url && url.includes(CFG.CHECK) && url.includes('bizId=null')) { if (url && url.includes(CFG.CHECK) && url.includes('bizId=null')) {
log('拦截 check(bizId=null)'); log('拦截 check(bizId=null)');
return new Response('{"code":-1,"msg":"等待有效bizId"}', { return new Response('{"code":-1,"msg":"等待有效bizId"}', {
@@ -410,7 +420,6 @@
const captured = { url, method: this._m, body, headers: this._h || {} }; const captured = { url, method: this._m, body, headers: this._h || {} };
setState({ captured }); setState({ captured });
try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {} try { sessionStorage.setItem('glm_rush_captured', JSON.stringify(captured)); } catch {}
log('捕获 preview (XHR)');
// 已经成功过 → 直接返回缓存 // 已经成功过 → 直接返回缓存
if (state.status === 'success' && state.lastSuccess) { if (state.status === 'success' && state.lastSuccess) {
@@ -427,12 +436,21 @@
return; return;
} }
// 主动模式/正在抢购 → 重试
if (state.proactive || state.status === 'retrying') {
log('抢购中, 启动重试 (XHR)...');
retry(url, { method: this._m, body, headers: this._h || {} }).then(result => { retry(url, { method: this._m, body, headers: this._h || {} }).then(result => {
fakeXHR(self, result.ok ? result.text : '{"code":-1,"msg":"重试失败"}'); fakeXHR(self, result.ok ? result.text : '{"code":-1,"msg":"重试失败"}');
}); });
return; return;
} }
// 普通捕获 → 放行原始请求,自动设定定时
log('已捕获请求参数, 等待抢购时间...');
autoScheduleIfNeeded();
return _xhrSend.call(this, body);
}
if (typeof url === 'string' && url.includes(CFG.CHECK) && url.includes('bizId=null')) { if (typeof url === 'string' && url.includes(CFG.CHECK) && url.includes('bizId=null')) {
fakeXHR(this, '{"code":-1,"msg":"等待有效bizId"}'); fakeXHR(this, '{"code":-1,"msg":"等待有效bizId"}');
return; return;
@@ -611,11 +629,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 });
@@ -623,11 +645,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('已获取到商品! 请立即点击购买按钮!'); }
} }
} }
@@ -635,40 +659,118 @@
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;
}
} 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;
}
}
function getServerNow() {
return Date.now() + serverTimeOffset;
}
/** 捕获请求后自动设定今天的抢购定时 */
function autoScheduleIfNeeded() {
if (state.timerId) return; // 已经设定了
if (state.status === 'retrying') return; // 正在抢
if (state.status === 'success') return; // 已经抢到了
const parts = CFG.rushTime.split(':').map(Number);
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()) {
// 已过今天的抢购时间 → 直接开始抢(可能正好在抢购窗口内)
const passedSec = (getServerNow() - target.getTime()) / 1000;
if (passedSec < 30) {
// 过了不到30秒还在窗口内直接开抢
log(`已过${CFG.rushTime} ${passedSec.toFixed(0)}秒, 立即开抢!`);
startProactive();
} else {
log(`今天${CFG.rushTime}已过, 明天自动抢购`);
} }
return; return;
} }
requestAnimationFrame(tick);
// 未到时间 → 自动设定定时
scheduleAt(CFG.rushTime);
log(`已自动设定 ${CFG.rushTime} 抢购`);
} }
requestAnimationFrame(tick);
}, earlyMs); // 定时到指定时间
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 });
} }
@@ -813,9 +915,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();
}
} }
// ═══════════════════════════════════════════ // ═══════════════════════════════════════════