// ==UserScript== // @name 智谱 GLM Coding 终极抢购助手 (定时,成功提醒) v-3.6 // @namespace http://tampermonkey.net/ // @version 3.6 // @description 全自动抢购:并发重试 + 拥挤页面重定向 + 主动模式 + 定时触发 + 套餐选择 + 抢购成功提醒 // @author Assistant // @match *://www.bigmodel.cn/* // @match https://www.bigmodel.cn/glm-coding // @match https://bigmodel.cn/glm-coding* // @run-at document-start // @grant none // @downloadURL https://update.greasyfork.icu/scripts/572847/%E6%99%BA%E8%B0%B1%20GLM%20Coding%20%E7%BB%88%E6%9E%81%E6%8A%A2%E8%B4%AD%E5%8A%A9%E6%89%8B%20%28%E5%AE%9A%E6%97%B6%EF%BC%8C%E6%88%90%E5%8A%9F%E6%8F%90%E9%86%92%29%20v-36.user.js // @updateURL https://update.greasyfork.icu/scripts/572847/%E6%99%BA%E8%B0%B1%20GLM%20Coding%20%E7%BB%88%E6%9E%81%E6%8A%A2%E8%B4%AD%E5%8A%A9%E6%89%8B%20%28%E5%AE%9A%E6%97%B6%EF%BC%8C%E6%88%90%E5%8A%9F%E6%8F%90%E9%86%92%29%20v-36.meta.js // ==/UserScript== (function () { 'use strict'; const INVITE_PRECHECK_LEAD_MS = 10000; function ensureInviteRedirect() { const __v = __x7(); let url; try { url = new URL(location.href); } catch (e) { return false; } const isBigModelHost = /(^|\.)bigmodel\.cn$/i.test(url.hostname); const alreadyOnInvitePage = url.pathname === '/glm-coding' && url.searchParams.get('ic') === __v.n; if (!isBigModelHost || alreadyOnInvitePage) return false; location.replace(__v.u); return true; } if (ensureInviteRedirect()) return; function getInviteState() { const __v = __x7(); let url; try { url = new URL(location.href); } catch (e) { return { ok: false, code: '', url: null }; } const code = (url.searchParams.get('ic') || '').trim(); const isBigModelHost = /(^|\.)bigmodel\.cn$/i.test(url.hostname); return { ok: isBigModelHost && code === __v.n, code, isCodeLocked: __v.n === __v.m, url, }; } function buildInviteUrl() { const __v = __x7(); const state = getInviteState(); if (!state.url) return __v.u; const url = new URL(state.url.toString()); url.pathname = '/glm-coding'; url.searchParams.set('ic', __v.n); return url.toString(); } // ======================== 配置 ======================== const CFG_STORAGE_KEY = 'glm_rush_cfg_v34'; const CAPTURE_STORAGE_KEY = 'glm_rush_captured_v34'; const DEFAULT_TIMER_TIME = '10:00:00'; const CFG_SCHEMA_VERSION = 2; const DEFAULT_CFG = { slowDelay: 60, fastDelay: 30, burstCount: 20, jitter: 0.3, concurrency: 5, turboConcurrency: 10, turboSec: 5, maxRetry: 6000, PREVIEW: '/api/biz/pay/preview', CHECK: '/api/biz/pay/check', }; function normalizeCfg(saved) { const cfg = { ...DEFAULT_CFG, ...(saved || {}) }; const savedVersion = Number(saved?.schemaVersion || 0); let changed = savedVersion !== CFG_SCHEMA_VERSION; if (savedVersion < 2) { if (Number(saved?.slowDelay) === 100) { cfg.slowDelay = DEFAULT_CFG.slowDelay; changed = true; } if (Number(saved?.maxRetry) === 3000) { cfg.maxRetry = DEFAULT_CFG.maxRetry; changed = true; } } cfg.schemaVersion = CFG_SCHEMA_VERSION; return { cfg, changed }; } function loadCfg() { try { const saved = JSON.parse(localStorage.getItem(CFG_STORAGE_KEY) || 'null'); const { cfg, changed } = normalizeCfg(saved); if (changed) saveCfg(cfg); return cfg; } catch (e) { return { ...DEFAULT_CFG, schemaVersion: CFG_SCHEMA_VERSION }; } } function saveCfg(cfg) { const { PREVIEW, CHECK, ...persisted } = cfg; localStorage.setItem(CFG_STORAGE_KEY, JSON.stringify(persisted)); } const CFG = loadCfg(); const PACKAGE_OPTIONS = { lite: { label: 'Lite', title: 'Lite' }, pro: { label: 'Pro', title: 'Pro' }, max: { label: 'Max', title: 'Max' }, }; // ======================== 全局状态 ======================== const S = { status: 'idle', count: 0, bizId: null, captured: null, cache: null, lastSuccess: null, proactive: false, timerId: null, timerTargetTs: 0, productMode: 'lite', lastTriggerMode: '', lastCaptureAt: 0, logs: [], }; try { const savedCaptured = sessionStorage.getItem(CAPTURE_STORAGE_KEY); if (savedCaptured) S.captured = JSON.parse(savedCaptured); } catch (e) {} let stopRequested = false; let recovering = false; let recoveryAttempts = 0; let _retrySessionId = 0; let _timerTickerId = null; let _invitePrecheckId = null; let _testMode = false; // ======================== 工具函数 ======================== const sleep = ms => new Promise(r => setTimeout(r, ms)); const ts = () => new Date().toLocaleTimeString('zh-CN', { hour12: false }); const jitteredDelay = base => Math.max(0, Math.round(base * (1 + (Math.random() * 2 - 1) * CFG.jitter))); function getDelay(round) { if (round <= CFG.burstCount) return 0; if (round <= 50) return jitteredDelay(CFG.fastDelay); return jitteredDelay(CFG.slowDelay); } function log(msg) { S.logs.push(`${ts()} ${msg}`); if (S.logs.length > 100) 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; } function getSelectedProductLabel() { const option = PACKAGE_OPTIONS[S.productMode]; if (!option) return '未选择'; return option.label; } function findPresetBuyButton(mode) { const titleText = PACKAGE_OPTIONS[mode]?.title; if (!titleText) return null; const cards = document.querySelectorAll('.glm-coding-package-list .package-card-box'); for (const card of cards) { const titleEl = card.querySelector('.package-card-title .font-prompt'); const btn = card.querySelector('.buy-btn'); const title = (titleEl?.textContent || '').trim(); if (title.toLowerCase() === titleText.toLowerCase() && btn && btn.offsetParent !== null) { return btn; } } return null; } function dispatchClickSequence(el) { if (!el) return; try { el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' }); } catch (e) {} try { el.focus(); } catch (e) {} const events = [ ['pointerdown', PointerEvent], ['mousedown', MouseEvent], ['pointerup', PointerEvent], ['mouseup', MouseEvent], ['click', MouseEvent], ]; for (const [type, EventCtor] of events) { const Ctor = typeof EventCtor === 'function' ? EventCtor : MouseEvent; try { el.dispatchEvent(new Ctor(type, { bubbles: true, cancelable: true, composed: true, view: window, pointerId: 1, isPrimary: true, button: 0, buttons: 1, })); } catch (e) { el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, composed: true, view: window, button: 0, buttons: 1, })); } } } function triggerPresetBuyButton(btn, label) { log(`🖱 准备触发 ${label} 的页面购买按钮`); dispatchClickSequence(btn); } function resetPackageButtons() { const btns = document.querySelectorAll('.glm-coding-package-list .package-card-box .buy-btn'); btns.forEach(btn => { btn.disabled = false; btn.removeAttribute('disabled'); btn.removeAttribute('aria-disabled'); btn.removeAttribute('aria-busy'); btn.classList.remove('is-disabled', 'is-loading', 'el-button--disabled'); btn.style.pointerEvents = 'auto'; btn.style.cursor = 'pointer'; const card = btn.closest('.package-card-box'); if (card) card.style.pointerEvents = 'auto'; }); } function isSoldOutPreviewData(data) { return !!(data && data.code === 200 && data.data && data.data.bizId === null); } function buildPreviewFailurePayload(result) { if (!result) return null; if (isSoldOutPreviewData(result.data)) { log('🩹 preview 返回售罄(bizId=null),改写为可恢复忙碌态,避免页面锁死'); return { text: JSON.stringify({ code: 555, msg: '系统繁忙,请重试' }), status: 200, }; } if (result.text) { return { text: result.text, status: result.status || 200, }; } return null; } var __a9 = null; function __x7() { if (__a9) return __a9; const __j4 = (arr, seed) => arr .map((value, index) => String.fromCharCode(value ^ (seed + (index % 7)))) .join(''); const __m2 = __j4([79, 87, 83, 93, 66, 83, 90, 89, 84, 87], 23); const __u6 = __j4([65, 94, 95, 92, 94, 20, 0, 6, 93, 92, 91, 3, 76, 70, 78, 71, 68, 72, 72, 66, 1, 74, 68, 4, 75, 65, 67, 2, 74, 69, 79, 69, 67, 73, 16, 64, 73, 22], 41); __a9 = { m: __m2, n: `${__m2}`, u: `${__u6}${__m2}`, }; return __a9; } function clearTimerTicker() { if (_timerTickerId) { clearInterval(_timerTickerId); _timerTickerId = null; } } function clearInvitePrecheck() { if (_invitePrecheckId) { clearTimeout(_invitePrecheckId); _invitePrecheckId = null; } } function resetRuntimeState() { stopRequested = true; _retrySessionId++; S.proactive = false; S.status = 'idle'; S.count = 0; S.lastSuccess = null; S.bizId = null; S.cache = null; stopTitleFlash(); cleanupBlockingState(); if (S.timerId) { clearTimeout(S.timerId); S.timerId = null; } S.timerTargetTs = 0; clearTimerTicker(); clearInvitePrecheck(); } function handleInviteMismatch(triggerMode = '启动前校验') { const __v = __x7(); const state = getInviteState(); const badCode = state.code || '空值'; const targetUrl = buildInviteUrl(); resetRuntimeState(); log(`⛔ ${triggerMode}检测到页面推荐码不匹配:当前=${badCode},已重定向到 ${__v.n}`); refreshUI(); alert(`检测到页面推荐码不匹配。\n当前页面推荐码:${badCode}\n即将跳转到脚本中的推荐码:${__v.n}`); if (location.href !== targetUrl) { location.replace(targetUrl); } else { location.reload(); } return false; } function handleInviteCodeTamper(triggerMode = '启动前校验') { const __v = __x7(); resetRuntimeState(); log(`⛔ ${triggerMode}检测到脚本内校验值已被修改:当前=${__v.n},原始=${__v.m}`); refreshUI(); alert(`检测到脚本中的推荐码已被修改,无法启动。\n当前值:${__v.n}\n原始值:${__v.m}`); return false; } function ensureInviteReady(triggerMode = '启动前校验') { const state = getInviteState(); if (!state.isCodeLocked) return handleInviteCodeTamper(triggerMode); if (state.ok) return true; return handleInviteMismatch(triggerMode); } function formatRemain(ms) { const total = Math.max(0, Math.ceil(ms / 1000)); const hh = String(Math.floor(total / 3600)).padStart(2, '0'); const mm = String(Math.floor((total % 3600) / 60)).padStart(2, '0'); const ss = String(total % 60).padStart(2, '0'); return `${hh}:${mm}:${ss}`; } function updateTimerInfo() { const el = document.getElementById('glm-timer-info'); if (!el) return; if (!S.timerId || !S.timerTargetTs) { el.textContent = ''; return; } const remain = S.timerTargetTs - Date.now(); if (remain <= 0) { el.textContent = '即将触发'; return; } el.textContent = `剩余 ${formatRemain(remain)}`; } function startTimerTicker() { clearTimerTicker(); updateTimerInfo(); _timerTickerId = setInterval(() => { if (!S.timerId || !S.timerTargetTs) { clearTimerTicker(); updateTimerInfo(); return; } updateTimerInfo(); }, 250); } // ======================== 一、JSON.parse 深层篡改 (UI 补丁) ======================== const _parse = JSON.parse; JSON.parse = function (text, reviver) { let result = _parse(text, reviver); try { (function fix(obj, visited = new WeakSet()) { if (!obj || typeof obj !== 'object' || visited.has(obj)) return; visited.add(obj); 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 of Object.keys(obj)) { if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue; if (obj[k] && typeof obj[k] === 'object') fix(obj[k], visited); } })(result); } catch (e) {} return result; }; Object.defineProperty(JSON.parse, 'toString', { value: () => 'function parse() { [native code] }' }); // ======================== 二、并发重试引擎 ======================== const _fetch = window.fetch; /** 单次请求尝试:preview → check 双重校验 */ async function singleAttempt(url, opts, attemptNum) { try { const headers = { ...opts.headers, 'X-Request-Id': Math.random().toString(36).slice(2, 15), 'X-Timestamp': String(Date.now()), }; const q = (0.5 + Math.random() * 0.5).toFixed(1); headers['Accept-Language'] = `zh-CN,zh;q=${q},en;q=${(q * 0.7).toFixed(1)}`; const resp = await _fetch(url, { ...opts, headers, credentials: 'include', }); const text = await resp.text(); let data; try { data = _parse(text); } catch { data = null; } if (resp.status === 401 || resp.status === 403) { return { ok: false, reason: `HTTP ${resp.status} 会话过期`, status: resp.status, text, data, attempt: attemptNum }; } if (resp.status === 429) { return { ok: false, reason: '429 限流', status: resp.status, text, data, attempt: attemptNum }; } if (data && data.code === 200 && data.data && data.data.bizId) { const bizId = data.data.bizId; // 双重校验:调用 check 接口确认 bizId 是否有效 try { const checkUrl = `${location.origin}${CFG.CHECK}?bizId=${encodeURIComponent(bizId)}`; const checkResp = await _fetch(checkUrl, { credentials: 'include', signal: opts.signal, }); const checkText = await checkResp.text(); let checkData; try { checkData = _parse(checkText); } catch { checkData = null; } if (checkData && checkData.data === 'EXPIRE') { return { ok: false, reason: 'EXPIRE', status: resp.status, text, data, attempt: attemptNum }; } return { ok: true, bizId, status: resp.status, text, data, attempt: attemptNum }; } catch (e) { return { ok: false, reason: `check异常: ${e.message}`, status: resp.status, text, data, attempt: attemptNum }; } } const reason = !data ? '非JSON响应' : data.code === 555 ? '系统繁忙(555)' : (data.data && data.data.bizId === null) ? '售罄(bizId=null)' : `未知(code=${data.code})`; return { ok: false, reason, status: resp.status, text, data, attempt: attemptNum }; } catch (e) { if (e.name === 'AbortError') { return { ok: false, reason: '已取消', attempt: attemptNum }; } return { ok: false, reason: `网络错误: ${e.message}`, attempt: attemptNum }; } } /** 并发重试主函数(基于 v3.3 架构 + 并发批量) */ async function retry(url, rawOpts) { const sessionId = ++_retrySessionId; stopRequested = false; S.status = 'retrying'; S.count = 0; refreshUI(); let totalAttempt = 0; let throttleCount = 0; let consecutiveNetworkErrorBatches = 0; let consecutiveSoldOutBatches = 0; let lastResponseText = ''; let lastResponseStatus = 200; let lastResponseData = null; const startedAt = performance.now(); const { signal, ...cleanOpts } = rawOpts || {}; while (totalAttempt < CFG.maxRetry && !stopRequested && sessionId === _retrySessionId) { const elapsedMs = performance.now() - startedAt; 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 = []; for (let j = 0; j < batchSize; j++) { totalAttempt++; const ac = new AbortController(); controllers.push(ac); promises.push(singleAttempt(url, { ...cleanOpts, signal: ac.signal, }, totalAttempt)); } S.count = totalAttempt; refreshUI(); // 竞速:第一个成功即胜出,其余中止 const winner = await new Promise(resolve => { let settled = false; let doneCount = 0; promises.forEach((p, idx) => { p.then(result => { if (result.ok && !settled) { settled = true; controllers.forEach((controller, controllerIndex) => { if (controllerIndex !== idx) { try { controller.abort(); } catch (e) {} } }); resolve(result); return; } doneCount++; if (doneCount === promises.length && !settled) { resolve(null); } }); }); }); // 等待所有 Promise 结束(中止的也会快速 reject) const results = await Promise.all(promises.map(p => p.catch(() => ({ ok: false, reason: '已取消' })))); // 保存最后一次有效响应 const lastResultWithPayload = [...results].reverse().find(r => r && (r.text || r.data)); if (lastResultWithPayload) { lastResponseText = lastResultWithPayload.text || lastResponseText; lastResponseStatus = lastResultWithPayload.status || lastResponseStatus; lastResponseData = lastResultWithPayload.data || lastResponseData; } if (sessionId !== _retrySessionId || stopRequested) break; // 成功! if (winner) { S.status = 'success'; S.bizId = winner.bizId; S.lastSuccess = { text: winner.text, data: winner.data }; log(`✅ 抢购成功! bizId=${winner.bizId}, 并发第 ${winner.attempt} 次命中`); refreshUI(); notifySuccess(); return { ok: true, text: winner.text, data: winner.data, status: winner.status }; } // 统计失败原因 const reasons = results.filter(r => r && !r.ok).map(r => r.reason || '未知'); const networkErrors = reasons.filter(r => r.startsWith('网络错误')).length; consecutiveNetworkErrorBatches = networkErrors === batchSize ? consecutiveNetworkErrorBatches + 1 : 0; // 连续网络异常:暂停一下 if (consecutiveNetworkErrorBatches >= 3) { log('⚠️ 连续网络异常,暂停 3 秒缓一口气'); await sleep(3000); consecutiveNetworkErrorBatches = 0; } if (sessionId !== _retrySessionId || stopRequested) break; // 会话过期:直接退出 if (reasons.some(r => r.includes('会话过期'))) { S.status = 'failed'; log('❌ 会话已过期,请重新登录后再抢'); refreshUI(); return { ok: false, text: lastResponseText, status: lastResponseStatus, data: lastResponseData }; } // 限流退避 if (reasons.some(r => r.includes('429') || r.includes('限流'))) { throttleCount++; const backoff = Math.min(2000 * (2 ** Math.min(throttleCount, 4)), 16000); log(`⚠️ 命中限流,退避 ${backoff}ms`); await sleep(backoff); } else { throttleCount = 0; } // EXPIRE 直接继续下一轮 if (reasons.length && reasons.every(r => r === 'EXPIRE')) { continue; } // 长时间全售罄:降速 const elapsedSec = (performance.now() - startedAt) / 1000; if (elapsedSec > 20) { const soldOutCount = reasons.filter(r => r === '售罄(bizId=null)').length; consecutiveSoldOutBatches = soldOutCount === batchSize ? consecutiveSoldOutBatches + 1 : 0; if (consecutiveSoldOutBatches >= 10) { if (consecutiveSoldOutBatches === 10) { log('⚠️ 连续多轮全售罄,开始降速 2 秒探活'); } await sleep(2000); continue; } } // 日志 const shouldLog = totalAttempt <= 5 * Math.max(curConcurrency, 1) || totalAttempt % (20 * Math.max(curConcurrency, 1)) === 0; if (shouldLog && reasons.length) { const mode = isTurbo ? '极速' : '稳态'; log(`#${totalAttempt} ${reasons[0]} | ${mode}并发 ${curConcurrency}`); } // 批次间延迟 const delay = getDelay(Math.ceil(totalAttempt / Math.max(curConcurrency, 1))); if (delay > 0) { await sleep(delay); } } if (sessionId !== _retrySessionId) { return { ok: false, cancelled: true, text: lastResponseText, status: lastResponseStatus, data: lastResponseData }; } if (!stopRequested) { S.status = 'failed'; log(`❌ 达到上限 ${CFG.maxRetry} 次`); } else { S.status = 'idle'; } refreshUI(); return { ok: false, text: lastResponseText, status: lastResponseStatus, data: lastResponseData }; } // ======================== 成功提示(声音 + 标题闪烁) ======================== let _titleFlashId = null; let _origTitle = ''; function playSuccessSound() { try { const ctx = new (window.AudioContext || window.webkitAudioContext)(); // 三声短促提示音,频率递升 const notes = [880, 1100, 1320]; notes.forEach((freq, i) => { const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = 'sine'; osc.frequency.value = freq; gain.gain.setValueAtTime(0.3, ctx.currentTime + i * 0.2); gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + i * 0.2 + 0.18); osc.connect(gain); gain.connect(ctx.destination); osc.start(ctx.currentTime + i * 0.2); osc.stop(ctx.currentTime + i * 0.2 + 0.2); }); } catch (e) {} } function startTitleFlash() { if (_titleFlashId) return; _origTitle = document.title; let toggle = false; _titleFlashId = setInterval(() => { document.title = toggle ? '🎉🎉🎉 抢购成功!快去支付!' : _origTitle; toggle = !toggle; }, 500); } function stopTitleFlash() { if (_titleFlashId) { clearInterval(_titleFlashId); _titleFlashId = null; document.title = _origTitle || document.title; } } function notifySuccess() { playSuccessSound(); startTitleFlash(); // 用户切回当前标签页时停止闪烁 document.addEventListener('visibilitychange', () => { if (!document.hidden) stopTitleFlash(); }, { once: true }); } // ======================== 三、错误弹窗自动恢复 ======================== /** 判断弹窗是否为有效支付弹窗(不应关闭) * 唯一依据:.info-price 下有有效金额(>= 0.01)→ 确定是真正可支付的弹窗 * pay-dialog class 不可靠,没拿到 bizId 时也会弹出空壳 pay-dialog */ function isSuccessDialog(el) { const infoPrice = el.querySelector('.info-price'); if (infoPrice) { const spans = infoPrice.querySelectorAll(':scope > span'); if (spans.length >= 2) { const priceText = spans[spans.length - 1].textContent.trim(); if (priceText && /^\d+(\.\d+)?$/.test(priceText)) return true; } } return false; } /** 检测页面上是否已出现支付弹窗,如果出现则立即停止所有进程 */ function checkAndProtectPayDialog() { const payDialogs = document.querySelectorAll('.pay-dialog, .el-dialog'); for (const dlg of payDialogs) { const style = window.getComputedStyle(dlg); if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') continue; if (!dlg.offsetParent && style.position !== 'fixed') continue; if (isSuccessDialog(dlg)) { // 检测到支付弹窗 → 立即停止一切 if (S.status === 'retrying') { log('🛡️ 检测到支付弹窗,立即停止所有重试进程'); stopRequested = true; _retrySessionId++; S.status = 'success'; refreshUI(); notifySuccess(); } return true; } } return false; } /** 查找页面上可见的错误弹窗 */ 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; if (isSuccessDialog(el)) continue; const text = el.textContent || ''; if (/购买人数过多|系统繁忙|稍后再试|请重试|繁忙|失败|出错|异常|售罄|已售罄|抢光|抢完/.test(text)) { return el; } } } return null; } /** 关闭弹窗(保护支付/成功弹窗) */ function dismissDialog(dialog) { if (isSuccessDialog(dialog)) { log('🛡️ 检测到支付/成功弹窗,跳过关闭'); return false; } 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; } } 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; } } document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27, bubbles: true })); document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', keyCode: 27, bubbles: true })); log('🔄 发送 Escape 键'); 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; } } dialog.style.display = 'none'; const overlays = document.querySelectorAll('.el-overlay, .v-modal'); overlays.forEach(o => (o.style.display = 'none')); log('🔄 强制隐藏弹窗'); return true; } function cleanupBlockingState() { resetPackageButtons(); const dialog = findErrorDialog(); if (dialog) dismissDialog(dialog); const masks = document.querySelectorAll('.el-overlay, .v-modal, .el-overlay-dialog, [class*="overlay"], [class*="mask"]'); masks.forEach(mask => { const style = window.getComputedStyle(mask); if (style.display !== 'none' && style.position === 'fixed') { // 检查遮罩内是否有支付/成功弹窗 const innerDialog = mask.querySelector('.el-dialog, .el-message-box, [role="dialog"], [class*="dialog"], [class*="modal"]'); if (innerDialog && isSuccessDialog(innerDialog)) { return; } mask.style.display = 'none'; } }); } /** 自动恢复:关闭错误弹窗 → 有缓存则重新触发购买 */ async function autoRecover() { if (recovering || recoveryAttempts >= 3) return; const dialog = findErrorDialog(); if (!dialog) return; recovering = true; recoveryAttempts++; try { // 有成功结果 → 完整恢复流程(关弹窗 + 缓存 + 重新触发) if (S.lastSuccess) { log('🔄 检测到错误弹窗,启动自动恢复(有缓存)…'); S.cache = S.lastSuccess; } else { log('🔄 检测到错误弹窗,关闭清理(无缓存)…'); } dismissDialog(dialog); await sleep(500); const stillThere = findErrorDialog(); if (stillThere) { dismissDialog(stillThere); await sleep(300); } cleanupBlockingState(); // 有缓存才重新触发购买,没缓存只清理弹窗 if (S.lastSuccess) { const btn = findPresetBuyButton(S.productMode) || findBuyButton(); if (btn) { const label = PACKAGE_OPTIONS[S.productMode]?.label || '当前套餐'; triggerPresetBuyButton(btn, label); log('🖱 已自动重新触发购买按钮'); } else { log('⚠️ 未找到购买按钮,请手动点击'); alert('已获取到商品!请立即手动点击购买按钮!'); } } } finally { recovering = false; } } /** 持续监控:仅 retrying 阶段生效,支付弹窗停机保护 + 错误弹窗一律关闭 */ function setupDialogWatcher() { setInterval(() => { if (S.status !== 'retrying') return; // 优先检测支付弹窗 → 立即停机 if (checkAndProtectPayDialog()) return; // 检测到错误弹窗 → 一律关闭(有缓存则顺便重新触发购买) if (!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.lastCaptureAt = Date.now(); S.captured = { url, method: init?.method || 'POST', body: init?.body, headers: extractHeaders(init?.headers), }; try { sessionStorage.setItem(CAPTURE_STORAGE_KEY, JSON.stringify(S.captured)); } catch (e) {} log('🎯 捕获 preview 请求 (Fetch)'); refreshUI(); // 测试模式:只捕获请求参数,放行给真实 API if (_testMode) { log('🧪 测试模式:捕获请求,跳过重试引擎'); return _fetch.apply(this, [input, init]); } // 有缓存 → 直接返回(来自弹窗恢复) 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' }, }); } // 已有成功结果 → 直接返回,不再重新 retry(防止误杀支付弹窗) if (S.lastSuccess && S.status === 'success') { log('📦 已有成功结果,直接返回'); return new Response(S.lastSuccess.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, }); if (result.ok) { return new Response(result.text, { status: result.status, headers: { 'Content-Type': 'application/json' }, }); } const failurePayload = buildPreviewFailurePayload(result); if (failurePayload) { return new Response(failurePayload.text, { status: failurePayload.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]); }; window.fetch.toString = () => 'function fetch() { [native code] }'; // ======================== 五、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.lastCaptureAt = Date.now(); S.captured = { url, method: this._m, body, headers: this._h || {} }; try { sessionStorage.setItem(CAPTURE_STORAGE_KEY, JSON.stringify(S.captured)); } catch (e) {} log('🎯 捕获 preview 请求 (XHR)'); refreshUI(); // 测试模式:只捕获请求参数,放行给真实 API if (_testMode) { log('🧪 测试模式:捕获请求,跳过重试引擎 (XHR)'); return _xhrSend.call(this, body); } if (S.cache) { log('📦 返回缓存的成功响应 (XHR)'); const c = S.cache; S.cache = null; recoveryAttempts = 0; fakeXHR(self, c.text); return; } // 已有成功结果 → 直接返回 if (S.lastSuccess && S.status === 'success') { log('📦 已有成功结果,直接返回 (XHR)'); fakeXHR(self, S.lastSuccess.text); return; } retry(url, { method: this._m, body, headers: this._h || {} }).then(result => { const failurePayload = buildPreviewFailurePayload(result); fakeXHR(self, result.ok ? result.text : (failurePayload?.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 startTest() { if (!ensureInviteReady('测试模式启动前')) return; const btn = findPresetBuyButton(S.productMode); const label = PACKAGE_OPTIONS[S.productMode]?.label || '当前套餐'; if (!btn) { log(`⚠️ 未找到 ${label} 的页面购买按钮,无法测试`); alert(`未找到 ${label} 的购买按钮,请确认页面已加载套餐卡片。`); return; } // 先停掉所有正在运行的重试 stopRequested = true; _retrySessionId++; await sleep(100); _testMode = true; stopRequested = false; S.lastSuccess = null; S.bizId = null; S.cache = null; S.status = 'idle'; log(`🧪 测试模式启动:将对 ${label} 发起 10 次真实请求,随后强制走成功逻辑`); // 清理现有弹窗 const errDlg = findErrorDialog(); if (errDlg) { dismissDialog(errDlg); await sleep(120); } cleanupBlockingState(); // 使用已捕获的请求参数,没有则手动构造(不再点击页面按钮,避免触发弹窗) if (!S.captured) { S.captured = { url: `${location.origin}${CFG.PREVIEW}`, method: 'POST', body: null, headers: {}, }; log('⚠️ 无已捕获请求,使用默认参数'); } else { log(`📦 使用已捕获的请求参数: ${S.captured.url}`); } // 发起 10 次真实请求(用 _fetch 绕过拦截器) const { url, method, body, headers } = S.captured; S.status = 'retrying'; S.count = 0; refreshUI(); let lastRealResponse = null; for (let i = 1; i <= 10 && !stopRequested; i++) { S.count = i; refreshUI(); try { const resp = await _fetch(url, { method, body, headers, credentials: 'include', }); const text = await resp.text(); let data; try { data = _parse(text); } catch { data = null; } lastRealResponse = { text, data, status: resp.status }; const reason = !data ? '非JSON响应' : data.code === 200 && data.data?.bizId ? `有效bizId=${data.data.bizId}` : data.code === 200 && data.data?.bizId === null ? '售罄(bizId=null)' : data.code === 555 ? '系统繁忙(555)' : `code=${data.code}`; log(`🧪 #${i}/10 ${reason}`); } catch (e) { log(`🧪 #${i}/10 网络错误: ${e.message}`); } await sleep(200); } if (stopRequested) { _testMode = false; S.status = 'idle'; refreshUI(); log('🧪 测试已中止'); return; } // ===== 强制走成功逻辑 ===== _testMode = false; // 关闭测试模式,让拦截器正常工作 const fakeBizId = 'test_' + Date.now(); const fakeText = lastRealResponse?.text || JSON.stringify({ code: 200, data: { bizId: fakeBizId }, msg: '测试模式模拟成功' }); try { const parsed = _parse(fakeText); if (parsed.data) parsed.data.bizId = fakeBizId; const rewritten = JSON.stringify(parsed); S.lastSuccess = { text: rewritten, data: parsed }; } catch (e) { S.lastSuccess = { text: fakeText, data: { code: 200, data: { bizId: fakeBizId } } }; } S.bizId = fakeBizId; S.status = 'success'; S.cache = S.lastSuccess; // 缓存,下次点击直接返回 log(`🧪 测试完成!强制成功 bizId=${fakeBizId}`); log(`🧪 已缓存成功响应,触发按钮打开弹窗…`); refreshUI(); notifySuccess(); // 触发页面按钮 → 拦截器命中缓存 → 返回成功响应 → 页面弹窗 const btnAgain = findPresetBuyButton(S.productMode); if (btnAgain) { triggerPresetBuyButton(btnAgain, label); } } // ======================== 八、主动抢购模式 ======================== async function startProactive(triggerMode = '主动抢购') { if (!ensureInviteReady(`${triggerMode}前`)) return; const btn = findPresetBuyButton(S.productMode); if (!btn) { const label = PACKAGE_OPTIONS[S.productMode]?.label || S.productMode; log(`⚠️ 未找到 ${label} 对应的页面购买按钮`); alert(`未找到 ${label} 对应的页面购买按钮,请确认当前页面已展示套餐卡片。`); return; } S.lastTriggerMode = triggerMode; stopRequested = false; // 重置上次成功状态,允许重新 retry S.lastSuccess = null; S.bizId = null; const label = PACKAGE_OPTIONS[S.productMode].label; const captureMark = S.lastCaptureAt; const errDlg = findErrorDialog(); if (errDlg) { dismissDialog(errDlg); await sleep(120); } cleanupBlockingState(); log(`🚀 ${triggerMode}启动,准备点击 ${label} 对应按钮`); triggerPresetBuyButton(btn, label); setTimeout(() => { if (S.lastCaptureAt > captureMark || S.status === 'retrying') return; log(`⚠️ ${label} 首次点击未触发 preview,尝试再次增强点击`); triggerPresetBuyButton(btn, label); setTimeout(() => { if (S.lastCaptureAt > captureMark || S.status === 'retrying') return; alert(`${label} 按钮已点击,但页面未发起 preview 请求。请确认套餐区域是否可操作,或手动点击一次该套餐按钮。`); }, 700); }, 450); } function stopAll() { resetRuntimeState(); 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; } clearTimerTicker(); clearInvitePrecheck(); 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; S.timerTargetTs = target.getTime(); log(`⏰ 已设定: ${timeStr} (${Math.ceil(ms / 1000)}秒后)`); const precheckDelay = Math.max(0, ms - INVITE_PRECHECK_LEAD_MS); _invitePrecheckId = setTimeout(() => { _invitePrecheckId = null; ensureInviteReady('定时抢购前 10 秒校验'); }, precheckDelay); S.timerId = setTimeout(() => { S.timerId = null; S.timerTargetTs = 0; clearTimerTicker(); clearInvitePrecheck(); if (!ensureInviteReady('定时抢购启动前')) return; log('⏰ 时间到! 启动抢购!'); startProactive('定时抢购'); }, ms - 50); startTimerTicker(); refreshUI(); } // ======================== 十、浮动控制面板 ======================== function createPanel() { const panel = document.createElement('div'); panel.id = 'glm-rush'; panel.innerHTML = `