diff --git a/CHANGELOG.md b/CHANGELOG.md index ea86f9e..083c034 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # 修复历史 +## 2026-05-29 + +- 发布用户脚本 v8.13,增强售罄状态判断的稳定性。 +- 黄金时间保护从 9:50-10:10 前移到 9:30-10:10,覆盖提前进场用户。 +- 今日售罄缓存不再直接让脚本停止;即使缓存显示全售罄,也会重新扫描确认。 +- 非黄金时间连续 3 轮未发现可买或补货时间后,脚本进入低频重试,不再弹出“脚本停止”。 +- 单个套餐需要连续 2 次确认售罄后才写入今日售罄缓存,降低页面文案或接口短暂变化导致的误停概率。 +- 发布用户脚本 v8.14:打开普通 GLM Coding 页面时会自动归一化到作者内置折扣入口,并保留多窗口参数。 +- 发布用户脚本 v8.15:折扣入口强制使用 `https://www.bigmodel.cn/glm-coding`,并在主循环前重复确认 `ic` 参数,避免页面中途清掉参数后继续抢购。 + ## 2026-05-27 - 修复宽屏窗口下腾讯验证码弹窗偏左时,后端找不到弹窗并提示 diff --git a/README.md b/README.md index 2df993a..90795fb 100644 --- a/README.md +++ b/README.md @@ -119,22 +119,18 @@ one-click-start.cmd http://127.0.0.1:8888 ``` -然后打开 GLM Coding Plan 页面: +然后打开 GLM Coding Plan 页面。推荐使用 95 折优惠链接进入: ```text -https://www.bigmodel.cn/glm-coding +https://www.bigmodel.cn/glm-coding?ic=9GXWL9KCGZ ``` -也推荐从 95 折优惠链接进入: - -👉 https://www.bigmodel.cn/glm-coding?ic=9GXWL9KCGZ - ## 抢购步骤 1. 先安装好油猴插件,配置好油猴脚本。使用 Chrome 时要在扩展页面开启开发者模式,然后找到 Tampermonkey 详情,把“允许用户脚本”“在无痕模式下启用”“允许访问文件网址”按需打开。 2. 下载并解压 Release 包,双击 `start-backend.cmd` 或 `one-click-start.cmd` 启动本地后端。 3. 打开抢购网址测试脚本是否正常。推荐由此进入:👉 95 折优惠链接:https://www.bigmodel.cn/glm-coding?ic=9GXWL9KCGZ -4. 每天 9 点 50 分前进入抢购页面准备,晚了可能就打不开了。提前准备好手机支付宝付款。 +4. 每天 9 点 30 分前进入抢购页面准备,晚了可能就打不开了。提前准备好手机支付宝付款。 5. 多开几个窗口,等快到 10 点的时候点击好验证码但不要确定,等 10 点一到再按确定。 6. 如果这波没抢到,就盯着一个窗口用 OCR 识别点击。默认不会自动关闭支付页面。注意:如果看到没有金额的支付页面,那就是没抢到,要关掉继续抢。这时可以使用快捷键快速操作。 diff --git a/docs/agent-skill.md b/docs/agent-skill.md index ee1ed84..6e54827 100644 --- a/docs/agent-skill.md +++ b/docs/agent-skill.md @@ -167,7 +167,7 @@ Use the user's own procedure when explaining the workflow: 1. Install Tampermonkey, configure the userscript, and enable required Chrome extension permissions. 2. Install the backend from GitHub manually or ask an AI assistant to follow this skill. 3. Open the rush page and test that everything works before the real rush window. -4. Enter the rush page before 9:50 every day. It may be hard to open later. +4. Enter the rush page before 9:30 every day. It may be hard to open later. 5. Prepare mobile Alipay payment in advance. A payment page with an amount can still fail if payment is too late. 6. Open several windows. Near 10:00, solve/click captchas but do not confirm too early; start confirming at 10:00. 7. If the first wave fails, keep one window and let local OCR identify and click. diff --git a/glm-coding-helper.user.js b/glm-coding-helper.user.js index 4d0023b..6ddc91e 100644 --- a/glm-coding-helper.user.js +++ b/glm-coding-helper.user.js @@ -1,273 +1,273 @@ // ==UserScript== // @name 智谱 GLM Coding Plan 抢购助手 + 本地 OCR 自动验证码 // @namespace http://tampermonkey.net/ -// @version 8.12 +// @version 8.15 // @description GLM Coding Rush / 智谱 GLM Coding Plan 抢购助手,一键抢购油猴脚本 / Tampermonkey userscript,配合本地 CPU/GPU OCR 自动识别中文点选验证码并点击,支持多窗口并发、限流重试和支付页安全保护 // @author mumumi // @include https://*bigmodel.cn/glm-coding* -// @match https://bigmodel.cn/glm-coding* -// @match https://www.bigmodel.cn/glm-coding* -// @match https://*.gtimg.com/* -// @match https://*.captcha.qcloud.com/* -// @include https://*bigmodel.cn/html/rate-limit.html* +// @match https://bigmodel.cn/glm-coding* +// @match https://www.bigmodel.cn/glm-coding* +// @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 -// @connect localhost:8888 -// @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 +// @grant GM_openInTab +// @grant GM_xmlhttpRequest +// @connect localhost:8888 +// @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 __glmHost = (() => { try { return location.hostname || ''; } catch { 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 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 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, ''); - const chars = (cleaned.match(/[\u4e00-\u9fff]/g) || []).slice(-3); - if (chars.length >= 3) return chars.join(''); - } - return ''; - } - - function fetchImageDataUrl(url) { - return new Promise((resolve, reject) => { - if (typeof GM_xmlhttpRequest !== 'undefined') { - 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; - } - fetch(url, { credentials: 'include' }) - .then(r => r.blob()) - .then(blob => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result); - reader.onerror = () => reject(new Error('FileReader failed')); - reader.readAsDataURL(blob); - }) - .catch(reject); - }); - } - - function postDirect(dataUrl, chars) { - const body = JSON.stringify({ - image: dataUrl, - text: chars, - ts: Date.now(), - source: 'tencent_iframe_direct', - }); - return new Promise((resolve, reject) => { - if (typeof GM_xmlhttpRequest !== 'undefined') { - 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')), - }); - return; - } - fetch(DIRECT_OCR_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body, - }).then(r => r.json()).then(resolve).catch(reject); - }); - } - - 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 (!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 (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, 1200); - } + 'use strict'; + + const __glmHost = (() => { try { return location.hostname || ''; } catch { 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 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 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, ''); + const chars = (cleaned.match(/[\u4e00-\u9fff]/g) || []).slice(-3); + if (chars.length >= 3) return chars.join(''); + } + return ''; + } + + function fetchImageDataUrl(url) { + return new Promise((resolve, reject) => { + if (typeof GM_xmlhttpRequest !== 'undefined') { + 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; + } + fetch(url, { credentials: 'include' }) + .then(r => r.blob()) + .then(blob => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result); + reader.onerror = () => reject(new Error('FileReader failed')); + reader.readAsDataURL(blob); + }) + .catch(reject); + }); + } + + function postDirect(dataUrl, chars) { + const body = JSON.stringify({ + image: dataUrl, + text: chars, + ts: Date.now(), + source: 'tencent_iframe_direct', + }); + return new Promise((resolve, reject) => { + if (typeof GM_xmlhttpRequest !== 'undefined') { + 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')), + }); + return; + } + fetch(DIRECT_OCR_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body, + }).then(r => r.json()).then(resolve).catch(reject); + }); + } + + 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 (!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 (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, 1200); + } // ── 去重保护:防止篡猴里装了改名导致的两个实例同时运行 ────────────────── if (document.documentElement.dataset.glmHelper === '1') { return; } document.documentElement.dataset.glmHelper = '1'; // ── 最早读配置(document-start 时还没有主流程)────────────────────────── - const EARLY_STORAGE_KEY = 'glm_coding_config_v5'; + const EARLY_STORAGE_KEY = 'glm_coding_config_v5'; const SAFE_DEFAULTS_VERSION = 2; const _ec = (() => { try { return JSON.parse(GM_getValue(EARLY_STORAGE_KEY, '{}')); } catch { return {}; } })(); if (_ec.SAFE_DEFAULTS_VERSION !== SAFE_DEFAULTS_VERSION) { @@ -279,8 +279,26 @@ 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`; + + 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; + } + } // ── 限流页立即跳回主页 ──────────────────────────────────────────────────── + 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; @@ -493,11 +511,11 @@ PACKAGES_PRIORITY : '2,3,1', CHECK_INTERVAL : 80, SMART_REFRESH : true, - AUTO_CLOSE_INVALID: false, - AUTO_CLICK_SUB : true, - AUTO_CAPTCHA_CLICK : true, - AUTO_CAPTCHA_CONFIRM: false, - }; + AUTO_CLOSE_INVALID: false, + AUTO_CLICK_SUB : true, + AUTO_CAPTCHA_CLICK : true, + AUTO_CAPTCHA_CONFIRM: false, + }; 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)); } @@ -566,22 +584,24 @@ // ── 扫描队列(过滤今日已确认售罄)──────────────────────────────────────── const tabs = String(CFG.TABS_PRIORITY).split(',').map(Number).filter(Boolean); const pkgs = String(CFG.PACKAGES_PRIORITY).split(',').map(Number).filter(Boolean); - const scanQueue = tabs.flatMap(t => pkgs.map(p => ({ tab: t, pkg: p }))).filter(({ tab: t, pkg: p }) => getS(t, p) !== 1); + 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) { - setTimeout(() => { setBar('📭 今日所有配置套餐均已售罄,脚本停止。', '#434343'); triggerPromo(); }, 800); - return; + scanQueue.push(...allTargets); + setTimeout(() => setBar('📭 今日缓存显示全售罄,仍会重新扫描确认。', '#434343'), 800); } // ── 状态机变量 ──────────────────────────────────────────────────────────── let state = 'SCANNING'; // SCANNING | TASK_UNIT | SLEEPING | DONE // SCANNING / TASK_UNIT - let qIdx = 0, sweepRestocks = [], lastTabSwitch = 0, sweepBusyCount = 0; + 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 lastCloseReason = ''; let sleepUntil = 0; - const MAX_RL = 3, MODAL_WAIT = 15000; + const MAX_RL = 3, MODAL_WAIT = 15000, EMPTY_SWEEP_CONFIRM = 3, EMPTY_SWEEP_RETRY_MS = 180000, SOLD_OUT_CONFIRM = 2; // ── 工具函数 ────────────────────────────────────────────────────────────── function parseRestock(text) { @@ -608,13 +628,13 @@ } function isRealBizId(id) { return id && !id.startsWith('debug-'); } - // ── v8.0: 黄金时间判断(9:50-10:10)────────────────────────────────────── + // ── v8.0: 黄金时间判断(9:30-10:10)────────────────────────────────────── function isGoldenTime() { const now = new Date(); const h = now.getHours(); const m = now.getMinutes(); const time = h * 60 + m; - const start = 9 * 60 + 50; // 9:50 + const start = 9 * 60 + 30; // 9:30 const end = 10 * 60 + 10; // 10:10 return time >= start && time <= end; } @@ -856,19 +876,19 @@ 自动点击订阅 ? - - - - -
+ + + +
+
@@ -888,13 +908,13 @@ 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, - AUTO_CLICK_SUB : panel.querySelector('#glm-acs').checked, - AUTO_CAPTCHA_CLICK: panel.querySelector('#glm-acc').checked, - AUTO_CAPTCHA_CONFIRM: panel.querySelector('#glm-acf').checked, - SAFE_DEFAULTS_VERSION, - }); + CHECK_INTERVAL : CFG.CHECK_INTERVAL, + AUTO_CLOSE_INVALID: panel.querySelector('#glm-aci').checked, + AUTO_CLICK_SUB : panel.querySelector('#glm-acs').checked, + AUTO_CAPTCHA_CLICK: panel.querySelector('#glm-acc').checked, + AUTO_CAPTCHA_CONFIRM: panel.querySelector('#glm-acf').checked, + SAFE_DEFAULTS_VERSION, + }); ov.remove(); alert('已保存,即将刷新。'); location.reload(); }; ov.onclick = e => { if (e.target === ov) ov.remove(); }; @@ -905,6 +925,7 @@ // ═══════════════════════════════════════════════════════════════════════════ function tick() { if (state === 'DONE') return; + if (ensureDiscountEntry()) return; if (state === 'SLEEPING') { const rem = sleepUntil - Date.now(); @@ -942,6 +963,7 @@ 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]},即将点击...`, '#389e0d'); return; @@ -968,24 +990,34 @@ } if (!sweepRestocks.length && isGoldenTime()) { setBar(`🔥 黄金时间!系统繁忙中,持续高频监控!`, '#ff4d4f'); - qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; return; + qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; emptySweepCount = 0; return; } if (!sweepRestocks.length) { - state = 'DONE'; setBar('📭 今日全部售罄,脚本停止。', '#434343'); triggerPromo(); return; + emptySweepCount++; + qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; + if (emptySweepCount < EMPTY_SWEEP_CONFIRM) { + setBar(`📭 暂未发现可买/补货时间,继续确认 ${emptySweepCount}/${EMPTY_SWEEP_CONFIRM}...`, '#434343'); + return; + } + state = 'SLEEPING'; + sleepUntil = Date.now() + EMPTY_SWEEP_RETRY_MS; + setBar(`📭 连续 ${EMPTY_SWEEP_CONFIRM} 轮未发现库存,${fmt(EMPTY_SWEEP_RETRY_MS)} 后重新扫描。脚本未停止。`, '#434343'); + return; } + emptySweepCount = 0; sweepRestocks.sort((a, b) => a.msUntil - b.msUntil); const nearest = sweepRestocks[0]; const sleep = calcSleepMs(nearest.msUntil); - // ── v8.0: 黄金时间(9:50-10:10)禁止刷新页面 ────────────────────────── + // ── v8.0: 黄金时间(9:30-10:10)禁止刷新页面 ────────────────────────── if (isGoldenTime()) { setBar(`🔥 黄金时间!补货倒计时 ${fmt(nearest.msUntil)},禁止刷新,高频监控!`, '#ff4d4f'); - qIdx = 0; sweepRestocks = []; return; + qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; return; } if (sleep === 0) { setBar(`⚡ 补货倒计时 ${fmt(nearest.msUntil)},高频监控!`, '#d4380d'); - qIdx = 0; sweepRestocks = []; return; + qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; return; } if (CFG.SMART_REFRESH) { state = 'SLEEPING'; sleepUntil = Date.now() + sleep; @@ -1129,7 +1161,9 @@ function exitTask() { // v8.0: 黄金时间内不标记售罄,持续重试 if (!isGoldenTime()) { - setS(taskTarget.tab, taskTarget.pkg, 1); + 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; @@ -1182,23 +1216,23 @@ if (window.__glmCaptchaPromptBridge === 1) return; window.__glmCaptchaPromptBridge = 1; - const RUSH_CONFIG = { - enabled: true, - targetHour: 10, - targetMin: 0, - targetSec: 0, + const RUSH_CONFIG = { + enabled: true, + targetHour: 10, + targetMin: 0, + targetSec: 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, ...JSON.parse(raw || '{}') }; - } catch { - return { AUTO_CAPTCHA_CLICK: true, AUTO_CAPTCHA_CONFIRM: false }; - } - })(); + pollTimeout: 20000, + }; + const CAPTCHA_CFG = (() => { + 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 getWindowIndex() { const params = new URLSearchParams(location.search); @@ -1422,9 +1456,9 @@ return chars.length >= 3 && chars.length <= 8; } - function extractCaptchaChars(text) { - var chars = (text || '').match(/[\u4e00-\u9fff]/g) || []; - return chars.slice(-3); + function extractCaptchaChars(text) { + var chars = (text || '').match(/[\u4e00-\u9fff]/g) || []; + return chars.slice(-3); } function findCaptchaContainer() { @@ -1695,11 +1729,11 @@ } } - 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']", + 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]', ]; for (var i = 0; i < selectors.length; i++) { @@ -1711,119 +1745,119 @@ 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) { - return new Promise(function(resolve, reject) { - if (typeof GM_xmlhttpRequest !== 'undefined') { - 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; - } - fetch(url).then(function(r) { return r.blob(); }).then(function(blob) { - var reader = new FileReader(); - reader.onload = function() { resolve(reader.result); }; - reader.onerror = function() { reject(new Error('FileReader failed')); }; - reader.readAsDataURL(blob); - }).catch(reject); - }); - } - - async function handleCaptchaDirectInPage(chars) { - if (!CAPTCHA_CFG.AUTO_CAPTCHA_CLICK) { - console.log('[captcha-direct-page] auto captcha click disabled'); - return; - } - if (rushState === 'solving') return; - rushState = 'solving'; - var payloadText = chars.join(''); - try { - var bgEl = findCaptchaBgElementDirect(); - if (!bgEl) throw new Error('no captcha background element'); - var bgUrl = captchaBgUrlFrom(bgEl); - if (!bgUrl) throw new Error('no captcha background url'); - 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)); - } - var rect = bgEl.getBoundingClientRect(); - console.log('[captcha-direct-page] click result:', JSON.stringify(result).substring(0, 260)); - for (var i = 0; i < result.click_coords.length; i++) { - var c = result.click_coords[i]; - var nx = Number(c.nx); - var ny = Number(c.ny); - if (!Number.isFinite(nx) && Number.isFinite(Number(c.rel_x))) nx = Number(c.rel_x) / rect.width; - if (!Number.isFinite(ny) && Number.isFinite(Number(c.rel_y))) ny = Number(c.rel_y) / rect.height; - if (!Number.isFinite(nx) || !Number.isFinite(ny)) continue; - dispatchClickAt(bgEl, nx * rect.width, ny * rect.height, c.char || String(i + 1)); - await new Promise(function(r) { setTimeout(r, 220); }); - } - await new Promise(function(r) { setTimeout(r, 350); }); - if (CAPTCHA_CFG.AUTO_CAPTCHA_CONFIRM) { - findAndClickConfirm(); - } else { - console.log('[captcha-direct-page] captcha confirm is disabled; waiting for manual confirm'); - } - } catch (e) { - console.error('[captcha-direct-page] error:', e); - captchaSent = false; - lastCaptchaText = ''; - } finally { - rushState = 'idle'; - } - } - - async function checkCaptchaPrompt() { - if (rushState === 'solving') return; + } + 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) { + return new Promise(function(resolve, reject) { + if (typeof GM_xmlhttpRequest !== 'undefined') { + 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; + } + fetch(url).then(function(r) { return r.blob(); }).then(function(blob) { + var reader = new FileReader(); + reader.onload = function() { resolve(reader.result); }; + reader.onerror = function() { reject(new Error('FileReader failed')); }; + reader.readAsDataURL(blob); + }).catch(reject); + }); + } + + async function handleCaptchaDirectInPage(chars) { + if (!CAPTCHA_CFG.AUTO_CAPTCHA_CLICK) { + console.log('[captcha-direct-page] auto captcha click disabled'); + return; + } + if (rushState === 'solving') return; + rushState = 'solving'; + var payloadText = chars.join(''); + try { + var bgEl = findCaptchaBgElementDirect(); + if (!bgEl) throw new Error('no captcha background element'); + var bgUrl = captchaBgUrlFrom(bgEl); + if (!bgUrl) throw new Error('no captcha background url'); + 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)); + } + var rect = bgEl.getBoundingClientRect(); + console.log('[captcha-direct-page] click result:', JSON.stringify(result).substring(0, 260)); + for (var i = 0; i < result.click_coords.length; i++) { + var c = result.click_coords[i]; + var nx = Number(c.nx); + var ny = Number(c.ny); + if (!Number.isFinite(nx) && Number.isFinite(Number(c.rel_x))) nx = Number(c.rel_x) / rect.width; + if (!Number.isFinite(ny) && Number.isFinite(Number(c.rel_y))) ny = Number(c.rel_y) / rect.height; + if (!Number.isFinite(nx) || !Number.isFinite(ny)) continue; + dispatchClickAt(bgEl, nx * rect.width, ny * rect.height, c.char || String(i + 1)); + await new Promise(function(r) { setTimeout(r, 220); }); + } + await new Promise(function(r) { setTimeout(r, 350); }); + if (CAPTCHA_CFG.AUTO_CAPTCHA_CONFIRM) { + findAndClickConfirm(); + } else { + console.log('[captcha-direct-page] captcha confirm is disabled; waiting for manual confirm'); + } + } catch (e) { + console.error('[captcha-direct-page] error:', e); + captchaSent = false; + lastCaptchaText = ''; + } finally { + rushState = 'idle'; + } + } + + async function checkCaptchaPrompt() { + if (rushState === 'solving') return; var found = findCaptchaPromptElement(); if (!found) { captchaSent = false; return; } @@ -1839,14 +1873,14 @@ console.log('[captcha] prompt:', payloadText); } - if (!captchaSent) { - captchaSent = true; - console.log('[captcha] page direct solver:', payloadText); - handleCaptchaDirectInPage(found.chars).catch(function(e) { console.error('[captcha-direct-page] unhandled:', e); }); - return; - if (RUSH_CONFIG.enabled) { - handleCaptchaRush(found.chars).catch(function(e) { console.error('[captcha-rush] unhandled:', e); }); - } else { + if (!captchaSent) { + captchaSent = true; + console.log('[captcha] page direct solver:', payloadText); + handleCaptchaDirectInPage(found.chars).catch(function(e) { console.error('[captcha-direct-page] unhandled:', e); }); + return; + if (RUSH_CONFIG.enabled) { + handleCaptchaRush(found.chars).catch(function(e) { console.error('[captcha-rush] unhandled:', e); }); + } else { serverRequest('POST', '/captcha', { text: payloadText, ts: Date.now() }) .then(function() { console.log('[captcha] legacy ok'); }) .catch(function(e) { console.log('[captcha] legacy err:', e); }); diff --git a/scripts/release/build_portable.ps1 b/scripts/release/build_portable.ps1 index f781130..78bd0c4 100644 --- a/scripts/release/build_portable.ps1 +++ b/scripts/release/build_portable.ps1 @@ -78,7 +78,7 @@ GLM Coding Helper portable CPU package 1. Install or update Tampermonkey script from glm-coding-helper.user.js. 2. Double-click start-backend.cmd or 启动后端.cmd. -3. Open https://www.bigmodel.cn/glm-coding. +3. Open https://www.bigmodel.cn/glm-coding?ic=9GXWL9KCGZ. This package includes the CPU Python environment and local model files. "@ diff --git a/scripts/userscripts/glm-coding-helper.user.js b/scripts/userscripts/glm-coding-helper.user.js index 4d0023b..6ddc91e 100644 --- a/scripts/userscripts/glm-coding-helper.user.js +++ b/scripts/userscripts/glm-coding-helper.user.js @@ -1,273 +1,273 @@ // ==UserScript== // @name 智谱 GLM Coding Plan 抢购助手 + 本地 OCR 自动验证码 // @namespace http://tampermonkey.net/ -// @version 8.12 +// @version 8.15 // @description GLM Coding Rush / 智谱 GLM Coding Plan 抢购助手,一键抢购油猴脚本 / Tampermonkey userscript,配合本地 CPU/GPU OCR 自动识别中文点选验证码并点击,支持多窗口并发、限流重试和支付页安全保护 // @author mumumi // @include https://*bigmodel.cn/glm-coding* -// @match https://bigmodel.cn/glm-coding* -// @match https://www.bigmodel.cn/glm-coding* -// @match https://*.gtimg.com/* -// @match https://*.captcha.qcloud.com/* -// @include https://*bigmodel.cn/html/rate-limit.html* +// @match https://bigmodel.cn/glm-coding* +// @match https://www.bigmodel.cn/glm-coding* +// @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 -// @connect localhost:8888 -// @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 +// @grant GM_openInTab +// @grant GM_xmlhttpRequest +// @connect localhost:8888 +// @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 __glmHost = (() => { try { return location.hostname || ''; } catch { 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 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 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, ''); - const chars = (cleaned.match(/[\u4e00-\u9fff]/g) || []).slice(-3); - if (chars.length >= 3) return chars.join(''); - } - return ''; - } - - function fetchImageDataUrl(url) { - return new Promise((resolve, reject) => { - if (typeof GM_xmlhttpRequest !== 'undefined') { - 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; - } - fetch(url, { credentials: 'include' }) - .then(r => r.blob()) - .then(blob => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result); - reader.onerror = () => reject(new Error('FileReader failed')); - reader.readAsDataURL(blob); - }) - .catch(reject); - }); - } - - function postDirect(dataUrl, chars) { - const body = JSON.stringify({ - image: dataUrl, - text: chars, - ts: Date.now(), - source: 'tencent_iframe_direct', - }); - return new Promise((resolve, reject) => { - if (typeof GM_xmlhttpRequest !== 'undefined') { - 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')), - }); - return; - } - fetch(DIRECT_OCR_URL, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body, - }).then(r => r.json()).then(resolve).catch(reject); - }); - } - - 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 (!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 (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, 1200); - } + 'use strict'; + + const __glmHost = (() => { try { return location.hostname || ''; } catch { 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 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 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, ''); + const chars = (cleaned.match(/[\u4e00-\u9fff]/g) || []).slice(-3); + if (chars.length >= 3) return chars.join(''); + } + return ''; + } + + function fetchImageDataUrl(url) { + return new Promise((resolve, reject) => { + if (typeof GM_xmlhttpRequest !== 'undefined') { + 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; + } + fetch(url, { credentials: 'include' }) + .then(r => r.blob()) + .then(blob => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result); + reader.onerror = () => reject(new Error('FileReader failed')); + reader.readAsDataURL(blob); + }) + .catch(reject); + }); + } + + function postDirect(dataUrl, chars) { + const body = JSON.stringify({ + image: dataUrl, + text: chars, + ts: Date.now(), + source: 'tencent_iframe_direct', + }); + return new Promise((resolve, reject) => { + if (typeof GM_xmlhttpRequest !== 'undefined') { + 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')), + }); + return; + } + fetch(DIRECT_OCR_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body, + }).then(r => r.json()).then(resolve).catch(reject); + }); + } + + 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 (!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 (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, 1200); + } // ── 去重保护:防止篡猴里装了改名导致的两个实例同时运行 ────────────────── if (document.documentElement.dataset.glmHelper === '1') { return; } document.documentElement.dataset.glmHelper = '1'; // ── 最早读配置(document-start 时还没有主流程)────────────────────────── - const EARLY_STORAGE_KEY = 'glm_coding_config_v5'; + const EARLY_STORAGE_KEY = 'glm_coding_config_v5'; const SAFE_DEFAULTS_VERSION = 2; const _ec = (() => { try { return JSON.parse(GM_getValue(EARLY_STORAGE_KEY, '{}')); } catch { return {}; } })(); if (_ec.SAFE_DEFAULTS_VERSION !== SAFE_DEFAULTS_VERSION) { @@ -279,8 +279,26 @@ 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`; + + 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; + } + } // ── 限流页立即跳回主页 ──────────────────────────────────────────────────── + 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; @@ -493,11 +511,11 @@ PACKAGES_PRIORITY : '2,3,1', CHECK_INTERVAL : 80, SMART_REFRESH : true, - AUTO_CLOSE_INVALID: false, - AUTO_CLICK_SUB : true, - AUTO_CAPTCHA_CLICK : true, - AUTO_CAPTCHA_CONFIRM: false, - }; + AUTO_CLOSE_INVALID: false, + AUTO_CLICK_SUB : true, + AUTO_CAPTCHA_CLICK : true, + AUTO_CAPTCHA_CONFIRM: false, + }; 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)); } @@ -566,22 +584,24 @@ // ── 扫描队列(过滤今日已确认售罄)──────────────────────────────────────── const tabs = String(CFG.TABS_PRIORITY).split(',').map(Number).filter(Boolean); const pkgs = String(CFG.PACKAGES_PRIORITY).split(',').map(Number).filter(Boolean); - const scanQueue = tabs.flatMap(t => pkgs.map(p => ({ tab: t, pkg: p }))).filter(({ tab: t, pkg: p }) => getS(t, p) !== 1); + 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) { - setTimeout(() => { setBar('📭 今日所有配置套餐均已售罄,脚本停止。', '#434343'); triggerPromo(); }, 800); - return; + scanQueue.push(...allTargets); + setTimeout(() => setBar('📭 今日缓存显示全售罄,仍会重新扫描确认。', '#434343'), 800); } // ── 状态机变量 ──────────────────────────────────────────────────────────── let state = 'SCANNING'; // SCANNING | TASK_UNIT | SLEEPING | DONE // SCANNING / TASK_UNIT - let qIdx = 0, sweepRestocks = [], lastTabSwitch = 0, sweepBusyCount = 0; + 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 lastCloseReason = ''; let sleepUntil = 0; - const MAX_RL = 3, MODAL_WAIT = 15000; + const MAX_RL = 3, MODAL_WAIT = 15000, EMPTY_SWEEP_CONFIRM = 3, EMPTY_SWEEP_RETRY_MS = 180000, SOLD_OUT_CONFIRM = 2; // ── 工具函数 ────────────────────────────────────────────────────────────── function parseRestock(text) { @@ -608,13 +628,13 @@ } function isRealBizId(id) { return id && !id.startsWith('debug-'); } - // ── v8.0: 黄金时间判断(9:50-10:10)────────────────────────────────────── + // ── v8.0: 黄金时间判断(9:30-10:10)────────────────────────────────────── function isGoldenTime() { const now = new Date(); const h = now.getHours(); const m = now.getMinutes(); const time = h * 60 + m; - const start = 9 * 60 + 50; // 9:50 + const start = 9 * 60 + 30; // 9:30 const end = 10 * 60 + 10; // 10:10 return time >= start && time <= end; } @@ -856,19 +876,19 @@ 自动点击订阅 ? - - - -
-
+ + + +
+
@@ -888,13 +908,13 @@ 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, - AUTO_CLICK_SUB : panel.querySelector('#glm-acs').checked, - AUTO_CAPTCHA_CLICK: panel.querySelector('#glm-acc').checked, - AUTO_CAPTCHA_CONFIRM: panel.querySelector('#glm-acf').checked, - SAFE_DEFAULTS_VERSION, - }); + CHECK_INTERVAL : CFG.CHECK_INTERVAL, + AUTO_CLOSE_INVALID: panel.querySelector('#glm-aci').checked, + AUTO_CLICK_SUB : panel.querySelector('#glm-acs').checked, + AUTO_CAPTCHA_CLICK: panel.querySelector('#glm-acc').checked, + AUTO_CAPTCHA_CONFIRM: panel.querySelector('#glm-acf').checked, + SAFE_DEFAULTS_VERSION, + }); ov.remove(); alert('已保存,即将刷新。'); location.reload(); }; ov.onclick = e => { if (e.target === ov) ov.remove(); }; @@ -905,6 +925,7 @@ // ═══════════════════════════════════════════════════════════════════════════ function tick() { if (state === 'DONE') return; + if (ensureDiscountEntry()) return; if (state === 'SLEEPING') { const rem = sleepUntil - Date.now(); @@ -942,6 +963,7 @@ 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]},即将点击...`, '#389e0d'); return; @@ -968,24 +990,34 @@ } if (!sweepRestocks.length && isGoldenTime()) { setBar(`🔥 黄金时间!系统繁忙中,持续高频监控!`, '#ff4d4f'); - qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; return; + qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; emptySweepCount = 0; return; } if (!sweepRestocks.length) { - state = 'DONE'; setBar('📭 今日全部售罄,脚本停止。', '#434343'); triggerPromo(); return; + emptySweepCount++; + qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; + if (emptySweepCount < EMPTY_SWEEP_CONFIRM) { + setBar(`📭 暂未发现可买/补货时间,继续确认 ${emptySweepCount}/${EMPTY_SWEEP_CONFIRM}...`, '#434343'); + return; + } + state = 'SLEEPING'; + sleepUntil = Date.now() + EMPTY_SWEEP_RETRY_MS; + setBar(`📭 连续 ${EMPTY_SWEEP_CONFIRM} 轮未发现库存,${fmt(EMPTY_SWEEP_RETRY_MS)} 后重新扫描。脚本未停止。`, '#434343'); + return; } + emptySweepCount = 0; sweepRestocks.sort((a, b) => a.msUntil - b.msUntil); const nearest = sweepRestocks[0]; const sleep = calcSleepMs(nearest.msUntil); - // ── v8.0: 黄金时间(9:50-10:10)禁止刷新页面 ────────────────────────── + // ── v8.0: 黄金时间(9:30-10:10)禁止刷新页面 ────────────────────────── if (isGoldenTime()) { setBar(`🔥 黄金时间!补货倒计时 ${fmt(nearest.msUntil)},禁止刷新,高频监控!`, '#ff4d4f'); - qIdx = 0; sweepRestocks = []; return; + qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; return; } if (sleep === 0) { setBar(`⚡ 补货倒计时 ${fmt(nearest.msUntil)},高频监控!`, '#d4380d'); - qIdx = 0; sweepRestocks = []; return; + qIdx = 0; sweepRestocks = []; sweepBusyCount = 0; return; } if (CFG.SMART_REFRESH) { state = 'SLEEPING'; sleepUntil = Date.now() + sleep; @@ -1129,7 +1161,9 @@ function exitTask() { // v8.0: 黄金时间内不标记售罄,持续重试 if (!isGoldenTime()) { - setS(taskTarget.tab, taskTarget.pkg, 1); + 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; @@ -1182,23 +1216,23 @@ if (window.__glmCaptchaPromptBridge === 1) return; window.__glmCaptchaPromptBridge = 1; - const RUSH_CONFIG = { - enabled: true, - targetHour: 10, - targetMin: 0, - targetSec: 0, + const RUSH_CONFIG = { + enabled: true, + targetHour: 10, + targetMin: 0, + targetSec: 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, ...JSON.parse(raw || '{}') }; - } catch { - return { AUTO_CAPTCHA_CLICK: true, AUTO_CAPTCHA_CONFIRM: false }; - } - })(); + pollTimeout: 20000, + }; + const CAPTCHA_CFG = (() => { + 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 getWindowIndex() { const params = new URLSearchParams(location.search); @@ -1422,9 +1456,9 @@ return chars.length >= 3 && chars.length <= 8; } - function extractCaptchaChars(text) { - var chars = (text || '').match(/[\u4e00-\u9fff]/g) || []; - return chars.slice(-3); + function extractCaptchaChars(text) { + var chars = (text || '').match(/[\u4e00-\u9fff]/g) || []; + return chars.slice(-3); } function findCaptchaContainer() { @@ -1695,11 +1729,11 @@ } } - 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']", + 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]', ]; for (var i = 0; i < selectors.length; i++) { @@ -1711,119 +1745,119 @@ 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) { - return new Promise(function(resolve, reject) { - if (typeof GM_xmlhttpRequest !== 'undefined') { - 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; - } - fetch(url).then(function(r) { return r.blob(); }).then(function(blob) { - var reader = new FileReader(); - reader.onload = function() { resolve(reader.result); }; - reader.onerror = function() { reject(new Error('FileReader failed')); }; - reader.readAsDataURL(blob); - }).catch(reject); - }); - } - - async function handleCaptchaDirectInPage(chars) { - if (!CAPTCHA_CFG.AUTO_CAPTCHA_CLICK) { - console.log('[captcha-direct-page] auto captcha click disabled'); - return; - } - if (rushState === 'solving') return; - rushState = 'solving'; - var payloadText = chars.join(''); - try { - var bgEl = findCaptchaBgElementDirect(); - if (!bgEl) throw new Error('no captcha background element'); - var bgUrl = captchaBgUrlFrom(bgEl); - if (!bgUrl) throw new Error('no captcha background url'); - 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)); - } - var rect = bgEl.getBoundingClientRect(); - console.log('[captcha-direct-page] click result:', JSON.stringify(result).substring(0, 260)); - for (var i = 0; i < result.click_coords.length; i++) { - var c = result.click_coords[i]; - var nx = Number(c.nx); - var ny = Number(c.ny); - if (!Number.isFinite(nx) && Number.isFinite(Number(c.rel_x))) nx = Number(c.rel_x) / rect.width; - if (!Number.isFinite(ny) && Number.isFinite(Number(c.rel_y))) ny = Number(c.rel_y) / rect.height; - if (!Number.isFinite(nx) || !Number.isFinite(ny)) continue; - dispatchClickAt(bgEl, nx * rect.width, ny * rect.height, c.char || String(i + 1)); - await new Promise(function(r) { setTimeout(r, 220); }); - } - await new Promise(function(r) { setTimeout(r, 350); }); - if (CAPTCHA_CFG.AUTO_CAPTCHA_CONFIRM) { - findAndClickConfirm(); - } else { - console.log('[captcha-direct-page] captcha confirm is disabled; waiting for manual confirm'); - } - } catch (e) { - console.error('[captcha-direct-page] error:', e); - captchaSent = false; - lastCaptchaText = ''; - } finally { - rushState = 'idle'; - } - } - - async function checkCaptchaPrompt() { - if (rushState === 'solving') return; + } + 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) { + return new Promise(function(resolve, reject) { + if (typeof GM_xmlhttpRequest !== 'undefined') { + 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; + } + fetch(url).then(function(r) { return r.blob(); }).then(function(blob) { + var reader = new FileReader(); + reader.onload = function() { resolve(reader.result); }; + reader.onerror = function() { reject(new Error('FileReader failed')); }; + reader.readAsDataURL(blob); + }).catch(reject); + }); + } + + async function handleCaptchaDirectInPage(chars) { + if (!CAPTCHA_CFG.AUTO_CAPTCHA_CLICK) { + console.log('[captcha-direct-page] auto captcha click disabled'); + return; + } + if (rushState === 'solving') return; + rushState = 'solving'; + var payloadText = chars.join(''); + try { + var bgEl = findCaptchaBgElementDirect(); + if (!bgEl) throw new Error('no captcha background element'); + var bgUrl = captchaBgUrlFrom(bgEl); + if (!bgUrl) throw new Error('no captcha background url'); + 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)); + } + var rect = bgEl.getBoundingClientRect(); + console.log('[captcha-direct-page] click result:', JSON.stringify(result).substring(0, 260)); + for (var i = 0; i < result.click_coords.length; i++) { + var c = result.click_coords[i]; + var nx = Number(c.nx); + var ny = Number(c.ny); + if (!Number.isFinite(nx) && Number.isFinite(Number(c.rel_x))) nx = Number(c.rel_x) / rect.width; + if (!Number.isFinite(ny) && Number.isFinite(Number(c.rel_y))) ny = Number(c.rel_y) / rect.height; + if (!Number.isFinite(nx) || !Number.isFinite(ny)) continue; + dispatchClickAt(bgEl, nx * rect.width, ny * rect.height, c.char || String(i + 1)); + await new Promise(function(r) { setTimeout(r, 220); }); + } + await new Promise(function(r) { setTimeout(r, 350); }); + if (CAPTCHA_CFG.AUTO_CAPTCHA_CONFIRM) { + findAndClickConfirm(); + } else { + console.log('[captcha-direct-page] captcha confirm is disabled; waiting for manual confirm'); + } + } catch (e) { + console.error('[captcha-direct-page] error:', e); + captchaSent = false; + lastCaptchaText = ''; + } finally { + rushState = 'idle'; + } + } + + async function checkCaptchaPrompt() { + if (rushState === 'solving') return; var found = findCaptchaPromptElement(); if (!found) { captchaSent = false; return; } @@ -1839,14 +1873,14 @@ console.log('[captcha] prompt:', payloadText); } - if (!captchaSent) { - captchaSent = true; - console.log('[captcha] page direct solver:', payloadText); - handleCaptchaDirectInPage(found.chars).catch(function(e) { console.error('[captcha-direct-page] unhandled:', e); }); - return; - if (RUSH_CONFIG.enabled) { - handleCaptchaRush(found.chars).catch(function(e) { console.error('[captcha-rush] unhandled:', e); }); - } else { + if (!captchaSent) { + captchaSent = true; + console.log('[captcha] page direct solver:', payloadText); + handleCaptchaDirectInPage(found.chars).catch(function(e) { console.error('[captcha-direct-page] unhandled:', e); }); + return; + if (RUSH_CONFIG.enabled) { + handleCaptchaRush(found.chars).catch(function(e) { console.error('[captcha-rush] unhandled:', e); }); + } else { serverRequest('POST', '/captcha', { text: payloadText, ts: Date.now() }) .then(function() { console.log('[captcha] legacy ok'); }) .catch(function(e) { console.log('[captcha] legacy err:', e); });