// ==UserScript== // @name 智谱 GLM Coding Plan 抢购助手 + 本地 OCR 自动验证码 // @namespace http://tampermonkey.net/ // @version 23.17 // @description GLM Coding Rush / 智谱 GLM Coding Plan 抢购助手,一键抢购油猴脚本 / Tampermonkey userscript,配合本地 CPU/GPU OCR(PP-OCRv6)自动识别中文点选验证码并点击,支持多窗口并发、限流重试和支付页安全保护。订阅入口被风控拦截时手动点「特惠订阅」即可,验证码自动打。 // @author mumumi // @include https://*bigmodel.cn/glm-coding* // @match https://bigmodel.cn/glm-coding* // @match https://www.bigmodel.cn/glm-coding* // @match https://platform.minimaxi.com/* // @match https://*.gtimg.com/* // @match https://*.captcha.qcloud.com/* // @include https://*bigmodel.cn/html/rate-limit.html* // @icon https://www.google.com/s2/favicons?sz=64&domain=bigmodel.cn // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_openInTab // @grant GM_xmlhttpRequest // @grant GM_addValueChangeListener // @connect localhost // @connect localhost:8888 // @connect 127.0.0.1 // @connect 127.0.0.1:8888 // @connect gtimg.com // @connect *.gtimg.com // @connect captcha.qcloud.com // @connect *.captcha.qcloud.com // @connect turing.captcha.qcloud.com // @run-at document-start // @license GNU GPLv3 // @source https://greasyfork.org/zh-CN/scripts/572157-glm-coding-plan%E6%8A%A2%E8%B4%AD%E5%8A%A9%E6%89%8B // @credit Based on mumumi's GLM Coding Plan helper; thanks to the original author. // ==/UserScript== (function () { 'use strict'; const SCRIPT_VERSION = '23.17'; const BOOT_BAR_ID = 'glm-helper-status-bar'; const __glmHost = (() => { try { return location.hostname || ''; } catch { return ''; } })(); const __inMiniMax = __glmHost === 'platform.minimaxi.com'; if (__inMiniMax) { initMiniMaxTokenPlanEntry(); return; } const __inTencentCaptchaFrame = __glmHost.includes('gtimg.com') || __glmHost.includes('captcha.qcloud.com'); if (__inTencentCaptchaFrame) { initTencentCaptchaDirectBridge(); return; } function initTencentCaptchaDirectBridge() { const DIRECT_OCR_URL = 'http://127.0.0.1:8888/captcha_direct'; const RUNTIME_PAUSE_KEY = 'glm_runtime_paused_v1'; const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); let solving = false; let lastBgUrl = ''; const captchaCfg = (() => { try { const raw = GM_getValue('glm_coding_config_v5', '{}'); return { AUTO_CAPTCHA_CLICK: true, AUTO_CAPTCHA_CONFIRM: false, ...JSON.parse(raw || '{}') }; } catch { return { AUTO_CAPTCHA_CLICK: true, AUTO_CAPTCHA_CONFIRM: false }; } })(); function log(msg) { console.log('[glm-captcha-direct] ' + msg); } function isRuntimePaused() { try { return GM_getValue(RUNTIME_PAUSE_KEY, false) === true; } catch { return false; } } function visible(el) { if (!el) return false; const style = getComputedStyle(el); if (style.display === 'none' || style.visibility === 'hidden') return false; const rect = el.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; } function bgUrlFrom(el) { if (!el) return ''; const text = (el.style && el.style.backgroundImage ? el.style.backgroundImage : '') || getComputedStyle(el).backgroundImage || ''; const match = text.match(/url\(["']?([^"')]+)["']?\)/); if (!match) return ''; try { return new URL(match[1], location.href).href; } catch { return match[1]; } } function findBgElement() { const selectors = [ '#slideBg', '.tencent-captcha-dy__verify-bg-img', '[class*="verify-bg"]', '.tencent-captcha-dy__bg-img', '.tencent-captcha-dy__image-area', ]; for (const selector of selectors) { const el = document.querySelector(selector); if (visible(el) && bgUrlFrom(el)) return el; } return null; } function findPromptText() { const selectors = [ '#instructionText', '.tencent-captcha-dy__header-text', '.tencent-captcha-dy__header-title-wrap .tencent-captcha-dy__header-text', '[class*="header-text"]', ]; for (const selector of selectors) { const el = document.querySelector(selector); if (!visible(el)) continue; const raw = (el.textContent || el.getAttribute('aria-label') || '').trim(); const cleaned = raw .replace(/^\s*\u8BF7\u4F9D\u6B21\u70B9\u51FB[:\uff1a]?\s*/, '') .replace(/\s+/g, ''); // 排除说明性文字("验证码""安全验证"等),它们也会被当成目标字 // 传给后端导致 detected 0 boxes。 if (/\u9A8C\u8BC1\u7801|\u5B89\u5168\u9A8C\u8BC1|\u70B9\u51FB\u56FE\u4E2D|\u70B9\u51FB\u8FDB\u884C|\u56FE\u5F62\u9A8C\u8BC1|\u8BF7\u70B9\u51FB|\u5B8C\u6210\u9A8C\u8BC1|\u8FDB\u884C\u9A8C\u8BC1/.test(cleaned)) continue; const chars = (cleaned.match(/[\u4e00-\u9fff]/g) || []).slice(-3); if (chars.length >= 3) return chars.join(''); } return ''; } function fetchImageDataUrl(url) { function doFetch() { return fetch(url, { credentials: 'include' }) .then(r => r.blob()) .then(blob => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = () => reject(new Error('FileReader failed')); reader.readAsDataURL(blob); })); } function doGM() { return new Promise((resolve, reject) => { if (typeof GM_xmlhttpRequest === 'undefined') { reject(new Error('GM_xmlhttpRequest unavailable')); return; } GM_xmlhttpRequest({ method: 'GET', url, responseType: 'blob', onload: (res) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = () => reject(new Error('FileReader failed')); reader.readAsDataURL(res.response); }, onerror: () => reject(new Error('image download failed')), }); }); } return doFetch().catch(() => doGM()); } function postDirect(dataUrl, chars) { const body = JSON.stringify({ image: dataUrl, text: chars, ts: Date.now(), source: 'tencent_iframe_direct', }); function doFetch() { return fetch(DIRECT_OCR_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body, }).then(r => r.json()); } function doGM() { return new Promise((resolve, reject) => { if (typeof GM_xmlhttpRequest === 'undefined') { reject(new Error('GM_xmlhttpRequest unavailable')); return; } GM_xmlhttpRequest({ method: 'POST', url: DIRECT_OCR_URL, headers: { 'Content-Type': 'application/json' }, data: body, onload: (res) => { try { resolve(JSON.parse(res.responseText)); } catch { reject(new Error('bad direct OCR JSON')); } }, onerror: () => reject(new Error('direct OCR request failed')), }); }); } // 同 serverRequest:localhost 路径优先走 GM 以避开 LNA fetch 拦截 return (typeof GM_xmlhttpRequest === 'function') ? doGM().catch(() => doFetch()) : doFetch(); } function dispatchClick(el, nx, ny, label) { const rect = el.getBoundingClientRect(); const win = el.ownerDocument.defaultView || window; const clientX = rect.left + nx * rect.width; const clientY = rect.top + ny * rect.height; const base = { bubbles: true, cancelable: true, view: win, clientX, clientY, button: 0, buttons: 1 }; const pointer = { ...base, pointerId: 1, pointerType: 'mouse', isPrimary: true, pressure: 0.5 }; try { if (win.PointerEvent) el.dispatchEvent(new win.PointerEvent('pointerdown', pointer)); } catch {} el.dispatchEvent(new win.MouseEvent('mousedown', base)); try { if (win.PointerEvent) el.dispatchEvent(new win.PointerEvent('pointerup', pointer)); } catch {} el.dispatchEvent(new win.MouseEvent('mouseup', base)); el.dispatchEvent(new win.MouseEvent('click', base)); log('clicked ' + (label || '') + ' @ ' + nx.toFixed(3) + ',' + ny.toFixed(3)); } function clickConfirm() { const selectors = [ '.verify-btn', '.tencent-captcha-dy__verify-confirm-btn', '.tencent-captcha-dy__btn-confirm', '.tencent-captcha-dy__footer .btn', ]; for (const selector of selectors) { const btn = document.querySelector(selector); if (visible(btn)) { btn.click(); log('confirm clicked: ' + selector); return true; } } return false; } function hasError() { const note = document.querySelector('#tcaptcha_note, .tencent-captcha-dy__verify-error-text'); return visible(note); } async function solveOnce() { if (isRuntimePaused()) return; if (!captchaCfg.AUTO_CAPTCHA_CLICK) return; const bgEl = findBgElement(); if (!bgEl) return; const bgUrl = bgUrlFrom(bgEl); if (!bgUrl || bgUrl === lastBgUrl) return; const chars = findPromptText(); if (chars.length < 3) return; if (hasError()) { const reload = document.querySelector('#reload, .tencent-captcha-dy__footer-icon--refresh img'); if (reload) reload.click(); lastBgUrl = ''; return; } lastBgUrl = bgUrl; log('capture ' + chars + ' from ' + bgUrl.slice(0, 90)); const dataUrl = await fetchImageDataUrl(bgUrl); const response = await postDirect(dataUrl, chars); const result = response && response.result; if (!result || !result.success || !Array.isArray(result.click_coords)) { log('direct OCR failed: ' + JSON.stringify(response).slice(0, 200)); return; } for (const point of result.click_coords) { const nx = Number(point.nx); const ny = Number(point.ny); if (!Number.isFinite(nx) || !Number.isFinite(ny)) continue; dispatchClick(bgEl, nx, ny, point.char || ''); await sleep(180); } await sleep(250); if (!isRuntimePaused() && captchaCfg.AUTO_CAPTCHA_CONFIRM) clickConfirm(); } async function tick() { if (solving) return; solving = true; try { await solveOnce(); } catch (e) { log('error: ' + e.message); lastBgUrl = ''; } finally { solving = false; } } log('started on ' + location.hostname); const observer = new MutationObserver(() => setTimeout(tick, 80)); const root = document.body || document.documentElement; observer.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); setTimeout(tick, 500); setInterval(tick, 200); // 验证码检测频率:200ms(之前 1200ms 太慢,验证码出来后要等最坏 1.2s 才开始处理) } // ── 去重保护:防止篡猴里装了改名导致的两个实例同时运行 ────────────────── function ensureStatusBarNode() { let bar = document.getElementById(BOOT_BAR_ID); if (bar) return bar; const parent = document.body || document.documentElement; if (!parent) return null; bar = document.createElement('div'); bar.id = BOOT_BAR_ID; bar.style.cssText = 'position:fixed;bottom:0;left:0;right:0;z-index:2147483647;padding:7px 16px;font:13px/1.5 system-ui,sans-serif;color:#fff;display:flex;align-items:center;justify-content:space-between;box-shadow:0 -2px 8px rgba(0,0,0,.25);transition:background .4s'; const text = document.createElement('span'); const x = document.createElement('button'); x.textContent = '×'; x.style.cssText = 'background:rgba(255,255,255,.2);border:none;color:#fff;width:22px;height:22px;border-radius:4px;cursor:pointer;font-size:16px;line-height:1;flex-shrink:0'; x.onclick = () => { bar.remove(); }; bar.append(text, x); parent.appendChild(bar); return bar; } function setBootBar(html, bg = '#1677ff') { const run = () => { const bar = ensureStatusBarNode(); if (!bar) return; bar.style.background = bg; bar.firstElementChild.innerHTML = `🤖 抢购助手 v${SCRIPT_VERSION}  |  ${html}`; }; if (document.body || document.documentElement) run(); else document.addEventListener('DOMContentLoaded', run, { once: true }); } function requestBackendHealth(timeoutMs = 2500) { return new Promise((resolve, reject) => { const url = 'http://127.0.0.1:8888/health?from=glm_helper_' + Date.now(); let done = false; const timer = setTimeout(() => { if (done) return; done = true; reject(new Error('timeout')); }, timeoutMs); function finish(ok, value) { if (done) return; done = true; clearTimeout(timer); ok ? resolve(value) : reject(value); } if (typeof GM_xmlhttpRequest === 'function') { GM_xmlhttpRequest({ method: 'GET', url, timeout: timeoutMs, onload: (res) => { try { finish(true, JSON.parse(res.responseText || '{}')); } catch (e) { finish(false, e); } }, onerror: () => finish(false, new Error('request failed')), ontimeout: () => finish(false, new Error('timeout')), }); return; } fetch(url, { cache: 'no-store' }) .then(r => r.json()) .then(data => finish(true, data)) .catch(e => finish(false, e)); }); } function bootBackendProbe() { setBootBar('准备中:默认不主动点击订阅;按 F9 开启自动点击,或等待 Rush 目标时间。正在检查本地 OCR 后端...', '#1677ff'); requestBackendHealth().then((h) => { const ready = `${h.ready_workers ?? '?'} / ${h.workers ?? '?'}`; const status = h.status || 'unknown'; const color = status === 'ok' ? '#237804' : '#d46b08'; setBootBar(`已连接本地 OCR 后端:${status},worker ${ready},端口 ${h.port || 8888}。默认不自动点击订阅;按 F9 开启,按 F8 暂停。`, color); }).catch(() => { setBootBar('未连接本地 OCR 后端:请先启动后端 127.0.0.1:8888。脚本仍会准备页面,但验证码识别不可用;默认不自动点击订阅,按 F9 可手动开启。', '#d46b08'); }); } if (document.documentElement.dataset.glmHelper === '1') { setBootBar('检测到页面上已有一个抢购助手实例,本实例未重复启动。若没有状态提示,请在篡改猴里只保留最新版脚本。', '#d46b08'); return; } document.documentElement.dataset.glmHelper = '1'; bootBackendProbe(); // ── 最早读配置(document-start 时还没有主流程)────────────────────────── const EARLY_STORAGE_KEY = 'glm_coding_config_v5'; const SAFE_DEFAULTS_VERSION = 4; const _ec = (() => { try { return JSON.parse(GM_getValue(EARLY_STORAGE_KEY, '{}')); } catch { return {}; } })(); if (_ec.SAFE_DEFAULTS_VERSION !== SAFE_DEFAULTS_VERSION) { _ec.AUTO_CLOSE_INVALID = false; _ec.AUTO_CLICK_SUB = false; const oldRushTarget = (_ec.RUSH_TARGET_HOUR == null && _ec.RUSH_TARGET_MIN == null && _ec.RUSH_TARGET_SEC == null) || (Number(_ec.RUSH_TARGET_HOUR) === 9 && Number(_ec.RUSH_TARGET_MIN) === 59 && Number(_ec.RUSH_TARGET_SEC) === 58); if (oldRushTarget) { _ec.RUSH_TARGET_HOUR = 10; _ec.RUSH_TARGET_MIN = 0; _ec.RUSH_TARGET_SEC = 0; } if (_ec.RUSH_RELEASE_ADVANCE_MS == null || Number(_ec.RUSH_RELEASE_ADVANCE_MS) === 40) { _ec.RUSH_RELEASE_ADVANCE_MS = 0; } _ec.SAFE_DEFAULTS_VERSION = SAFE_DEFAULTS_VERSION; GM_setValue(EARLY_STORAGE_KEY, JSON.stringify(_ec)); } const EARLY_AUTO_CLOSE_INVALID = _ec.AUTO_CLOSE_INVALID === true; // 邀请码活动已下线,注释注入逻辑。活动恢复时取消注释即可。 // const GLM_DISCOUNT_CODE = ['9G', 'XW', 'L9', 'KC', 'GZ'].join(''); // const GLM_CODING_URL = () => `https://www.bigmodel.cn/glm-coding?ic=${GLM_DISCOUNT_CODE}&closedialog=true`; const GLM_CODING_URL = () => 'https://www.bigmodel.cn/glm-coding'; // function ensureDiscountEntry() { // try { // if (!/\/glm-coding(?:\/|$)/.test(location.pathname || '')) return false; // const u = new URL(location.href); // u.protocol = 'https:'; // u.hostname = 'www.bigmodel.cn'; // if (location.protocol === 'https:' && location.hostname === 'www.bigmodel.cn' && // u.searchParams.get('ic') === GLM_DISCOUNT_CODE && u.searchParams.get('closedialog') === 'true') return false; // u.searchParams.set('ic', GLM_DISCOUNT_CODE); // u.searchParams.set('closedialog', 'true'); // location.replace(u.toString()); // return true; // } catch { // return false; // } // } function initMiniMaxTokenPlanEntry() { const MINIMAX_CODE = ['KkI', '58QB', '9Ip'].join(''); const MINIMAX_TOKEN_PLAN_URL = () => `https://platform.minimaxi.com/subscribe/token-plan?code=${MINIMAX_CODE}&source=link`; try { GM_registerMenuCommand('打开 MiniMax Token Plan 优惠入口', () => { location.href = MINIMAX_TOKEN_PLAN_URL(); }); } catch {} try { const u = new URL(location.href); if (u.pathname !== '/subscribe/token-plan') return; if (u.searchParams.get('code') === MINIMAX_CODE && u.searchParams.get('source') === 'link') return; u.searchParams.set('code', MINIMAX_CODE); u.searchParams.set('source', 'link'); location.replace(u.toString()); } catch {} } // ── 限流页立即跳回主页 ──────────────────────────────────────────────────── // 邀请码活动已下线,ensureDiscountEntry 已注释;活动恢复时取消下一行注释即可。 // if (!location.href.includes('rate-limit.html') && ensureDiscountEntry()) return; if (location.href.includes('rate-limit.html') && EARLY_AUTO_CLOSE_INVALID) { location.replace(GLM_CODING_URL()); return; } if (location.href.includes('rate-limit.html')) { window.addEventListener('DOMContentLoaded', () => { const notice = document.createElement('div'); notice.textContent = 'GLM Coding Helper: auto-close is disabled. Handle this rate-limit page manually, or enable auto-close in the helper config.'; notice.style.cssText = 'position:fixed;left:0;right:0;bottom:0;z-index:2147483647;padding:10px 16px;background:#d46b08;color:#fff;font:14px/1.5 system-ui,sans-serif'; document.body.appendChild(notice); }); return; } // ── v8.0: 无条件激活售罄按钮 - JSON.parse 劫持 ────────────────────────── const _oP = JSON.parse; JSON.parse = function (t, r) { const o = _oP(t, r); try { (function f(x) { if (!x || typeof x !== 'object') return; if ('isSoldOut' in x && x.isSoldOut === true) x.isSoldOut = false; if ('soldOut' in x && x.soldOut === true) x.soldOut = false; if ('disabled' in x && x.disabled === true && (x.price !== undefined || x.productId || x.title)) x.disabled = false; if ('stock' in x && x.stock === 0) x.stock = 999; for (const k in x) f(x[k]); })(o); } catch {} return o; }; // ── 购买状态(fetch 拦截器 ↔ UI 主循环共享)───────────────────────────── const PS = { inProgress : false, result : null, // null | 'success' | 'sold_out' | 'busy' | 'error' bizId : null, payAmount : null, rawCode : null, // v8.9: 记录原始错误码(555/500等) }; let everSucceeded = false; // v8.9: 一旦拿到过有效 bizId,永不关闭弹窗 // ── fetch 拦截(/api/biz/pay/preview 和 check)────────────────────────── const _oF = window.fetch; // v8.0: 从 Cookie 提取 token 和从页面提取组织/项目信息 function getAuthHeaders() { const token = document.cookie.match(/bigmodel_token_production=([^;]+)/)?.[1] || ''; const headers = { 'authorization': token, 'content-type': 'application/json;charset=UTF-8', 'accept': 'application/json, text/plain, */*', 'accept-language': 'zh', 'set-language': 'zh' }; // 尝试从 localStorage 获取组织和项目 ID try { const orgId = localStorage.getItem('bigmodel_organization') || ''; const projId = localStorage.getItem('bigmodel_project') || ''; headers['bigmodel-organization'] = orgId; headers['bigmodel-project'] = projId; } catch {} return headers; } window.fetch = async function (...a) { const url = (typeof a[0] === 'string' ? a[0] : a[0]?.url) || ''; // 拦截 preview:只发一次,不重试(验证码只能用一次) if (url.includes('/api/biz/pay/preview')) { PS.inProgress = true; PS.result = null; PS.bizId = null; PS.payAmount = null; // 提取原始 body const [urlOrReq, init = {}] = a; let body = init.body; if (!body && urlOrReq instanceof Request) { body = await urlOrReq.clone().text(); } const authHeaders = getAuthHeaders(); try { const r = await _oF(url, { method: 'POST', headers: authHeaders, body: body, credentials: 'include' }); const txt = await r.text(); let d; try { d = _oP(txt); } catch { d = {}; } console.log('[GLM v8.0 DEBUG] preview响应:', d); console.log('[GLM v8.0 DEBUG] soldOut值:', d?.data?.soldOut, '类型:', typeof d?.data?.soldOut); if (r.status === 405) { // 405 是阿里云 WAF 速率风控(响应是 HTML 拦截页,不是 JSON)。 // 单独标识 risk_control,配合冷却机制减速,避免立即重试越撞越多。 console.log('[GLM] preview 405 风控拦截(阿里云 WAF),标记 risk_control'); PS.result = 'risk_control'; PS.rawCode = 405; PS.inProgress = false; return new Response(txt, { status: r.status, headers: { 'Content-Type': 'application/json' } }); } if (d?.code === 200 && d?.data?.bizId) { PS.result = 'success'; PS.bizId = d.data.bizId; PS.payAmount = d.data.payAmount; PS.inProgress = false; everSucceeded = true; return new Response(txt, { status: 200, headers: { 'Content-Type': 'application/json' } }); } else if (d?.code === 555 || (d?.code >= 500 && d?.code !== 200)) { console.log(`[GLM v8.9] preview 错误 code:${d?.code} msg:${d?.msg},脚本将自动重试`); PS.result = 'busy'; PS.rawCode = d?.code; PS.inProgress = false; return new Response( JSON.stringify({ code: 500, msg: '系统繁忙,脚本自动重试中', data: null, success: false }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } else if (d?.code === 200 && d?.data?.soldOut === true) { console.log('[GLM v8.9] preview返回200+soldOut,原样透传,脚本记录sold_out'); PS.result = 'sold_out'; PS.inProgress = false; return new Response(txt, { status: r.status, headers: { 'Content-Type': 'application/json' } }); } else { console.log('[GLM v8.9] preview 非预期错误 code:', d?.code, 'msg:', d?.msg, '→ 标记busy'); PS.result = 'busy'; PS.rawCode = d?.code; PS.inProgress = false; return new Response(txt, { status: r.status, headers: { 'Content-Type': 'application/json' } }); } } catch (e) { PS.result = 'error'; PS.inProgress = false; throw e; } } // 拦截 check:如果 bizId 为 null,直接返回失败 if (url.includes('/api/biz/pay/check')) { if (url.includes('bizId=null') || !PS.bizId) { return new Response( '{"code":500,"msg":"无效的 bizId","data":null,"success":false}', { status: 200, headers: { 'Content-Type': 'application/json' } } ); } } const res = await _oF.apply(this, a); const rCt = res.headers.get('content-type') || ''; if (rCt.includes('json') && (url.includes('/api/') || url.includes('bigmodel'))) { try { const txt = await res.clone().text(); const mod = txt .replace(/"isSoldOut"\s*:\s*true/g, '"isSoldOut":false') .replace(/"soldOut"\s*:\s*true/g, '"soldOut":false') .replace(/"stock"\s*:\s*0/g, '"stock":999') .replace(/"disabled"\s*:\s*true/g, '"disabled":false') .replace(/"available"\s*:\s*false/g, '"available":true') .replace(/"purchasable"\s*:\s*false/g, '"purchasable":true'); if (mod !== txt) console.log('[GLM v8.4] API响应已修改:', url.slice(0, 80)); return new Response(mod, { status: res.status, statusText: res.statusText, headers: res.headers }); } catch (e) { return res; } } return res; }; // XHR 兜底(重定向到上方 fetch 拦截器) const _xO = XMLHttpRequest.prototype.open; const _xS = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (m, u, ...r) { this._u = u; this._m = m; return _xO.call(this, m, u, ...r); }; XMLHttpRequest.prototype.send = function (...a) { if ((this._u || '').includes('/api/biz/pay/preview')) { const self = this; window.fetch(this._u, { method: this._m || 'POST', body: a[0], credentials: 'include' }).then(async r => { const txt = await r.text(); const dp = (k, v) => Object.defineProperty(self, k, { value: v, configurable: true }); dp('readyState', 4); dp('status', 200); dp('statusText', 'OK'); dp('responseText', txt); dp('response', txt); const ev = new Event('readystatechange'); if (typeof self.onreadystatechange === 'function') self.onreadystatechange(ev); self.dispatchEvent(ev); ['load', 'loadend'].forEach(t => { const e = new ProgressEvent(t); if (typeof self[`on${t}`] === 'function') self[`on${t}`](e); self.dispatchEvent(e); }); }); return; } return _xS.apply(this, a); }; // ── 每日套餐状态(localStorage,按日隔离)──────────────────────────────── // -1未知 0进行中(重启复位) 1今日售罄 2今日已购 const _today = new Date().toISOString().slice(0, 10); const _dsKey = `glm_ds_${_today}`; let _ds = (() => { try { return JSON.parse(localStorage.getItem(_dsKey) || '{}'); } catch { return {}; } })(); Object.keys(_ds).forEach(k => { if (_ds[k] === 0) _ds[k] = -1; }); _flush(); function _flush() { localStorage.setItem(_dsKey, JSON.stringify(_ds)); } function getS(t, p) { return _ds[`${t}-${p}`] ?? -1; } function setS(t, p, v) { _ds[`${t}-${p}`] = v; _flush(); } if (Object.values(_ds).includes(2)) { setTimeout(() => setBar('🎉 今日已订阅成功,脚本停止。', '#237804'), 800); return; } // ── 配置 ────────────────────────────────────────────────────────────────── const STORAGE_KEY = 'glm_coding_config_v5'; const RUNTIME_PAUSE_KEY = 'glm_runtime_paused_v1'; // 跨窗口批量暂停/恢复用的广播 key(Shift+F8 写,其他窗口监听)。 // 与 RUNTIME_PAUSE_KEY 分开:后者负责持久化(新窗口继承暂停态),广播 key 只做实时联动。 const PAUSE_BROADCAST_KEY = 'glm_runtime_pause_broadcast_v1'; const TABS_MAP = { 1: '连续包月', 2: '连续包季', 3: '连续包年' }; const PKGS_MAP = { 1: 'Lite', 2: 'Pro', 3: 'Max' }; const DEF = { TABS_PRIORITY : '1', PACKAGES_PRIORITY : '2,3,1', CHECK_INTERVAL : 80, SMART_REFRESH : true, AUTO_CLOSE_INVALID: false, AUTO_CLICK_SUB : false, AUTO_CAPTCHA_CLICK : true, AUTO_CAPTCHA_CONFIRM: false, CAPTCHA_CLICK_DELAY_MODE : 'range', CAPTCHA_CLICK_DELAY_MS : 325, CAPTCHA_CLICK_DELAY_MIN_MS: 250, CAPTCHA_CLICK_DELAY_MAX_MS: 400, CAPTCHA_CLICK_DELAY_JITTER_PERCENT: 20, // 首击前延迟(验证码 DOM 渲染/动画需要时间,识别太快会导致第一击落空)。随机区间。 CAPTCHA_FIRST_CLICK_DELAY_MIN_MS: 150, CAPTCHA_FIRST_CLICK_DELAY_MAX_MS: 300, RUSH_ENABLED : false, RUSH_TARGET_HOUR : 10, RUSH_TARGET_MIN : 0, RUSH_TARGET_SEC : 0, RUSH_HOLD_WINDOW_MS : 10000, RUSH_RELEASE_ADVANCE_MS: 0, HOTKEY_PAUSE: 'F8', HOTKEY_PAUSE_ALL: 'Shift+F8', HOTKEY_AUTO_CLICK_SUB: 'F9', // 405 风控冷却(毫秒)。preview 接口返回 405 = 阿里云 WAF 速率风控, // 立即重试会越撞越多,关弹窗后进入冷却,让 WAF 阈值下降再恢复抢。 // 0 = 不冷却(关闭整个 405 减速机制)。默认 20000ms(20秒)。 RISK_CONTROL_COOLDOWN_MS: 20000, }; function loadCfg() { try { const s = GM_getValue(STORAGE_KEY, null); return s ? { ...DEF, ...JSON.parse(s) } : { ...DEF }; } catch { return { ...DEF }; } } function saveCfg(c) { GM_setValue(STORAGE_KEY, JSON.stringify(c)); } const CFG = loadCfg(); let runtimePaused = (() => { try { return GM_getValue(RUNTIME_PAUSE_KEY, false) === true; } catch { return false; } })(); // 跨标签页联动:Shift+F8 写广播 key,其他 glm-coding 窗口监听并同步。 // 只覆盖同浏览器普通窗口(无痕/跨浏览器受扩展隔离限制,不在此方案范围内)。 if (typeof GM_addValueChangeListener === 'function') { GM_addValueChangeListener(PAUSE_BROADCAST_KEY, (key, oldVal, newVal, remote) => { if (!remote) return; // 只响应其他标签页的变更,避免回环 const msg = (() => { try { return typeof newVal === 'string' ? JSON.parse(newVal) : null; } catch { return null; } })(); if (!msg || typeof msg.paused !== 'boolean') return; if (msg.paused !== runtimePaused) { setRuntimePaused(msg.paused, 'broadcast'); } }); } const RUSH_LATENCY_KEY = 'glm_rush_latency_v1'; function rushNumber(value, fallback) { const n = parseInt(value, 10); return Number.isFinite(n) ? n : fallback; } function getRushTargetTimestamp(now = Date.now()) { const target = new Date(now); target.setHours( rushNumber(CFG.RUSH_TARGET_HOUR, 10), rushNumber(CFG.RUSH_TARGET_MIN, 0), rushNumber(CFG.RUSH_TARGET_SEC, 0), 0 ); return target.getTime(); } function getRushRemainingMs(now = Date.now()) { return getRushTargetTimestamp(now) - now; } function isRushTargetReached(now = Date.now()) { return CFG.RUSH_ENABLED && getRushRemainingMs(now) <= 0; } function readyModeText() { if (CFG.AUTO_CLICK_SUB) return `自动点击订阅已开启,${autoClickSubHotkey()} 可关闭`; if (CFG.RUSH_ENABLED) { const remaining = Math.max(0, getRushRemainingMs()); if (remaining > 0) return `准备中:默认不主动点击订阅,按 ${autoClickSubHotkey()} 手动开启,或等待目标时间 ${fmt(remaining)}`; return '目标时间已到:允许自动点击订阅'; } return `准备中:默认不主动点击订阅,按 ${autoClickSubHotkey()} 开启,或手动点击订阅`; } function medianRush(values) { const sorted = values.filter(Number.isFinite).sort((a, b) => a - b); if (!sorted.length) return null; return sorted[Math.floor(sorted.length / 2)]; } function computeRushReleaseAdvance(rttMs) { if (!Number.isFinite(rttMs)) return 0; const oneWay = Math.max(0, rttMs / 2); return Math.max(0, Math.min(180, Math.round(oneWay - 20))); } async function calibrateRushLatency() { if (!CFG.RUSH_ENABLED) return; const samples = []; const url = GLM_CODING_URL() + '&rush_probe=' + Date.now(); for (let i = 0; i < 3; i++) { try { const t0 = performance.now(); await fetch(url + '_' + i, { method: 'GET', credentials: 'include', cache: 'no-store' }); samples.push(performance.now() - t0); } catch (e) {} if (i < 2) await new Promise(r => setTimeout(r, 160)); } const rtt = medianRush(samples); if (rtt == null) return; const manualAdvance = Math.max(0, parseInt(CFG.RUSH_RELEASE_ADVANCE_MS, 10) || 0); const autoAdvance = computeRushReleaseAdvance(rtt); const advance = manualAdvance > 0 ? manualAdvance : autoAdvance; const payload = { rttMs: Math.round(rtt), advanceMs: advance, at: Date.now(), source: manualAdvance > 0 ? 'manual' : 'auto' }; GM_setValue(RUSH_LATENCY_KEY, JSON.stringify(payload)); console.log('[GLM] rush latency calibrated:', payload); } GM_registerMenuCommand('⚙️ 打开配置面板', openConfigPanel); GM_registerMenuCommand('🗑️ 清除今日套餐状态缓存', () => { localStorage.removeItem(_dsKey); alert('今日状态已清除,即将刷新。'); location.reload(); }); GM_registerMenuCommand('🚀 一键多开窗口', openMultipleWindows); function normalizeHotkeyString(value, fallback = '') { const raw = String(value || '').trim(); if (!raw) return fallback; const aliases = { esc: 'Escape', escape: 'Escape', ins: 'Insert', insert: 'Insert', del: 'Delete', delete: 'Delete', space: 'Space', ' ': 'Space', pause: 'Pause', home: 'Home', end: 'End', pgup: 'PageUp', pageup: 'PageUp', pgdn: 'PageDown', pagedown: 'PageDown', }; const parts = raw.split('+').map(s => s.trim()).filter(Boolean); const mods = []; let key = ''; for (const part of parts) { const lower = part.toLowerCase(); if (lower === 'ctrl' || lower === 'control') { if (!mods.includes('Ctrl')) mods.push('Ctrl'); continue; } if (lower === 'alt') { if (!mods.includes('Alt')) mods.push('Alt'); continue; } if (lower === 'shift') { if (!mods.includes('Shift')) mods.push('Shift'); continue; } if (lower === 'meta' || lower === 'cmd' || lower === 'win') { if (!mods.includes('Meta')) mods.push('Meta'); continue; } if (/^f([1-9]|1[0-2])$/i.test(part)) key = part.toUpperCase(); else if (aliases[lower]) key = aliases[lower]; else if (part.length === 1) key = part.toUpperCase(); else key = part; } if (!key) return fallback; return [...mods, key].join('+'); } function hotkeyFromEvent(e) { const mods = []; if (e.ctrlKey) mods.push('Ctrl'); if (e.altKey) mods.push('Alt'); if (e.shiftKey) mods.push('Shift'); if (e.metaKey) mods.push('Meta'); let key = e.key || ''; if (!key || ['Control', 'Alt', 'Shift', 'Meta'].includes(key)) return ''; if (key === ' ') key = 'Space'; if (/^f([1-9]|1[0-2])$/i.test(key)) key = key.toUpperCase(); else if (key.length === 1) key = key.toUpperCase(); return [...mods, key].join('+'); } function hotkeyMatches(e, hotkey) { return normalizeHotkeyString(hotkey) === hotkeyFromEvent(e); } const pauseHotkey = () => normalizeHotkeyString(CFG.HOTKEY_PAUSE, 'F8'); const pauseAllHotkey = () => normalizeHotkeyString(CFG.HOTKEY_PAUSE_ALL, 'Shift+F8'); const autoClickSubHotkey = () => normalizeHotkeyString(CFG.HOTKEY_AUTO_CLICK_SUB, 'F9'); GM_registerMenuCommand(`⏯️ 暂停/恢复脚本 (${pauseHotkey()})`, toggleRuntimePause); GM_registerMenuCommand(`⏯️ 批量暂停/恢复(同浏览器) (${pauseAllHotkey()})`, toggleRuntimePauseAll); GM_registerMenuCommand(`🖱️ 切换自动点击订阅 (${autoClickSubHotkey()})`, toggleAutoClickSub); function saveRuntimeCfgPatch(patch) { Object.assign(CFG, patch); saveCfg({ ...CFG }); } function setRuntimePaused(paused, source = 'hotkey') { runtimePaused = paused === true; GM_setValue(RUNTIME_PAUSE_KEY, runtimePaused); const sourceLabel = ({ 'hotkey': '快捷键', 'pause-all': '批量', 'broadcast': '远程同步', })[source] || source; if (runtimePaused) { setBar(`⏸️ 脚本已暂停(${sourceLabel})。${pauseHotkey()} 恢复。`, '#722ed1'); } else { // 恢复时:如果正在 WAITING,重置 taskClickTime(让超时从恢复时刻重新算, // 避免暂停期间 elapsed 虚高导致误超时);如果 WAITING 但弹窗已不在 // (暂停期间被手动关了),直接回 IDLE 重试,避免干等到超时。 if (state === 'TASK_UNIT' && taskPhase === 'WAITING') { taskClickTime = Date.now(); const hasModal = findRLModal() || isPayDialog() || isSuccessDialog(); if (!hasModal) { taskPhase = 'IDLE'; setBar(`▶️ 脚本已恢复(${sourceLabel}),弹窗已关,重试点订阅。`, '#237804'); return; } } setBar(`▶️ 脚本已恢复(${sourceLabel})。`, '#237804'); } } function toggleRuntimePause() { setRuntimePaused(!runtimePaused); } // 批量暂停/恢复:切换当前窗口状态,并写广播 key 让同浏览器其他 glm-coding 窗口同步。 function toggleRuntimePauseAll() { const next = !runtimePaused; setRuntimePaused(next, 'pause-all'); try { GM_setValue(PAUSE_BROADCAST_KEY, JSON.stringify({ paused: next, ts: Date.now() })); } catch (e) {} } function toggleAutoClickSub() { const next = !CFG.AUTO_CLICK_SUB; saveRuntimeCfgPatch({ AUTO_CLICK_SUB: next }); setBar(`${next ? '✅ 已开启' : '🛑 已关闭'}自动点击订阅(${autoClickSubHotkey()})`, next ? '#237804' : '#d46b08'); } function isEditableHotkeyTarget(el) { if (!el) return false; const tag = (el.tagName || '').toLowerCase(); return tag === 'input' || tag === 'textarea' || tag === 'select' || el.isContentEditable; } function handleControlHotkeys(e) { if (isEditableHotkeyTarget(e.target)) return false; // Shift+F8 必须先判:和单键 F8 共享主键,单键分支会先吃掉无 shift 的事件, // 但有 shift 时单键 hotkeyMatches 不匹配,所以顺序其实不影响;放前面语义更清晰。 if (hotkeyMatches(e, pauseAllHotkey())) { e.preventDefault(); toggleRuntimePauseAll(); return true; } if (hotkeyMatches(e, pauseHotkey())) { e.preventDefault(); toggleRuntimePause(); return true; } if (hotkeyMatches(e, autoClickSubHotkey())) { e.preventDefault(); toggleAutoClickSub(); return true; } return false; } // ── v8.0: ESC 键快速关闭弹窗 ────────────────────────────────────────────── document.addEventListener('keydown', (e) => { if (handleControlHotkeys(e)) return; if (e.key === 'Escape' || e.keyCode === 27) { const busyDlg = document.querySelector('.el-dialog__wrapper .empty-data-wrap'); if (busyDlg) { const wrapper = busyDlg.closest('.el-dialog__wrapper'); if (wrapper && getComputedStyle(wrapper).display !== 'none') { closeModal(wrapper); console.log('[GLM] ESC 关闭系统繁忙弹窗'); return; } } const payDlg = getPayDialog(); if (payDlg) { closePayDialog(); console.log('[GLM] ESC 关闭支付弹窗'); } } else if (e.key === 'Enter' || e.keyCode === 13 || e.key === ' ') { e.preventDefault(); var confirmBtn = document.querySelector('.tencent-captcha-dy__verify-confirm-btn'); if (confirmBtn) { var cr = confirmBtn.getBoundingClientRect(); if (cr.width > 0 && cr.height > 0) { confirmBtn.click(); console.log('[GLM] ' + e.key + ' 点击验证码确认按钮'); return; } } var altBtn = document.querySelector('.tencent-captcha-dy__btn-confirm'); if (altBtn) { var ar = altBtn.getBoundingClientRect(); if (ar.width > 0 && ar.height > 0) { altBtn.click(); console.log('[GLM] ' + e.key + ' 点击验证码确认按钮(alt)'); return; } } } }); // ── v8.0: 一键多开窗口函数 ──────────────────────────────────────────────── function openMultipleWindows() { const count = prompt('请输入要打开的窗口数量(默认 2,上限 10;窗口越多越容易撞 RPM 风控,按需选择):', '2'); if (!count) return; const n = parseInt(count); if (isNaN(n) || n < 1 || n > 10) { alert('请输入 1-10 之间的数字'); return; } const baseUrl = GLM_CODING_URL(); for (let i = 0; i < n; i++) { setTimeout(() => { const url = baseUrl + (i > 0 ? `&wi=${i}` : ''); GM_openInTab(url, { active: false, insert: true, setParent: true }); }, i * 300); } alert(`✅ 已打开 ${n} 个标签页!\n\n多窗口抢购流程:\n1. 每个窗口自动解验证码(不点确定)\n2. 等待到 10:00:00 + 错开时间\n3. 自动点击确认发送请求\n\n💡 窗口0最先点,之后每个错开2秒\n⚠️ 默认推荐 2 个窗口,单窗口单发是当前最稳的策略,多窗口会按窗口数放大请求数量,直接撞 RPM 上限`); } // ── 扫描队列(过滤今日已确认售罄)──────────────────────────────────────── const tabs = String(CFG.TABS_PRIORITY).split(',').map(Number).filter(Boolean); const pkgs = String(CFG.PACKAGES_PRIORITY).split(',').map(Number).filter(Boolean); const allTargets = tabs.flatMap(t => pkgs.map(p => ({ tab: t, pkg: p }))); const scanQueue = allTargets.filter(({ tab: t, pkg: p }) => getS(t, p) !== 1); if (!scanQueue.length) { scanQueue.push(...allTargets); setTimeout(() => setBar('📭 今日缓存显示全售罄,仍会重新扫描确认。', '#434343'), 800); } // ── 状态机变量 ──────────────────────────────────────────────────────────── let state = 'SCANNING'; // SCANNING | TASK_UNIT | DONE // SCANNING / TASK_UNIT let qIdx = 0, sweepRestocks = [], lastTabSwitch = 0, sweepBusyCount = 0, emptySweepCount = 0; const soldOutHits = Object.create(null); let taskTarget = null, taskPhase = 'IDLE', taskClickTime = 0, taskRLCount = 0; let taskClickCaptchaSeq = 0; // 本次点击时记录的验证码计数器基线;WAITING 里比对是否增长判断验证码是否弹出 let lastCloseReason = ''; let riskCooldownUntil = 0; // 405 风控冷却结束时间戳;冷却期内不点订阅 const MAX_RL = 3, MODAL_WAIT = 15000, EMPTY_SWEEP_CONFIRM = 3, SOLD_OUT_CONFIRM = 2; // ── 工具函数 ────────────────────────────────────────────────────────────── function parseRestock(text) { const m = (text || '').match(/0?(\d{1,2})月0?(\d{1,2})日\s*(\d{1,2}):0?(\d{1,2})/); if (!m) return null; const t = new Date(new Date().getFullYear(), +m[1] - 1, +m[2], +m[3], +m[4]); return { dateStr: `${+m[1]}月${+m[2]}日`, msUntil: t - Date.now() }; } function todayStr() { const d = new Date(); return `${d.getMonth() + 1}月${d.getDate()}日`; } function fmt(ms) { if (ms <= 0) return '0秒'; const s = Math.floor(ms / 1000), m = Math.floor(s / 60), h = Math.floor(m / 60); return h ? `${h}h${m % 60}m` : m ? `${m}分${s % 60}秒` : `${s}秒`; } function calcSleepMs(ms) { if (ms > 3600000) return 240000; if (ms > 1800000) return 180000; if (ms > 900000) return 120000; if (ms > 300000) return 60000; if (ms > 120000) return 30000; if (ms > 60000) return 10000; if (ms > 10000) return 3000; return 0; } function clampInt(value, min, max, fallback) { const n = parseInt(value, 10); if (!Number.isFinite(n)) return fallback; return Math.max(min, Math.min(max, n)); } function randInt(min, max) { min = Math.floor(Number(min) || 0); max = Math.floor(Number(max) || 0); if (max < min) { const t = min; min = max; max = t; } return Math.floor(Math.random() * (max - min + 1)) + min; } function isRealBizId(id) { return id && !id.startsWith('debug-'); } // ── v8.19: 黄金时间判断(9:30-11:00)──────────────────────────────────── function isGoldenTime() { const now = new Date(); const h = now.getHours(); const m = now.getMinutes(); const time = h * 60 + m; const start = 9 * 60 + 30; // 9:30 const end = 11 * 60 + 0; // 11:00 return time >= start && time <= end; } // ── DOM 访问 ────────────────────────────────────────────────────────────── const tabEl = n => document.querySelectorAll('#switchTabBox .switch-tab-item')[n - 1]; const btnEl = n => document.querySelector(`.glm-coding-package-list > div:nth-child(${n}) > div > .package-card-btn-box > button`); const canBuy = b => b && !b.disabled && !b.classList.contains('is-disabled') && !b.classList.contains('disabled') && !/售罄|补货|暂时|抢购人数过多|刷新再试|请稍后/.test(b.innerText || ''); const isSoldOut = b => /售罄|补货|暂时|抢购人数过多|刷新再试|请稍后/.test(b?.innerText || ''); const isBusy = b => /抢购人数过多|请刷新/.test(b?.innerText || ''); // ── 弹窗检测 ────────────────────────────────────────────────────────────── function findRLModal() { for (const w of document.querySelectorAll('.el-dialog__wrapper')) if (getComputedStyle(w).display !== 'none' && (w.innerText || '').includes('当前购买人数较多')) return w; return null; } function getPayDialog() { const d = document.querySelector('.pay-dialog'); if (!d) return null; const w = d.closest('.el-dialog__wrapper'); if (!w || getComputedStyle(w).display === 'none') return null; if ((w.innerText || '').includes('当前购买人数较多')) return null; return d; } function isPayDialog() { return !!getPayDialog(); } function isSuccessDialog() { const w = document.querySelector('.pay-success-dialog-box')?.closest('.el-dialog__wrapper'); return w ? getComputedStyle(w).display !== 'none' : false; } function closeModal(w) { w?.querySelector('.el-dialog__close')?.click(); } function closePayDialog() { const d = getPayDialog(); if (d) closeModal(d.closest('.el-dialog__wrapper')); } // ── v8.9: 小飞机检测:弹窗里出现系统繁忙的"小飞机"图标 ─────────────────── function hasAirplaneInDialog() { const dlg = document.querySelector('.pay-dialog'); if (!dlg) return false; return !!dlg.querySelector('.empty-data-wrap, .empty-data'); } function isAirplanePayDialog(rlWrapper) { if (!rlWrapper) return false; return !!rlWrapper.querySelector('.pay-dialog .empty-data-wrap, .pay-dialog .empty-data'); } // ── 对话框实付金额读取(双通道)────────────────────────────────────────── // 通道A:扫码区 .info-price 最后一个 (纯数字,如"149") // 通道B:计算明细区"实付金额"对应的 .price-item(含¥,如"¥149") // // 两通道任一 > 0 即视为有效。 function readDialogPrices() { const dlg = getPayDialog(); if (!dlg) return null; // 通道A let scanPrice = 0; const infoPrice = dlg.querySelector('.info-price'); if (infoPrice) { const spans = infoPrice.querySelectorAll('span'); // price-icon 是 ¥,后面的 span 是数值 for (let i = spans.length - 1; i >= 0; i--) { const v = parseFloat(spans[i].textContent.trim()); if (!isNaN(v) && v > 0) { scanPrice = v; break; } } } // 通道B let actualPrice = 0; dlg.querySelectorAll('.calculate-content-item').forEach(li => { if ([...li.querySelectorAll('div')].some(d => d.textContent.includes('实付金额'))) { const v = parseFloat((li.querySelector('.price-item')?.textContent || '').replace(/[¥,]/g, '').trim()); if (!isNaN(v) && v > 0) actualPrice = v; } }); return { scanPrice, actualPrice, any: scanPrice > 0 || actualPrice > 0 }; } // ── v8.9: 弹窗关闭决策(三态返回)──────────────────────────────────────── // 返回: 'close' → 关弹窗试下一个 | 'keep' → 不关 | 'warn' → 异常,告知用户 // 无金额无效页兜底:GLM 有时先显示无金额/没抢到,过 ~1 秒才弹出真金额订单(#16/#40), // 所以无金额不能立刻关,要持续无金额超过 1.7s 才判无效页关闭。 let noPriceSince = 0; const NO_PRICE_GRACE_MS = 1700; function checkPayDialog() { const dlg = getPayDialog(); if (!dlg) { noPriceSince = 0; return 'keep'; } if (window.__glmRushConfirmed) { window.__glmRushDialogSeen = 1; return 'keep'; } // 只有当前拿到有效 bizId,才锁住支付弹窗,避免历史成功状态误判 if (everSucceeded && PS.bizId) return 'keep'; // 接口还没返回 if (PS.inProgress) return 'keep'; // ── 情况 A:接口 555 系统繁忙 → 关弹窗试下一个 if (PS.result === 'busy') return 'close'; // ── 情况 A2:接口 405 风控 → 关弹窗进入冷却(冷却在 tick 里处理) if (PS.result === 'risk_control') return 'close'; // ── 情况 B:接口返回 200+soldOut → 关弹窗试下一个(但前端可能因 JSON.parse 劫持而正常显示了价格) if (PS.result === 'sold_out') { if (Date.now() - taskClickTime >= 1500) { const prices = readDialogPrices(); if (prices?.any) { console.log('[GLM v8.9] soldOut但DOM有价格,保留弹窗(前端劫持覆盖了soldOut)'); return 'keep'; } } return 'close'; } // ── 情况 D:接口没说售罄/繁忙,但弹窗里出现小飞机 → 异常不一致 // 注意:405 风控时小飞机也可能出现,但不要在这里 return warn 抢走 risk_control 的冷却路径 if (hasAirplaneInDialog() && PS.result !== 'risk_control') return 'warn'; // ── 情况 C:接口成功/其它 → 看弹窗实际有没有金额 const prices = readDialogPrices(); if (prices?.any) { // 有有效金额(真支付页),清掉无金额计时,保留弹窗等用户支付。 noPriceSince = 0; return 'keep'; } // 无金额(疑似无效支付页)。GLM 有时会延迟 ~1s 才渲染出真金额订单, // 不能立刻关——持续无金额超过宽限期才判无效页关闭。 if (!noPriceSince) noPriceSince = Date.now(); if (Date.now() - noPriceSince >= NO_PRICE_GRACE_MS) { console.log('[GLM v8.9] 无金额持续 ' + (Date.now() - noPriceSince) + 'ms,判定为无效支付页'); return 'close'; } console.log('[GLM v8.9] 弹窗暂无金额,宽限等待 ' + (Date.now() - noPriceSince) + '/' + NO_PRICE_GRACE_MS + 'ms'); return 'keep'; } // ── 底部状态栏 ──────────────────────────────────────────────────────────── var _bar = null; function setBar(html, bg = '#1677ff') { if (!_bar) { _bar = ensureStatusBarNode(); if (!_bar) return; } _bar.style.background = bg; _bar.firstElementChild.innerHTML = `🤖 抢购助手  |  ${html}`; } // ── 支付报警:视口边框红色闪烁 ──────────────────────────────────────────── let _alarm = null; function showPayAlarm() { if (_alarm) return; if (!document.getElementById('glm-alarm-s')) { const s = document.createElement('style'); s.id = 'glm-alarm-s'; s.textContent = '@keyframes glm-al{0%,100%{box-shadow:inset 0 0 0 12px rgba(220,38,38,.92)}50%{box-shadow:inset 0 0 0 12px rgba(220,38,38,.08)}}'; document.head.appendChild(s); } _alarm = document.createElement('div'); _alarm.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:2147483646;animation:glm-al .5s steps(1) infinite'; const lbl = document.createElement('div'); lbl.style.cssText = 'position:absolute;top:12px;left:50%;transform:translateX(-50%);background:rgba(220,38,38,.95);color:#fff;padding:5px 22px;border-radius:20px;font:700 15px system-ui,sans-serif;white-space:nowrap;letter-spacing:.5px'; lbl.textContent = '⚠️ 请立即扫码支付!'; _alarm.appendChild(lbl); document.body.appendChild(_alarm); } // ── 推广弹窗 ────────────────────────────────────────────────────────────── function triggerPromo() { const ov = document.createElement('div'); ov.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:2147483645;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(5px);font-family:system-ui,sans-serif'; ov.innerHTML = `

