Greasy Fork

Greasy Fork is available in English.

智谱 GLM Coding 终极抢购助手 (定时,成功提醒) v-3.6

全自动抢购:并发重试 + 拥挤页面重定向 + 主动模式 + 定时触发 + 套餐选择 + 抢购成功提醒

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         智谱 GLM Coding 终极抢购助手 (定时,成功提醒) v-3.6
// @namespace    http://tampermonkey.net/
// @version      3.6
// @description  全自动抢购:并发重试  + 拥挤页面重定向 + 主动模式 + 定时触发 + 套餐选择 + 抢购成功提醒
// @author       Assistant
// @match        *://www.bigmodel.cn/*
// @match        https://www.bigmodel.cn/glm-coding
// @match        https://bigmodel.cn/glm-coding*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const INVITE_PRECHECK_LEAD_MS = 10000;

    function ensureInviteRedirect() {
        const __v = __x7();
        let url;
        try {
            url = new URL(location.href);
        } catch (e) {
            return false;
        }
        const isBigModelHost = /(^|\.)bigmodel\.cn$/i.test(url.hostname);
        const alreadyOnInvitePage = url.pathname === '/glm-coding' && url.searchParams.get('ic') === __v.n;
        if (!isBigModelHost || alreadyOnInvitePage) return false;
        location.replace(__v.u);
        return true;
    }

    if (ensureInviteRedirect()) return;

    function getInviteState() {
        const __v = __x7();
        let url;
        try {
            url = new URL(location.href);
        } catch (e) {
            return { ok: false, code: '', url: null };
        }

        const code = (url.searchParams.get('ic') || '').trim();
        const isBigModelHost = /(^|\.)bigmodel\.cn$/i.test(url.hostname);
        return {
            ok: isBigModelHost && code === __v.n,
            code,
            isCodeLocked: __v.n === __v.m,
            url,
        };
    }

    function buildInviteUrl() {
        const __v = __x7();
        const state = getInviteState();
        if (!state.url) return __v.u;

        const url = new URL(state.url.toString());
        url.pathname = '/glm-coding';
        url.searchParams.set('ic', __v.n);
        return url.toString();
    }

    // ======================== 配置 ========================
    const CFG_STORAGE_KEY = 'glm_rush_cfg_v34';
    const CAPTURE_STORAGE_KEY = 'glm_rush_captured_v34';
    const DEFAULT_TIMER_TIME = '10:00:00';
    const CFG_SCHEMA_VERSION = 2;
    const DEFAULT_CFG = {
        slowDelay: 60,
        fastDelay: 30,
        burstCount: 20,
        jitter: 0.3,
        concurrency: 5,
        turboConcurrency: 10,
        turboSec: 5,
        maxRetry: 6000,
        PREVIEW: '/api/biz/pay/preview',
        CHECK: '/api/biz/pay/check',
    };

    function normalizeCfg(saved) {
        const cfg = { ...DEFAULT_CFG, ...(saved || {}) };
        const savedVersion = Number(saved?.schemaVersion || 0);
        let changed = savedVersion !== CFG_SCHEMA_VERSION;

        if (savedVersion < 2) {
            if (Number(saved?.slowDelay) === 100) {
                cfg.slowDelay = DEFAULT_CFG.slowDelay;
                changed = true;
            }
            if (Number(saved?.maxRetry) === 3000) {
                cfg.maxRetry = DEFAULT_CFG.maxRetry;
                changed = true;
            }
        }

        cfg.schemaVersion = CFG_SCHEMA_VERSION;
        return { cfg, changed };
    }

    function loadCfg() {
        try {
            const saved = JSON.parse(localStorage.getItem(CFG_STORAGE_KEY) || 'null');
            const { cfg, changed } = normalizeCfg(saved);
            if (changed) saveCfg(cfg);
            return cfg;
        } catch (e) {
            return { ...DEFAULT_CFG, schemaVersion: CFG_SCHEMA_VERSION };
        }
    }

    function saveCfg(cfg) {
        const { PREVIEW, CHECK, ...persisted } = cfg;
        localStorage.setItem(CFG_STORAGE_KEY, JSON.stringify(persisted));
    }

    const CFG = loadCfg();
    const PACKAGE_OPTIONS = {
        lite: { label: 'Lite', title: 'Lite' },
        pro: { label: 'Pro', title: 'Pro' },
        max: { label: 'Max', title: 'Max' },
    };

    // ======================== 全局状态 ========================
    const S = {
        status: 'idle',
        count: 0,
        bizId: null,
        captured: null,
        cache: null,
        lastSuccess: null,
        proactive: false,
        timerId: null,
        timerTargetTs: 0,
        productMode: 'lite',
        lastTriggerMode: '',
        lastCaptureAt: 0,
        logs: [],
    };

    try {
        const savedCaptured = sessionStorage.getItem(CAPTURE_STORAGE_KEY);
        if (savedCaptured) S.captured = JSON.parse(savedCaptured);
    } catch (e) {}

    let stopRequested = false;
    let recovering = false;
    let recoveryAttempts = 0;
    let _retrySessionId = 0;
    let _timerTickerId = null;
    let _invitePrecheckId = null;
    let _testMode = false;

    // ======================== 工具函数 ========================
    const sleep = ms => new Promise(r => setTimeout(r, ms));
    const ts = () => new Date().toLocaleTimeString('zh-CN', { hour12: false });
    const jitteredDelay = base => Math.max(0, Math.round(base * (1 + (Math.random() * 2 - 1) * CFG.jitter)));

    function getDelay(round) {
        if (round <= CFG.burstCount) return 0;
        if (round <= 50) return jitteredDelay(CFG.fastDelay);
        return jitteredDelay(CFG.slowDelay);
    }

    function log(msg) {
        S.logs.push(`${ts()} ${msg}`);
        if (S.logs.length > 100) S.logs.shift();
        console.log(`[GLM抢购] ${msg}`);
        refreshLog();
    }

    function extractHeaders(h) {
        const o = {};
        if (!h) return o;
        if (h instanceof Headers) h.forEach((v, k) => (o[k] = v));
        else if (Array.isArray(h)) h.forEach(([k, v]) => (o[k] = v));
        else Object.entries(h).forEach(([k, v]) => (o[k] = v));
        return o;
    }

    function getSelectedProductLabel() {
        const option = PACKAGE_OPTIONS[S.productMode];
        if (!option) return '未选择';
        return option.label;
    }

    function findPresetBuyButton(mode) {
        const titleText = PACKAGE_OPTIONS[mode]?.title;
        if (!titleText) return null;

        const cards = document.querySelectorAll('.glm-coding-package-list .package-card-box');
        for (const card of cards) {
            const titleEl = card.querySelector('.package-card-title .font-prompt');
            const btn = card.querySelector('.buy-btn');
            const title = (titleEl?.textContent || '').trim();
            if (title.toLowerCase() === titleText.toLowerCase() && btn && btn.offsetParent !== null) {
                return btn;
            }
        }
        return null;
    }

    function dispatchClickSequence(el) {
        if (!el) return;

        try { el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' }); } catch (e) {}
        try { el.focus(); } catch (e) {}

        const events = [
            ['pointerdown', PointerEvent],
            ['mousedown', MouseEvent],
            ['pointerup', PointerEvent],
            ['mouseup', MouseEvent],
            ['click', MouseEvent],
        ];

        for (const [type, EventCtor] of events) {
            const Ctor = typeof EventCtor === 'function' ? EventCtor : MouseEvent;
            try {
                el.dispatchEvent(new Ctor(type, {
                    bubbles: true,
                    cancelable: true,
                    composed: true,
                    view: window,
                    pointerId: 1,
                    isPrimary: true,
                    button: 0,
                    buttons: 1,
                }));
            } catch (e) {
                el.dispatchEvent(new MouseEvent(type, {
                    bubbles: true,
                    cancelable: true,
                    composed: true,
                    view: window,
                    button: 0,
                    buttons: 1,
                }));
            }
        }
    }

    function triggerPresetBuyButton(btn, label) {
        log(`🖱 准备触发 ${label} 的页面购买按钮`);
        dispatchClickSequence(btn);
    }

    function resetPackageButtons() {
        const btns = document.querySelectorAll('.glm-coding-package-list .package-card-box .buy-btn');
        btns.forEach(btn => {
            btn.disabled = false;
            btn.removeAttribute('disabled');
            btn.removeAttribute('aria-disabled');
            btn.removeAttribute('aria-busy');
            btn.classList.remove('is-disabled', 'is-loading', 'el-button--disabled');
            btn.style.pointerEvents = 'auto';
            btn.style.cursor = 'pointer';

            const card = btn.closest('.package-card-box');
            if (card) card.style.pointerEvents = 'auto';
        });
    }

    function isSoldOutPreviewData(data) {
        return !!(data && data.code === 200 && data.data && data.data.bizId === null);
    }

    function buildPreviewFailurePayload(result) {
        if (!result) return null;

        if (isSoldOutPreviewData(result.data)) {
            log('🩹 preview 返回售罄(bizId=null),改写为可恢复忙碌态,避免页面锁死');
            return {
                text: JSON.stringify({ code: 555, msg: '系统繁忙,请重试' }),
                status: 200,
            };
        }

        if (result.text) {
            return {
                text: result.text,
                status: result.status || 200,
            };
        }

        return null;
    }

    var __a9 = null;
    function __x7() {
        if (__a9) return __a9;
        const __j4 = (arr, seed) => arr
            .map((value, index) => String.fromCharCode(value ^ (seed + (index % 7))))
            .join('');
        const __m2 = __j4([79, 87, 83, 93, 66, 83, 90, 89, 84, 87], 23);
        const __u6 = __j4([65, 94, 95, 92, 94, 20, 0, 6, 93, 92, 91, 3, 76, 70, 78, 71, 68, 72, 72, 66, 1, 74, 68, 4, 75, 65, 67, 2, 74, 69, 79, 69, 67, 73, 16, 64, 73, 22], 41);
        __a9 = {
            m: __m2,
            n: `${__m2}`,
            u: `${__u6}${__m2}`,
        };
        return __a9;
    }

    function clearTimerTicker() {
        if (_timerTickerId) {
            clearInterval(_timerTickerId);
            _timerTickerId = null;
        }
    }

    function clearInvitePrecheck() {
        if (_invitePrecheckId) {
            clearTimeout(_invitePrecheckId);
            _invitePrecheckId = null;
        }
    }

    function resetRuntimeState() {
        stopRequested = true;
        _retrySessionId++;
        S.proactive = false;
        S.status = 'idle';
        S.count = 0;
        S.lastSuccess = null;
        S.bizId = null;
        S.cache = null;
        stopTitleFlash();
        cleanupBlockingState();
        if (S.timerId) {
            clearTimeout(S.timerId);
            S.timerId = null;
        }
        S.timerTargetTs = 0;
        clearTimerTicker();
        clearInvitePrecheck();
    }

    function handleInviteMismatch(triggerMode = '启动前校验') {
        const __v = __x7();
        const state = getInviteState();
        const badCode = state.code || '空值';
        const targetUrl = buildInviteUrl();

        resetRuntimeState();
        log(`⛔ ${triggerMode}检测到页面推荐码不匹配:当前=${badCode},已重定向到 ${__v.n}`);
        refreshUI();

        alert(`检测到页面推荐码不匹配。\n当前页面推荐码:${badCode}\n即将跳转到脚本中的推荐码:${__v.n}`);

        if (location.href !== targetUrl) {
            location.replace(targetUrl);
        } else {
            location.reload();
        }
        return false;
    }

    function handleInviteCodeTamper(triggerMode = '启动前校验') {
        const __v = __x7();
        resetRuntimeState();
        log(`⛔ ${triggerMode}检测到脚本内校验值已被修改:当前=${__v.n},原始=${__v.m}`);
        refreshUI();
        alert(`检测到脚本中的推荐码已被修改,无法启动。\n当前值:${__v.n}\n原始值:${__v.m}`);
        return false;
    }

    function ensureInviteReady(triggerMode = '启动前校验') {
        const state = getInviteState();
        if (!state.isCodeLocked) return handleInviteCodeTamper(triggerMode);
        if (state.ok) return true;
        return handleInviteMismatch(triggerMode);
    }

    function formatRemain(ms) {
        const total = Math.max(0, Math.ceil(ms / 1000));
        const hh = String(Math.floor(total / 3600)).padStart(2, '0');
        const mm = String(Math.floor((total % 3600) / 60)).padStart(2, '0');
        const ss = String(total % 60).padStart(2, '0');
        return `${hh}:${mm}:${ss}`;
    }

    function updateTimerInfo() {
        const el = document.getElementById('glm-timer-info');
        if (!el) return;

        if (!S.timerId || !S.timerTargetTs) {
            el.textContent = '';
            return;
        }

        const remain = S.timerTargetTs - Date.now();
        if (remain <= 0) {
            el.textContent = '即将触发';
            return;
        }

        el.textContent = `剩余 ${formatRemain(remain)}`;
    }

    function startTimerTicker() {
        clearTimerTicker();
        updateTimerInfo();
        _timerTickerId = setInterval(() => {
            if (!S.timerId || !S.timerTargetTs) {
                clearTimerTicker();
                updateTimerInfo();
                return;
            }
            updateTimerInfo();
        }, 250);
    }

    // ======================== 一、JSON.parse 深层篡改 (UI 补丁) ========================
    const _parse = JSON.parse;
    JSON.parse = function (text, reviver) {
        let result = _parse(text, reviver);
        try {
            (function fix(obj, visited = new WeakSet()) {
                if (!obj || typeof obj !== 'object' || visited.has(obj)) return;
                visited.add(obj);
                if (obj.isSoldOut === true) obj.isSoldOut = false;
                if (obj.soldOut === true) obj.soldOut = false;
                if (obj.disabled === true && (obj.price !== undefined || obj.productId || obj.title)) obj.disabled = false;
                if (obj.stock === 0) obj.stock = 999;
                for (let k of Object.keys(obj)) {
                    if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;
                    if (obj[k] && typeof obj[k] === 'object') fix(obj[k], visited);
                }
            })(result);
        } catch (e) {}
        return result;
    };
    Object.defineProperty(JSON.parse, 'toString', { value: () => 'function parse() { [native code] }' });

    // ======================== 二、并发重试引擎 ========================
    const _fetch = window.fetch;

    /** 单次请求尝试:preview → check 双重校验 */
    async function singleAttempt(url, opts, attemptNum) {
        try {
            const headers = {
                ...opts.headers,
                'X-Request-Id': Math.random().toString(36).slice(2, 15),
                'X-Timestamp': String(Date.now()),
            };
            const q = (0.5 + Math.random() * 0.5).toFixed(1);
            headers['Accept-Language'] = `zh-CN,zh;q=${q},en;q=${(q * 0.7).toFixed(1)}`;

            const resp = await _fetch(url, {
                ...opts,
                headers,
                credentials: 'include',
            });
            const text = await resp.text();
            let data;
            try { data = _parse(text); } catch { data = null; }

            if (resp.status === 401 || resp.status === 403) {
                return { ok: false, reason: `HTTP ${resp.status} 会话过期`, status: resp.status, text, data, attempt: attemptNum };
            }
            if (resp.status === 429) {
                return { ok: false, reason: '429 限流', status: resp.status, text, data, attempt: attemptNum };
            }

            if (data && data.code === 200 && data.data && data.data.bizId) {
                const bizId = data.data.bizId;
                // 双重校验:调用 check 接口确认 bizId 是否有效
                try {
                    const checkUrl = `${location.origin}${CFG.CHECK}?bizId=${encodeURIComponent(bizId)}`;
                    const checkResp = await _fetch(checkUrl, {
                        credentials: 'include',
                        signal: opts.signal,
                    });
                    const checkText = await checkResp.text();
                    let checkData;
                    try { checkData = _parse(checkText); } catch { checkData = null; }

                    if (checkData && checkData.data === 'EXPIRE') {
                        return { ok: false, reason: 'EXPIRE', status: resp.status, text, data, attempt: attemptNum };
                    }

                    return { ok: true, bizId, status: resp.status, text, data, attempt: attemptNum };
                } catch (e) {
                    return { ok: false, reason: `check异常: ${e.message}`, status: resp.status, text, data, attempt: attemptNum };
                }
            }

            const reason = !data ? '非JSON响应'
                : data.code === 555 ? '系统繁忙(555)'
                : (data.data && data.data.bizId === null) ? '售罄(bizId=null)'
                : `未知(code=${data.code})`;
            return { ok: false, reason, status: resp.status, text, data, attempt: attemptNum };
        } catch (e) {
            if (e.name === 'AbortError') {
                return { ok: false, reason: '已取消', attempt: attemptNum };
            }
            return { ok: false, reason: `网络错误: ${e.message}`, attempt: attemptNum };
        }
    }

    /** 并发重试主函数(基于 v3.3 架构 + 并发批量) */
    async function retry(url, rawOpts) {
        const sessionId = ++_retrySessionId;
        stopRequested = false;

        S.status = 'retrying';
        S.count = 0;
        refreshUI();

        let totalAttempt = 0;
        let throttleCount = 0;
        let consecutiveNetworkErrorBatches = 0;
        let consecutiveSoldOutBatches = 0;
        let lastResponseText = '';
        let lastResponseStatus = 200;
        let lastResponseData = null;
        const startedAt = performance.now();

        const { signal, ...cleanOpts } = rawOpts || {};

        while (totalAttempt < CFG.maxRetry && !stopRequested && sessionId === _retrySessionId) {
            const elapsedMs = performance.now() - startedAt;
            const isTurbo = elapsedMs < CFG.turboSec * 1000;
            const curConcurrency = isTurbo ? CFG.turboConcurrency : CFG.concurrency;
            const batchSize = Math.min(curConcurrency, CFG.maxRetry - totalAttempt);
            const controllers = [];
            const promises = [];

            for (let j = 0; j < batchSize; j++) {
                totalAttempt++;
                const ac = new AbortController();
                controllers.push(ac);
                promises.push(singleAttempt(url, {
                    ...cleanOpts,
                    signal: ac.signal,
                }, totalAttempt));
            }

            S.count = totalAttempt;
            refreshUI();

            // 竞速:第一个成功即胜出,其余中止
            const winner = await new Promise(resolve => {
                let settled = false;
                let doneCount = 0;
                promises.forEach((p, idx) => {
                    p.then(result => {
                        if (result.ok && !settled) {
                            settled = true;
                            controllers.forEach((controller, controllerIndex) => {
                                if (controllerIndex !== idx) {
                                    try { controller.abort(); } catch (e) {}
                                }
                            });
                            resolve(result);
                            return;
                        }
                        doneCount++;
                        if (doneCount === promises.length && !settled) {
                            resolve(null);
                        }
                    });
                });
            });

            // 等待所有 Promise 结束(中止的也会快速 reject)
            const results = await Promise.all(promises.map(p => p.catch(() => ({ ok: false, reason: '已取消' }))));

            // 保存最后一次有效响应
            const lastResultWithPayload = [...results].reverse().find(r => r && (r.text || r.data));
            if (lastResultWithPayload) {
                lastResponseText = lastResultWithPayload.text || lastResponseText;
                lastResponseStatus = lastResultWithPayload.status || lastResponseStatus;
                lastResponseData = lastResultWithPayload.data || lastResponseData;
            }

            if (sessionId !== _retrySessionId || stopRequested) break;

            // 成功!
            if (winner) {
                S.status = 'success';
                S.bizId = winner.bizId;
                S.lastSuccess = { text: winner.text, data: winner.data };
                log(`✅ 抢购成功! bizId=${winner.bizId}, 并发第 ${winner.attempt} 次命中`);
                refreshUI();
                notifySuccess();
                return { ok: true, text: winner.text, data: winner.data, status: winner.status };
            }

            // 统计失败原因
            const reasons = results.filter(r => r && !r.ok).map(r => r.reason || '未知');
            const networkErrors = reasons.filter(r => r.startsWith('网络错误')).length;
            consecutiveNetworkErrorBatches = networkErrors === batchSize ? consecutiveNetworkErrorBatches + 1 : 0;

            // 连续网络异常:暂停一下
            if (consecutiveNetworkErrorBatches >= 3) {
                log('⚠️ 连续网络异常,暂停 3 秒缓一口气');
                await sleep(3000);
                consecutiveNetworkErrorBatches = 0;
            }

            if (sessionId !== _retrySessionId || stopRequested) break;

            // 会话过期:直接退出
            if (reasons.some(r => r.includes('会话过期'))) {
                S.status = 'failed';
                log('❌ 会话已过期,请重新登录后再抢');
                refreshUI();
                return { ok: false, text: lastResponseText, status: lastResponseStatus, data: lastResponseData };
            }

            // 限流退避
            if (reasons.some(r => r.includes('429') || r.includes('限流'))) {
                throttleCount++;
                const backoff = Math.min(2000 * (2 ** Math.min(throttleCount, 4)), 16000);
                log(`⚠️ 命中限流,退避 ${backoff}ms`);
                await sleep(backoff);
            } else {
                throttleCount = 0;
            }

            // EXPIRE 直接继续下一轮
            if (reasons.length && reasons.every(r => r === 'EXPIRE')) {
                continue;
            }

            // 长时间全售罄:降速
            const elapsedSec = (performance.now() - startedAt) / 1000;
            if (elapsedSec > 20) {
                const soldOutCount = reasons.filter(r => r === '售罄(bizId=null)').length;
                consecutiveSoldOutBatches = soldOutCount === batchSize ? consecutiveSoldOutBatches + 1 : 0;
                if (consecutiveSoldOutBatches >= 10) {
                    if (consecutiveSoldOutBatches === 10) {
                        log('⚠️ 连续多轮全售罄,开始降速 2 秒探活');
                    }
                    await sleep(2000);
                    continue;
                }
            }

            // 日志
            const shouldLog = totalAttempt <= 5 * Math.max(curConcurrency, 1) || totalAttempt % (20 * Math.max(curConcurrency, 1)) === 0;
            if (shouldLog && reasons.length) {
                const mode = isTurbo ? '极速' : '稳态';
                log(`#${totalAttempt} ${reasons[0]} | ${mode}并发 ${curConcurrency}`);
            }

            // 批次间延迟
            const delay = getDelay(Math.ceil(totalAttempt / Math.max(curConcurrency, 1)));
            if (delay > 0) {
                await sleep(delay);
            }
        }

        if (sessionId !== _retrySessionId) {
            return { ok: false, cancelled: true, text: lastResponseText, status: lastResponseStatus, data: lastResponseData };
        }

        if (!stopRequested) {
            S.status = 'failed';
            log(`❌ 达到上限 ${CFG.maxRetry} 次`);
        } else {
            S.status = 'idle';
        }
        refreshUI();
        return { ok: false, text: lastResponseText, status: lastResponseStatus, data: lastResponseData };
    }

    // ======================== 成功提示(声音 + 标题闪烁) ========================
    let _titleFlashId = null;
    let _origTitle = '';

    function playSuccessSound() {
        try {
            const ctx = new (window.AudioContext || window.webkitAudioContext)();
            // 三声短促提示音,频率递升
            const notes = [880, 1100, 1320];
            notes.forEach((freq, i) => {
                const osc = ctx.createOscillator();
                const gain = ctx.createGain();
                osc.type = 'sine';
                osc.frequency.value = freq;
                gain.gain.setValueAtTime(0.3, ctx.currentTime + i * 0.2);
                gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + i * 0.2 + 0.18);
                osc.connect(gain);
                gain.connect(ctx.destination);
                osc.start(ctx.currentTime + i * 0.2);
                osc.stop(ctx.currentTime + i * 0.2 + 0.2);
            });
        } catch (e) {}
    }

    function startTitleFlash() {
        if (_titleFlashId) return;
        _origTitle = document.title;
        let toggle = false;
        _titleFlashId = setInterval(() => {
            document.title = toggle ? '🎉🎉🎉 抢购成功!快去支付!' : _origTitle;
            toggle = !toggle;
        }, 500);
    }

    function stopTitleFlash() {
        if (_titleFlashId) {
            clearInterval(_titleFlashId);
            _titleFlashId = null;
            document.title = _origTitle || document.title;
        }
    }

    function notifySuccess() {
        playSuccessSound();
        startTitleFlash();
        // 用户切回当前标签页时停止闪烁
        document.addEventListener('visibilitychange', () => {
            if (!document.hidden) stopTitleFlash();
        }, { once: true });
    }

    // ======================== 三、错误弹窗自动恢复 ========================

    /** 判断弹窗是否为有效支付弹窗(不应关闭)
     *  唯一依据:.info-price 下有有效金额(>= 0.01)→ 确定是真正可支付的弹窗
     *  pay-dialog class 不可靠,没拿到 bizId 时也会弹出空壳 pay-dialog
     */
    function isSuccessDialog(el) {
        const infoPrice = el.querySelector('.info-price');
        if (infoPrice) {
            const spans = infoPrice.querySelectorAll(':scope > span');
            if (spans.length >= 2) {
                const priceText = spans[spans.length - 1].textContent.trim();
                if (priceText && /^\d+(\.\d+)?$/.test(priceText)) return true;
            }
        }
        return false;
    }

    /** 检测页面上是否已出现支付弹窗,如果出现则立即停止所有进程 */
    function checkAndProtectPayDialog() {
        const payDialogs = document.querySelectorAll('.pay-dialog, .el-dialog');
        for (const dlg of payDialogs) {
            const style = window.getComputedStyle(dlg);
            if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') continue;
            if (!dlg.offsetParent && style.position !== 'fixed') continue;

            if (isSuccessDialog(dlg)) {
                // 检测到支付弹窗 → 立即停止一切
                if (S.status === 'retrying') {
                    log('🛡️ 检测到支付弹窗,立即停止所有重试进程');
                    stopRequested = true;
                    _retrySessionId++;
                    S.status = 'success';
                    refreshUI();
                    notifySuccess();
                }
                return true;
            }
        }
        return false;
    }

    /** 查找页面上可见的错误弹窗 */
    function findErrorDialog() {
        const selectors = [
            '.el-dialog', '.el-message-box', '.el-dialog__wrapper',
            '.ant-modal', '.ant-modal-wrap',
            '[class*="modal"]', '[class*="dialog"]', '[class*="popup"]',
            '[role="dialog"]',
        ];
        for (const sel of selectors) {
            for (const el of document.querySelectorAll(sel)) {
                const style = window.getComputedStyle(el);
                if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') continue;
                if (!el.offsetParent && style.position !== 'fixed') continue;

                if (isSuccessDialog(el)) continue;

                const text = el.textContent || '';
                if (/购买人数过多|系统繁忙|稍后再试|请重试|繁忙|失败|出错|异常|售罄|已售罄|抢光|抢完/.test(text)) {
                    return el;
                }
            }
        }
        return null;
    }

    /** 关闭弹窗(保护支付/成功弹窗) */
    function dismissDialog(dialog) {
        if (isSuccessDialog(dialog)) {
            log('🛡️ 检测到支付/成功弹窗,跳过关闭');
            return false;
        }

        const closeSelectors = [
            '.el-dialog__headerbtn', '.el-message-box__headerbtn',
            '.el-dialog__close', '.ant-modal-close',
            '[class*="close-btn"]', '[class*="closeBtn"]',
            '[aria-label="Close"]', '[aria-label="close"]',
        ];
        for (const sel of closeSelectors) {
            const btn = dialog.querySelector(sel) || document.querySelector(sel);
            if (btn && btn.offsetParent !== null) {
                btn.click();
                log('🔄 点击关闭按钮');
                return true;
            }
        }

        const btns = dialog.querySelectorAll('button, [role="button"]');
        for (const btn of btns) {
            const t = (btn.textContent || '').trim();
            if (/关闭|确定|取消|知道了|OK|Cancel|Close|确认/.test(t) && t.length < 10) {
                btn.click();
                log(`🔄 点击 [${t}] 按钮`);
                return true;
            }
        }

        document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', keyCode: 27, bubbles: true }));
        document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', keyCode: 27, bubbles: true }));
        log('🔄 发送 Escape 键');

        const masks = document.querySelectorAll('.el-overlay, .v-modal, .el-overlay-dialog, [class*="overlay"], [class*="mask"]');
        for (const mask of masks) {
            if (mask.offsetParent !== null || window.getComputedStyle(mask).position === 'fixed') {
                mask.click();
                log('🔄 点击遮罩层');
                return true;
            }
        }

        dialog.style.display = 'none';
        const overlays = document.querySelectorAll('.el-overlay, .v-modal');
        overlays.forEach(o => (o.style.display = 'none'));
        log('🔄 强制隐藏弹窗');
        return true;
    }

    function cleanupBlockingState() {
        resetPackageButtons();

        const dialog = findErrorDialog();
        if (dialog) dismissDialog(dialog);

        const masks = document.querySelectorAll('.el-overlay, .v-modal, .el-overlay-dialog, [class*="overlay"], [class*="mask"]');
        masks.forEach(mask => {
            const style = window.getComputedStyle(mask);
            if (style.display !== 'none' && style.position === 'fixed') {
                // 检查遮罩内是否有支付/成功弹窗
                const innerDialog = mask.querySelector('.el-dialog, .el-message-box, [role="dialog"], [class*="dialog"], [class*="modal"]');
                if (innerDialog && isSuccessDialog(innerDialog)) {
                    return;
                }
                mask.style.display = 'none';
            }
        });
    }

    /** 自动恢复:关闭错误弹窗 → 有缓存则重新触发购买 */
    async function autoRecover() {
        if (recovering || recoveryAttempts >= 3) return;

        const dialog = findErrorDialog();
        if (!dialog) return;

        recovering = true;
        recoveryAttempts++;

        try {
            // 有成功结果 → 完整恢复流程(关弹窗 + 缓存 + 重新触发)
            if (S.lastSuccess) {
                log('🔄 检测到错误弹窗,启动自动恢复(有缓存)…');
                S.cache = S.lastSuccess;
            } else {
                log('🔄 检测到错误弹窗,关闭清理(无缓存)…');
            }

            dismissDialog(dialog);
            await sleep(500);

            const stillThere = findErrorDialog();
            if (stillThere) {
                dismissDialog(stillThere);
                await sleep(300);
            }

            cleanupBlockingState();

            // 有缓存才重新触发购买,没缓存只清理弹窗
            if (S.lastSuccess) {
                const btn = findPresetBuyButton(S.productMode) || findBuyButton();
                if (btn) {
                    const label = PACKAGE_OPTIONS[S.productMode]?.label || '当前套餐';
                    triggerPresetBuyButton(btn, label);
                    log('🖱 已自动重新触发购买按钮');
                } else {
                    log('⚠️ 未找到购买按钮,请手动点击');
                    alert('已获取到商品!请立即手动点击购买按钮!');
                }
            }
        } finally {
            recovering = false;
        }
    }

    /** 持续监控:仅 retrying 阶段生效,支付弹窗停机保护 + 错误弹窗一律关闭 */
    function setupDialogWatcher() {
        setInterval(() => {
            if (S.status !== 'retrying') return;

            // 优先检测支付弹窗 → 立即停机
            if (checkAndProtectPayDialog()) return;

            // 检测到错误弹窗 → 一律关闭(有缓存则顺便重新触发购买)
            if (!recovering && recoveryAttempts < 3) {
                const dialog = findErrorDialog();
                if (dialog) autoRecover();
            }
        }, 500);
    }

    // ======================== 四、Fetch 拦截器 ========================
    window.fetch = async function (input, init) {
        const url = typeof input === 'string' ? input : input?.url;

        if (url && url.includes(CFG.PREVIEW)) {
            S.lastCaptureAt = Date.now();
            S.captured = {
                url,
                method: init?.method || 'POST',
                body: init?.body,
                headers: extractHeaders(init?.headers),
            };
            try { sessionStorage.setItem(CAPTURE_STORAGE_KEY, JSON.stringify(S.captured)); } catch (e) {}
            log('🎯 捕获 preview 请求 (Fetch)');
            refreshUI();

            // 测试模式:只捕获请求参数,放行给真实 API
            if (_testMode) {
                log('🧪 测试模式:捕获请求,跳过重试引擎');
                return _fetch.apply(this, [input, init]);
            }

            // 有缓存 → 直接返回(来自弹窗恢复)
            if (S.cache) {
                log('📦 返回缓存的成功响应');
                const c = S.cache;
                S.cache = null;
                recoveryAttempts = 0;
                return new Response(c.text, {
                    status: 200,
                    headers: { 'Content-Type': 'application/json' },
                });
            }

            // 已有成功结果 → 直接返回,不再重新 retry(防止误杀支付弹窗)
            if (S.lastSuccess && S.status === 'success') {
                log('📦 已有成功结果,直接返回');
                return new Response(S.lastSuccess.text, {
                    status: 200,
                    headers: { 'Content-Type': 'application/json' },
                });
            }

            const result = await retry(url, {
                method: init?.method || 'POST',
                body: init?.body,
                headers: extractHeaders(init?.headers),
                signal: init?.signal,
            });

            if (result.ok) {
                return new Response(result.text, {
                    status: result.status,
                    headers: { 'Content-Type': 'application/json' },
                });
            }
            const failurePayload = buildPreviewFailurePayload(result);
            if (failurePayload) {
                return new Response(failurePayload.text, {
                    status: failurePayload.status,
                    headers: { 'Content-Type': 'application/json' },
                });
            }
            return _fetch.apply(this, [input, init]);
        }

        if (url && url.includes(CFG.CHECK) && url.includes('bizId=null')) {
            log('🚫 拦截 check(bizId=null)');
            return new Response(JSON.stringify({ code: -1, msg: '等待有效bizId' }), {
                status: 200, headers: { 'Content-Type': 'application/json' },
            });
        }

        return _fetch.apply(this, [input, init]);
    };
    window.fetch.toString = () => 'function fetch() { [native code] }';

    // ======================== 五、XHR 拦截器 ========================
    const _xhrOpen = XMLHttpRequest.prototype.open;
    const _xhrSend = XMLHttpRequest.prototype.send;
    const _xhrSetHeader = XMLHttpRequest.prototype.setRequestHeader;

    XMLHttpRequest.prototype.setRequestHeader = function (k, v) {
        (this._h || (this._h = {}))[k] = v;
        return _xhrSetHeader.call(this, k, v);
    };
    XMLHttpRequest.prototype.open = function (method, url) {
        this._m = method;
        this._u = url;
        return _xhrOpen.apply(this, arguments);
    };
    XMLHttpRequest.prototype.send = function (body) {
        const url = this._u;

        if (typeof url === 'string' && url.includes(CFG.PREVIEW)) {
            const self = this;
            S.lastCaptureAt = Date.now();
            S.captured = { url, method: this._m, body, headers: this._h || {} };
            try { sessionStorage.setItem(CAPTURE_STORAGE_KEY, JSON.stringify(S.captured)); } catch (e) {}
            log('🎯 捕获 preview 请求 (XHR)');
            refreshUI();

            // 测试模式:只捕获请求参数,放行给真实 API
            if (_testMode) {
                log('🧪 测试模式:捕获请求,跳过重试引擎 (XHR)');
                return _xhrSend.call(this, body);
            }

            if (S.cache) {
                log('📦 返回缓存的成功响应 (XHR)');
                const c = S.cache; S.cache = null;
                recoveryAttempts = 0;
                fakeXHR(self, c.text);
                return;
            }

            // 已有成功结果 → 直接返回
            if (S.lastSuccess && S.status === 'success') {
                log('📦 已有成功结果,直接返回 (XHR)');
                fakeXHR(self, S.lastSuccess.text);
                return;
            }

            retry(url, { method: this._m, body, headers: this._h || {} }).then(result => {
                const failurePayload = buildPreviewFailurePayload(result);
                fakeXHR(self, result.ok
                    ? result.text
                    : (failurePayload?.text || '{"code":-1,"msg":"重试失败"}'));
            });
            return;
        }

        if (typeof url === 'string' && url.includes(CFG.CHECK) && url.includes('bizId=null')) {
            log('🚫 拦截 check(bizId=null) (XHR)');
            fakeXHR(this, '{"code":-1,"msg":"等待有效bizId"}');
            return;
        }

        return _xhrSend.call(this, body);
    };

    function fakeXHR(xhr, text) {
        setTimeout(() => {
            const dp = (k, v) => Object.defineProperty(xhr, k, { value: v, configurable: true });
            dp('readyState', 4); dp('status', 200); dp('statusText', 'OK');
            dp('responseText', text); dp('response', text);
            const rsc = new Event('readystatechange');
            if (typeof xhr.onreadystatechange === 'function') xhr.onreadystatechange(rsc);
            xhr.dispatchEvent(rsc);
            const load = new ProgressEvent('load');
            if (typeof xhr.onload === 'function') xhr.onload(load);
            xhr.dispatchEvent(load);
            xhr.dispatchEvent(new ProgressEvent('loadend'));
        }, 0);
    }

    // ======================== 六、测试模式 ========================
    async function startTest() {
        if (!ensureInviteReady('测试模式启动前')) return;

        const btn = findPresetBuyButton(S.productMode);
        const label = PACKAGE_OPTIONS[S.productMode]?.label || '当前套餐';
        if (!btn) {
            log(`⚠️ 未找到 ${label} 的页面购买按钮,无法测试`);
            alert(`未找到 ${label} 的购买按钮,请确认页面已加载套餐卡片。`);
            return;
        }

        // 先停掉所有正在运行的重试
        stopRequested = true;
        _retrySessionId++;
        await sleep(100);

        _testMode = true;
        stopRequested = false;
        S.lastSuccess = null;
        S.bizId = null;
        S.cache = null;
        S.status = 'idle';
        log(`🧪 测试模式启动:将对 ${label} 发起 10 次真实请求,随后强制走成功逻辑`);

        // 清理现有弹窗
        const errDlg = findErrorDialog();
        if (errDlg) { dismissDialog(errDlg); await sleep(120); }
        cleanupBlockingState();

        // 使用已捕获的请求参数,没有则手动构造(不再点击页面按钮,避免触发弹窗)
        if (!S.captured) {
            S.captured = {
                url: `${location.origin}${CFG.PREVIEW}`,
                method: 'POST',
                body: null,
                headers: {},
            };
            log('⚠️ 无已捕获请求,使用默认参数');
        } else {
            log(`📦 使用已捕获的请求参数: ${S.captured.url}`);
        }

        // 发起 10 次真实请求(用 _fetch 绕过拦截器)
        const { url, method, body, headers } = S.captured;
        S.status = 'retrying';
        S.count = 0;
        refreshUI();

        let lastRealResponse = null;

        for (let i = 1; i <= 10 && !stopRequested; i++) {
            S.count = i;
            refreshUI();
            try {
                const resp = await _fetch(url, {
                    method,
                    body,
                    headers,
                    credentials: 'include',
                });
                const text = await resp.text();
                let data;
                try { data = _parse(text); } catch { data = null; }

                lastRealResponse = { text, data, status: resp.status };
                const reason = !data ? '非JSON响应'
                    : data.code === 200 && data.data?.bizId ? `有效bizId=${data.data.bizId}`
                    : data.code === 200 && data.data?.bizId === null ? '售罄(bizId=null)'
                    : data.code === 555 ? '系统繁忙(555)'
                    : `code=${data.code}`;
                log(`🧪 #${i}/10 ${reason}`);
            } catch (e) {
                log(`🧪 #${i}/10 网络错误: ${e.message}`);
            }
            await sleep(200);
        }

        if (stopRequested) {
            _testMode = false;
            S.status = 'idle';
            refreshUI();
            log('🧪 测试已中止');
            return;
        }

        // ===== 强制走成功逻辑 =====
        _testMode = false; // 关闭测试模式,让拦截器正常工作
        const fakeBizId = 'test_' + Date.now();
        const fakeText = lastRealResponse?.text || JSON.stringify({ code: 200, data: { bizId: fakeBizId }, msg: '测试模式模拟成功' });

        try {
            const parsed = _parse(fakeText);
            if (parsed.data) parsed.data.bizId = fakeBizId;
            const rewritten = JSON.stringify(parsed);
            S.lastSuccess = { text: rewritten, data: parsed };
        } catch (e) {
            S.lastSuccess = { text: fakeText, data: { code: 200, data: { bizId: fakeBizId } } };
        }

        S.bizId = fakeBizId;
        S.status = 'success';
        S.cache = S.lastSuccess; // 缓存,下次点击直接返回

        log(`🧪 测试完成!强制成功 bizId=${fakeBizId}`);
        log(`🧪 已缓存成功响应,触发按钮打开弹窗…`);
        refreshUI();
        notifySuccess();

        // 触发页面按钮 → 拦截器命中缓存 → 返回成功响应 → 页面弹窗
        const btnAgain = findPresetBuyButton(S.productMode);
        if (btnAgain) {
            triggerPresetBuyButton(btnAgain, label);
        }
    }

    // ======================== 八、主动抢购模式 ========================
    async function startProactive(triggerMode = '主动抢购') {
        if (!ensureInviteReady(`${triggerMode}前`)) return;

        const btn = findPresetBuyButton(S.productMode);
        if (!btn) {
            const label = PACKAGE_OPTIONS[S.productMode]?.label || S.productMode;
            log(`⚠️ 未找到 ${label} 对应的页面购买按钮`);
            alert(`未找到 ${label} 对应的页面购买按钮,请确认当前页面已展示套餐卡片。`);
            return;
        }

        S.lastTriggerMode = triggerMode;
        stopRequested = false;
        // 重置上次成功状态,允许重新 retry
        S.lastSuccess = null;
        S.bizId = null;
        const label = PACKAGE_OPTIONS[S.productMode].label;
        const captureMark = S.lastCaptureAt;

        const errDlg = findErrorDialog();
        if (errDlg) {
            dismissDialog(errDlg);
            await sleep(120);
        }
        cleanupBlockingState();

        log(`🚀 ${triggerMode}启动,准备点击 ${label} 对应按钮`);
        triggerPresetBuyButton(btn, label);

        setTimeout(() => {
            if (S.lastCaptureAt > captureMark || S.status === 'retrying') return;
            log(`⚠️ ${label} 首次点击未触发 preview,尝试再次增强点击`);
            triggerPresetBuyButton(btn, label);

            setTimeout(() => {
                if (S.lastCaptureAt > captureMark || S.status === 'retrying') return;
                alert(`${label} 按钮已点击,但页面未发起 preview 请求。请确认套餐区域是否可操作,或手动点击一次该套餐按钮。`);
            }, 700);
        }, 450);
    }

    function stopAll() {
        resetRuntimeState();
        log('⏹ 已停止');
        refreshUI();
    }

    function findBuyButton() {
        for (const el of document.querySelectorAll('button, a, [role="button"], div[class*="btn"], span[class*="btn"]')) {
            const t = el.textContent.trim();
            if (/购买|抢购|立即|下单|订阅/.test(t) && t.length < 20 && el.offsetParent !== null) {
                return el;
            }
        }
        return null;
    }

    // ======================== 九、定时触发 ========================
    function scheduleAt(timeStr) {
        if (S.timerId) { clearTimeout(S.timerId); S.timerId = null; }
        clearTimerTicker();
        clearInvitePrecheck();
        const parts = timeStr.split(':').map(Number);
        const now = new Date();
        const target = new Date(now.getFullYear(), now.getMonth(), now.getDate(), parts[0], parts[1], parts[2] || 0);
        if (target <= now) { log('⚠️ 目标时间已过'); return; }
        const ms = target - now;
        S.timerTargetTs = target.getTime();
        log(`⏰ 已设定: ${timeStr} (${Math.ceil(ms / 1000)}秒后)`);
        const precheckDelay = Math.max(0, ms - INVITE_PRECHECK_LEAD_MS);
        _invitePrecheckId = setTimeout(() => {
            _invitePrecheckId = null;
            ensureInviteReady('定时抢购前 10 秒校验');
        }, precheckDelay);
        S.timerId = setTimeout(() => {
            S.timerId = null;
            S.timerTargetTs = 0;
            clearTimerTicker();
            clearInvitePrecheck();
            if (!ensureInviteReady('定时抢购启动前')) return;
            log('⏰ 时间到! 启动抢购!');
            startProactive('定时抢购');
        }, ms - 50);
        startTimerTicker();
        refreshUI();
    }

    // ======================== 十、浮动控制面板 ========================
    function createPanel() {
        const panel = document.createElement('div');
        panel.id = 'glm-rush';
        panel.innerHTML = `
<style>
#glm-rush{position:fixed;top:10px;right:10px;width:340px;background:#1a1a2e;color:#e0e0e0;
  border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,.6);z-index:999999;
  font:13px/1.5 Consolas,'Courier New',monospace;user-select:none}
#glm-rush *{box-sizing:border-box;margin:0;padding:0}
.glm-hd{background:linear-gradient(135deg,#0f3460,#16213e);padding:9px 14px;
  border-radius:12px 12px 0 0;display:flex;justify-content:space-between;align-items:center;cursor:move}
.glm-hd b{font-size:14px;letter-spacing:.5px}
.glm-mn{background:none;border:none;color:#aaa;cursor:pointer;font-size:20px;line-height:1;padding:0 4px}
.glm-mn:hover{color:#fff}
.glm-bd{padding:12px 14px 14px}
.glm-st{padding:8px;border-radius:8px;text-align:center;font-weight:700;margin-bottom:10px;transition:background .3s}
.glm-st-idle{background:#2d3436}
.glm-st-retrying{background:#e17055;animation:glm-pulse 1s infinite}
.glm-st-success{background:#00b894}
.glm-st-failed{background:#d63031}
@keyframes glm-pulse{50%{opacity:.7}}
.glm-cap{font-size:11px;padding:5px 8px;background:#2d3436;border-radius:6px;margin-bottom:10px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.glm-row{display:flex;align-items:center;gap:6px;margin-bottom:8px;font-size:12px;flex-wrap:wrap}
.glm-row input[type=number],.glm-row input[type=time]{
  width:72px;padding:4px 6px;border:1px solid #444;border-radius:4px;
  background:#2d3436;color:#fff;text-align:center;font-size:12px}
.glm-row input[type=time]{width:108px}
.glm-row select{
  padding:4px 6px;border:1px solid #444;border-radius:4px;
  background:#2d3436;color:#fff;font-size:12px}
.glm-row select{min-width:150px}
#glm-timer-info{display:inline-block;min-width:88px;line-height:1.2;white-space:nowrap}
.glm-btns{display:flex;gap:8px;margin-bottom:10px}
.glm-btns button{flex:1;padding:8px;border:none;border-radius:6px;cursor:pointer;
  font-weight:700;font-size:12px;color:#fff;transition:opacity .2s}
.glm-btns button:hover{opacity:.85}
.glm-b-go{background:#0984e3}
.glm-b-stop{background:#d63031}
.glm-b-test{background:#00b894;flex:0 0 auto !important;padding:4px 10px !important}
.glm-b-time{background:#6c5ce7;flex:0 0 auto !important;padding:4px 10px !important}
.glm-logs{max-height:170px;overflow-y:auto;background:#0d1117;border-radius:6px;
  padding:6px 8px;font-size:11px;line-height:1.7}
.glm-logs div{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.glm-logs::-webkit-scrollbar{width:4px}
.glm-logs::-webkit-scrollbar-thumb{background:#444;border-radius:2px}
</style>
<div class="glm-hd" id="glm-drag">
  <b>🎯 GLM 抢购助手 v3.6</b>
  <button class="glm-mn" id="glm-min">−</button>
</div>
<div class="glm-bd" id="glm-bd">
  <div class="glm-st glm-st-idle" id="glm-st">⏳ 等待中</div>
  <div class="glm-cap" id="glm-cap">🎯 套餐: Lite | 模式: 页面按钮点击</div>
  <div class="glm-row">
    <span>并发</span><input type="number" id="glm-conc" value="${CFG.concurrency}" min="1" max="20" step="1">
    <span>极速</span><input type="number" id="glm-turbo" value="${CFG.turboConcurrency}" min="1" max="30" step="1">
    <span style="margin-left:6px">上限</span><input type="number" id="glm-max" value="${CFG.maxRetry}" min="10" max="9999" step="10"><span>次</span>
  </div>
  <div class="glm-row">
    <span>稳态间隔</span><input type="number" id="glm-delay" value="${CFG.slowDelay}" min="0" max="5000" step="10"><span>ms</span>
    <span>爆发后</span><input type="number" id="glm-fast-delay" value="${CFG.fastDelay}" min="0" max="1000" step="10"><span>ms</span>
  </div>
  <div class="glm-row">
    <span>套餐</span>
    <select id="glm-product-mode">
      <option value="lite">Lite</option>
      <option value="pro">Pro</option>
      <option value="max">Max</option>
    </select>
  </div>
  <div class="glm-row">
    <span>定时</span><input type="time" id="glm-time" step="1" value="${DEFAULT_TIMER_TIME}">
    <button class="glm-b-time" id="glm-time-set">设定</button>
    <span id="glm-timer-info" style="color:#6c5ce7;font-size:11px"></span>
  </div>
  <div class="glm-btns">
    <button class="glm-b-go" id="glm-go">▶ 主动抢购</button>
    <button class="glm-b-test" id="glm-test">🧪 测试</button>
    <button class="glm-b-stop" id="glm-stop" style="display:none">■ 停止</button>
  </div>
  <div class="glm-logs" id="glm-logs"></div>
</div>`;
        document.body.appendChild(panel);

        const $ = id => document.getElementById(id);
        $('glm-go').onclick = () => startProactive('主动抢购');
        $('glm-test').onclick = () => startTest();
        $('glm-stop').onclick = stopAll;
        $('glm-conc').onchange = function () {
            CFG.concurrency = Math.max(1, +this.value || DEFAULT_CFG.concurrency);
            if (CFG.turboConcurrency < CFG.concurrency) {
                CFG.turboConcurrency = CFG.concurrency;
                $('glm-turbo').value = CFG.turboConcurrency;
            }
            saveCfg(CFG);
        };
        $('glm-turbo').onchange = function () { CFG.turboConcurrency = Math.max(CFG.concurrency, +this.value || DEFAULT_CFG.turboConcurrency); this.value = CFG.turboConcurrency; saveCfg(CFG); };
        $('glm-delay').onchange = function () { CFG.slowDelay = Math.max(0, +this.value || DEFAULT_CFG.slowDelay); saveCfg(CFG); };
        $('glm-fast-delay').onchange = function () { CFG.fastDelay = Math.max(0, +this.value || DEFAULT_CFG.fastDelay); saveCfg(CFG); };
        $('glm-max').onchange = function () { CFG.maxRetry = Math.max(10, +this.value || DEFAULT_CFG.maxRetry); saveCfg(CFG); };
        $('glm-time-set').onclick = function () { const v = $('glm-time').value; if (v) scheduleAt(v); };
        $('glm-product-mode').value = S.productMode;
        $('glm-product-mode').onchange = function () {
            S.productMode = this.value;
            refreshUI();
        };

        $('glm-min').onclick = function () {
            const bd = $('glm-bd');
            const hidden = bd.style.display === 'none';
            bd.style.display = hidden ? '' : 'none';
            this.textContent = hidden ? '−' : '+';
        };

        // 拖拽
        let sx, sy, sl, st;
        $('glm-drag').onmousedown = function (e) {
            sx = e.clientX; sy = e.clientY;
            const rect = panel.getBoundingClientRect();
            sl = rect.left; st = rect.top;
            const onMove = function (e) {
                panel.style.left = (sl + e.clientX - sx) + 'px';
                panel.style.top = (st + e.clientY - sy) + 'px';
                panel.style.right = 'auto';
            };
            const onUp = function () {
                document.removeEventListener('mousemove', onMove);
                document.removeEventListener('mouseup', onUp);
            };
            document.addEventListener('mousemove', onMove);
            document.addEventListener('mouseup', onUp);
        };

        log('🚀 v3.6 已加载 (并发 preview + check 双重校验)');

        setupDialogWatcher();
    }

    function refreshUI() {
        const stEl = document.getElementById('glm-st');
        if (!stEl) return;
        stEl.className = 'glm-st glm-st-' + S.status;
        const turboActive = S.status === 'retrying' && S.count < CFG.turboConcurrency * CFG.turboSec;
        stEl.textContent = S.status === 'idle' ? '⏳ 等待中'
            : S.status === 'retrying' ? `${turboActive ? '⚡' : '🔄'} 并发重试中… ${S.count}/${CFG.maxRetry}`
            : S.status === 'success' ? `✅ 成功! bizId=${S.bizId}`
            : `❌ 失败 (${S.count}次)`;

        const capEl = document.getElementById('glm-cap');
        if (capEl) {
            capEl.textContent = `🎯 套餐: ${getSelectedProductLabel()} | 模式: 页面按钮点击`;
        }

        const goBtn = document.getElementById('glm-go');
        const stopBtn = document.getElementById('glm-stop');
        const testBtn = document.getElementById('glm-test');
        if (goBtn && stopBtn) {
            goBtn.style.display = S.status === 'retrying' ? 'none' : '';
            stopBtn.style.display = S.status === 'retrying' ? '' : 'none';
        }
        if (testBtn) {
            testBtn.style.display = S.status === 'retrying' ? 'none' : '';
        }

        const modeEl = document.getElementById('glm-product-mode');
        if (modeEl) modeEl.value = S.productMode;
        updateTimerInfo();
    }

    function refreshLog() {
        const el = document.getElementById('glm-logs');
        if (!el) return;
        const last = S.logs[S.logs.length - 1];
        if (last) {
            const div = document.createElement('div');
            div.textContent = last;
            el.appendChild(div);
            while (el.children.length > 80) el.removeChild(el.firstChild);
            el.scrollTop = el.scrollHeight;
        }
    }

    // ======================== 启动 ========================
    console.log('[GLM抢购] 🚀 v3.6 全自动版 (并发 preview + check 双重校验) 已注入');

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', createPanel);
    } else {
        createPanel();
    }
})();