Greasy Fork

Greasy Fork is available in English.

GLM Coding Plan抢购助手

智能监控,支持配置多级备选抢购;自动穿透限流弹窗;默认使用拼团折扣码更优惠,介意误用

当前为 2026-04-08 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         GLM Coding Plan抢购助手
// @namespace    http://tampermonkey.net/
// @version      6.6
// @description  智能监控,支持配置多级备选抢购;自动穿透限流弹窗;默认使用拼团折扣码更优惠,介意误用
// @author       mumumi
// @include      https://*bigmodel.cn/glm-coding*
// @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
// @run-at       document-start
// @license      GNU GPLv3
// ==/UserScript==

(function () {
    'use strict';

    // ── 最早读配置(document-start 时还没有主流程,只读 INTERCEPT 标志)──────
    const _ec = (() => { try { return JSON.parse(GM_getValue('glm_coding_config_v5', '{}')); } catch { return {}; } })();

    // ── 调试模式:JSON.parse 钩子让页面按钮显示可购(重启后生效)────────────
    // 仅影响前端渲染,不改变购买逻辑;真正有货仍需接口返回有效 bizId
    if (_ec.INTERCEPT) {
        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;
                for (const k in x) f(x[k]);
            })(o); } catch {}
            return o;
        };
    }

    // ── 购买状态(供 fetch 拦截器与 UI 主循环共享)────────────────────────────
    const PS = {
        inProgress: false,
        result: null,       // null | 'success' | 'sold_out'
        bizId: null,
        payAmount: null,
    };

    // ── preview 接口拦截(始终开启)──────────────────────────────────────────
    // 真实模式:连续重试直到拿到有效 bizId(最多12秒),超时后返回售罄响应
    // 调试模式:立刻返回伪造的成功响应,触发支付弹窗验证完整流程
    const _oF = window.fetch;
    const _sl = ms => new Promise(r => setTimeout(r, ms));

    window.fetch = async function (...a) {
        const url = (typeof a[0] === 'string' ? a[0] : a[0]?.url) || '';
        if (!url.includes('/api/biz/pay/preview')) return _oF.apply(this, a);

        PS.inProgress = true; PS.result = null; PS.bizId = null; PS.payAmount = null;

        if (_ec.INTERCEPT) {
            // 调试模式:提取 productId 后伪造成功响应
            let pid = 'debug';
            try { pid = JSON.parse(a[1]?.body || '{}').productId || pid; } catch {}
            PS.result = 'success'; PS.payAmount = 149; PS.bizId = 'debug-' + pid; PS.inProgress = false;
            return new Response(
                JSON.stringify({ code: 200, success: true, data: { productId: pid, bizId: 'debug-' + pid, soldOut: false, payAmount: 149, originalAmount: 149, renewAmount: 149 } }),
                { status: 200, headers: { 'Content-Type': 'application/json' } }
            );
        }

        // 真实模式:剥离 AbortSignal,防止前端超时中断重试
        const [, ini = {}] = a;
        const { signal, ...ci } = ini;

        for (let i = 0; i < 120; i++) {
            try {
                const r = await _oF(url, { ...ci, credentials: 'include' });
                const txt = await r.text();
                let d; try { d = JSON.parse(txt); } catch { await _sl(100); continue; }

                if (d?.code === 200 && d?.data?.bizId) {
                    PS.result = 'success'; PS.bizId = d.data.bizId; PS.payAmount = d.data.payAmount; PS.inProgress = false;
                    return new Response(txt, { status: 200, headers: { 'Content-Type': 'application/json' } });
                }
                // soldOut:true 或其他错误码 → 继续重试,可能是高峰期瞬间售罄
            } catch {}
            await _sl(100);
        }

        // 120次(12秒)均未获得有效 bizId → 确认售罄
        PS.result = 'sold_out'; PS.inProgress = false;
        return new Response(
            '{"code":200,"data":{"soldOut":true,"bizId":null},"success":true,"msg":""}',
            { status: 200, headers: { 'Content-Type': 'application/json' } }
        );
    };

    // XHR 兜底(重定向到上方的 fetch 拦截器)
    const _xO = XMLHttpRequest.prototype.open, _xS = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.open = function (m, u, ...r) { this._u = u; 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);
    };

    // ── 限流页立即跳回 ────────────────────────────────────────────────────────
    if (location.href.includes('rate-limit.html')) {
        location.replace('https://www.bigmodel.cn/glm-coding?ic=UMYTQHW8I2&closedialog=true');
        return;
    }

    // ── 配置 ──────────────────────────────────────────────────────────────────
    const STORAGE_KEY = 'glm_coding_config_v5';
    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: 100, SMART_REFRESH: true, INTERCEPT: false, SCHEDULE_TIME: '' };

    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();

    // ── 每日套餐状态(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 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);

    if (!scanQueue.length) {
        setTimeout(() => { setBar('📭 今日所有配置套餐均已售罄,脚本停止。', '#434343'); triggerPromo(); }, 800);
        return;
    }

    // ── 状态机变量 ────────────────────────────────────────────────────────────
    let state = 'SCANNING';   // SCANNING | TASK_UNIT | SLEEPING | DONE
    let qIdx = 0, sweepRestocks = [], lastTabSwitch = 0;
    let taskTarget = null, taskPhase = 'IDLE', taskClickTime = 0, taskRLCount = 0;
    let sleepUntil = 0;
    const MAX_RL = 3, MODAL_WAIT = 15000;

    // ── 工具函数 ──────────────────────────────────────────────────────────────
    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 parseScheduleTime(str) {
        if (!str) return null;
        const p = str.split(':').map(Number);
        const d = new Date();
        const t = new Date(d.getFullYear(), d.getMonth(), d.getDate(), p[0], p[1], p[2] || 0);
        return t.getTime();
    }

    // ── DOM 访问 ──────────────────────────────────────────────────────────────
    const tabEl     = n => document.querySelector(`#switchTabBox > div > .switch-tab-item:nth-child(${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.innerText || '').includes('特惠订阅');
    const isSoldOut = 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 isPayDialog() {
        // .pay-dialog 出现且不含限流提示 = 支付二维码弹窗
        const d = document.querySelector('.pay-dialog');
        if (!d) return false;
        const w = d.closest('.el-dialog__wrapper');
        if (!w || getComputedStyle(w).display === 'none') return false;
        return !(w.innerText || '').includes('当前购买人数较多');
    }
    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(); }

    // ── 底部状态栏 ────────────────────────────────────────────────────────────
    let _bar = null;
    function setBar(html, bg = '#1677ff') {
        if (!_bar) {
            _bar = document.createElement('div');
            _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 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 = null; };
            _bar.append(document.createElement('span'), x);
            document.body.appendChild(_bar);
        }
        _bar.style.background = bg;
        _bar.firstElementChild.innerHTML = `🤖 <b>抢购助手</b> &nbsp;|&nbsp; ${html}`;
    }

    // ── 支付报警:视口边框红色闪烁,pointer-events:none 不遮挡弹窗 ──────────
    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');
        // 整个视口但 pointer-events:none,只有内嵌阴影 → 仅边缘可见,不挡中间弹窗
        _alarm.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:2147483646;animation:glm-al .5s steps(1) infinite';
        // 顶部横幅(弹窗通常在 15vh 以下,顶部有空间)
        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() {
        if (Date.now() > new Date('2026-04-30T23:59:59').getTime()) { setBar('所有套餐今日售罄,脚本停止。', '#434343'); return; }
        const PP = [
            { name: 'MiniMax',   desc: '¥29起,赠视频/语音/音乐额度', color: '#4CAF50', url: 'https://platform.minimaxi.com/subscribe/token-plan?code=FoGlERWIF3&source=link' },
            { name: '字节·方舟', desc: '首月低至¥8.9,多模型切换',    color: '#FF6B35', url: 'https://volcengine.com/L/YIeVPueJ2O4/' },
            { name: '讯飞星辰',  desc: '¥19起,首月¥3.9最低门槛',    color: '#00BFFF', url: 'https://maas.xfyun.cn/packageSubscription?inviteCode=MAAS-C6BE3A3B' },
            { name: '无问芯穹',  desc: '多模型聚合,首月¥19.9',       color: '#7B68EE', url: 'https://cloud.infini-ai.com/login?redirect=/genstudio/invitation&invite_code=IyveoKRS' },
        ];
        const rows = PP.map(p => `
            <a href="${p.url}" target="_blank" style="display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-radius:8px;text-decoration:none;border:1px solid #eee;background:#fafafa" onmouseover="this.style.background='#f0f7ff'" onmouseout="this.style.background='#fafafa'">
                <div style="display:flex;align-items:center;gap:10px">
                    <span style="width:8px;height:8px;border-radius:50%;background:${p.color};display:inline-block"></span>
                    <span style="font-weight:600;font-size:14px;color:#111">${p.name}</span>
                    <span style="font-size:12px;color:#888">${p.desc}</span>
                </div>
                <span style="font-size:12px;color:${p.color};font-weight:500">立即开通 →</span>
            </a>`).join('');
        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 = `
            <div style="background:#fff;width:520px;border-radius:16px;overflow:hidden;box-shadow:0 25px 50px -12px rgba(0,0,0,.5);max-height:90vh;display:flex;flex-direction:column">
                <div style="background:linear-gradient(135deg,#1e3c72,#2a5298);padding:24px 24px 20px;color:#fff">
                    <h2 style="margin:0 0 6px;font-size:20px">GLM Coding Plan 全部售罄 🫠</h2>
                    <p style="margin:0;opacity:.85;font-size:14px">配置的所有套餐今日已售罄,补货后脚本将继续监控</p>
                </div>
                <div style="padding:16px 20px;overflow-y:auto;flex:1">
                    <div style="font-size:13px;color:#888;margin-bottom:12px">👇 以下平台也有编程套餐和折扣链接</div>
                    <div style="display:flex;flex-direction:column;gap:8px">${rows}</div>
                </div>
                <div style="padding:14px 20px;border-top:1px solid #f0f0f0;text-align:right">
                    <button id="promo-x" style="background:none;border:1px solid #ddd;color:#888;padding:7px 18px;border-radius:6px;cursor:pointer;font-size:13px">关闭并停止脚本</button>
                </div>
            </div>`;
        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 = `
            <div style="font-size:13px;font-weight:bold;margin-bottom:8px;color:#444">${title}</div>
            <div style="display:flex;align-items:stretch;gap:10px;margin-bottom:20px;height:140px">
                <div style="flex:1;border:1px solid #ddd;border-radius:6px;display:flex;flex-direction:column;background:#fafafa">
                    <div style="padding:6px 10px;border-bottom:1px solid #ddd;font-size:12px;color:#666;background:#f0f0f0;border-radius:6px 6px 0 0">备选池</div>
                    <ul class="tf-left" style="list-style:none;padding:5px;margin:0;flex:1;overflow-y:auto">
                        ${avail.map(k => `<li data-val="${k}" class="tf-item">${dataMap[k]}</li>`).join('')}
                    </ul>
                </div>
                <div style="display:flex;flex-direction:column;justify-content:center;gap:8px">
                    <button type="button" class="tf-btn tf-r">▶</button>
                    <button type="button" class="tf-btn tf-l">◀</button>
                </div>
                <div style="flex:1;border:1px solid #ddd;border-radius:6px;display:flex;flex-direction:column;background:#fff">
                    <div style="padding:6px 10px;border-bottom:1px solid #ddd;font-size:12px;color:#666;background:#e6f7ff;border-radius:6px 6px 0 0">选中且排序(自上而下)</div>
                    <ul class="tf-right" style="list-style:none;padding:5px;margin:0;flex:1;overflow-y:auto">
                        ${sel.map(k => `<li data-val="${k}" class="tf-item">${dataMap[k]}</li>`).join('')}
                    </ul>
                </div>
                <div style="display:flex;flex-direction:column;justify-content:center;gap:8px">
                    <button type="button" class="tf-btn tf-up">▲</button>
                    <button type="button" class="tf-btn tf-dn">▼</button>
                </div>
            </div>`;
        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();
        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:540px;padding:24px;border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.3)';
        panel.innerHTML = `
            <h3 style="margin:0 0 20px;font-size:18px;color:#1a1a1a">⚙️ 抢购助手配置</h3>
            <div id="glm-wp"></div>
            <div id="glm-wt"></div>
            <div style="margin-bottom:20px;padding-top:10px;border-top:1px dashed #eee;display:flex;flex-direction:column;gap:10px">
                <label style="display:flex;align-items:center;cursor:pointer">
                    <input type="checkbox" id="glm-sm" ${CFG.SMART_REFRESH ? 'checked' : ''} style="margin-right:8px">
                    <span style="font-size:14px;color:#555">启用智能刷新(梯度嗅探补货时间)</span>
                </label>
                <label style="display:flex;align-items:center;cursor:pointer">
                    <input type="checkbox" id="glm-ic" ${CFG.INTERCEPT ? 'checked' : ''} style="margin-right:8px">
                    <span style="font-size:14px;color:#c41d7f">调试模式:强制显示可购按钮 + 伪造接口(重启生效)</span>
                </label>
                <div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
                    <span style="font-size:14px;color:#555">定时自动点击:</span>
                    <input type="time" id="glm-sched" step="1" value="${CFG.SCHEDULE_TIME || ''}" style="padding:4px 8px;border:1px solid #d9d9d9;border-radius:4px;font-size:13px;color:#333">
                    <span style="font-size:12px;color:#999">不填则由扫描自动触发</span>
                </div>
            </div>
            <div style="display:flex;justify-content:flex-end;gap:10px">
                <button id="glm-cc" style="padding:8px 16px;border:1px solid #ddd;background:#f5f5f5;border-radius:6px;cursor:pointer;color:#666">取消</button>
                <button id="glm-cs" style="padding:8px 20px;border:none;background:#1890ff;color:#fff;border-radius:6px;cursor:pointer;font-weight:700">保存并刷新</button>
            </div>`;
        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, '订阅周期优先级');
        panel.querySelector('#glm-cc').onclick = () => ov.remove();
        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, INTERCEPT: panel.querySelector('#glm-ic').checked, CHECK_INTERVAL: CFG.CHECK_INTERVAL, SCHEDULE_TIME: panel.querySelector('#glm-sched').value });
            ov.remove(); alert('已保存,即将刷新。'); location.reload();
        };
        ov.onclick = e => { if (e.target === ov) ov.remove(); };
    }

    GM_registerMenuCommand('⚙️ 打开配置面板', openConfigPanel);
    GM_registerMenuCommand('🗑️ 清除今日套餐状态缓存', () => { localStorage.removeItem(_dsKey); alert('今日状态已清除,即将刷新。'); location.reload(); });

    // ── 主循环 ────────────────────────────────────────────────────────────────
    function tick() {
        if (state === 'DONE') return;
        if (state === 'SLEEPING') {
            const rem = sleepUntil - Date.now();
            if (rem <= 0) location.replace('https://www.bigmodel.cn/glm-coding?ic=UMYTQHW8I2&closedialog=true');
            else setBar(`💤 休眠中,<b>${fmt(rem)}</b> 后刷新`, '#434343');
            return;
        }
        if (state === 'TASK_UNIT') { doTaskUnit(); return; }
        doScan();
    }

    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)) {
            // 若配置了定时且尚未到时间,停在此处等候(不前进队列)
            const schedTs = parseScheduleTime(CFG.SCHEDULE_TIME);
            if (schedTs && Date.now() < schedTs - 800) {
                setBar(`⏰ 预就位 ${TABS_MAP[tab]} · ${PKGS_MAP[pkg]},<b>${fmt(schedTs - Date.now())}</b> 后自动点击`, '#7c3aed');
                return;
            }
            taskTarget = { tab, pkg }; taskPhase = 'IDLE'; taskRLCount = 0;
            setS(tab, pkg, 0); state = 'TASK_UNIT';
            setBar(`🎯 发现可购!${TABS_MAP[tab]} · ${PKGS_MAP[pkg]},即将点击...`, '#389e0d');
            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 (!sweepRestocks.length) {
            state = 'DONE'; setBar('📭 今日全部售罄,脚本停止。', '#434343'); triggerPromo(); return;
        }
        sweepRestocks.sort((a, b) => a.msUntil - b.msUntil);
        const nearest = sweepRestocks[0];
        const sleep = calcSleepMs(nearest.msUntil);
        if (sleep === 0) {
            setBar(`⚡ 补货倒计时 <b>${fmt(nearest.msUntil)}</b>,高频监控!`, '#d4380d');
            qIdx = 0; sweepRestocks = []; return;
        }
        if (CFG.SMART_REFRESH) {
            state = 'SLEEPING'; sleepUntil = Date.now() + sleep;
            setBar(`💤 补货还需 <b>${fmt(nearest.msUntil)}</b>,<b>${fmt(sleep)}</b> 后刷新`, '#434343');
        } else { qIdx = 0; sweepRestocks = []; }
    }

    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;
            }
            // 重置购买状态,点击按钮,fetch 拦截器接管后续重试
            PS.result = null; PS.inProgress = true;
            b.click(); taskClickTime = Date.now(); taskPhase = 'WAITING';
            setBar(`🔄 已点击,接口重试中... ${TABS_MAP[tab]} · ${PKGS_MAP[pkg]}(限流 ${taskRLCount}/${MAX_RL})`, '#d46b08');
            return;
        }

        if (taskPhase === 'WAITING') {
            // 1. 限流弹窗(购买人数较多)→ 关闭后重试
            const rlw = findRLModal();
            if (rlw) {
                closeModal(rlw); taskRLCount++;
                if (taskRLCount >= MAX_RL) {
                    setBar(`🔁 连续 ${MAX_RL} 次限流,即将刷新...`, '#cf1322');
                    setTimeout(() => location.replace('https://www.bigmodel.cn/glm-coding?ic=UMYTQHW8I2&closedialog=true'), 50);
                    return;
                }
                setBar(`⚠️ 限流 ${taskRLCount}/${MAX_RL},重试中...`, '#d46b08');
                taskPhase = 'IDLE'; return;
            }

            // 2. 支付二维码弹窗 = 抢购成功,立即停止并报警提示扫码
            //    接口有金额(PS.payAmount>0)是最可靠判断;弹窗出现也作为兜底
            if (isPayDialog()) {
                const hasAmount = PS.payAmount && PS.payAmount > 0;
                const waited3s  = Date.now() - taskClickTime > 3000;
                if (hasAmount || waited3s) {
                    setS(tab, pkg, 2); state = 'DONE';
                    showPayAlarm();
                    setBar('💳 <b>抢购成功!请立即扫码支付!</b> 脚本已停止。', '#16a34a');
                }
                // 弹窗刚出现但金额未渲染,等一会儿
                return;
            }

            // 3. 付款完成弹窗(兜底)
            if (isSuccessDialog()) {
                setS(tab, pkg, 2); state = 'DONE';
                setBar('🎉 订阅成功!恭喜!', '#237804'); return;
            }

            // 4. fetch 已返回 sold_out,给 Vue 留 2s 渲染弹窗;若没弹窗则直接退出
            if (!PS.inProgress && PS.result === 'sold_out' && Date.now() - taskClickTime > 2000) {
                exitTask(); return;
            }

            // 5. 超时保底(15s)
            if (Date.now() - taskClickTime > MODAL_WAIT) {
                if (isSoldOut(b)) exitTask(); else taskPhase = 'IDLE';
            }
        }
    }

    function exitTask() {
        setS(taskTarget.tab, taskTarget.pkg, 1);
        setBar(`📦 ${TABS_MAP[taskTarget.tab]} · ${PKGS_MAP[taskTarget.pkg]} 售罄,继续...`);
        qIdx++; taskTarget = null; taskPhase = 'IDLE'; taskRLCount = 0;
        state = 'SCANNING';
    }

    // ── 启动 ──────────────────────────────────────────────────────────────────
    setInterval(tick, CFG.CHECK_INTERVAL);

})();