GLM Coding Plan 全部售罄 🫠

配置的所有套餐今日已售罄,补货后脚本将继续监控

脚本已停止当前轮次。你可以保持页面打开,等下一轮补货时重新启动。
当前使用作者内置 GLM Coding Plan 折扣入口
`; document.body.appendChild(ov); ov.querySelector('#promo-x').onclick = () => ov.remove(); ov.onclick = e => { if (e.target === ov) ov.remove(); }; } // ── 配置面板 ────────────────────────────────────────────────────────────── function buildTransferBox(ct, dataMap, selectedStr, title) { const sel = selectedStr.split(',').filter(Boolean); const avail = Object.keys(dataMap).filter(k => !sel.includes(k)); ct.innerHTML = `
${title}
备选池
    ${avail.map(k => `
  • ${dataMap[k]}
  • `).join('')}
选中且排序(自上而下)
    ${sel.map(k => `
  • ${dataMap[k]}
  • `).join('')}
`; const L = ct.querySelector('.tf-left'), R = ct.querySelector('.tf-right'); ct.querySelectorAll('ul').forEach(ul => ul.addEventListener('click', e => { if (e.target.tagName === 'LI') { ct.querySelectorAll('.tf-item').forEach(i => i.classList.remove('active')); e.target.classList.add('active'); } })); ct.querySelector('.tf-r').onclick = () => { const a = L.querySelector('.active'); if (a) { R.appendChild(a); a.classList.remove('active'); } }; ct.querySelector('.tf-l').onclick = () => { const a = R.querySelector('.active'); if (a) { L.appendChild(a); a.classList.remove('active'); } }; ct.querySelector('.tf-up').onclick = () => { const a = R.querySelector('.active'); if (a?.previousElementSibling) R.insertBefore(a, a.previousElementSibling); }; ct.querySelector('.tf-dn').onclick = () => { const a = R.querySelector('.active'); if (a?.nextElementSibling) R.insertBefore(a.nextElementSibling, a); }; return () => [...R.querySelectorAll('.tf-item')].map(i => i.dataset.val).join(','); } function openConfigPanel() { document.getElementById('glm-cfg-ov')?.remove(); const delayMin = Number.isFinite(parseInt(CFG.CAPTCHA_CLICK_DELAY_MIN_MS, 10)) ? parseInt(CFG.CAPTCHA_CLICK_DELAY_MIN_MS, 10) : 250; const delayMaxRaw = Number.isFinite(parseInt(CFG.CAPTCHA_CLICK_DELAY_MAX_MS, 10)) ? parseInt(CFG.CAPTCHA_CLICK_DELAY_MAX_MS, 10) : 400; const delayMax = Math.max(delayMin, delayMaxRaw); const fcMin = Number.isFinite(parseInt(CFG.CAPTCHA_FIRST_CLICK_DELAY_MIN_MS, 10)) ? parseInt(CFG.CAPTCHA_FIRST_CLICK_DELAY_MIN_MS, 10) : 150; const fcMaxRaw = Number.isFinite(parseInt(CFG.CAPTCHA_FIRST_CLICK_DELAY_MAX_MS, 10)) ? parseInt(CFG.CAPTCHA_FIRST_CLICK_DELAY_MAX_MS, 10) : 300; const fcMax = Math.max(fcMin, fcMaxRaw); if (!document.getElementById('glm-tf-s')) { const s = document.createElement('style'); s.id = 'glm-tf-s'; s.textContent = '.tf-item{padding:6px 10px;margin-bottom:4px;border-radius:4px;cursor:pointer;font-size:13px;color:#333;border:1px solid transparent;transition:all .15s}.tf-item:hover{background:#f5f5f5}.tf-item.active{background:#e6f7ff;border-color:#91d5ff;color:#1890ff;font-weight:700}.tf-btn{padding:4px 8px;font-size:10px;cursor:pointer;border:1px solid #d9d9d9;border-radius:4px;background:#fff;color:#555;height:28px;transition:.2s}.tf-btn:hover{border-color:#40a9ff;color:#40a9ff}'; document.head.appendChild(s); } const ov = document.createElement('div'); ov.id = 'glm-cfg-ov'; ov.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:2147483646;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px);font-family:system-ui,sans-serif'; const panel = document.createElement('div'); panel.style.cssText = 'background:#fff;color:#333;width:560px;padding:24px;border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.3);max-height:90vh;overflow-y:auto'; panel.innerHTML = `

