// ==UserScript== // @name 智谱 GLM Coding 终极抢购助手 (全自动版) // @namespace http://tampermonkey.net/ // @version 3.2 // @description 全自动抢购:自动重试 preview + check 双重校验 + 错误弹窗自动恢复 + 主动模式 + 定时触发 // @author Assistant // @match *://www.bigmodel.cn/* // @run-at document-start // @grant none // ==/UserScript== (function () { 'use strict'; // ======================== 配置 ======================== const CFG = { delay: 100, maxRetry: 300, PREVIEW: '/api/biz/pay/preview', CHECK: '/api/biz/pay/check', }; // ======================== 全局状态 ======================== const S = { status: 'idle', count: 0, bizId: null, captured: null, cache: null, lastSuccess: null, // 最近一次成功的响应,用于弹窗恢复 proactive: false, timerId: null, logs: [], }; let stopRequested = false; let recovering = false; let recoveryAttempts = 0; // ======================== 工具函数 ======================== const sleep = ms => new Promise(r => setTimeout(r, ms)); const ts = () => new Date().toLocaleTimeString('zh-CN', { hour12: false }); function log(msg) { S.logs.push(`${ts()} ${msg}`); if (S.logs.length > 80) S.logs.shift(); console.log(`[GLM抢购] ${msg}`); refreshLog(); } function extractHeaders(h) { const o = {}; if (!h) return o; if (h instanceof Headers) h.forEach((v, k) => (o[k] = v)); else if (Array.isArray(h)) h.forEach(([k, v]) => (o[k] = v)); else Object.entries(h).forEach(([k, v]) => (o[k] = v)); return o; } // ======================== 一、JSON.parse 深层篡改 (UI 补丁) ======================== const _parse = JSON.parse; JSON.parse = function (text, reviver) { let result = _parse(text, reviver); try { (function fix(obj) { if (!obj || typeof obj !== 'object') return; if (obj.isSoldOut === true) obj.isSoldOut = false; if (obj.soldOut === true) obj.soldOut = false; if (obj.disabled === true && (obj.price !== undefined || obj.productId || obj.title)) obj.disabled = false; if (obj.stock === 0) obj.stock = 999; for (let k in obj) if (obj[k] && typeof obj[k] === 'object') fix(obj[k]); })(result); } catch (e) {} return result; }; // ======================== 二、核心重试引擎 ======================== const _fetch = window.fetch; let _retryPromise = null; async function retry(url, opts) { if (_retryPromise) { log('⏳ 合并到当前重试…'); return _retryPromise; } stopRequested = false; _retryPromise = (async () => { S.status = 'retrying'; S.count = 0; refreshUI(); // 剥离 AbortSignal,防止前端超时中断我们的重试 const { signal, ...cleanOpts } = opts || {}; for (let i = 1; i <= CFG.maxRetry; i++) { if (stopRequested) { log('⏹ 重试被手动停止'); break; } S.count = i; refreshUI(); try { const resp = await _fetch(url, { ...cleanOpts, credentials: 'include' }); const text = await resp.text(); let data; try { data = _parse(text); } catch { data = null; } if (data && data.code === 200 && data.data && data.data.bizId) { const bizId = data.data.bizId; log(`🔑 获取到 bizId=${bizId},正在校验 check 接口…`); // 关键校验:调用 check 接口确认 bizId 是否有效 try { const checkUrl = `${location.origin}${CFG.CHECK}?bizId=${bizId}`; const checkResp = await _fetch(checkUrl, { credentials: 'include' }); const checkText = await checkResp.text(); let checkData; try { checkData = _parse(checkText); } catch { checkData = null; } if (checkData && checkData.data === 'EXPIRE') { // bizId 已过期,继续重试 preview log(`#${i} bizId 已过期(EXPIRE),继续重试…`); await sleep(CFG.delay); continue; } // check 返回非 EXPIRE → 真正成功! S.status = 'success'; S.bizId = bizId; S.lastSuccess = { text, data }; log(`✅ 抢购成功! bizId=${bizId}, check 校验通过 (第${i}次)`); refreshUI(); recoveryAttempts = 0; setTimeout(autoRecover, 600); return { ok: true, text, data, status: resp.status }; } catch (checkErr) { // check 接口本身出错,也继续重试 log(`#${i} check 校验异常: ${checkErr.message},继续重试…`); await sleep(CFG.delay); continue; } } const why = !data ? '非JSON响应' : data.code === 555 ? '系统繁忙(555)' : (data.data && data.data.bizId === null) ? '售罄(bizId=null)' : `未知(code=${data.code})`; if (i <= 5 || i % 20 === 0) log(`#${i} ${why}`); } catch (e) { if (i <= 3 || i % 20 === 0) log(`#${i} 网络错误: ${e.message}`); } await sleep(CFG.delay); } if (!stopRequested) { S.status = 'failed'; log(`❌ 达到上限 ${CFG.maxRetry} 次`); } else { S.status = 'idle'; } refreshUI(); return { ok: false }; })(); try { return await _retryPromise; } finally { _retryPromise = null; } } // ======================== 三、错误弹窗自动恢复 ======================== /** 查找页面上可见的错误弹窗 */ function findErrorDialog() { const selectors = [ '.el-dialog', '.el-message-box', '.el-dialog__wrapper', '.ant-modal', '.ant-modal-wrap', '[class*="modal"]', '[class*="dialog"]', '[class*="popup"]', '[role="dialog"]', ]; for (const sel of selectors) { for (const el of document.querySelectorAll(sel)) { // 必须可见 const style = window.getComputedStyle(el); if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') continue; if (!el.offsetParent && style.position !== 'fixed') continue; const text = el.textContent || ''; if (/购买人数过多|系统繁忙|稍后再试|请重试|繁忙|失败|出错|异常/.test(text)) { return el; } } } return null; } /** 关闭弹窗 */ function dismissDialog(dialog) { // 策略 1:找关闭按钮 const closeSelectors = [ '.el-dialog__headerbtn', '.el-message-box__headerbtn', '.el-dialog__close', '.ant-modal-close', '[class*="close-btn"]', '[class*="closeBtn"]', '[aria-label="Close"]', '[aria-label="close"]', ]; for (const sel of closeSelectors) { const btn = dialog.querySelector(sel) || document.querySelector(sel); if (btn && btn.offsetParent !== null) { btn.click(); log('🔄 点击关闭按钮'); return true; } } // 策略 2:找弹窗内的 确定/取消/关闭 按钮 const btns = dialog.querySelectorAll('button, [role="button"]'); for (const btn of btns) { const t = (btn.textContent || '').trim(); if (/关闭|确定|取消|知道了|OK|Cancel|Close|确认/.test(t) && t.length < 10) { btn.click(); log(`🔄 点击 [${t}] 按钮`); return true; } } // 策略 3:按 Escape document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27, bubbles: true })); document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', keyCode: 27, bubbles: true })); log('🔄 发送 Escape 键'); // 策略 4:点击遮罩层 const masks = document.querySelectorAll('.el-overlay, .v-modal, .el-overlay-dialog, [class*="overlay"], [class*="mask"]'); for (const mask of masks) { if (mask.offsetParent !== null || window.getComputedStyle(mask).position === 'fixed') { mask.click(); log('🔄 点击遮罩层'); return true; } } // 策略 5:强制隐藏 dialog.style.display = 'none'; const overlays = document.querySelectorAll('.el-overlay, .v-modal'); overlays.forEach(o => (o.style.display = 'none')); log('🔄 强制隐藏弹窗'); return true; } /** 自动恢复:关闭错误弹窗 → 重新触发购买 */ async function autoRecover() { if (recovering || recoveryAttempts >= 3) return; if (!S.lastSuccess) return; const dialog = findErrorDialog(); if (!dialog) return; // 没有错误弹窗,说明前端已正常处理,无需恢复 recovering = true; recoveryAttempts++; try { log('🔄 检测到错误弹窗,启动自动恢复…'); // 把成功响应放入缓存,供拦截器下一次返回 S.cache = S.lastSuccess; // 关闭错误弹窗 dismissDialog(dialog); await sleep(500); // 再次确认弹窗关了(有些弹窗有动画延迟) const stillThere = findErrorDialog(); if (stillThere) { dismissDialog(stillThere); await sleep(300); } // 重新触发购买按钮 → 拦截器返回缓存响应 → 前端弹出支付二维码 const btn = findBuyButton(); if (btn) { btn.click(); log('🖱 已自动重新点击购买按钮'); } else { log('⚠️ 未找到购买按钮,请手动点击'); alert('已获取到商品!请立即手动点击购买按钮!'); } } finally { recovering = false; } } /** 持续监控:每 500ms 检查一次是否有需要恢复的错误弹窗 */ function setupDialogWatcher() { setInterval(() => { if (S.lastSuccess && !recovering && recoveryAttempts < 3) { const dialog = findErrorDialog(); if (dialog) autoRecover(); } }, 500); } // ======================== 四、Fetch 拦截器 ======================== window.fetch = async function (input, init) { const url = typeof input === 'string' ? input : input?.url; if (url && url.includes(CFG.PREVIEW)) { S.captured = { url, method: init?.method || 'POST', body: init?.body, headers: extractHeaders(init?.headers), }; log('🎯 捕获 preview 请求 (Fetch)'); refreshUI(); // 有缓存 → 直接返回(来自主动模式或弹窗恢复) if (S.cache) { log('📦 返回缓存的成功响应'); const c = S.cache; S.cache = null; recoveryAttempts = 0; // 重置恢复计数 return new Response(c.text, { status: 200, headers: { 'Content-Type': 'application/json' }, }); } const result = await retry(url, { method: init?.method || 'POST', body: init?.body, headers: extractHeaders(init?.headers), signal: init?.signal, // 传入以便 retry 内部剥离 }); if (result.ok) { return new Response(result.text, { status: result.status, headers: { 'Content-Type': 'application/json' }, }); } return _fetch.apply(this, [input, init]); } if (url && url.includes(CFG.CHECK) && url.includes('bizId=null')) { log('🚫 拦截 check(bizId=null)'); return new Response(JSON.stringify({ code: -1, msg: '等待有效bizId' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } return _fetch.apply(this, [input, init]); }; // ======================== 五、XHR 拦截器 ======================== const _xhrOpen = XMLHttpRequest.prototype.open; const _xhrSend = XMLHttpRequest.prototype.send; const _xhrSetHeader = XMLHttpRequest.prototype.setRequestHeader; XMLHttpRequest.prototype.setRequestHeader = function (k, v) { (this._h || (this._h = {}))[k] = v; return _xhrSetHeader.call(this, k, v); }; XMLHttpRequest.prototype.open = function (method, url) { this._m = method; this._u = url; return _xhrOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function (body) { const url = this._u; if (typeof url === 'string' && url.includes(CFG.PREVIEW)) { const self = this; S.captured = { url, method: this._m, body, headers: this._h || {} }; log('🎯 捕获 preview 请求 (XHR)'); refreshUI(); if (S.cache) { log('📦 返回缓存的成功响应 (XHR)'); const c = S.cache; S.cache = null; recoveryAttempts = 0; fakeXHR(self, c.text); return; } retry(url, { method: this._m, body, headers: this._h || {} }).then(result => { fakeXHR(self, result.ok ? result.text : '{"code":-1,"msg":"重试失败"}'); }); return; } if (typeof url === 'string' && url.includes(CFG.CHECK) && url.includes('bizId=null')) { log('🚫 拦截 check(bizId=null) (XHR)'); fakeXHR(this, '{"code":-1,"msg":"等待有效bizId"}'); return; } return _xhrSend.call(this, body); }; function fakeXHR(xhr, text) { setTimeout(() => { const dp = (k, v) => Object.defineProperty(xhr, k, { value: v, configurable: true }); dp('readyState', 4); dp('status', 200); dp('statusText', 'OK'); dp('responseText', text); dp('response', text); const rsc = new Event('readystatechange'); if (typeof xhr.onreadystatechange === 'function') xhr.onreadystatechange(rsc); xhr.dispatchEvent(rsc); const load = new ProgressEvent('load'); if (typeof xhr.onload === 'function') xhr.onload(load); xhr.dispatchEvent(load); xhr.dispatchEvent(new ProgressEvent('loadend')); }, 0); } // ======================== 六、主动抢购模式 ======================== async function startProactive() { if (!S.captured) { log('⚠️ 请先手动点一次购买/订阅按钮'); alert('请先手动点一次购买/订阅按钮,让脚本捕获请求参数'); return; } S.proactive = true; log('🚀 主动抢购模式启动'); const { url, method, body, headers } = S.captured; const result = await retry(url, { method, body, headers }); S.proactive = false; if (result.ok) { S.cache = { text: result.text, data: result.data }; log('🎉 主动模式成功! 触发购买流程…'); // 先关可能存在的弹窗 const errDlg = findErrorDialog(); if (errDlg) { dismissDialog(errDlg); await sleep(300); } const btn = findBuyButton(); if (btn) { btn.click(); log('🖱 已自动点击购买按钮'); } else { log('⚠️ 未找到按钮,请手动点击'); alert('已获取到商品!请立即点击购买按钮!'); } } } function stopAll() { stopRequested = true; S.proactive = false; S.status = 'idle'; S.count = 0; if (S.timerId) { clearTimeout(S.timerId); S.timerId = null; } log('⏹ 已停止'); refreshUI(); } function findBuyButton() { for (const el of document.querySelectorAll('button, a, [role="button"], div[class*="btn"], span[class*="btn"]')) { const t = el.textContent.trim(); if (/购买|抢购|立即|下单|订阅/.test(t) && t.length < 20 && el.offsetParent !== null) { return el; } } return null; } // ======================== 七、定时触发 ======================== function scheduleAt(timeStr) { if (S.timerId) { clearTimeout(S.timerId); S.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)}秒后)`); S.timerId = setTimeout(() => { S.timerId = null; log('⏰ 时间到! 启动抢购!'); if (S.captured) { startProactive(); } else { const btn = findBuyButton(); if (btn) { btn.click(); log('🖱 定时: 已点击购买按钮'); } else { log('⚠️ 未找到按钮'); alert('定时到了!请手动点击购买!'); } } }, ms - 50); refreshUI(); } // ======================== 八、浮动控制面板 ======================== function createPanel() { const panel = document.createElement('div'); panel.id = 'glm-rush'; panel.innerHTML = `
🎯 GLM 抢购助手 v3.2
⏳ 等待中
📡 请求: 未捕获 — 请先点一次购买按钮
间隔ms 上限
定时
`; document.body.appendChild(panel); const $ = id => document.getElementById(id); $('glm-go').onclick = startProactive; $('glm-stop').onclick = stopAll; $('glm-delay').onchange = function () { CFG.delay = Math.max(50, +this.value || 100); }; $('glm-max').onchange = function () { CFG.maxRetry = Math.max(10, +this.value || 300); }; $('glm-time-set').onclick = function () { const v = $('glm-time').value; if (v) scheduleAt(v); }; $('glm-min').onclick = function () { const bd = $('glm-bd'); const hidden = bd.style.display === 'none'; bd.style.display = hidden ? '' : 'none'; this.textContent = hidden ? '−' : '+'; }; // 拖拽 let sx, sy, sl, st; $('glm-drag').onmousedown = function (e) { sx = e.clientX; sy = e.clientY; const rect = panel.getBoundingClientRect(); sl = rect.left; st = rect.top; const onMove = function (e) { panel.style.left = (sl + e.clientX - sx) + 'px'; panel.style.top = (st + e.clientY - sy) + 'px'; panel.style.right = 'auto'; }; const onUp = function () { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); }; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); }; log('🚀 v3.2 已加载 (preview+check 双重校验)'); // 启动错误弹窗监控 setupDialogWatcher(); } function refreshUI() { const stEl = document.getElementById('glm-st'); if (!stEl) return; stEl.className = 'glm-st glm-st-' + S.status; stEl.textContent = S.status === 'idle' ? '⏳ 等待中' : S.status === 'retrying' ? `🔄 重试中… ${S.count}/${CFG.maxRetry}` : S.status === 'success' ? `✅ 成功! bizId=${S.bizId}` : `❌ 失败 (${S.count}次)`; const capEl = document.getElementById('glm-cap'); if (capEl) { capEl.textContent = S.captured ? `📡 已捕获: ${S.captured.method} …${S.captured.url.split('?')[0].slice(-30)}` : '📡 请求: 未捕获 — 请先点一次购买按钮'; } const goBtn = document.getElementById('glm-go'); const stopBtn = document.getElementById('glm-stop'); if (goBtn && stopBtn) { goBtn.style.display = S.status === 'retrying' ? 'none' : ''; stopBtn.style.display = S.status === 'retrying' ? '' : 'none'; } } function refreshLog() { const el = document.getElementById('glm-logs'); if (!el) return; const last = S.logs[S.logs.length - 1]; if (last) { const div = document.createElement('div'); div.textContent = last; el.appendChild(div); while (el.children.length > 80) el.removeChild(el.firstChild); el.scrollTop = el.scrollHeight; } } // ======================== 启动 ======================== console.log('[GLM抢购] 🚀 v3.2 全自动版 (preview+check 双重校验) 已注入'); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createPanel); } else { createPanel(); } })();