Greasy Fork

来自缓存

Greasy Fork is available in English.

智谱 GLM Coding 快速订阅助手 (优化版)

优化性能与结构,解除按钮限制,自动分配快捷键映射

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         智谱 GLM Coding 快速订阅助手 (优化版)
// @name:en      智谱 GLM Coding Fast Subscription Helper (Optimized)
// @namespace    http://tampermonkey.net/
// @version      7.2.0
// @description  优化性能与结构,解除按钮限制,自动分配快捷键映射
// @description:en Optimize performance and structure, remove button restrictions, and automatically allocate shortcut key mappings
// @author       Tim
// @match        *://www.bigmodel.cn/*
// @match        https://www.bigmodel.cn/glm-coding
// @match        https://bigmodel.cn/glm-coding*
// @run-at       document-start
// @grant        none
// @license      MIT
// @buy me a coff   邀请链接,邀请码新购,下单立减5%金额 https://www.bigmodel.cn/glm-coding?ic=EAADSLV8VZ
// ==/UserScript==

(function () {
    'use strict';

    // ==========================================
    // 配置与常量
    // ==========================================
    const CONFIG = {
        KEYWORDS: /购买|订阅|立即|下单|选购/,
        SELECTORS: 'button, .ant-btn, .btn',
        CLOSE_SELECTOR: '.el-dialog__headerbtn', // 弹窗关闭按钮
        DEBOUNCE_TIME: 150,
        PANEL_ID: 'glm-helper-panel',
        TIME_URL: 'https://time.is/zh/Beijing'
    };

    // ==========================================
    // 工具函数
    // ==========================================
    const Utils = {
        debounce(fn, delay) {
            let timer = null;
            return function (...args) {
                if (timer) clearTimeout(timer);
                timer = setTimeout(() => fn.apply(this, args), delay);
            };
        },
        deepModify(obj) {
            if (!obj || typeof obj !== 'object') return;

            // 处理当前层级
            if ('isSoldOut' in obj) obj.isSoldOut = false;
            if ('soldOut' in obj) obj.soldOut = false;
            if ('stock' in obj && obj.stock === 0) obj.stock = 999;
            if ('disabled' in obj && (obj.price !== undefined || obj.productId || obj.title)) {
                obj.disabled = false;
            }

            // 递归子对象
            for (let key in obj) {
                if (Object.prototype.hasOwnProperty.call(obj, key) && obj[key] && typeof obj[key] === 'object') {
                    this.deepModify(obj[key]);
                }
            }
        },
        getPlanInfo(btn) {
            const btnText = btn.innerText.trim().replace(/\n/g, ' ');
            // 提取动作名:优先保留“订阅中”、“处理中”等状态词
            const action = btnText.length > 5 ? btnText.substring(0, 5) : btnText;
            let container = btn.parentElement;
            let planName = '';

            // 向上回溯 4 层寻找卡片容器
            for (let i = 0; i < 4; i++) {
                if (!container) break;
                // 常见的标题识别逻辑:寻找卡片中字相对较少且在顶部的显著文字
                const candidates = Array.from(container.querySelectorAll('div, span, h1, h2, p'))
                    .filter(el => {
                        const text = el.innerText.trim();
                        return text.length > 0 &&
                               text.length < 15 &&
                               !text.includes('¥') &&
                               !text.includes(btn.innerText.trim());
                    });

                if (candidates.length > 0) {
                    // 通常第一个匹配项(在 DOM 顺序上靠前)是标题
                    planName = candidates[0].innerText.trim();
                    break;
                }
                container = container.parentElement;
            }

            return planName ? `${action}-${planName}` : btn.innerText.trim().substring(0, 8);
        },
        getSubscriptionPeriod() {
            // 扫描常见的激活态类名:active, is-active, selected, checked
            const periodElements = Array.from(document.querySelectorAll('div, span, li, button'))
                .filter(el => {
                    const text = el.innerText.trim();
                    const isPeriod = /包月|包季|包年|按月|按年/.test(text);
                    if (!isPeriod) return false;

                    const activeClasses = ['active', 'is-active', 'selected', 'checked', 'current'];
                    const hasActiveClass = activeClasses.some(cls => el.classList.contains(cls)) ||
                                          el.getAttribute('aria-checked') === 'true' ||
                                          el.getAttribute('aria-selected') === 'true';

                    return hasActiveClass;
                });

            return periodElements.length > 0 ? periodElements[0].innerText.trim() : '未识别';
        }
    };

    // ==========================================
    // UI 管理器
    // ==========================================
    const UIManager = {
        panel: null,
        init() {
            if (this.panel) return;
            this.panel = document.createElement('div');
            this.panel.id = CONFIG.PANEL_ID;
            Object.assign(this.panel.style, {
                position: 'fixed',
                left: '20px',
                top: '50%',
                transform: 'translateY(-50%)',
                backgroundColor: 'rgba(0, 0, 0, 0.85)',
                color: '#fff',
                padding: '15px',
                borderRadius: '12px',
                zIndex: '10000',
                fontSize: '13px',
                lineHeight: '1.6',
                pointerEvents: 'auto', // 恢复交互以支持点击链接
                boxShadow: '0 8px 24px rgba(0,0,0,0.4)',
                border: '1px solid #444',
                display: 'none',
                backdropFilter: 'blur(4px)',
                fontFamily: 'Inter, system-ui, sans-serif'
            });
            document.documentElement.appendChild(this.panel);
        },
        update(buttons) {
            if (!this.panel) this.init();

            this.panel.style.display = 'block'; // 持久显示
            const period = Utils.getSubscriptionPeriod();
            const header = `
                <div style="border-bottom: 1px solid #444; margin-bottom: 10px; padding-bottom: 8px;">
                    <b style="color: #00ff88; display: block; font-size: 14px;">🚀 快速订阅助手</b>
                    <span style="color: #00e5ff; font-size: 11px;">[ 当前周期 ] ${period}</span>
                </div>
            `;

            // 订阅按钮快捷键列表
            let list = buttons.map((btnObj, index) => {
                const label = btnObj.label;
                const isProcessing = label.includes('中') || label.includes('载') || label.includes('处理');
                const style = isProcessing ? 'color: #00e5ff; font-style: italic; opacity: 0.8;' : 'color: #fff;';

                return `<div><span style="color: #ffcc00; font-weight: bold; margin-right: 8px;">[ ${index + 1} ]</span><span style="${style}">${label}</span></div>`;
            }).join('');

            // 始终显示 Esc 快捷键
            const separator = list ? '<div style="border-top: 1px solid #444; margin-top: 8px; padding-top: 4px;"></div>' : '';
            list += `${separator}<div><span style="color: #00e5ff; font-weight: bold; margin-right: 8px;">[ Esc ]</span>关闭当前弹窗</div>`;

            // 始终显示对时引导
            list += `<div style="margin-top: 4px;"><span style="color: #ff8c00; font-weight: bold; margin-right: 8px;">[ Time ]</span><a href="${CONFIG.TIME_URL}" target="_blank" rel="noopener noreferrer" style="color: #bbb; text-decoration: underline; pointer-events: auto; cursor: pointer;">⌚ 精准对时</a></div>`;

            this.panel.innerHTML = header + list;
        }
    };

    // ==========================================
    // 动作管理器
    // ==========================================
    const ActionManager = {
        buttons: [],
        refresh: Utils.debounce(function () {
            // 1. 解除原生置灰
            document.querySelectorAll('button[disabled], .ant-btn-disabled').forEach(btn => {
                btn.removeAttribute('disabled');
                btn.classList.remove('ant-btn-disabled');
            });

            // 2. 扫描有效按钮并提取套餐信息
            const rawButtons = Array.from(document.querySelectorAll(CONFIG.SELECTORS));
            this.buttons = rawButtons.filter(btn => {
                const isVisible = btn.offsetWidth > 0 && btn.offsetHeight > 0;
                return isVisible && CONFIG.KEYWORDS.test(btn.innerText);
            }).map(btn => ({
                element: btn,
                label: Utils.getPlanInfo(btn)
            }));

            // 3. 更新 UI
            UIManager.update(this.buttons);
        }, CONFIG.DEBOUNCE_TIME),

        trigger(index) {
            const btnObj = this.buttons[index - 1];
            if (btnObj && btnObj.element) {
                btnObj.element.click();
                console.log(`[助手] 触发快捷键 ${index} ->`, btnObj.label);
            }
        }
    };

    // ==========================================
    // 拦截器
    // ==========================================
    const Interceptor = {
        init() {
            this.patchJSON();
            this.patchFetch();
            this.patchXHR();
        },
        patchJSON() {
            const originalParse = JSON.parse;
            JSON.parse = function (text, reviver) {
                const result = originalParse(text, reviver);
                try { Utils.deepModify(result); } catch (e) { }
                return result;
            };
        },
        patchFetch() {
            const originalFetch = window.fetch;
            window.fetch = async function (...args) {
                const response = await originalFetch.apply(this, args);
                const contentType = response.headers.get('content-type') || '';
                if (contentType.includes('application/json')) {
                    try {
                        const clone = response.clone();
                        let data = await clone.json();
                        Utils.deepModify(data);
                        return new Response(JSON.stringify(data), {
                            status: response.status,
                            statusText: response.statusText,
                            headers: response.headers
                        });
                    } catch (e) { }
                }
                return response;
            };
        },
        patchXHR() {
            const originalOpen = XMLHttpRequest.prototype.open;
            XMLHttpRequest.prototype.open = function (method, url, ...rest) {
                this.addEventListener('readystatechange', () => {
                    if (this.readyState === 4 && this.status === 200) {
                        try {
                            const contentType = this.getResponseHeader('content-type') || '';
                            if (contentType.includes('application/json')) {
                                let data = JSON.parse(this.responseText);
                                Utils.deepModify(data);
                                Object.defineProperty(this, 'responseText', {
                                    get: () => JSON.stringify(data),
                                    configurable: true
                                });
                                Object.defineProperty(this, 'response', {
                                    get: () => data,
                                    configurable: true
                                });
                            }
                        } catch (e) { }
                    }
                });
                return originalOpen.apply(this, [method, url, ...rest]);
            };
        }
    };

    // ==========================================
    // 启动初始化
    // ==========================================
    Interceptor.init();

    window.addEventListener('keydown', (e) => {
        // 特殊快捷键:Esc 关闭弹窗
        if (e.key === 'Escape') {
            const closeBtns = Array.from(document.querySelectorAll(CONFIG.CLOSE_SELECTOR));
            // 找到最后一个(通常是最新弹出的)且可见的关闭按钮
            const targetBtn = closeBtns.reverse().find(btn => btn.offsetWidth > 0 && btn.offsetHeight > 0);

            if (targetBtn) {
                e.preventDefault();
                targetBtn.click();
                console.log('[助手] 触发快捷键 Esc -> 已执行关闭操作', targetBtn);
            } else {
                console.log('[助手] 触发快捷键 Esc -> 未在页面上找到可见的关闭按钮');
            }
            return;
        }

        if (!isNaN(e.key) && e.key !== '0' && !e.ctrlKey && !e.altKey && !e.metaKey) {
            const activeElement = document.activeElement;
            if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable)) {
                return;
            }
            ActionManager.trigger(parseInt(e.key));
        }
    });

    const observer = new MutationObserver(() => ActionManager.refresh());

    // 尽早注入浮窗容器
    UIManager.init();

    // 监听 DOM 加载(增强监听范围,包括属性和字符变化)
    const observerConfig = { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['class', 'disabled'] };

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            observer.observe(document.body, observerConfig);
            ActionManager.refresh();
        });
    } else {
        observer.observe(document.body, observerConfig);
        ActionManager.refresh();
    }

    console.log('[助手] 优化版已就绪,享受极速订阅体验。');
})();