⚙️ 抢购助手配置

405 风控冷却 ms ?
验证码点字延时策略 ?
最小延时 ms 最大延时 ms
首击前延时 ?
最小 ms 最大 ms
目标时间 : :
快捷键 ?
暂停/恢复 批量暂停(同浏览器) 自动点击订阅
推荐:F8/F9、Insert/Home/End。批量暂停默认 Shift+F8,覆盖同浏览器所有 glm-coding 窗口。避免 F5/F11/F12、Ctrl/Alt 组合和输入法快捷键。
`; ov.appendChild(panel); document.body.appendChild(ov); const getPkgs = buildTransferBox(document.getElementById('glm-wp'), PKGS_MAP, CFG.PACKAGES_PRIORITY, '套餐优先级'); const getTabs = buildTransferBox(document.getElementById('glm-wt'), TABS_MAP, CFG.TABS_PRIORITY, '订阅周期优先级'); function bindHotkeyInput(input) { input.title = '点击后按键录入'; input.addEventListener('keydown', e => { e.preventDefault(); e.stopPropagation(); const hk = hotkeyFromEvent(e); if (hk) input.value = hk; }); input.addEventListener('focus', () => input.select()); } bindHotkeyInput(panel.querySelector('#glm-hk-pause')); bindHotkeyInput(panel.querySelector('#glm-hk-pauseall')); bindHotkeyInput(panel.querySelector('#glm-hk-sub')); panel.querySelector('#glm-cc').onclick = () => ov.remove(); panel.querySelector('#glm-multi').onclick = () => { openMultipleWindows(); }; panel.querySelector('#glm-cs').onclick = () => { const p = getPkgs(), t = getTabs(); if (!p || !t) { alert('请至少各选一个!'); return; } saveCfg({ TABS_PRIORITY : t, PACKAGES_PRIORITY : p, SMART_REFRESH : panel.querySelector('#glm-sm').checked, CHECK_INTERVAL : CFG.CHECK_INTERVAL, AUTO_CLOSE_INVALID: panel.querySelector('#glm-aci').checked, RISK_CONTROL_COOLDOWN_MS: parseInt(panel.querySelector('#glm-rccd').value, 10), AUTO_CLICK_SUB : panel.querySelector('#glm-acs').checked, AUTO_CAPTCHA_CLICK: panel.querySelector('#glm-acc').checked, AUTO_CAPTCHA_CONFIRM: panel.querySelector('#glm-acf').checked, CAPTCHA_CLICK_DELAY_MODE: 'range', CAPTCHA_CLICK_DELAY_MS: Math.round((parseInt(panel.querySelector('#glm-cdmin').value, 10) + parseInt(panel.querySelector('#glm-cdmax').value, 10)) / 2), CAPTCHA_CLICK_DELAY_MIN_MS: parseInt(panel.querySelector('#glm-cdmin').value, 10), CAPTCHA_CLICK_DELAY_MAX_MS: parseInt(panel.querySelector('#glm-cdmax').value, 10), CAPTCHA_CLICK_DELAY_JITTER_PERCENT: CFG.CAPTCHA_CLICK_DELAY_JITTER_PERCENT, CAPTCHA_FIRST_CLICK_DELAY_MIN_MS: parseInt(panel.querySelector('#glm-fcdmin').value, 10), CAPTCHA_FIRST_CLICK_DELAY_MAX_MS: parseInt(panel.querySelector('#glm-fcdmax').value, 10), RUSH_ENABLED: panel.querySelector('#glm-re').checked, RUSH_TARGET_HOUR: parseInt(panel.querySelector('#glm-rh').value, 10), RUSH_TARGET_MIN: parseInt(panel.querySelector('#glm-rm').value, 10), RUSH_TARGET_SEC: parseInt(panel.querySelector('#glm-rs').value, 10), RUSH_RELEASE_ADVANCE_MS: CFG.RUSH_RELEASE_ADVANCE_MS, HOTKEY_PAUSE: normalizeHotkeyString(panel.querySelector('#glm-hk-pause').value, 'F8'), HOTKEY_PAUSE_ALL: normalizeHotkeyString(panel.querySelector('#glm-hk-pauseall').value, 'Shift+F8'), HOTKEY_AUTO_CLICK_SUB: normalizeHotkeyString(panel.querySelector('#glm-hk-sub').value, 'F9'), SAFE_DEFAULTS_VERSION, }); ov.remove(); alert('已保存,即将刷新。'); location.reload(); }; ov.onclick = e => { if (e.target === ov) ov.remove(); }; } // ═══════════════════════════════════════════════════════════════════════════ // 主循环 // ═══════════════════════════════════════════════════════════════════════════ function tick() { if (state === 'DONE') return; if (runtimePaused) { setBar(`⏸️ 脚本已暂停。${pauseHotkey()} 恢复。`, '#722ed1'); return; } // 405 风控冷却期内不点订阅,避免立即重试越撞越多 if (riskCooldownUntil && Date.now() < riskCooldownUntil) { const remain = Math.ceil((riskCooldownUntil - Date.now()) / 1000); setBar(`🛑 风控冷却中(剩 ${remain}s),可在配置面板「405 风控冷却」调整`, '#cf1322'); return; } if (riskCooldownUntil && Date.now() >= riskCooldownUntil) { riskCooldownUntil = 0; // 冷却结束,清掉 } if (window.__glmRushConfirmed && window.__glmRushDialogSeen && !getPayDialog()) { window.__glmRushConfirmed = 0; window.__glmRushDialogSeen = 0; console.log('[GLM] rush lock cleared'); } // 邀请码活动已下线,ensureDiscountEntry 已注释;活动恢复时取消下一行注释即可。 // if (ensureDiscountEntry()) return; if (state === 'TASK_UNIT') { doTaskUnit(); return; } doScan(); } // ═══════════════════════════════════════════════════════════════════════════ // SCANNING / TASK_UNIT 逻辑 // ═══════════════════════════════════════════════════════════════════════════ function doScan() { if (qIdx >= scanQueue.length) { onSweepDone(); return; } const { tab, pkg } = scanQueue[qIdx]; const te = tabEl(tab); if (!te) return; if (!te.classList.contains('active')) { te.click(); te.scrollIntoView({ behavior: 'auto', block: 'center' }); lastTabSwitch = Date.now(); setBar(`🔄 切换到 ${TABS_MAP[tab]}...`); return; } if (Date.now() - lastTabSwitch < 400) return; const b = btnEl(pkg); if (canBuy(b)) { taskTarget = { tab, pkg }; taskPhase = 'IDLE'; taskRLCount = 0; soldOutHits[`${tab}-${pkg}`] = 0; setS(tab, pkg, 0); state = 'TASK_UNIT'; setBar(`🎯 发现可购!${TABS_MAP[tab]} · ${PKGS_MAP[pkg]},${readyModeText()}`, CFG.RUSH_ENABLED && !CFG.AUTO_CLICK_SUB ? '#722ed1' : '#389e0d'); return; } if (isBusy(b)) { sweepBusyCount++; setBar(`⚡ 系统繁忙 ${TABS_MAP[tab]} · ${PKGS_MAP[pkg]},跳过...`); qIdx++; return; } const ri = parseRestock(b?.innerText); if (ri?.dateStr === todayStr() && ri.msUntil > 0) sweepRestocks.push(ri); setBar(`🔍 扫描 ${TABS_MAP[tab]} · ${PKGS_MAP[pkg]} (${qIdx + 1}/${scanQueue.length})`); qIdx++; } function onSweepDone() { if (sweepBusyCount >= scanQueue.length) { // 所有套餐都显示"抢购人数过多/请刷新"——不自动刷新(自动刷新太激进会触发更严重风控), // 停下来让用户手动刷新页面,直到看到"特惠订阅"按钮再继续。 state = 'DONE'; setBar('🛑 所有套餐显示「抢购人数过多,请刷新再试」。请手动刷新页面(F5),直到看到「特惠订阅」按钮。自动刷新容易触发更严重风控。', '#ff4d4f'); return; } if (!sweepRestocks.length) { emptySweepCount++; qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; if (emptySweepCount < EMPTY_SWEEP_CONFIRM) { setBar(`📭 暂未发现可买/补货时间,继续确认 ${emptySweepCount}/${EMPTY_SWEEP_CONFIRM}...`, '#434343'); return; } emptySweepCount = 0; setBar('📭 连续多轮未发现库存,继续高频扫描...', '#434343'); return; } emptySweepCount = 0; sweepRestocks.sort((a, b) => a.msUntil - b.msUntil); const nearest = sweepRestocks[0]; const sleep = calcSleepMs(nearest.msUntil); qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; if (sleep === 0) { setBar(`⚡ 补货倒计时 ${fmt(nearest.msUntil)},高频监控!`, '#d4380d'); return; } if (CFG.SMART_REFRESH) { setBar(`🕙 补货还需 ${fmt(nearest.msUntil)},持续扫描中`, '#434343'); } } function doTaskUnit() { const { tab, pkg } = taskTarget; const te = tabEl(tab); if (!te) return; if (!te.classList.contains('active')) { te.click(); return; } const b = btnEl(pkg); if (taskPhase === 'IDLE') { if (isSoldOut(b)) { exitTask(); return; } if (!canBuy(b)) { setBar(`⏳ 等待按钮就绪... ${TABS_MAP[tab]} · ${PKGS_MAP[pkg]}`, '#d46b08'); return; } const rushReached = isRushTargetReached(); if (!CFG.AUTO_CLICK_SUB && !rushReached) { if (CFG.RUSH_ENABLED) { const remaining = Math.max(0, getRushRemainingMs()); setBar(`🎯 发现可购!${TABS_MAP[tab]} · ${PKGS_MAP[pkg]},冲刺模式等待目标时间 ${fmt(remaining)};也可按 ${autoClickSubHotkey()} 手动开启自动点击`, '#722ed1'); } else { setBar(`🎯 发现可购!${TABS_MAP[tab]} · ${PKGS_MAP[pkg]},默认不自动点击;请手动点击或按 ${autoClickSubHotkey()} 开启`, '#389e0d'); } return; } PS.result = null; PS.inProgress = true; try { taskClickCaptchaSeq = GM_getValue('glm_captcha_seen_seq', 0) | 0; } catch (e) { taskClickCaptchaSeq = 0; } b.click(); taskClickTime = Date.now(); taskPhase = 'WAITING'; setBar(`🔄 已点击,接口重试中... ${TABS_MAP[tab]} · ${PKGS_MAP[pkg]}(限流 ${taskRLCount}/${MAX_RL})`, '#d46b08'); return; } if (taskPhase === 'WAITING') { const rlw = findRLModal(); if (rlw) { if (isAirplanePayDialog(rlw)) { if (PS.result !== 'busy' && PS.result !== 'sold_out' && PS.result !== 'risk_control') { setBar('⚠️ API返回200但弹窗显示小飞机("购买人数较多"),可能是前后端不一致。不自动关闭,请手动确认后关闭弹窗,脚本会继续。', '#ff4d4f'); return; } if (!CFG.AUTO_CLOSE_INVALID) { state = 'DONE'; setBar('⚠️ 自动关闭无效弹窗未开启,请手动确认后关闭此支付/限流弹窗', '#d46b08'); return; } closeModal(rlw); const curName = `${TABS_MAP[tab]}·${PKGS_MAP[pkg]}`; // 405 风控时设冷却。0 = 不冷却(直接重试);非 0 = 冷却对应毫秒。 if (PS.result === 'risk_control') { const cd = CFG.RISK_CONTROL_COOLDOWN_MS; if (cd > 0) riskCooldownUntil = Date.now() + cd; } const nextIdx = qIdx + 1; const isLoop = nextIdx >= scanQueue.length; qIdx = isLoop ? 0 : nextIdx; taskTarget = null; taskPhase = 'IDLE'; taskRLCount = 0; state = 'SCANNING'; const reason = PS.result === 'busy' ? `✈️ 系统繁忙(${PS.rawCode || 555}),关闭弹窗` : PS.result === 'risk_control' ? `🛑 风控拦截(405),关闭弹窗并冷却` : `📉 ${curName} 售罄`; lastCloseReason = reason; setBar(`${reason},${isLoop ? '🔄 轮询一圈,从头重试...' : '试下一个...'}`, '#d46b08'); return; } if (!CFG.AUTO_CLOSE_INVALID) { setBar('⚠️ 限流弹窗("购买人数较多"),请手动关闭后重试', '#d46b08'); return; } closeModal(rlw); taskRLCount++; if (taskRLCount >= MAX_RL) { if (isGoldenTime()) { setBar('🔥 黄金时间!连续限流但禁止刷新,继续重试!', '#ff4d4f'); taskRLCount = 0; taskPhase = 'IDLE'; } else { setBar(`🔁 连续 ${MAX_RL} 次限流,即将刷新...`, '#cf1322'); setTimeout(() => location.replace(GLM_CODING_URL()), 50); } return; } setBar(`⚠️ 限流 ${taskRLCount}/${MAX_RL},自动关闭后重试...`, '#d46b08'); taskPhase = 'IDLE'; return; } if (isPayDialog()) { const verdict = checkPayDialog(); if (verdict === 'close') { if (!CFG.AUTO_CLOSE_INVALID) { state = 'DONE'; setBar('⚠️ 检测到异常支付弹窗,请手动确认是否需要扫码!', '#d46b08'); return; } const reason = PS.result === 'busy' ? `✈️ 系统繁忙(${PS.rawCode || 555}),关闭弹窗` : PS.result === 'risk_control' ? `🛑 风控拦截(405),关闭弹窗并冷却` : `📉 ${TABS_MAP[taskTarget.tab]}·${PKGS_MAP[taskTarget.pkg]} 售罄`; // 405 风控时设冷却。0 = 不冷却(直接重试);非 0 = 冷却对应毫秒。 if (PS.result === 'risk_control') { const cd = CFG.RISK_CONTROL_COOLDOWN_MS; if (cd > 0) riskCooldownUntil = Date.now() + cd; } closePayDialog(); lastCloseReason = reason; const nextIdx = qIdx + 1; const isLoop = nextIdx >= scanQueue.length; qIdx = isLoop ? 0 : nextIdx; taskTarget = null; taskPhase = 'IDLE'; taskRLCount = 0; state = 'SCANNING'; setBar(`${reason},${isLoop ? '🔄 轮询一圈,从头重试...' : '试下一个...'} `, '#d46b08'); return; } if (verdict === 'warn') { setBar('⚠️ 弹窗显示小飞机但API未返回繁忙/售罄,前后端不一致。不自动关闭,请手动确认。如果有二维码请扫码支付!', '#ff4d4f'); return; } const prices = readDialogPrices(); if (everSucceeded && PS.bizId || prices?.any) { state = 'DONE'; if (everSucceeded && PS.bizId) showPayAlarm(); setBar('💳 支付弹窗已出现!请立即扫码支付! 脚本已停止。', '#16a34a'); } else { setBar(`🔄 ${TABS_MAP[tab]}·${PKGS_MAP[pkg]} 弹窗等待确认...`, '#1677ff'); } return; } if (isSuccessDialog()) { setS(tab, pkg, 2); state = 'DONE'; setBar('🎉 订阅成功!恭喜!', '#237804'); return; } if (!PS.inProgress && PS.result === 'sold_out' && Date.now() - taskClickTime > 2000) { exitTask(); return; } const elapsed = Date.now() - taskClickTime; const prefix = lastCloseReason ? `${lastCloseReason} → ` : ''; // 极简逻辑:点击后只等 preview 响应(PS.result 被设置)或弹窗出现。 // 不判断验证码/prompt 状态——那些和主流程无关,识别完会自然推动 preview。 // 5 秒内没有任何结果(preview 没回/弹窗没出)= 卡住了,立即重试点订阅。 if (PS.inProgress) { setBar(`${prefix}⏳ ${TABS_MAP[tab]}·${PKGS_MAP[pkg]} 接口请求中... (${(elapsed/1000).toFixed(1)}s)`, '#1677ff'); } else { setBar(`${prefix}🔐 ${TABS_MAP[tab]}·${PKGS_MAP[pkg]} 等待响应... (${(elapsed/1000).toFixed(1)}s)`, '#1677ff'); } if (elapsed > 10000) { // 10 秒没结果 = 卡住(验证码没弹/没识别完/被拦),重试点订阅。 // 10 秒兜底:正常识别(含老 CPU)5-8 秒够,超 10 秒就是卡了。 setBar(`⚡ ${TABS_MAP[tab]}·${PKGS_MAP[pkg]} 超时,重试点订阅...`, '#d4380d'); taskPhase = 'IDLE'; } } } function exitTask() { // v8.0: 黄金时间内不标记售罄,持续重试 if (!isGoldenTime()) { const key = `${taskTarget.tab}-${taskTarget.pkg}`; soldOutHits[key] = (soldOutHits[key] || 0) + 1; if (soldOutHits[key] >= SOLD_OUT_CONFIRM) setS(taskTarget.tab, taskTarget.pkg, 1); } setBar(`📦 ${TABS_MAP[taskTarget.tab]} · ${PKGS_MAP[taskTarget.pkg]} 售罄,继续...`); qIdx++; taskTarget = null; taskPhase = 'IDLE'; taskRLCount = 0; state = 'SCANNING'; } // ── v8.4: DOM 级按钮强制启用(安全网)───────────────────────────────────── function forceEnableButtons() { document.querySelectorAll('.buy-btn[disabled], .buy-btn.is-disabled, .buy-btn.disabled').forEach(b => { b.removeAttribute('disabled'); b.classList.remove('is-disabled', 'disabled'); }); } // ── 启动 ────────────────────────────────────────────────────────────────── // v8.0: 未登录检测 function checkLogin() { const token = document.cookie.match(/bigmodel_token_production=([^;]+)/)?.[1]; if (!token) { setBar('⚠️ 未登录,请先注册/登录', '#ff4d4f'); setTimeout(() => { // 邀请码活动已下线,注释邀请码注册引导;活动恢复时取消注释即可。 // if (confirm('检测到未登录,是否前往注册页面?\n\n使用邀请码注册可获得额外优惠!')) { // window.location.href = 'https://www.bigmodel.cn/invite?icode=PKFZ8PflAmrZ4AYh%2BAPxo33uFJ1nZ0jLLgipQkYjpcA%3D'; // } }, 1000); return false; } return true; } if (checkLogin()) { setBar(`✅ 脚本已加载,${readyModeText()}`, CFG.RUSH_ENABLED && !CFG.AUTO_CLICK_SUB ? '#722ed1' : '#1677ff'); calibrateRushLatency(); setInterval(tick, CFG.CHECK_INTERVAL); const _startDOM = () => { setInterval(forceEnableButtons, 500); new MutationObserver(forceEnableButtons).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['disabled', 'class'] }); }; if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', _startDOM); else _startDOM(); } })(); // ---- captcha prompt bridge v2: multi-window rush mode ---- (function () { 'use strict'; if (window.__glmCaptchaPromptBridge === 1) return; window.__glmCaptchaPromptBridge = 1; const RUSH_CFG = (() => { try { const raw = GM_getValue('glm_coding_config_v5', '{}'); const cfg = JSON.parse(raw || '{}'); const ph = parseInt(cfg.RUSH_TARGET_HOUR, 10); const pm = parseInt(cfg.RUSH_TARGET_MIN, 10); const ps = parseInt(cfg.RUSH_TARGET_SEC, 10); const manualAdvance = Math.max(0, parseInt(cfg.RUSH_RELEASE_ADVANCE_MS, 10) || 0); const latencyRaw = GM_getValue('glm_rush_latency_v1', '{}'); const latency = JSON.parse(latencyRaw || '{}'); const latencyFresh = latency && Number.isFinite(Number(latency.advanceMs)) && Date.now() - Number(latency.at || 0) < 10 * 60 * 1000; const releaseAdvanceMs = manualAdvance > 0 ? manualAdvance : (latencyFresh ? Math.max(0, Math.min(180, Number(latency.advanceMs))) : 0); return { enabled: cfg.RUSH_ENABLED === true, targetHour: Number.isFinite(ph) ? ph : 10, targetMin: Number.isFinite(pm) ? pm : 0, targetSec: Number.isFinite(ps) ? ps : 0, holdWindowMs: Math.max(0, parseInt(cfg.RUSH_HOLD_WINDOW_MS, 10) || 10000), releaseAdvanceMs: releaseAdvanceMs, staggerMs: 2000, pollInterval: 50, pollTimeout: 20000, }; } catch { return { enabled: false, targetHour: 10, targetMin: 0, targetSec: 0, holdWindowMs: 10000, releaseAdvanceMs: 0, staggerMs: 2000, pollInterval: 50, pollTimeout: 20000 }; } })(); const CAPTCHA_CFG = (() => { try { const raw = GM_getValue('glm_coding_config_v5', '{}'); return { AUTO_CAPTCHA_CLICK: true, AUTO_CAPTCHA_CONFIRM: false, CAPTCHA_CLICK_DELAY_MODE: 'fixed', CAPTCHA_CLICK_DELAY_MS: 325, CAPTCHA_CLICK_DELAY_MIN_MS: 250, CAPTCHA_CLICK_DELAY_MAX_MS: 400, CAPTCHA_CLICK_DELAY_JITTER_PERCENT: 20, ...JSON.parse(raw || '{}') }; } catch { return { AUTO_CAPTCHA_CLICK: true, AUTO_CAPTCHA_CONFIRM: false, CAPTCHA_CLICK_DELAY_MODE: 'fixed', CAPTCHA_CLICK_DELAY_MS: 325, CAPTCHA_CLICK_DELAY_MIN_MS: 250, CAPTCHA_CLICK_DELAY_MAX_MS: 400, CAPTCHA_CLICK_DELAY_JITTER_PERCENT: 20 }; } })(); function clampCaptchaInt(value, min, max, fallback) { var n = parseInt(value, 10); if (!Number.isFinite(n)) return fallback; return Math.max(min, Math.min(max, n)); } function randCaptchaInt(min, max) { min = Math.floor(Number(min) || 0); max = Math.floor(Number(max) || 0); if (max < min) { var t = min; min = max; max = t; } return Math.floor(Math.random() * (max - min + 1)) + min; } function normalizeCaptchaDirectDelayMode(mode) { mode = String(mode || 'fixed').trim().toLowerCase(); return (mode === 'range' || mode === 'jitter') ? mode : 'fixed'; } function readCaptchaDirectDelayOverride() { try { var raw = window.__glmCaptchaDelayExperiment; if (!raw) return null; if (typeof raw === 'string') return JSON.parse(raw); if (typeof raw === 'object') return raw; } catch {} return null; } function normalizeCaptchaDirectDelayConfig(config) { config = config || {}; var stepDelays = Array.isArray(config.stepDelays) ? config.stepDelays.map(function (value) { return clampCaptchaInt(value, 1, 10000, 220); }).filter(function (value) { return Number.isFinite(value) && value > 0; }) : null; return { delayMode: normalizeCaptchaDirectDelayMode(config.delayMode), gapMs: clampCaptchaInt(config.gapMs, 1, 10000, 220), minGapMs: clampCaptchaInt(config.minGapMs, 1, 10000, 200), maxGapMs: clampCaptchaInt(config.maxGapMs, 1, 10000, 500), jitterPercent: clampCaptchaInt(config.jitterPercent, 0, 1000, 20), stepDelays: stepDelays && stepDelays.length ? stepDelays : null, firstClickMinMs: clampCaptchaInt(config.firstClickMinMs, 0, 10000, 150), firstClickMaxMs: clampCaptchaInt(config.firstClickMaxMs, 0, 10000, 300), }; } function getCaptchaDirectDelayConfig() { var baseConfig = normalizeCaptchaDirectDelayConfig({ delayMode: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_MODE, gapMs: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_MS, minGapMs: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_MIN_MS, maxGapMs: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_MAX_MS, jitterPercent: CAPTCHA_CFG.CAPTCHA_CLICK_DELAY_JITTER_PERCENT, firstClickMinMs: Number.isFinite(parseInt(CAPTCHA_CFG.CAPTCHA_FIRST_CLICK_DELAY_MIN_MS, 10)) ? parseInt(CAPTCHA_CFG.CAPTCHA_FIRST_CLICK_DELAY_MIN_MS, 10) : 150, firstClickMaxMs: Number.isFinite(parseInt(CAPTCHA_CFG.CAPTCHA_FIRST_CLICK_DELAY_MAX_MS, 10)) ? parseInt(CAPTCHA_CFG.CAPTCHA_FIRST_CLICK_DELAY_MAX_MS, 10) : 300, }); var override = readCaptchaDirectDelayOverride(); if (!override) return baseConfig; return normalizeCaptchaDirectDelayConfig({ delayMode: override.delayMode != null ? override.delayMode : baseConfig.delayMode, gapMs: override.gapMs != null ? override.gapMs : baseConfig.gapMs, minGapMs: override.minGapMs != null ? override.minGapMs : baseConfig.minGapMs, maxGapMs: override.maxGapMs != null ? override.maxGapMs : baseConfig.maxGapMs, jitterPercent: override.jitterPercent != null ? override.jitterPercent : baseConfig.jitterPercent, stepDelays: Array.isArray(override.stepDelays) ? override.stepDelays : baseConfig.stepDelays, firstClickMinMs: override.firstClickMinMs != null ? override.firstClickMinMs : baseConfig.firstClickMinMs, firstClickMaxMs: override.firstClickMaxMs != null ? override.firstClickMaxMs : baseConfig.firstClickMaxMs, }); } function getWindowIndex() { const params = new URLSearchParams(location.search); return parseInt(params.get('wi') || '0', 10); } function getTargetTimestamp() { const now = new Date(); const target = new Date(now.getFullYear(), now.getMonth(), now.getDate(), RUSH_CFG.targetHour, RUSH_CFG.targetMin, RUSH_CFG.targetSec, 0); const offset = getWindowIndex() * RUSH_CFG.staggerMs; return target.getTime() + offset; } const captchaSession = { lastText: '', lastBgUrl: '', sent: false, state: 'idle', // 失败保护:错一次冷却一会、连续错够 N 次直接停手,避免被腾讯 SDK 自我重置 // 的循环吸进 50ms 一发的自激振荡(一直打同一张图把后端打爆)。 failCount: 0, failBgUrl: '', cooldownUntil: 0, stopped: false, }; const CAPTCHA_FAIL_COOLDOWN_MS = 1500; // 单次失败冷却 1.5 秒 const CAPTCHA_FAIL_HARD_LIMIT = 3; // 同一张图连续失败 ≥3 次后彻底停手 // rush 黄金窗口:到点后 holdWindowMs 内。窗口期内失败只冷却不 hard-stop, // 保证 10:00 抢购黄金窗口不会被提前累积的失败熔断焊死(导致到点不识别不点确定)。 function isRushWindowActive() { if (!RUSH_CFG.enabled) return false; var remaining = getTargetTimestamp() - Date.now(); return remaining <= RUSH_CFG.holdWindowMs; } function getCaptchaLastText() { return captchaSession.lastText; } function setCaptchaLastText(text) { captchaSession.lastText = text || ''; } function getCaptchaLastBgUrl() { return captchaSession.lastBgUrl; } function setCaptchaLastBgUrl(url) { captchaSession.lastBgUrl = url || ''; } function isCaptchaSent() { return captchaSession.sent === true; } function setCaptchaSent(sent) { captchaSession.sent = sent === true; } function getCaptchaState() { return captchaSession.state; } function setCaptchaState(state) { captchaSession.state = state; } function isCaptchaStopped() { return captchaSession.stopped === true; } function inCaptchaCooldown() { return Date.now() < captchaSession.cooldownUntil; } // 失败计数绑定到"同一张图":换图(腾讯 SDK 重置、换新挑战、rush 窗口新验证码)时重置 failCount, // 避免跨图累积误伤;自激振荡(同一张图反复开火)只在 failBgUrl 不变时才累计。 function noteCaptchaFailure(failBgUrl) { if (failBgUrl && captchaSession.failBgUrl && failBgUrl !== captchaSession.failBgUrl) { captchaSession.failCount = 0; captchaSession.stopped = false; } captchaSession.failBgUrl = failBgUrl || ''; captchaSession.failCount += 1; captchaSession.cooldownUntil = Date.now() + CAPTCHA_FAIL_COOLDOWN_MS; // rush 黄金窗口(到点后 holdWindowMs 内)只冷却不 hard-stop,保证窗口期不会被焊死。 if (captchaSession.failCount >= CAPTCHA_FAIL_HARD_LIMIT && !isRushWindowActive()) { captchaSession.stopped = true; console.warn('[captcha] 连续失败 ' + captchaSession.failCount + ' 次(同一图),自动验证已停止;' + '请手动完成本次验证码,然后刷新页面或重新打开抢购弹窗以恢复自动模式。'); } else if (captchaSession.failCount >= CAPTCHA_FAIL_HARD_LIMIT) { console.warn('[captcha] 失败 ' + captchaSession.failCount + ' 次,rush 窗口期内仅冷却不熔断;' + '冷却 ' + CAPTCHA_FAIL_COOLDOWN_MS + 'ms 后再试'); } else { console.warn('[captcha] 失败 ' + captchaSession.failCount + '/' + CAPTCHA_FAIL_HARD_LIMIT + ',冷却 ' + CAPTCHA_FAIL_COOLDOWN_MS + 'ms 后再试'); } } function noteCaptchaSuccess() { captchaSession.failCount = 0; captchaSession.cooldownUntil = 0; captchaSession.failBgUrl = ''; captchaSession.stopped = false; } function resetCaptchaSession() { setCaptchaSent(false); setCaptchaLastText(''); setCaptchaLastBgUrl(''); // 完整重置:清掉熔断状态,否则一旦 hard-stop 过,到点后验证码也再不识别(rush 卡点根因)。 captchaSession.failCount = 0; captchaSession.cooldownUntil = 0; captchaSession.failBgUrl = ''; captchaSession.stopped = false; } function serverRequest(method, path, data) { function doFetch() { return fetch('http://localhost:8888' + path, { method: method, headers: { 'Content-Type': 'application/json' }, body: data ? JSON.stringify(data) : undefined, }).then(r => r.json()); } function doGM() { return new Promise((resolve, reject) => { if (typeof GM_xmlhttpRequest === 'undefined') { reject(new Error('GM_xmlhttpRequest unavailable')); return; } GM_xmlhttpRequest({ method: method, url: 'http://localhost:8888' + path, headers: { 'Content-Type': 'application/json' }, data: data ? JSON.stringify(data) : undefined, onload: (r) => { try { resolve(JSON.parse(r.responseText)); } catch { resolve({ raw: r.responseText }); } }, onerror: (e) => { // GM onerror 的 e 通常是 ProgressEvent 或 Tampermonkey 的错误对象, // 用 status/statusText 给出可读信息,便于诊断 var info = 'unknown'; if (e && typeof e === 'object') { info = 'status=' + (e.status || 0) + ' statusText=' + (e.statusText || '') + ' error=' + (e.error || ''); } reject(new Error('GM_xmlhttpRequest error: ' + info)); }, }); }); } // Chrome 130+ 的 Local Network Access 在某些配置下会硬拦 https → loopback 的 // fetch,preflight 都发不出去。fetch 路径每次都报 "Permission was denied for // the loopback address space",等 catch 进 GM 时业务上下文可能已经过期。 // 因此 localhost 请求**优先走 GM_xmlhttpRequest**(扩展进程,绕过页面网络栈), // 只有当 GM 不可用(非 Tampermonkey 环境)时才退回 fetch。 return (typeof GM_xmlhttpRequest === 'function') ? doGM().catch(() => doFetch()) : doFetch(); } function pollResult(ts) { return new Promise((resolve, reject) => { const start = Date.now(); function poll() { if (Date.now() - start > RUSH_CFG.pollTimeout) { reject(new Error('poll timeout')); return; } serverRequest('POST', '/result', { ts: ts }).then(d => { if (d.has_result) resolve(d.result); else setTimeout(poll, RUSH_CFG.pollInterval); }).catch(() => setTimeout(poll, RUSH_CFG.pollInterval)); } poll(); }); } function waitForTargetTime() { return new Promise(resolve => { const targetTs = getTargetTimestamp(); const remaining = targetTs - Date.now(); if (remaining <= 0) { resolve(); return; } const winIdx = getWindowIndex(); console.log('[captcha-rush] #' + winIdx + ' wait ' + Math.ceil(remaining / 1000) + 's to target...'); function check() { if (Date.now() >= targetTs) resolve(); else setTimeout(check, 50); } setTimeout(check, Math.max(0, remaining - 5000)); }); } function shouldHoldRushConfirm(nowTs) { if (!RUSH_CFG.enabled) return false; const targetTs = getTargetTimestamp(); const remaining = targetTs - nowTs; return remaining > RUSH_CFG.releaseAdvanceMs && remaining <= RUSH_CFG.holdWindowMs; } function waitForRushRelease() { return new Promise(resolve => { function check() { const remaining = getTargetTimestamp() - Date.now(); if (remaining <= RUSH_CFG.releaseAdvanceMs) resolve(); else setTimeout(check, remaining > 2000 ? 20 : 5); } check(); }); } function findAndClickConfirm() { var selectors = [ '.tencent-captcha-dy__btn-confirm', '.tencent-captcha-dy__footer .btn', '[class*="captcha"] [class*="confirm"]', '[class*="captcha"] [class*="submit"]', ]; for (var si = 0; si < selectors.length; si++) { var btns = document.querySelectorAll(selectors[si]); for (var bi = 0; bi < btns.length; bi++) { var btn = btns[bi]; if (!btn) continue; var style = getComputedStyle(btn); if (style.display === 'none' || style.visibility === 'hidden') continue; var rect = btn.getBoundingClientRect(); if (rect.width <= 0 || rect.height <= 0) continue; console.log('[captcha-rush] click confirm: ' + selectors[si]); btn.click(); return true; } } return false; } function rushStatus(html, bg) { try { setBar(html, bg); } catch (e) {} } async function handleCaptchaRush(chars) { var winIdx = getWindowIndex(); var payloadText = chars.join(''); setCaptchaState('solving'); rushStatus('\uD83D\uDD10 [#' + winIdx + '] \u9A8C\u8BC1\u7801\u8BC6\u522B\u4E2D: ' + payloadText + '...', '#1890ff'); try { var sendRes = await serverRequest('POST', '/captcha', { text: payloadText, ts: Date.now() }); var ts = sendRes.ts; if (!ts) throw new Error('no ts'); console.log('[captcha-rush] [#' + winIdx + '] sent ts=' + ts); rushStatus('\u23F3 [#' + winIdx + '] \u7B49\u5F85\u8BC6\u522B...', '#faad14'); var result = await pollResult(ts); if (!result || !result.result || !result.result.success) { setCaptchaState('idle'); resetCaptchaSession(); rushStatus('\u274C [#' + winIdx + '] \u8BC6\u522B\u5931\u8D25: ' + (result && result.result ? result.result.error : '?') + ' \u2192 \u5FEB\u901F\u91CD\u8BD5', '#ff4d4f'); return; } var predText = result.result.pred_text; var conf = result.result.confidence; console.log('[captcha-rush] [#' + winIdx + '] done: ' + predText + ' conf=' + conf); rushStatus('\u2705 [#' + winIdx + '] \u9A8C\u8BC1\u7801\u5DF2\u89E3: ' + predText + ' (' + (conf * 100).toFixed(0) + '%)', '#52c41a'); var coords = result.result.click_coords || []; console.log('[captcha-rush] [#' + winIdx + '] click_coords count=' + coords.length + ' result=' + JSON.stringify(result.result).substring(0, 200)); if (coords.length > 0) { var clickTarget = document.querySelector('.tencent-captcha-dy__verify-bg-img') || document.querySelector('[class*="verify-bg"]') || document.querySelector('.tencent-captcha-dy__image-area') || findCaptchaContainer(); if (clickTarget) { console.log('[captcha-rush] click target: ' + clickTarget.tagName + '.' + (clickTarget.className||'').substring(0,40)); var tgtRect = clickTarget.getBoundingClientRect(); console.log('[captcha-rush] target rect: ' + Math.round(tgtRect.width) + 'x' + Math.round(tgtRect.height) + ' @ (' + Math.round(tgtRect.left) + ',' + Math.round(tgtRect.top) + ')'); rushStatus('\u2705 [#' + winIdx + '] \u6B63\u5728\u70B9\u51FB ' + coords.length + ' \u5B57...', '#237804'); for (var ci = 0; ci < coords.length; ci++) { var c = coords[ci]; var nx = c.nx || (c.rel_x / 422); var ny = c.ny || (c.rel_y / 305); var cx = tgtRect.left + nx * tgtRect.width; var cy = tgtRect.top + ny * tgtRect.height; console.log('[captcha-rush] click #' + (ci+1) + ' "' + c.char + '" norm=(' + nx.toFixed(3) + ',' + ny.toFixed(3) + ') screen=(' + Math.round(cx) + ',' + Math.round(cy) + ')'); dispatchClickAt(clickTarget, nx * tgtRect.width, ny * tgtRect.height, String(ci + 1)); await new Promise(function(r) { setTimeout(r, 350); }); } rushStatus('\u2705 [#' + winIdx + '] \u70B9\u51FB\u5B8C\u6215! \u7B49\u5F85\u5361\u70B9...', '#237804'); } else { console.warn('[captcha-rush] [#' + winIdx + '] no click target found'); } } await new Promise(function(r) { setTimeout(r, 500); }); setCaptchaState('idle'); (async function() { var isRushMode = isGoldenTime(); if (isRushMode) { await waitForTargetTime(); rushStatus('\uD83D\uDE80 [#' + winIdx + '] \u5361\u70B9\u53D1\u9001!', '#ff4d4f'); } else { await new Promise(function(r) { setTimeout(r, 300); }); rushStatus('\uD83D\uDE80 [#' + winIdx + '] \u81EA\u52A8\u786E\u8BA4...', '#237804'); } setCaptchaState('confirming'); var clicked = findAndClickConfirm(); if (clicked) { setCaptchaState('confirmed'); rushStatus('\uD83C\uDFAF [#' + winIdx + '] \u5DF2\u70B9\u786E\u8BA4!' + (isRushMode ? ' (\u5361\u70B0)' : ''), '#237804'); } else { setCaptchaState('idle'); rushStatus('\u26A0\uFE0F [#' + winIdx + '] \u672A\u627E\u5230\u786E\u8BA4\u6309\u94AE!', '#faad14'); } })(); } catch (e) { setCaptchaState('idle'); resetCaptchaSession(); console.error('[captcha-rush] error:', e); rushStatus('\u274C [#' + getWindowIndex() + '] \u5F02\u5E38: ' + e.message + ' \u2192 \u5FEB\u901F\u91CD\u8BD5', '#ff4d4f'); } } function visible(el) { if (!el) return false; var style = getComputedStyle(el); if (style.display === 'none' || style.visibility === 'hidden') return false; var rect = el.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; } function getCaptchaPromptText(el) { if (!el) return ''; var raw = ( el.getAttribute('aria-label') || el.getAttribute('title') || el.textContent || '' ).trim(); return raw.replace(/^\u8BF7\u4F9D\u6B21\u70B9\u51FB[:\uff1a]?\s*/, '').trim(); } function isPointClickPrompt(text) { if (!text) return false; if (/(\u62D6\u52A8|\u62FC\u56FE|\u6ED1\u5757)/.test(text)) return false; if (/^\u8BF7\u4F9D\u6B21\u70B9\u51FB/.test(text)) return true; // 排除说明性/标题性文字("验证码""安全验证""请点击图中文字"等), // 它们也是 3-8 个汉字,会被下面的兜底分支误判为目标字,导致后端 // 在点选图里找不到这些"字" → detected 0 boxes。 if (/\u9A8C\u8BC1\u7801|\u9A8C\u8BC1|\u5B89\u5168\u9A8C\u8BC1|\u70B9\u51FB\u8FDB\u884C|\u70B9\u51FB\u56FE\u4E2D|\u56FE\u5F62\u9A8C\u8BC1|\u8BF7\u70B9\u51FB|\u5B8C\u6210\u9A8C\u8BC1|\u8FDB\u884C\u9A8C\u8BC1/.test(text)) return false; var chars = (text.match(/[\u4e00-\u9fff]/g) || []); return chars.length >= 3 && chars.length <= 8; } function extractCaptchaChars(text) { var chars = (text || '').match(/[\u4e00-\u9fff]/g) || []; return chars.slice(-3); } function findCaptchaContainer() { var MIN_AREA = 20000; var candidates = []; var selectors = [ '.tencent-captcha-dy__image', '.tencent-captcha-dy__bg-img', '.tencent-captcha-dy__bg', '.tencent-captcha-dy__body', '[class*="captcha-dy"] [class*="image"]', '[class*="captcha-dy"] [class*="bg"]', 'img[class*="captcha"]', 'div[class*="captcha"] img', 'canvas[class*="captcha"]', '[class*="captcha-dy"] img', '[class*="captcha-dy"] canvas', ]; for (var i = 0; i < selectors.length; i++) { var els = document.querySelectorAll(selectors[i]); for (var k = 0; k < els.length; k++) { var el = els[k]; if (!el || !visible(el)) continue; var r = el.getBoundingClientRect(); var area = r.width * r.height; if (area >= MIN_AREA) { candidates.push({ el: el, area: area, w: r.width, h: r.height }); console.log('[captcha] candidate: ' + selectors[i] + ' size=' + Math.round(r.width) + 'x' + Math.round(r.height)); } } } if (candidates.length === 0) { var allImgs = document.querySelectorAll('img'); for (var j = 0; j < allImgs.length; j++) { var img = allImgs[j]; if (!visible(img)) continue; var ir = img.getBoundingClientRect(); var iarea = ir.width * ir.height; if (iarea >= MIN_AREA) { var parent = img.closest('[class*="captcha"]'); if (parent) { candidates.push({ el: img, area: iarea, w: ir.width, h: ir.height }); } } } } if (candidates.length === 0) { var wrapSelectors = [ '.tencent-captcha-dy__wrap', '.tencent-captcha-dy__container', '[id*="tcaptcha"]', '[class*="captcha-dy"][class*="wrap"]', '[class*="captcha-dy"][class*="content"]', ]; for (var wi = 0; wi < wrapSelectors.length; wi++) { var wrapEl = document.querySelector(wrapSelectors[wi]); if (wrapEl && visible(wrapEl)) { var wr = wrapEl.getBoundingClientRect(); if (wr.width * wr.height >= MIN_AREA) { console.log('[captcha] fallback to wrap: ' + wrapSelectors[wi] + ' size=' + Math.round(wr.width) + 'x' + Math.round(wr.height)); return wrapEl; } } } return null; } candidates.sort(function(a, b) { return b.area - a.area; }); var best = candidates[0]; console.log('[captcha] selected container: ' + best.w + 'x' + best.h + ' area=' + Math.round(best.area)); console.log('[captcha] tag=' + best.el.tagName + ' class=' + (best.el.className || '').substring(0, 80)); console.log('[captcha] bg=' + window.getComputedStyle(best.el).backgroundImage.substring(0, 100)); var _dbgAll = best.el.querySelectorAll('*'); for (var di = 0; di < Math.min(_dbgAll.length, 20); di++) { var de = _dbgAll[di]; var dr = de.getBoundingClientRect(); if (dr.width * dr.height < 500) continue; var dbgBg = ''; try { dbgBg = window.getComputedStyle(de).backgroundImage.substring(0, 80); } catch(e) {} var dbgSrc = ''; if (de.tagName === 'IMG') dbgSrc = ' src=' + (de.src || '').substring(0, 60); if (de.tagName === 'CANVAS') dbgSrc = ' canvas=' + de.width + 'x' + de.height; console.log('[captcha-dbg] ' + de.tagName + '.' + (de.className||'').substring(0,30) + ' ' + Math.round(dr.width) + 'x' + Math.round(dr.height) + dbgSrc + ' bg=' + dbgBg); } return best.el; } function captureElementAsBase64(el) { try { if (el.tagName === 'CANVAS') { return el.toDataURL('image/png'); } if (el.tagName === 'IMG') { var c = document.createElement('canvas'); c.width = el.naturalWidth || el.width || 300; c.height = el.naturalHeight || el.height || 200; var ctx = c.getContext('2d'); ctx.drawImage(el, 0, 0); return c.toDataURL('image/png'); } var allImgChildren = el.querySelectorAll('img'); for (var mi = 0; mi < allImgChildren.length; mi++) { var mimg = allImgChildren[mi]; if (!visible(mimg)) continue; var mw = mimg.naturalWidth || mimg.width || 0; var mh = mimg.naturalHeight || mimg.height || 0; if (mw * mh >= 10000) { console.log('[captcha] using child img for capture, size=' + mw + 'x' + mh); var ic = document.createElement('canvas'); ic.width = mw; ic.height = mh; var ictx = ic.getContext('2d'); ictx.drawImage(mimg, 0, 0); return ic.toDataURL('image/png'); } } var canvasChild = el.querySelector('canvas'); if (canvasChild && visible(canvasChild)) { console.log('[captcha] using child canvas for capture'); return canvasChild.toDataURL('image/png'); } var bgUrl = null; var computedBg = window.getComputedStyle(el).backgroundImage; if (computedBg && computedBg !== 'none' && computedBg.indexOf('url(') !== -1) { bgUrl = computedBg.replace(/url\(["']?/, '').replace(/["']?\)$/, ''); console.log('[captcha] found css background-image, url=' + bgUrl.substring(0, 80)); } if (!bgUrl) { var _walkEl = el; for (var bi = 0; bi < 10 && _walkEl; bi++) { var parentBg = window.getComputedStyle(_walkEl).backgroundImage; if (parentBg && parentBg !== 'none' && parentBg.indexOf('url(') !== -1) { bgUrl = parentBg.replace(/url\(["']?/, '').replace(/["']?\)$/, ''); console.log('[captcha] found bg on ancestor level ' + bi + ', url=' + bgUrl.substring(0, 80)); break; } _walkEl = _walkEl.parentElement; } } if (bgUrl) { return new Promise(function(resolve) { var bgImg = new Image(); bgImg.crossOrigin = 'anonymous'; bgImg.onload = function() { console.log('[captcha] bg image loaded, size=' + bgImg.naturalWidth + 'x' + bgImg.naturalHeight); var bc = document.createElement('canvas'); bc.width = bgImg.naturalWidth || bgImg.width || 330; bc.height = bgImg.naturalHeight || bgImg.height || 236; var bctx = bc.getContext('2d'); bctx.drawImage(bgImg, 0, 0); resolve(bc.toDataURL('image/png')); }; bgImg.onerror = function() { resolve(null); }; bgImg.src = bgUrl; }); } var rect = el.getBoundingClientRect(); var w = Math.floor(rect.width * window.devicePixelRatio); var h = Math.floor(rect.height * window.devicePixelRatio); var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('width', w); svg.setAttribute('height', h); var fo = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'); fo.setAttribute('width', w); fo.setAttribute('height', h); var clone = el.cloneNode(true); var allStyles = window.getComputedStyle(el); var styleStr = ''; for (var si = 0; si < allStyles.length; si++) { var prop = allStyles[si]; styleStr += prop + ':' + allStyles.getPropertyValue(prop) + ';'; } clone.setAttribute('style', styleStr); fo.appendChild(clone); svg.appendChild(fo); var svgData = new XMLSerializer().serializeToString(svg); var svgBase64 = btoa(unescape(encodeURIComponent(svgData))); var dataUrl = 'data:image/svg+xml;base64,' + svgBase64; var imgForDraw = new Image(); imgForDraw.src = dataUrl; return new Promise(function(resolve) { imgForDraw.onload = function() { var fc = document.createElement('canvas'); fc.width = w; fc.height = h; var fctx = fc.getContext('2d'); fctx.drawImage(imgForDraw, 0, 0); resolve(fc.toDataURL('image/png')); }; imgForDraw.onerror = function() { resolve(null); }; }); } catch(e) { console.error('[captcha] capture error:', e); return null; } } function dispatchClickAt(el, relX, relY, label) { var rect = el.getBoundingClientRect(); var clientX = rect.left + relX; var clientY = rect.top + relY; var evtWin = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; var opts = { bubbles: true, cancelable: true, view: evtWin, clientX: clientX, clientY: clientY, screenX: clientX + evtWin.screenX, screenY: clientY + evtWin.screenY, button: 0, buttons: 1, pointerId: 1, pointerType: 'mouse', isPrimary: true }; el.dispatchEvent(new PointerEvent('pointerdown', opts)); el.dispatchEvent(new MouseEvent('mousedown', opts)); el.dispatchEvent(new PointerEvent('pointerup', opts)); el.dispatchEvent(new MouseEvent('mouseup', opts)); el.dispatchEvent(new MouseEvent('click', opts)); console.log('[captcha] clicked @ (' + Math.round(clientX) + ',' + Math.round(clientY) + ')'); showClickMarker(clientX, clientY, label || ''); } var _clickMarkers = []; function clearClickMarkers() { for (var mi = 0; mi < _clickMarkers.length; mi++) { if (_clickMarkers[mi] && _clickMarkers[mi].parentNode) { _clickMarkers[mi].parentNode.removeChild(_clickMarkers[mi]); } } _clickMarkers = []; } function showClickMarker(x, y, label) { try { var hostDoc = document; if (typeof unsafeWindow !== 'undefined' && unsafeWindow.document) { hostDoc = unsafeWindow.document; } var marker = hostDoc.createElement('div'); marker.style.cssText = 'position:fixed;left:' + (x - 18) + 'px;top:' + (y - 18) + 'px;width:36px;height:36px;border-radius:50%;border:3px solid #ff0000;' + 'background:rgba(255,0,0,0.25);pointer-events:none;z-index:2147483647;' + 'box-shadow:0 0 8px rgba(255,0,0,0.8);display:flex;align-items:center;justify-content:center;' + 'font-size:14px;font-weight:bold;color:#ff0000;font-family:monospace;'; marker.textContent = label || ''; hostDoc.body.appendChild(marker); _clickMarkers.push(marker); setTimeout(function() { if (marker.parentNode) marker.parentNode.removeChild(marker); var idx = _clickMarkers.indexOf(marker); if (idx > -1) _clickMarkers.splice(idx, 1); }, 700); } catch(e) { console.error('[captcha] marker error:', e); } } function findCaptchaPromptElement() { var selectors = [ '.tencent-captcha-dy__header-text', '.tencent-captcha-dy__header-title-wrap .tencent-captcha-dy__header-text', "div[class*='tencent-captcha'] div[class*='header-text']", // aria-label 兜底:限定在腾讯验证码容器内,避免抓到页面其它元素(如按钮 // aria-label="验证码"、标题等)的说明性文字,导致后端 0 boxes。 '.tencent-captcha-dy[aria-label]', '[class*="tencent-captcha-dy__"][aria-label]', ]; for (var i = 0; i < selectors.length; i++) { var el = document.querySelector(selectors[i]); if (!el || !visible(el)) continue; var text = getCaptchaPromptText(el); if (!isPointClickPrompt(text)) continue; var chars = extractCaptchaChars(text); if (chars.length >= 3) { return { selector: selectors[i], text: text, chars: chars }; } } return null; } function captchaBgUrlFrom(el) { if (!el) return ''; var bg = ''; try { bg = (el.style && el.style.backgroundImage) || window.getComputedStyle(el).backgroundImage || ''; } catch(e) {} var match = bg.match(/url\(["']?([^"')]+)/); if (!match) return ''; try { return new URL(match[1], location.href).href; } catch(e) { return match[1]; } } function findCaptchaBgElementDirect() { var selectors = [ '#slideBg', '.tencent-captcha-dy__verify-bg-img', '.tencent-captcha-dy__bg-img', '[class*="verify-bg"]', '[class*="bg-img"]', '.tencent-captcha-dy__image-area' ]; for (var i = 0; i < selectors.length; i++) { var els = document.querySelectorAll(selectors[i]); for (var j = 0; j < els.length; j++) { var el = els[j]; if (visible(el) && captchaBgUrlFrom(el)) return el; } } return null; } function fetchCaptchaImageDirect(url) { function doFetch() { return fetch(url).then(function(r) { return r.blob(); }).then(function(blob) { return new Promise(function(resolve, reject) { var reader = new FileReader(); reader.onload = function() { resolve(reader.result); }; reader.onerror = function() { reject(new Error('FileReader failed')); }; reader.readAsDataURL(blob); }); }); } function doGM() { return new Promise(function(resolve, reject) { if (typeof GM_xmlhttpRequest === 'undefined') { reject(new Error('GM_xmlhttpRequest unavailable')); return; } GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'blob', onload: function(r) { var reader = new FileReader(); reader.onload = function() { resolve(reader.result); }; reader.onerror = function() { reject(new Error('FileReader failed')); }; reader.readAsDataURL(r.response); }, onerror: function() { reject(new Error('image download failed')); } }); }); } return doFetch().catch(function() { return doGM(); }); } function resolveCaptchaDirectTarget(bgEl, bgUrl) { bgEl = bgEl || findCaptchaBgElementDirect(); if (!bgEl) throw new Error('no captcha background element'); bgUrl = bgUrl || captchaBgUrlFrom(bgEl); if (!bgUrl) throw new Error('no captcha background url'); return { bgEl: bgEl, bgUrl: bgUrl }; } async function requestCaptchaDirectResult(payloadText, bgUrl) { console.log('[captcha-direct-page] bg:', bgUrl.substring(0, 120)); var image = await fetchCaptchaImageDirect(bgUrl); var resp = await serverRequest('POST', '/captcha_direct', { image: image, text: payloadText, remark: payloadText, ts: Date.now(), source: 'glm-coding-helper-page-direct' }); var result = resp && resp.result; if (!result || !result.success || !Array.isArray(result.click_coords)) { throw new Error('bad direct result: ' + JSON.stringify(resp).substring(0, 180)); } return result; } function normalizeCaptchaDirectCoord(coord, rect) { var nx = Number(coord.nx); var ny = Number(coord.ny); if (!Number.isFinite(nx) && Number.isFinite(Number(coord.rel_x))) nx = Number(coord.rel_x) / rect.width; if (!Number.isFinite(ny) && Number.isFinite(Number(coord.rel_y))) ny = Number(coord.rel_y) / rect.height; if (!Number.isFinite(nx) || !Number.isFinite(ny)) return null; return { nx: nx, ny: ny }; } function createCaptchaDirectClickSchedule() { return { ...getCaptchaDirectDelayConfig(), afterClicksMs: 350, }; } function buildCaptchaDirectFixedDelayPlan(result, schedule) { var delays = []; for (var i = 0; i < result.click_coords.length; i++) { delays.push(schedule.gapMs); } return delays; } function buildCaptchaDirectRangeDelayPlan(result, schedule) { var minGap = Math.min(schedule.minGapMs, schedule.maxGapMs); var maxGap = Math.max(schedule.minGapMs, schedule.maxGapMs); var delays = []; for (var i = 0; i < result.click_coords.length; i++) { delays.push(randCaptchaInt(minGap, maxGap)); } return delays; } function buildCaptchaDirectJitterDelayPlan(result, schedule) { var baseGap = schedule.gapMs; var jitterPercent = Math.max(0, schedule.jitterPercent); var jitterDelta = Math.round(baseGap * jitterPercent / 100); var minGap = Math.max(1, baseGap - jitterDelta); var maxGap = Math.max(minGap, baseGap + jitterDelta); var delays = []; for (var i = 0; i < result.click_coords.length; i++) { delays.push(randCaptchaInt(minGap, maxGap)); } return delays; } function buildCaptchaDirectStepDelayPlan(result, schedule) { var delays = []; var stepDelays = Array.isArray(schedule.stepDelays) ? schedule.stepDelays : []; for (var i = 0; i < result.click_coords.length; i++) { delays.push(stepDelays[i] != null ? stepDelays[i] : schedule.gapMs); } return delays; } function resolveCaptchaDirectDelayPlanBuilder(schedule) { if (Array.isArray(schedule.stepDelays) && schedule.stepDelays.length) return buildCaptchaDirectStepDelayPlan; if (schedule.delayMode === 'range') return buildCaptchaDirectRangeDelayPlan; if (schedule.delayMode === 'jitter') return buildCaptchaDirectJitterDelayPlan; return buildCaptchaDirectFixedDelayPlan; } function buildCaptchaDirectDelayPlan(result, schedule) { return resolveCaptchaDirectDelayPlanBuilder(schedule)(result, schedule); } function buildCaptchaDirectClickPlan(rect, result, schedule) { var delays = buildCaptchaDirectDelayPlan(result, schedule); var steps = []; for (var i = 0; i < result.click_coords.length; i++) { var coord = result.click_coords[i]; var normalized = normalizeCaptchaDirectCoord(coord, rect); if (!normalized) continue; steps.push({ x: normalized.nx * rect.width, y: normalized.ny * rect.height, label: coord.char || String(i + 1), delayMs: delays[i], }); } return { steps: steps, afterClicksMs: schedule.afterClicksMs, }; } async function sleepCaptchaDirectClickGap(delayMs) { await new Promise(function(r) { setTimeout(r, delayMs); }); } async function sleepCaptchaDirectAfterClicks(plan) { await new Promise(function(r) { setTimeout(r, plan.afterClicksMs); }); } async function runCaptchaDirectClickStep(bgEl, step, shouldWaitAfter) { dispatchClickAt(bgEl, step.x, step.y, step.label); if (shouldWaitAfter) { await sleepCaptchaDirectClickGap(step.delayMs); } } async function clickCaptchaDirectCoords(bgEl, result) { var schedule = createCaptchaDirectClickSchedule(); var rect = bgEl.getBoundingClientRect(); var plan = buildCaptchaDirectClickPlan(rect, result, schedule); console.log('[captcha-direct-page] click result:', JSON.stringify(result).substring(0, 260)); // 首击前延迟:验证码 DOM 渲染/动画需要时间,OCR 太快(v6 几十 ms)会导致第一击落空。 // 用随机区间,避免固定值被识别为机器。 var fcMin = Math.min(schedule.firstClickMinMs || 0, schedule.firstClickMaxMs || 0); var fcMax = Math.max(schedule.firstClickMinMs || 0, schedule.firstClickMaxMs || 0); if (fcMax > 0) { var firstDelay = randCaptchaInt(fcMin, fcMax); console.log('[captcha-direct-page] first-click delay: ' + firstDelay + 'ms'); await sleepCaptchaDirectClickGap(firstDelay); } for (var i = 0; i < plan.steps.length; i++) { await runCaptchaDirectClickStep(bgEl, plan.steps[i], i < plan.steps.length - 1); } await sleepCaptchaDirectAfterClicks(plan); } async function finishCaptchaDirectConfirm() { if (RUSH_CFG.enabled) { var nowTs = Date.now(); var targetTs = getTargetTimestamp(); var remaining = targetTs - nowTs; if (remaining > RUSH_CFG.holdWindowMs) { console.log('[captcha-rush] outside hold window, skip auto confirm for ' + Math.ceil(remaining / 1000) + 's'); return; } if (shouldHoldRushConfirm(nowTs)) { console.log('[captcha-rush] hold confirm for ' + Math.max(0, targetTs - nowTs).toFixed(0) + 'ms'); await waitForRushRelease(); console.log('[captcha-rush] release confirm'); } var clicked = findAndClickConfirm(); if (clicked) window.__glmRushConfirmed = Date.now(); } else if (CAPTCHA_CFG.AUTO_CAPTCHA_CONFIRM) { findAndClickConfirm(); } else { console.log('[captcha-direct-page] captcha confirm is disabled; waiting for manual confirm'); } } async function handleCaptchaDirectInPage(chars, bgEl, bgUrl) { if (!CAPTCHA_CFG.AUTO_CAPTCHA_CLICK) { console.log('[captcha-direct-page] auto captcha click disabled'); return; } if (getCaptchaState() === 'solving') return; setCaptchaState('solving'); var payloadText = chars.join(''); try { var target = resolveCaptchaDirectTarget(bgEl, bgUrl); var result = await requestCaptchaDirectResult(payloadText, target.bgUrl); await clickCaptchaDirectCoords(target.bgEl, result); await finishCaptchaDirectConfirm(); noteCaptchaSuccess(); } catch (e) { console.error('[captcha-direct-page] error:', e); // 注意:这里**不要**调 resetCaptchaSession()。原来的逻辑会立刻把 sent 清成 // false,下一个 50ms tick 又对同一张图开火,形成自激振荡(被腾讯 SDK 的自我 // 重置吸进死循环)。改为 noteCaptchaFailure 触发冷却 + 累计计数,连续失败 // 够多次后干脆停手等用户介入。传入 bgUrl 让计数绑定到"同一张图",换图即重置。 noteCaptchaFailure(bgUrl); } finally { setCaptchaState('idle'); } } function collectCaptchaChallenge() { var found = findCaptchaPromptElement(); if (!found) { setCaptchaSent(false); return null; } var bgEl = findCaptchaBgElementDirect(); var bgUrl = captchaBgUrlFrom(bgEl); if (!bgEl || !bgUrl) { setCaptchaSent(false); return null; } var payloadText = found.chars.join(''); if (!payloadText) { setCaptchaSent(false); return null; } return { found: found, bgEl: bgEl, bgUrl: bgUrl, payloadText: payloadText }; } function syncCaptchaChallengeText(challenge) { if (challenge.payloadText !== getCaptchaLastText() || challenge.bgUrl !== getCaptchaLastBgUrl()) { setCaptchaLastText(challenge.payloadText); setCaptchaLastBgUrl(challenge.bgUrl); setCaptchaSent(false); try { GM_setValue('glm_captcha_seen_seq', (GM_getValue('glm_captcha_seen_seq', 0) | 0) + 1); } catch (e) {} console.log('[captcha] sel:', challenge.found.selector); console.log('[captcha] raw:', challenge.found.text); console.log('[captcha] prompt:', challenge.payloadText); } } function tryStartCaptchaDirectSolve(challenge) { if (isCaptchaSent()) return; setCaptchaSent(true); console.log('[captcha] page direct solver:', challenge.payloadText); handleCaptchaDirectInPage(challenge.found.chars, challenge.bgEl, challenge.bgUrl).catch(function(e) { console.error('[captcha-direct-page] unhandled:', e); }); } async function checkCaptchaPrompt() { if (isCaptchaStopped()) return; // 连续失败被熔断后彻底停手 if (inCaptchaCooldown()) return; // 失败冷却中,不开新一发 if (getCaptchaState() === 'solving') return; var challenge = collectCaptchaChallenge(); if (!challenge) return; syncCaptchaChallengeText(challenge); tryStartCaptchaDirectSolve(challenge); } setInterval(checkCaptchaPrompt, 50); console.log('[captcha] bridge v2 started | rush=' + RUSH_CFG.enabled + ' | wi=' + getWindowIndex() + ' | target=' + String(RUSH_CFG.targetHour).padStart(2,'0') + ':' + String(RUSH_CFG.targetMin).padStart(2,'0') + ':' + String(RUSH_CFG.targetSec).padStart(2,'0') + '+' + (getWindowIndex() * RUSH_CFG.staggerMs / 1000) + 's'); })();