Greasy Fork

Greasy Fork is available in English.

GLM Coding Plan抢购助手

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         GLM Coding Plan抢购助手
// @namespace    http://tampermonkey.net/
// @version      8.1
// @description  智能监控,支持配置多级备选抢购;自动穿透限流弹窗;默认使用拼团折扣码更优惠,介意误用
// @author       mumumi
// @include      https://*bigmodel.cn/glm-coding*
// @include      https://*bigmodel.cn/html/rate-limit.html*
// @include      https://platform.minimaxi.com/subscribe/token-plan*
// @include      https://*volcengine.com*
// @include      https://maas.xfyun.cn/packageSubscription*
// @include      https://cloud.infini-ai.com*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bigmodel.cn
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_openInTab
// @run-at       document-start
// @license      GNU GPLv3
// ==/UserScript==

(function () {
    'use strict';

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

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

    // ── 其他平台优惠检测 ──────────────────────────────────────────────────────
    const PLATFORM_CHECKS = [
        { host: 'platform.minimaxi.com', params: ['code'], name: 'MiniMax', code: 'UMYTQHW8I2', url: 'https://platform.minimaxi.com/subscribe/token-plan?code=UMYTQHW8I2&source=link' },
        { host: 'volcengine.com', params: ['invite', 'inviteCode'], name: '字节方舟', code: 'UMYTQHW8I2', url: 'https://volcengine.com/?invite=UMYTQHW8I2' },
        { host: 'maas.xfyun.cn', params: ['inviteCode'], name: '讯飞星辰', code: 'UMYTQHW8I2', url: 'https://maas.xfyun.cn/packageSubscription?inviteCode=UMYTQHW8I2' },
        { host: 'cloud.infini-ai.com', params: ['invite_code'], name: '无问芯穹', code: 'UMYTQHW8I2', url: 'https://cloud.infini-ai.com/login?redirect=/genstudio/invitation&invite_code=UMYTQHW8I2' },
    ];

    const currentPlatform = PLATFORM_CHECKS.find(p => location.hostname.includes(p.host));
    if (currentPlatform) {
        const urlParams = new URLSearchParams(location.search);
        const hasCode = currentPlatform.params.some(param => urlParams.has(param));

        if (!hasCode && !sessionStorage.getItem('promo_dismissed_' + currentPlatform.name)) {
            setTimeout(() => {
                setBar(`⚠️ 未使用 ${currentPlatform.name} 优惠链接`, '#ff9800');
                if (confirm(`智能检测到折扣渠道,是否使用优惠推广码 ${currentPlatform.code}?`)) {
                    location.href = currentPlatform.url;
                } else {
                    sessionStorage.setItem('promo_dismissed_' + currentPlatform.name, '1');
                }
            }, 1500);
        }
        return;
    }

    // ── v8.0: 无条件激活售罄按钮 - JSON.parse 劫持 ──────────────────────────
    const _oP = JSON.parse;
    JSON.parse = function (t, r) {
        const o = _oP(t, r);
        try { (function f(x) {
            if (!x || typeof x !== 'object') return;
            if ('isSoldOut' in x && x.isSoldOut === true) x.isSoldOut = false;
            if ('soldOut'   in x && x.soldOut   === true) x.soldOut   = false;
            if ('disabled'  in x && x.disabled  === true && (x.price !== undefined || x.productId || x.title)) x.disabled = false;
            if ('stock'     in x && x.stock     === 0) x.stock = 999;
            for (const k in x) f(x[k]);
        })(o); } catch {}
        return o;
    };

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

    // ── fetch 拦截(/api/biz/pay/preview 和 check)──────────────────────────
    const _oF = window.fetch;

    // v8.0: 从 Cookie 提取 token 和从页面提取组织/项目信息
    function getAuthHeaders() {
        const token = document.cookie.match(/bigmodel_token_production=([^;]+)/)?.[1] || '';
        const headers = {
            'authorization': token,
            'content-type': 'application/json;charset=UTF-8',
            'accept': 'application/json, text/plain, */*',
            'accept-language': 'zh',
            'set-language': 'zh'
        };

        // 尝试从 localStorage 获取组织和项目 ID
        try {
            const orgId = localStorage.getItem('bigmodel_organization') || 'org-0E486bA654cF4ceBbA31873c4432D407';
            const projId = localStorage.getItem('bigmodel_project') || 'proj_D9637f2f1DE74e57980C70E47d1ea61d';
            headers['bigmodel-organization'] = orgId;
            headers['bigmodel-project'] = projId;
        } catch {}

        return headers;
    }

    window.fetch = async function (...a) {
        const url = (typeof a[0] === 'string' ? a[0] : a[0]?.url) || '';

        // 拦截 preview:只发一次,不重试(验证码只能用一次)
        if (url.includes('/api/biz/pay/preview')) {
            PS.inProgress = true;
            PS.result     = null;
            PS.bizId      = null;
            PS.payAmount  = null;

            // 提取原始 body
            const [urlOrReq, init = {}] = a;
            let body = init.body;
            if (!body && urlOrReq instanceof Request) {
                body = await urlOrReq.clone().text();
            }

            const authHeaders = getAuthHeaders();

            try {
                const r = await _oF(url, {
                    method: 'POST',
                    headers: authHeaders,
                    body: body,
                    credentials: 'include'
                });
                const txt = await r.text();
                let d;
                try { d = _oP(txt); } catch { d = {}; }

                console.log('[GLM v8.0 DEBUG] preview响应:', d);
                console.log('[GLM v8.0 DEBUG] soldOut值:', d?.data?.soldOut, '类型:', typeof d?.data?.soldOut);

                if (d?.code === 200 && d?.data?.bizId) {
                    // 成功获取 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' } });
                } else if (d?.code === 555) {
                    // 系统繁忙 → 标记状态,返回错误响应(阻止前端打开系统繁忙弹窗)
                    console.log('[GLM v8.0] preview 系统繁忙,脚本将自动重试');
                    PS.result = 'busy';
                    PS.inProgress = false;
                    return new Response(
                        JSON.stringify({ code: 500, msg: '系统繁忙,脚本自动重试中', data: null, success: false }),
                        { status: 200, headers: { 'Content-Type': 'application/json' } }
                    );
                } else if (d?.data?.soldOut === true) {
                    // 售罄 → 标记状态,返回错误响应(阻止前端打开支付弹窗)
                    PS.result = 'sold_out';
                    PS.inProgress = false;
                    return new Response(
                        JSON.stringify({ code: 500, msg: '商品已售罄', data: null, success: false }),
                        { status: 200, headers: { 'Content-Type': 'application/json' } }
                    );
                } else {
                    // 其他错误(如验证码失败)
                    PS.result = 'error';
                    PS.inProgress = false;
                    return new Response(txt, { status: r.status, headers: { 'Content-Type': 'application/json' } });
                }
            } catch (e) {
                PS.result = 'error';
                PS.inProgress = false;
                throw e;
            }
        }

        // 拦截 check:如果 bizId 为 null,直接返回失败
        if (url.includes('/api/biz/pay/check')) {
            if (url.includes('bizId=null') || !PS.bizId) {
                return new Response(
                    '{"code":500,"msg":"无效的 bizId","data":null,"success":false}',
                    { status: 200, headers: { 'Content-Type': 'application/json' } }
                );
            }
        }

        return _oF.apply(this, a);
    };

    // XHR 兜底(重定向到上方 fetch 拦截器)
    const _xO = XMLHttpRequest.prototype.open;
    const _xS = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.open = function (m, u, ...r) { this._u = u; this._m = m; return _xO.call(this, m, u, ...r); };
    XMLHttpRequest.prototype.send = function (...a) {
        if ((this._u || '').includes('/api/biz/pay/preview')) {
            const self = this;
            window.fetch(this._u, { method: this._m || 'POST', body: a[0], credentials: 'include' }).then(async r => {
                const txt = await r.text();
                const dp = (k, v) => Object.defineProperty(self, k, { value: v, configurable: true });
                dp('readyState', 4); dp('status', 200); dp('statusText', 'OK');
                dp('responseText', txt); dp('response', txt);
                const ev = new Event('readystatechange');
                if (typeof self.onreadystatechange === 'function') self.onreadystatechange(ev);
                self.dispatchEvent(ev);
                ['load', 'loadend'].forEach(t => {
                    const e = new ProgressEvent(t);
                    if (typeof self[`on${t}`] === 'function') self[`on${t}`](e);
                    self.dispatchEvent(e);
                });
            });
            return;
        }
        return _xS.apply(this, a);
    };

    // ── 每日套餐状态(localStorage,按日隔离)────────────────────────────────
    // -1未知  0进行中(重启复位)  1今日售罄  2今日已购
    const _today = new Date().toISOString().slice(0, 10);
    const _dsKey = `glm_ds_${_today}`;
    let _ds = (() => { try { return JSON.parse(localStorage.getItem(_dsKey) || '{}'); } catch { return {}; } })();
    Object.keys(_ds).forEach(k => { if (_ds[k] === 0) _ds[k] = -1; });
    _flush();
    function _flush()       { localStorage.setItem(_dsKey, JSON.stringify(_ds)); }
    function getS(t, p)     { return _ds[`${t}-${p}`] ?? -1; }
    function setS(t, p, v)  { _ds[`${t}-${p}`] = v; _flush(); }

    if (Object.values(_ds).includes(2)) {
        setTimeout(() => setBar('🎉 今日已订阅成功,脚本停止。', '#237804'), 800);
        return;
    }

    // ── 配置 ──────────────────────────────────────────────────────────────────
    const STORAGE_KEY = 'glm_coding_config_v5';
    const TABS_MAP    = { 1: '连续包月', 2: '连续包季', 3: '连续包年' };
    const PKGS_MAP    = { 1: 'Lite',    2: 'Pro',      3: 'Max'      };
    const DEF = {
        TABS_PRIORITY     : '1',
        PACKAGES_PRIORITY : '2,3,1',
        CHECK_INTERVAL    : 80,
        SMART_REFRESH     : true,
    };

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

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

    // ── v8.0: ESC 键快速关闭弹窗 ──────────────────────────────────────────────
    document.addEventListener('keydown', (e) => {
        if (e.key === 'Escape' || e.keyCode === 27) {
            // 优先关闭系统繁忙弹窗
            const busyDlg = document.querySelector('.el-dialog__wrapper .empty-data-wrap');
            if (busyDlg) {
                const wrapper = busyDlg.closest('.el-dialog__wrapper');
                if (wrapper && getComputedStyle(wrapper).display !== 'none') {
                    closeModal(wrapper);
                    console.log('[GLM v8.0] ESC 键关闭系统繁忙弹窗');
                    return;
                }
            }
            // 其次关闭支付弹窗
            const payDlg = getPayDialog();
            if (payDlg) {
                closePayDialog();
                console.log('[GLM v8.0] ESC 键关闭支付弹窗');
            }
        }
    });

    // ── v8.0: 一键多开窗口函数 ────────────────────────────────────────────────
    function openMultipleWindows() {
        const count = prompt('请输入要打开的窗口数量(建议 2-5 个):', '3');
        if (!count) return;
        const n = parseInt(count);
        if (isNaN(n) || n < 1 || n > 10) { alert('请输入 1-10 之间的数字'); return; }
        const baseUrl = 'https://www.bigmodel.cn/glm-coding?ic=UMYTQHW8I2&closedialog=true';
        for (let i = 0; i < n; i++) {
            setTimeout(() => {
                // 使用 GM_openInTab 绕过浏览器弹窗拦截
                GM_openInTab(baseUrl, { active: false, insert: true, setParent: true });
            }, i * 300);
        }
        alert(`✅ 已打开 ${n} 个标签页!\n\n💡 建议:\n1. 提前完成验证码(选3个字)\n2. 9:58 准备就绪\n3. 10:00 一到立即点确认`);
    }

    // ── 扫描队列(过滤今日已确认售罄)────────────────────────────────────────
    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

    // SCANNING / TASK_UNIT
    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 isRealBizId(id) { return id && !id.startsWith('debug-'); }

    // ── v8.0: 黄金时间判断(9:50-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 end = 10 * 60 + 10;   // 10:10
        return time >= start && time <= end;
    }

    // ── 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 getPayDialog() {
        const d = document.querySelector('.pay-dialog');
        if (!d) return null;
        const w = d.closest('.el-dialog__wrapper');
        if (!w || getComputedStyle(w).display === 'none') return null;
        if ((w.innerText || '').includes('当前购买人数较多')) return null;
        return d;
    }
    function isPayDialog()     { return !!getPayDialog(); }
    function isSuccessDialog() {
        const w = document.querySelector('.pay-success-dialog-box')?.closest('.el-dialog__wrapper');
        return w ? getComputedStyle(w).display !== 'none' : false;
    }
    function closeModal(w)   { w?.querySelector('.el-dialog__close')?.click(); }
    function closePayDialog() {
        const d = getPayDialog();
        if (d) closeModal(d.closest('.el-dialog__wrapper'));
    }

    // ── 对话框实付金额读取(双通道)──────────────────────────────────────────
    //   通道A:扫码区 .info-price 最后一个 <span>(纯数字,如"149")
    //   通道B:计算明细区"实付金额"对应的 .price-item(含¥,如"¥149")
    //
    //   两通道任一 > 0 即视为有效。
    function readDialogPrices() {
        const dlg = getPayDialog();
        if (!dlg) return null;

        // 通道A
        let scanPrice = 0;
        const infoPrice = dlg.querySelector('.info-price');
        if (infoPrice) {
            const spans = infoPrice.querySelectorAll('span');
            // price-icon 是 ¥,后面的 span 是数值
            for (let i = spans.length - 1; i >= 0; i--) {
                const v = parseFloat(spans[i].textContent.trim());
                if (!isNaN(v) && v > 0) { scanPrice = v; break; }
            }
        }

        // 通道B
        let actualPrice = 0;
        dlg.querySelectorAll('.calculate-content-item').forEach(li => {
            if ([...li.querySelectorAll('div')].some(d => d.textContent.includes('实付金额'))) {
                const v = parseFloat((li.querySelector('.price-item')?.textContent || '').replace(/[¥,]/g, '').trim());
                if (!isNaN(v) && v > 0) actualPrice = v;
            }
        });

        return { scanPrice, actualPrice, any: scanPrice > 0 || actualPrice > 0 };
    }

    // ── 底部状态栏 ────────────────────────────────────────────────────────────
    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}`;
    }

    // ── 支付报警:视口边框红色闪烁 ────────────────────────────────────────────
    let _alarm = null;
    function showPayAlarm() {
        if (_alarm) return;
        if (!document.getElementById('glm-alarm-s')) {
            const s = document.createElement('style'); s.id = 'glm-alarm-s';
            s.textContent = '@keyframes glm-al{0%,100%{box-shadow:inset 0 0 0 12px rgba(220,38,38,.92)}50%{box-shadow:inset 0 0 0 12px rgba(220,38,38,.08)}}';
            document.head.appendChild(s);
        }
        _alarm = document.createElement('div');
        _alarm.style.cssText = 'position:fixed;inset:0;pointer-events:none;z-index:2147483646;animation:glm-al .5s steps(1) infinite';
        const lbl = document.createElement('div');
        lbl.style.cssText = 'position:absolute;top:12px;left:50%;transform:translateX(-50%);background:rgba(220,38,38,.95);color:#fff;padding:5px 22px;border-radius:20px;font:700 15px system-ui,sans-serif;white-space:nowrap;letter-spacing:.5px';
        lbl.textContent = '⚠️  请立即扫码支付!';
        _alarm.appendChild(lbl);
        document.body.appendChild(_alarm);
    }

    // ── 推广弹窗 ──────────────────────────────────────────────────────────────
    function triggerPromo() {
        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:560px;padding:24px;border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,.3);max-height:90vh;overflow-y:auto';
        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:12px">
                <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>
            </div>
            <div style="display:flex;justify-content:space-between;gap:10px">
                <button id="glm-multi" style="padding:8px 16px;border:1px solid #52c41a;background:#f6ffed;color:#52c41a;border-radius:6px;cursor:pointer;font-weight:600">🚀 一键多开</button>
                <div style="display:flex;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>
            </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-multi').onclick = () => { openMultipleWindows(); };
        panel.querySelector('#glm-cs').onclick = () => {
            const p = getPkgs(), t = getTabs();
            if (!p || !t) { alert('请至少各选一个!'); return; }
            saveCfg({
                TABS_PRIORITY     : t,
                PACKAGES_PRIORITY : p,
                SMART_REFRESH     : panel.querySelector('#glm-sm').checked,
                CHECK_INTERVAL    : CFG.CHECK_INTERVAL,
            });
            ov.remove(); alert('已保存,即将刷新。'); location.reload();
        };
        ov.onclick = e => { if (e.target === ov) ov.remove(); };
    }

    // ═══════════════════════════════════════════════════════════════════════════
    //  主循环
    // ═══════════════════════════════════════════════════════════════════════════
    function tick() {
        if (state === 'DONE') return;

        if (state === 'SLEEPING') {
            const rem = sleepUntil - Date.now();
            if (rem <= 0) {
                // v8.0: 黄金时间禁止刷新
                if (isGoldenTime()) {
                    setBar('🔥 黄金时间!取消刷新,继续高频监控!', '#ff4d4f');
                    state = 'SCANNING'; qIdx = 0; sweepRestocks = [];
                } else {
                    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();
    }

    // ═══════════════════════════════════════════════════════════════════════════
    //  SCANNING / TASK_UNIT 逻辑
    // ═══════════════════════════════════════════════════════════════════════════
    function doScan() {
        if (qIdx >= scanQueue.length) { onSweepDone(); return; }
        const { tab, pkg } = scanQueue[qIdx];
        const te = tabEl(tab);
        if (!te) return;
        if (!te.classList.contains('active')) {
            te.click(); te.scrollIntoView({ behavior: 'auto', block: 'center' });
            lastTabSwitch = Date.now(); setBar(`🔄 切换到 ${TABS_MAP[tab]}...`); return;
        }
        if (Date.now() - lastTabSwitch < 400) return;

        const b = btnEl(pkg);
        if (canBuy(b)) {
            taskTarget = { tab, pkg }; taskPhase = 'IDLE'; taskRLCount = 0;
            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);

        // ── v8.0: 黄金时间(9:50-10:10)禁止刷新页面 ──────────────────────────
        if (isGoldenTime()) {
            setBar(`🔥 黄金时间!补货倒计时 <b>${fmt(nearest.msUntil)}</b>,禁止刷新,高频监控!`, '#ff4d4f');
            qIdx = 0; sweepRestocks = []; return;
        }

        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;
            }
            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') {
            const rlw = findRLModal();
            if (rlw) {
                closeModal(rlw); taskRLCount++;
                if (taskRLCount >= MAX_RL) {
                    // v8.0: 黄金时间禁止刷新
                    if (isGoldenTime()) {
                        setBar('🔥 黄金时间!连续限流但禁止刷新,继续重试!', '#ff4d4f');
                        taskRLCount = 0; taskPhase = 'IDLE';
                    } else {
                        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;
            }

            if (isPayDialog()) {
                // v8.0: 检测异常支付弹窗(soldOut 但弹出了支付界面,bizId 为 null)
                if (!PS.inProgress && PS.result === 'sold_out' && !isRealBizId(PS.bizId)) {
                    console.log('[GLM v8.0] 检测到异常支付弹窗(soldOut + bizId=null),自动关闭并重试');
                    closePayDialog();
                    taskPhase = 'IDLE';
                    return;
                }

                const prices = readDialogPrices();
                const hasApiAmount = PS.payAmount && PS.payAmount > 0 && isRealBizId(PS.bizId);
                const waited3s = Date.now() - taskClickTime > 3000;

                if (prices?.any) {
                    // DOM 有实付金额 → 成功
                    state = 'DONE'; showPayAlarm();
                    setBar(`💳 <b>抢购成功!实付 ¥${prices.actualPrice || prices.scanPrice},请立即扫码支付!</b>`, '#16a34a');
                    return;
                }
                if (hasApiAmount || waited3s) {
                    // 接口金额有效,等 DOM 渲染
                    showPayAlarm();
                    setBar('💳 <b>抢购成功!请立即扫码支付!</b> 脚本已停止。', '#16a34a');
                    state = 'DONE';
                    return;
                }
                // 弹窗刚出现,等价格
                return;
            }

            if (isSuccessDialog()) {
                setS(tab, pkg, 2); state = 'DONE';
                setBar('🎉 订阅成功!恭喜!', '#237804'); return;
            }

            if (!PS.inProgress && PS.result === 'sold_out' && Date.now() - taskClickTime > 2000) {
                exitTask(); return;
            }
            if (Date.now() - taskClickTime > MODAL_WAIT) {
                if (isSoldOut(b)) exitTask(); else taskPhase = 'IDLE';
            }
        }
    }

    function exitTask() {
        // v8.0: 黄金时间内不标记售罄,持续重试
        if (!isGoldenTime()) {
            setS(taskTarget.tab, taskTarget.pkg, 1);
        }
        setBar(`📦 ${TABS_MAP[taskTarget.tab]} · ${PKGS_MAP[taskTarget.pkg]} 售罄,继续...`);
        qIdx++; taskTarget = null; taskPhase = 'IDLE'; taskRLCount = 0;
        state = 'SCANNING';
    }

    // ── 启动 ──────────────────────────────────────────────────────────────────
    // v8.0: 未登录检测
    function checkLogin() {
        const token = document.cookie.match(/bigmodel_token_production=([^;]+)/)?.[1];
        if (!token) {
            setBar('⚠️ 未登录,请先注册/登录', '#ff4d4f');
            setTimeout(() => {
                if (confirm('检测到未登录,是否前往注册页面?\n\n使用邀请码注册可获得额外优惠!')) {
                    window.location.href = 'https://www.bigmodel.cn/invite?icode=myJkHzzJR1ezhm6rWJqv2H3uFJ1nZ0jLLgipQkYjpcA%3D';
                }
            }, 1000);
            return false;
        }
        return true;
    }

    if (checkLogin()) {
        setInterval(tick, CFG.CHECK_INTERVAL);
    }

})();