MaidUpgrade Pro

Auto check-in (silent API) + Human-like post scrolling + Smart auto replying (custom phrase database and daily limits) + Read-only idle mode + Glassmorphism floating panel

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

Advertisement:

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

Advertisement:

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         MaidUpgrade Pro
// @name:zh-CN   女仆论坛全自动升级助手 (极速拟真安全版)
// @namespace    http://tampermonkey.net/
// @version      7.2
// @description  Auto check-in (silent API) + Human-like post scrolling + Smart auto replying (custom phrase database and daily limits) + Read-only idle mode + Glassmorphism floating panel
// @description:zh-CN  自动签到(静默 API) + 拟真看帖滚动 + 智能自动水贴(自定义词库与日上限) + 纯阅读挂机模式 + 玻璃拟态控制台
// @author       mochu
// @match        *://bbs.bt.sb
// @match        *://bbs.bt.sb/*
// @match        https://bbs.bt.sb
// @match        https://bbs.bt.sb/*
// @icon         https://bbs.bt.sb/uploads/icon/icon-99224e973faa6f7d.png
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';
    console.error('🚀 [MaidUpgrade Pro] v7.2 核心驱动加载中...');

    // ═══════════════════════════════════════════
    //  默 认 回 复 语 词 库 (v7.2 精选拟真语料库)
    // ═══════════════════════════════════════════
    const DEFAULT_PHRASES = [
        '路过顶贴,支持一下!感谢大佬分享。',
        '非常实用的技术分享,先收藏了!',
        '原来是这样,今天又学到了新姿势。',
        '祝论坛越办越好,打卡盖楼围观。',
        '前排留名,感谢楼主提供这么好的资源!',
        '日常签到水一把,低调路过。',
        '这个思路太赞了,对我有很大启发!',
        '战略性留名,感谢楼主无私分享,先赞后看!',
        '满满的干货啊!正愁找不到这个,及时雨!',
        '楼主出品,必属精品,果断收藏投币走一波。',
        '感谢大佬深夜放福利,码字不易,必须顶起来!',
        '每一个默默分享的大佬,都值得一记大大的赞!',
        '找了很久的资源,终于在这里找到了,感谢楼主。',
        '楼主好人一生平安,纯支持,顶上去让更多人看到!',
        '细节拉满!这个解决方案比我之前见过的都优雅。',
        '刚好卡在某个技术点上,看完大佬的分析瞬间豁然开朗!',
        '思路清奇,角度刁钻,收藏起来留着慢慢消化。',
        '理论与实操兼备,这才是高质量技术帖该有的样子。',
        '细节决定成败,大佬对细节的把控简直无敌,受教了!',
        '打卡留名,日常前排围观大佬操作。',
        '今天的活跃度就靠这篇好帖了,顶起盖楼!',
        '潜水多年,被这篇神帖硬生生炸出来了,必须支持!',
        '日常签到,顺便给楼主的优秀分享递茶。',
        '前排小板凳已搬好,坐等楼主后续更新!',
        '纯路过,单看这排版就知道是精品,顶一下。',
        '顺着大佬的思路理了一遍,确实避开了好多坑,感谢!',
        '马住马住,回头实践一下!',
        '业界良心,必须顶上去。',
        '学到了学到了,涨姿势!',
        '瑞思拜!大佬太强了。',
        '已收藏,感谢无私分享!',
        '留名备用,迟早用得上。'
    ];

    // ═══════════════════════════════════════════
    //  单 一 树 状 存 储 管 理 器 (仿 NodeSeek Pro)
    // ═══════════════════════════════════════════
    const DEFAULT_SETTINGS = {
        version: '7.2',
        cfg_auto_checkin: true,
        cfg_auto_read: true,
        cfg_auto_reply: false,
        cfg_max_replies: 5,
        cfg_read_min: 15,
        cfg_read_max: 30,
        cfg_phrases: DEFAULT_PHRASES.join('\n'),
        is_running: false,
        stat_date: '',
        stat_replies: 0,
        stat_reads: 0,
        replied_list: [],
        read_list: [],
        panel_collapsed: false,
        panel_position: null
    };

    let settingsCache = null;

    const getStore = () => {
        if (settingsCache) return settingsCache;
        
        let saved = null;
        try {
            saved = GM_getValue('nv_helper_settings', null);
        } catch (e) {
            console.error('[MaidUpgrade Pro] GM_getValue 发生异常:', e);
        }

        if (!saved) {
            // 升级迁移机制:如果检测到 旧的分散式存储,进行零损平滑迁移
            saved = {};
            const migrateKey = (oldKey, newKey, def) => {
                try {
                    const val = GM_getValue(oldKey, undefined);
                    saved[newKey] = val === undefined ? def : val;
                } catch {
                    saved[newKey] = def;
                }
            };
            
            migrateKey('cfg_auto_checkin', 'cfg_auto_checkin', true);
            migrateKey('cfg_auto_read', 'cfg_auto_read', true);
            migrateKey('cfg_auto_reply', 'cfg_auto_reply', false);
            migrateKey('cfg_max_replies', 'cfg_max_replies', 5);
            migrateKey('cfg_read_min', 'cfg_read_min', 15);
            migrateKey('cfg_read_max', 'cfg_read_max', 30);
            migrateKey('cfg_phrases', 'cfg_phrases', DEFAULT_PHRASES.join('\n'));
            migrateKey('is_running', 'is_running', false);
            migrateKey('stat_date', 'stat_date', '');
            migrateKey('stat_replies', 'stat_replies', 0);
            migrateKey('stat_reads', 'stat_reads', 0);
            migrateKey('panel_collapsed', 'panel_collapsed', false);
            
            try {
                saved.replied_list = JSON.parse(GM_getValue('replied_list', '[]'));
            } catch (e) {
                saved.replied_list = [];
            }
            try {
                saved.read_list = JSON.parse(GM_getValue('read_list', '[]'));
            } catch (e) {
                saved.read_list = [];
            }
            try {
                saved.panel_position = JSON.parse(GM_getValue('panel_position', 'null'));
            } catch (e) {
                saved.panel_position = null;
            }
        }
        
        // 填充所有未定义字段为默认值
        for (const key in DEFAULT_SETTINGS) {
            if (saved[key] === undefined) {
                saved[key] = DEFAULT_SETTINGS[key];
            }
        }
        
        settingsCache = saved;
        try {
            GM_setValue('nv_helper_settings', settingsCache);
        } catch (e) {
            console.error('[MaidUpgrade Pro] GM_setValue 保存异常:', e);
        }
        return settingsCache;
    };

    const getCfg = (key, defVal) => {
        const store = getStore();
        return store[key] === undefined ? defVal : store[key];
    };

    const setCfg = (key, val) => {
        const store = getStore();
        store[key] = val;
        try {
            GM_setValue('nv_helper_settings', store);
        } catch (e) {
            console.error('[MaidUpgrade Pro] GM_setValue 保存异常:', e);
        }
    };

    // 获取今日的 Key(用于统计和签到限制)
    function getTodayKey() {
        const d = new Date();
        return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
    }

    // ═══════════════════════════════════════════
    //  全 局 样 式 注 入 (精致毛玻璃 UI)
    // ═══════════════════════════════════════════
    const STYLE = `
        :root {
            --nv-bg: rgba(20, 20, 28, 0.85);
            --nv-border: rgba(255, 255, 255, 0.08);
            --nv-primary: #8B5CF6;
            --nv-primary-hover: #7C3AED;
            --nv-success: #10B981;
            --nv-info: #3B82F6;
            --nv-warn: #F59E0B;
            --nv-text: #F3F4F6;
            --nv-text-sec: #9CA3AF;
        }

        .nv-panel {
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 320px;
            background: var(--nv-bg);
            backdrop-filter: blur(16px) saturate(180%);
            -webkit-backdrop-filter: blur(16px) saturate(180%);
            border: 1px solid var(--nv-border);
            border-radius: 16px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
            color: var(--nv-text);
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            z-index: 999999;
            overflow: hidden;
            transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
            user-select: none;
        }

        .nv-panel-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 12px 16px;
            background: rgba(255, 255, 255, 0.03);
            border-bottom: 1px solid var(--nv-border);
            cursor: move;
        }

        .nv-panel-title {
            font-size: 13px;
            font-weight: 700;
            background: linear-gradient(135deg, #A78BFA, #60A5FA);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            display: flex;
            align-items: center;
            gap: 6px;
        }

        .nv-btn-icon {
            background: none;
            border: none;
            color: var(--nv-text-sec);
            cursor: pointer;
            font-size: 14px;
            padding: 4px;
            border-radius: 6px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s;
        }
        .nv-btn-icon:hover {
            background: rgba(255, 255, 255, 0.08);
            color: var(--nv-text);
        }

        .nv-panel-body {
            padding: 14px 16px;
            display: flex;
            flex-direction: column;
            gap: 12px;
            max-height: 480px;
            overflow-y: auto;
        }

        .nv-panel-body::-webkit-scrollbar {
            width: 4px;
        }
        .nv-panel-body::-webkit-scrollbar-thumb {
            background: rgba(255, 255, 255, 0.1);
            border-radius: 4px;
        }

        .nv-section {
            display: flex;
            flex-direction: column;
            gap: 8px;
            background: rgba(255, 255, 255, 0.02);
            padding: 10px;
            border-radius: 10px;
            border: 1px solid rgba(255, 255, 255, 0.02);
        }

        .nv-status-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-size: 12px;
        }

        .nv-badge {
            padding: 2px 8px;
            border-radius: 20px;
            font-size: 10px;
            font-weight: 600;
        }
        .nv-badge-success { background: rgba(16, 185, 129, 0.15); color: #34D399; border: 1px solid rgba(16, 185, 129, 0.2); }
        .nv-badge-info { background: rgba(59, 130, 246, 0.15); color: #60A5FA; border: 1px solid rgba(59, 130, 246, 0.2); }
        .nv-badge-warn { background: rgba(245, 158, 11, 0.15); color: #FBBF24; border: 1px solid rgba(245, 158, 11, 0.2); }
        .nv-badge-wait { background: rgba(156, 163, 175, 0.15); color: #D1D5DB; border: 1px solid rgba(156, 163, 175, 0.2); }

        .nv-toggle-row {
            display: flex;
            align-items: center;
            font-size: 12px;
        }
        .nv-toggle-label {
            display: flex;
            align-items: center;
            gap: 8px;
            cursor: pointer;
            width: 100%;
        }
        .nv-toggle-label input[type="checkbox"] {
            accent-color: var(--nv-primary);
            width: 14px;
            height: 14px;
            cursor: pointer;
        }

        .nv-param-row {
            display: flex;
            flex-direction: column;
            gap: 4px;
            font-size: 11px;
            color: var(--nv-text-sec);
        }
        .nv-param-row label {
            display: flex;
            justify-content: space-between;
        }
        .nv-param-row label span {
            color: var(--nv-text);
            font-weight: 600;
        }

        .nv-input-num {
            background: rgba(255, 255, 255, 0.05);
            border: 1px solid var(--nv-border);
            border-radius: 6px;
            color: var(--nv-text);
            padding: 2px 6px;
            font-size: 11px;
            outline: none;
            text-align: center;
            transition: border-color 0.2s;
        }
        .nv-input-num:focus {
            border-color: var(--nv-primary);
        }

        .nv-textarea {
            width: 100%;
            background: rgba(0, 0, 0, 0.2);
            border: 1px solid var(--nv-border);
            border-radius: 8px;
            color: var(--nv-text);
            padding: 6px;
            font-size: 11px;
            resize: vertical;
            outline: none;
            box-sizing: border-box;
        }
        .nv-textarea:focus {
            border-color: var(--nv-primary);
        }

        .nv-logs {
            height: 80px;
            background: rgba(0, 0, 0, 0.3);
            border: 1px solid var(--nv-border);
            border-radius: 8px;
            padding: 6px;
            font-family: monospace;
            font-size: 10px;
            overflow-y: auto;
            white-space: pre-wrap;
            color: #A7F3D0;
            display: flex;
            flex-direction: column;
            gap: 2px;
        }

        .nv-actions {
            display: flex;
            gap: 10px;
        }
        .nv-btn {
            flex: 1;
            padding: 8px 12px;
            border: none;
            border-radius: 10px;
            font-size: 12px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 6px;
        }
        .nv-btn-primary {
            background: linear-gradient(135deg, var(--nv-primary), #6366F1);
            color: white;
            box-shadow: 0 4px 12px rgba(139, 92, 246, 0.2);
        }
        .nv-btn-primary:hover {
            opacity: 0.95;
            transform: translateY(-1px);
        }
        .nv-btn-primary:active {
            transform: translateY(0);
        }
        .nv-btn-secondary {
            background: rgba(255, 255, 255, 0.08);
            color: var(--nv-text);
            border: 1px solid var(--nv-border);
        }
        .nv-btn-secondary:hover:not(:disabled) {
            background: rgba(255, 255, 255, 0.12);
        }
        .nv-btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            transform: none !important;
            box-shadow: none !important;
        }

        /* 迷你悬浮标样式 */
        .nv-panel-collapsed {
            position: fixed;
            bottom: 20px;
            right: 20px;
            width: 48px;
            height: 48px;
            background: var(--nv-bg);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            border: 1px solid var(--nv-border);
            border-radius: 50%;
            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            z-index: 999999;
            font-size: 20px;
            transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
        }
        .nv-panel-collapsed:hover {
            transform: scale(1.1) rotate(15deg);
            background: rgba(139, 92, 246, 0.2);
            border-color: var(--nv-primary);
        }
    `;

    // ═══════════════════════════════════════════
    //  工 具 函 数
    // ═══════════════════════════════════════════
    const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
    const getCurrentUrl = () => window.location.href;

    // 从 URL 深度提取 Rhex 唯一的 Post ID (CUID)
    function extractPostId(url) {
        if (!url) return null;
        // Regex extracts any string starting with 'cmp' followed by exactly 22 alphanumeric characters (25 chars in total)
        const match = url.match(/(cmp[a-z0-9]{22})/i);
        return match ? match[1].toLowerCase() : null;
    }

    // Toast 浮窗提示
    function showToast(msg, color = '#10B981') {
        const el = document.createElement('div');
        Object.assign(el.style, {
            position: 'fixed', top: '20px', left: '50%', transform: 'translateX(-50%) translateY(-20px)',
            zIndex: '9999999', padding: '12px 24px', borderRadius: '12px',
            background: color, color: '#fff', fontSize: '13px', fontWeight: '600',
            boxShadow: '0 10px 25px rgba(0,0,0,0.2)', opacity: '0',
            transition: 'all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)', pointerEvents: 'none'
        });
        el.textContent = `⭐ ${msg}`;
        document.body.appendChild(el);
        requestAnimationFrame(() => {
            el.style.opacity = '1';
            el.style.transform = 'translateX(-50%) translateY(0)';
        });
        setTimeout(() => {
            el.style.opacity = '0';
            el.style.transform = 'translateX(-50%) translateY(-20px)';
            setTimeout(() => el.remove(), 400);
        }, 3000);
    }

    // 突破 Rhex 论坛 React 19 状态锁
    function setReactInputValue(el, value) {
        const valueSetter = Object.getOwnPropertyDescriptor(el, 'value')?.set;
        const prototype = Object.getPrototypeOf(el);
        const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set;
        if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
            prototypeValueSetter.call(el, value);
        } else if (valueSetter) {
            valueSetter.call(el, value);
        } else {
            el.value = value;
        }
        el.dispatchEvent(new Event('input', { bubbles: true }));
    }

    // ═══════════════════════════════════════════
    //  控 制 台 类 与 UI 实 现
    // ═══════════════════════════════════════════
    class UpgradeHelper {
        constructor() {
            this.today = getTodayKey();
            this.initStorage();
            this.initDOM();
            this.bindEvents();
            this.updateUIStatus();
            this.startDailyCheckIn();

            // 如果挂机状态是开启的,加载后自动开始运作
            if (this.isRunning) {
                this.addLog('🔄 检测到挂机正在进行中,3秒后自动恢复执行...');
                setTimeout(() => this.runRoutine(), 3000);
            } else {
                this.addLog('💤 挂机已暂停。点击“开始挂机”按钮启动。');
            }
        }

        // 初始化本地运行数据
        initStorage() {
            // 从统一的 JSON 存储中读取开关状态
            this.cfgAutoCheckin = getCfg('cfg_auto_checkin', true);
            this.cfgAutoRead = getCfg('cfg_auto_read', true);
            this.cfgAutoReply = getCfg('cfg_auto_reply', false);
            this.isRunning = getCfg('is_running', false);

            // 参数值
            this.maxReplies = getCfg('cfg_max_replies', 5);
            this.readMin = getCfg('cfg_read_min', 15);
            this.readMax = getCfg('cfg_read_max', 30);
            this.phrases = getCfg('cfg_phrases', DEFAULT_PHRASES.join('\n'));

            // 每日计数器重置
            const savedDate = getCfg('stat_date', '');
            if (savedDate !== this.today) {
                setCfg('stat_date', this.today);
                setCfg('stat_replies', 0);
                setCfg('stat_reads', 0);
                this.todayReplies = 0;
                this.todayReads = 0;
            } else {
                this.todayReplies = getCfg('stat_replies', 0);
                this.todayReads = getCfg('stat_reads', 0);
            }

            // 黑名单/已读列表
            this.repliedList = getCfg('replied_list', []);
            this.readList = getCfg('read_list', []);
        }

        initDOM() {
            // 注入 CSS 样式
            const styleEl = document.createElement('style');
            styleEl.innerHTML = STYLE;
            document.head.appendChild(styleEl);

            // 创建控制面板
            this.panel = document.createElement('div');
            this.panel.className = 'nv-panel';
            this.panel.id = 'nv-panel-root';
            this.panel.innerHTML = `
                <div id="nv-panel-header" class="nv-panel-header">
                    <span class="nv-panel-title">⭐ MaidUpgrade Pro v7.2</span>
                    <button id="nv-minimize-btn" class="nv-btn-icon" title="折叠">➖</button>
                </div>
                <div class="nv-panel-body">
                    <!-- 状态展示 -->
                    <div class="nv-section">
                        <div class="nv-status-row">
                            <span>每日签到:</span>
                            <span id="nv-stat-checkin" class="nv-badge nv-badge-wait">检测中...</span>
                        </div>
                        <div class="nv-status-row">
                            <span>今日水贴:</span>
                            <span id="nv-stat-replies" class="nv-badge nv-badge-info">0 / 0</span>
                        </div>
                        <div class="nv-status-row">
                            <span>今日浏览:</span>
                            <span id="nv-stat-reads" class="nv-badge nv-badge-info">0 篇</span>
                        </div>
                        <div class="nv-status-row">
                            <span>助手状态:</span>
                            <span id="nv-stat-running" class="nv-badge nv-badge-wait">未启动</span>
                        </div>
                    </div>

                    <!-- 开关控制 -->
                    <div class="nv-section">
                        <div class="nv-toggle-row">
                            <label class="nv-toggle-label">
                                <input type="checkbox" id="chk-checkin" ${this.cfgAutoCheckin ? 'checked' : ''}> 自动静默签到
                            </label>
                        </div>
                        <div class="nv-toggle-row">
                            <label class="nv-toggle-label">
                                <input type="checkbox" id="chk-read" ${this.cfgAutoRead ? 'checked' : ''}> 自动挂机浏览
                            </label>
                        </div>
                        <div class="nv-toggle-row">
                            <label class="nv-toggle-label">
                                <input type="checkbox" id="chk-reply" ${this.cfgAutoReply ? 'checked' : ''}> 自动水贴升级
                            </label>
                        </div>
                    </div>

                    <!-- 高级参数 -->
                    <div class="nv-section">
                        <div class="nv-param-row">
                            <div style="display: flex; justify-content: space-between; align-items: center;">
                                <span style="color: var(--nv-text); font-weight: 600;">每日水贴上限:</span>
                                <div style="display: flex; gap: 4px; align-items: center;">
                                    <input type="number" id="num-max-replies" class="nv-input-num" style="width: 70px;" min="1" max="500" value="${this.maxReplies}">
                                    <span style="font-size: 11px;">次</span>
                                </div>
                            </div>
                        </div>
                        <div class="nv-param-row">
                            <label>看贴等待时间 (秒):</label>
                            <div style="display: flex; gap: 8px; align-items: center;">
                                <input type="number" id="num-min" class="nv-input-num" style="width: 60px;" value="${this.readMin}">
                                <span>至</span>
                                <input type="number" id="num-max" class="nv-input-num" style="width: 60px;" value="${this.readMax}">
                                <span>秒随机</span>
                            </div>
                        </div>
                    </div>

                    <!-- 自定义词库 -->
                    <div class="nv-section">
                        <label style="font-size: 11px; margin-bottom: 2px; display: block; color: var(--nv-text-sec);">自定义回复词库 (一行一条,自动去重):</label>
                        <textarea id="txt-phrases" class="nv-textarea" rows="3">${this.phrases}</textarea>
                    </div>

                    <!-- 动态日志 -->
                    <div class="nv-section" style="padding-bottom: 0;">
                        <div id="nv-logger-box" class="nv-logs"></div>
                    </div>

                    <!-- 控制按钮 -->
                    <div class="nv-actions">
                        <button id="btn-nv-start" class="nv-btn nv-btn-primary" style="flex: 2;">🟢 开始挂机</button>
                        <button id="btn-nv-pause" class="nv-btn nv-btn-secondary" disabled>🟡 暂停</button>
                        <button id="btn-nv-reset" class="nv-btn nv-btn-secondary" title="重置今日计数与已水贴黑名单" style="flex: 1; padding: 8px 4px;">🧹 重置</button>
                    </div>
                </div>
            `;
            document.body.appendChild(this.panel);

            // 创建折叠后的迷你悬浮标
            this.collapsedIcon = document.createElement('div');
            this.collapsedIcon.className = 'nv-panel-collapsed';
            this.collapsedIcon.innerHTML = '⭐';
            this.collapsedIcon.style.display = 'none';
            document.body.appendChild(this.collapsedIcon);

            // 读取折叠状态
            this.isCollapsed = getCfg('panel_collapsed', false);
            if (this.isCollapsed) {
                this.panel.style.display = 'none';
                this.collapsedIcon.style.display = 'flex';
            }

            // 读取悬浮窗坐标并恢复位置
            const panelPos = getCfg('panel_position', null);
            if (panelPos) {
                this.panel.style.bottom = 'auto';
                this.panel.style.right = 'auto';
                this.panel.style.left = panelPos.left + 'px';
                this.panel.style.top = panelPos.top + 'px';
            }
        }

        bindEvents() {
            // 折叠与展开事件
            const minimizeBtn = this.panel.querySelector('#nv-minimize-btn');
            minimizeBtn.addEventListener('click', () => {
                this.panel.style.display = 'none';
                this.collapsedIcon.style.display = 'flex';
                setCfg('panel_collapsed', true);
            });

            this.collapsedIcon.addEventListener('click', () => {
                this.panel.style.display = 'block';
                this.collapsedIcon.style.display = 'none';
                setCfg('panel_collapsed', false);
            });

            // 绑定表单交互,实时保存配置
            const chkCheckin = this.panel.querySelector('#chk-checkin');
            chkCheckin.addEventListener('change', (e) => {
                this.cfgAutoCheckin = e.target.checked;
                setCfg('cfg_auto_checkin', this.cfgAutoCheckin);
                this.addLog(`⚙️ 自动签到开关已${this.cfgAutoCheckin ? '【开启】' : '【关闭】'}`);
                this.startDailyCheckIn();
            });

            const chkRead = this.panel.querySelector('#chk-read');
            chkRead.addEventListener('change', (e) => {
                this.cfgAutoRead = e.target.checked;
                setCfg('cfg_auto_read', this.cfgAutoRead);
                this.addLog(`⚙️ 挂机浏览开关已${this.cfgAutoRead ? '【开启】' : '【关闭】'}`);
            });

            const chkReply = this.panel.querySelector('#chk-reply');
            chkReply.addEventListener('change', (e) => {
                this.cfgAutoReply = e.target.checked;
                setCfg('cfg_auto_reply', this.cfgAutoReply);
                this.addLog(`⚙️ 自动水贴升级开关已${this.cfgAutoReply ? '【开启】' : '【关闭】'}`);
                if (this.cfgAutoReply) {
                    showToast('自动水贴已启用,请注意安全!', '#F59E0B');
                }
            });

            // 直接填写数字上限绑定
            const numMaxReplies = this.panel.querySelector('#num-max-replies');
            numMaxReplies.addEventListener('change', (e) => {
                let val = parseInt(e.target.value) || 5;
                if (val < 1) val = 1;
                this.maxReplies = val;
                numMaxReplies.value = val;
                setCfg('cfg_max_replies', this.maxReplies);
                this.updateUIStatus();
                this.addLog(`⚙️ 每日水贴上限已更新为: ${this.maxReplies} 次`);
            });
            numMaxReplies.addEventListener('input', (e) => {
                let val = parseInt(e.target.value);
                if (!isNaN(val) && val >= 1) {
                    this.maxReplies = val;
                    setCfg('cfg_max_replies', this.maxReplies);
                    this.updateUIStatus();
                }
            });

            const numMin = this.panel.querySelector('#num-min');
            const numMax = this.panel.querySelector('#num-max');
            const saveTimeConfig = () => {
                let min = parseInt(numMin.value) || 5;
                let max = parseInt(numMax.value) || 10;
                if (min > max) { min = max; numMin.value = min; }
                this.readMin = min;
                this.readMax = max;
                setCfg('cfg_read_min', min);
                setCfg('cfg_read_max', max);
                this.addLog(`⚙️ 阅读等待时间配置已更新: ${min} 至 ${max} 秒`);
            };
            numMin.addEventListener('change', saveTimeConfig);
            numMax.addEventListener('change', saveTimeConfig);

            // 词库配置全自纠错与去重过滤器
            const txtPhrases = this.panel.querySelector('#txt-phrases');
            txtPhrases.addEventListener('change', (e) => {
                const list = e.target.value.split('\n')
                    .map(p => p.trim())
                    .filter(p => p.length > 0);
                
                const uniqueList = [...new Set(list)];
                this.phrases = uniqueList.join('\n');
                txtPhrases.value = this.phrases;
                
                setCfg('cfg_phrases', this.phrases);
                this.addLog(`⚙️ 自定义回复词库已更新,已过滤空行并自动去重 (共 ${uniqueList.length} 条)`);
            });

            // 拖拽面板功能
            const header = this.panel.querySelector('#nv-panel-header');
            let isDragging = false;
            let startX, startY;

            header.addEventListener('mousedown', (e) => {
                if (e.target.tagName === 'BUTTON') return;
                isDragging = true;
                startX = e.clientX - this.panel.offsetLeft;
                startY = e.clientY - this.panel.offsetTop;
                e.preventDefault();
            });

            document.addEventListener('mousemove', (e) => {
                if (!isDragging) return;
                let left = e.clientX - startX;
                let top = e.clientY - startY;

                // 边界限制
                left = Math.max(0, Math.min(window.innerWidth - this.panel.offsetWidth, left));
                top = Math.max(0, Math.min(window.innerHeight - this.panel.offsetHeight, top));

                this.panel.style.bottom = 'auto';
                this.panel.style.right = 'auto';
                this.panel.style.left = left + 'px';
                this.panel.style.top = top + 'px';
            });

            document.addEventListener('mouseup', () => {
                if (isDragging) {
                    isDragging = false;
                    // 保存悬浮面板坐标
                    setCfg('panel_position', {
                        left: this.panel.offsetLeft,
                        top: this.panel.offsetTop
                    });
                }
            });

            // 开始、暂停与重置按钮
            const btnStart = this.panel.querySelector('#btn-nv-start');
            const btnPause = this.panel.querySelector('#btn-nv-pause');
            const btnReset = this.panel.querySelector('#btn-nv-reset');

            btnStart.addEventListener('click', () => {
                this.isRunning = true;
                setCfg('is_running', true);
                btnStart.disabled = true;
                btnPause.disabled = false;
                this.addLog('🟢 挂机升级助手启动中...');
                showToast('升级助手已启动!', '#10B981');
                this.updateUIStatus();
                this.runRoutine();
            });

            btnPause.addEventListener('click', () => {
                this.isRunning = false;
                setCfg('is_running', false);
                btnStart.disabled = false;
                btnPause.disabled = true;
                this.addLog('🟡 挂机升级助手暂停,可随时继续!');
                showToast('已暂停挂机!', '#F59E0B');
                this.updateUIStatus();
            });

            btnReset.addEventListener('click', () => {
                if (confirm('🧹 确认清空今日统计数据以及“已水贴”黑名单缓存吗?\n(重置后可对同一个帖子重新挂机测试)')) {
                    this.todayReplies = 0;
                    this.todayReads = 0;
                    this.repliedList = [];
                    this.readList = [];
                    setCfg('stat_replies', 0);
                    setCfg('stat_reads', 0);
                    setCfg('replied_list', []);
                    setCfg('read_list', []);
                    this.updateUIStatus();
                    this.addLog('🧹 本地计数与已回复黑名单缓存已全部清空重置!');
                    showToast('重置成功!', '#3B82F6');
                }
            });

            if (this.isRunning) {
                btnStart.disabled = true;
                btnPause.disabled = false;
            }
        }

        // 添加日志到控制台
        addLog(msg) {
            const loggerBox = this.panel.querySelector('#nv-logger-box');
            if (loggerBox) {
                const time = new Date().toLocaleTimeString();
                const logItem = document.createElement('div');
                logItem.textContent = `[${time}] ${msg}`;
                loggerBox.appendChild(logItem);
                // 限制最多 50 行,防止DOM过多卡顿
                while (loggerBox.children.length > 50) {
                    loggerBox.children[0].remove();
                }
                loggerBox.scrollTop = loggerBox.scrollHeight;
            }
            console.log(`[MaidUpgrade Pro] ${msg}`);
        }

        // 更新状态数据面板显示
        updateUIStatus() {
            const badgeCheckin = this.panel.querySelector('#nv-stat-checkin');
            const badgeReplies = this.panel.querySelector('#nv-stat-replies');
            const badgeReads = this.panel.querySelector('#nv-stat-reads');
            const badgeRunning = this.panel.querySelector('#nv-stat-running');

            // 签到状态
            const isTodayChecked = localStorage.getItem(`nv_checkin_${this.today}`);
            if (isTodayChecked) {
                badgeCheckin.className = 'nv-badge nv-badge-success';
                badgeCheckin.textContent = '已签到';
            } else {
                badgeCheckin.className = 'nv-badge nv-badge-warn';
                badgeCheckin.textContent = '未签到';
            }

            // 水贴进度
            badgeReplies.textContent = `${this.todayReplies} / ${this.maxReplies}`;
            if (this.todayReplies >= this.maxReplies) {
                badgeReplies.className = 'nv-badge nv-badge-success';
            } else {
                badgeReplies.className = 'nv-badge nv-badge-info';
            }

            // 浏览计数
            badgeReads.textContent = `${this.todayReads} 篇`;

            // 运行状态
            if (this.isRunning) {
                badgeRunning.className = 'nv-badge nv-badge-success';
                badgeRunning.textContent = '正在运行';
            } else {
                badgeRunning.className = 'nv-badge nv-badge-wait';
                badgeRunning.textContent = '已暂停';
            }
        }

        // 每日签到(静默 API 版)
        async startDailyCheckIn() {
            if (!this.cfgAutoCheckin) return;
            const checkinKey = `nv_checkin_${this.today}`;
            if (localStorage.getItem(checkinKey)) {
                this.updateUIStatus();
                return;
            }

            // 检查登录状态
            if (!this.checkLoginStatus()) {
                this.addLog('⚠️ 未检测到登录状态,放弃签到API调用');
                return;
            }

            this.addLog('📡 正在静默向 API 发送每日签到请求...');
            try {
                const resp = await fetch('https://bbs.bt.sb/api/check-in', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'include', // 携带 Cookie 凭证
                    body: JSON.stringify({ action: 'check-in' })
                });

                if (resp.ok) {
                    const data = await resp.json().catch(() => ({}));
                    const payload = data?.data || data;
                    localStorage.setItem(checkinKey, '1');
                    this.updateUIStatus();

                    if (payload.alreadyCheckedIn) {
                        this.addLog('📌 今日已经签过到啦!');
                        showToast('今日已签到!', '#3B82F6');
                    } else {
                        const reward = payload.points || payload.reward || '';
                        const streak = payload.currentStreak ? `(连续${payload.currentStreak}天)` : '';
                        this.addLog(`✅ 签到成功!额外奖励 +${reward} 积分 ${streak}`);
                        showToast(`签到成功!+${reward} 积分`, '#10B981');
                    }
                } else if (resp.status === 401 || resp.status === 403) {
                    this.addLog('⚠️ 签到失败:无访问权限,请检查是否处于登录状态。');
                } else {
                    this.addLog(`⚠️ 签到异常:HTTP ${resp.status}`);
                }
            } catch (err) {
                this.addLog(`❌ 签到请求失败:${err.message}`);
            }
        }

        // 检测用户登录状态
        checkLoginStatus() {
            const loginBtn = document.querySelector('a[href="/login"]');
            if (loginBtn && loginBtn.offsetParent !== null) {
                return false; // 有登录按钮且可见,代表未登录
            }
            const avatar = document.querySelector('[class*="avatar"], [class*="Avatar"], img[alt*="avatar"]');
            if (avatar) return true;
            return !loginBtn;
        }

        // ═══════════════════════════════════════════
        //  核 心 控制 流 (Routine)
        // ═══════════════════════════════════════════
        async runRoutine() {
            if (!this.isRunning) return;

            // 首先执行签到
            await this.startDailyCheckIn();

            // 如果挂机浏览没有开启,我们什么也不做
            if (!this.cfgAutoRead) {
                this.addLog('💤 自动挂机浏览未开启,无任务。');
                return;
            }

            // 判断当前所在页面
            if (getCurrentUrl().includes('/posts/')) {
                // 在帖子详情页,执行看帖/回复逻辑
                await this.handlePostPage();
            } else {
                // 在列表页/首页,寻找符合条件的新帖并跳转
                await this.handleListPage();
            }
        }

        // 列表页处理逻辑:寻找可以浏览的新贴(CUID 深度去重新机制)
        async handleListPage() {
            this.addLog('🔍 正在扫描可用帖子链接...');
            await delay(1500);

            // 1. 获取 DOM 中所有包含帖子详情的 a 标签链接
            let rawLinks = Array.from(document.querySelectorAll('a'))
                .map(a => a.href)
                .filter(href => href && href.includes('/posts/') && !href.endsWith('/posts/'));

            // 2. 按 CUID 对候选帖子进行完全去重与筛选,排除已回复和已读的帖子
            let seenPids = new Set();
            let candidates = [];

            for (let url of rawLinks) {
                const pid = extractPostId(url);
                if (!pid) continue;
                
                // 如果当前循环已遇到该 CUID,或者该 CUID 已在本地回复/阅读黑名单中,彻底排除
                if (seenPids.has(pid)) continue;
                if (this.repliedList.includes(pid)) continue;
                if (this.readList.includes(pid)) continue;
                
                seenPids.add(pid);
                candidates.push({ pid, url });
            }

            // 3. 兜底策略:如果全部链接都处理过了,但在列表页里还有没回复过的帖子,可以选择只读过但没回复的帖子
            if (candidates.length === 0) {
                seenPids.clear();
                for (let url of rawLinks) {
                    const pid = extractPostId(url);
                    if (!pid) continue;
                    
                    if (seenPids.has(pid)) continue;
                    if (this.repliedList.includes(pid)) continue;
                    
                    seenPids.add(pid);
                    candidates.push({ pid, url });
                }
            }

            if (candidates.length > 0) {
                // 🎲 真正的无偏物理随机数摇号!
                let selected = candidates[Math.floor(Math.random() * candidates.length)];
                let targetPost = selected.url;
                
                this.addLog(`🎲 随机锁定未处理帖子 (共 ${candidates.length} 个候选),2秒后跳转...`);
                await delay(2000);
                window.location.href = targetPost;
                
                // SPA 挂靠:延时触发强制刷新,以重置油猴运行环境
                setTimeout(() => {
                    if (window.location.href !== getCurrentUrl()) window.location.reload();
                }, 500);
            } else {
                this.addLog('⚠️ 当前页面所有的帖子你都已经回复过了!10秒后尝试刷新页面寻找新帖子...');
                await delay(10000);
                if (this.isRunning) {
                    window.location.reload();
                }
            }
        }

        // 帖子详情页处理逻辑:看帖模拟和回复
        async handlePostPage() {
            this.addLog('📖 已进入帖子,开始模拟人类阅读行为...');
            await delay(1500);

            // 模拟人类平滑向下滚动阅读
            const scrollHeight = document.body.scrollHeight;
            this.addLog('📜 平滑滚动至帖子中部...');
            window.scrollTo({ top: scrollHeight * 0.4, behavior: 'smooth' });
            await delay(2000);

            this.addLog('📜 平滑滚动至评论区...');
            window.scrollTo({ top: scrollHeight * 0.85, behavior: 'smooth' });
            await delay(3000);

            // 随机等待时间计算
            const waitTime = Math.floor(Math.random() * (this.readMax - this.readMin + 1)) + this.readMin;

            // 获取当前帖子唯一的 Post CUID
            const currentPostId = extractPostId(getCurrentUrl());

            // 是否需要自动水贴回复?
            const canWater = this.cfgAutoReply &&
                             (this.todayReplies < this.maxReplies) &&
                             currentPostId && 
                             !this.repliedList.includes(currentPostId);

            if (canWater) {
                this.addLog(`💬 计划在本帖子下水贴。随机阅读倒计时: ${waitTime} 秒后自动输入回复...`);
                await this.countdown(waitTime);

                if (!this.isRunning) return;

                // 执行回复
                const replySuccess = await this.executeReply();
                if (replySuccess) {
                    this.todayReplies++;
                    setCfg('stat_replies', this.todayReplies);
                    
                    if (currentPostId) this.repliedList.push(currentPostId);
                    if (this.repliedList.length > 500) this.repliedList.shift();
                    setCfg('replied_list', this.repliedList);
                    
                    this.updateUIStatus();
                    this.addLog(`✅ 水贴任务完成!今日水贴已达: ${this.todayReplies} / ${this.maxReplies}`);
                }
            } else {
                // 纯挂机浏览模式
                this.addLog(`👀 纯阅读挂机中。随机阅读倒计时: ${waitTime} 秒...`);
                await this.countdown(waitTime);

                if (!this.isRunning) return;

                this.todayReads++;
                setCfg('stat_reads', this.todayReads);
                
                if (currentPostId) this.readList.push(currentPostId);
                if (this.readList.length > 500) this.readList.shift();
                setCfg('read_list', this.readList);
                
                this.updateUIStatus();
                this.addLog(`✅ 成功阅读此帖,今日已刷 ${this.todayReads} 篇。`);
            }

            // 结束,返回首页继续下一个
            this.addLog('🚀 任务结束,3秒后返回主页寻找下一个目标帖子...');
            await delay(3000);
            if (this.isRunning) {
                window.location.href = 'https://bbs.bt.sb/';
                setTimeout(() => {
                    window.location.reload();
                }, 500);
            }
        }

        // 倒计时动画提示
        async countdown(seconds) {
            const badgeRunning = this.panel.querySelector('#nv-stat-running');
            for (let i = seconds; i > 0; i--) {
                if (!this.isRunning) return;
                badgeRunning.className = 'nv-badge nv-badge-warn';
                badgeRunning.textContent = `倒计时 ${i}s`;
                await delay(1000);
            }
            if (this.isRunning) {
                badgeRunning.className = 'nv-badge nv-badge-success';
                badgeRunning.textContent = '正在运行';
            }
        }

        // 执行发帖/回复动作 (100% 稳定的 API 静默评论机制)
        async executeReply() {
            this.addLog('✍️ 正在筹备回复任务...');
            
            // 从 URL 提取 postId (Rhex 论坛帖子 25位纯净 CUID)
            let postId = extractPostId(window.location.href);

            // 获取回复的随机文本
            const phraseList = this.phrases.split('\n').map(p => p.trim()).filter(p => p.length > 0);
            const finalPhrases = phraseList.length > 0 ? phraseList : DEFAULT_PHRASES;
            const randomText = finalPhrases[Math.floor(Math.random() * finalPhrases.length)];

            // 【第一选择:API 静默提交】直接秒发,100% 成功!
            if (postId && postId.startsWith('cmp')) {
                this.addLog(`📡 锁定 Post CUID: ${postId},正在通过后台 API 发送评论回复...`);
                try {
                    const resp = await fetch('https://bbs.bt.sb/api/comments/create', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        credentials: 'include', // 携带当前已登录的 Cookie 凭证
                        body: JSON.stringify({
                            postId: postId,
                            content: randomText,
                            useAnonymousIdentity: false, // 设为 false 以便将经验/积分加给您的账号
                            commentView: 'tree'
                        })
                    });

                    if (resp.ok) {
                        const data = await resp.json().catch(() => ({}));
                        this.addLog(`✅ API 静默回复成功!内容: "${randomText}"`);
                        showToast(`静默回复成功!`, '#10B981');
                        return true;
                    } else {
                        this.addLog(`⚠️ API 回复返回异常 (HTTP ${resp.status}),准备滑入 DOM 模拟兜底方案...`);
                    }
                } catch (err) {
                    this.addLog(`⚠️ API 提交网络异常: ${err.message},准备滑入 DOM 模拟兜底...`);
                }
            } else {
                this.addLog('⚠️ 未能在 URL 中识别到有效的 Post CUID,正在滑入 DOM 模拟兜底方案...');
            }

            // 【第二选择:DOM 模拟兜底】
            let textarea = document.querySelector('.ProseMirror') || 
                           document.querySelector('[contenteditable="true"]') ||
                           Array.from(document.querySelectorAll('textarea')).find(el => el.offsetWidth > 0 || el.offsetHeight > 0);

            if (!textarea) {
                this.addLog('📜 正在向下滑动以加载评论区...');
                window.scrollTo(0, document.body.scrollHeight);
                await delay(1500);
                textarea = document.querySelector('.ProseMirror') || 
                           document.querySelector('[contenteditable="true"]') ||
                           Array.from(document.querySelectorAll('textarea')).find(el => el.offsetWidth > 0 || el.offsetHeight > 0);
            }

            if (!textarea) {
                this.addLog('❌ 无法定位到评论输入框,挂机跳过该贴。');
                return false;
            }

            // 锁定按钮
            let container = textarea.closest('form') || 
                            textarea.closest('div[class*="editor"]') || 
                            textarea.closest('div[class*="reply"]') || 
                            textarea.closest('div[class*="comment"]') ||
                            textarea.parentElement?.parentElement?.parentElement || 
                            document.body;

            this.addLog('✍️ 正在局部定位对应的提交按钮...');
            let submitBtn = container.querySelector('button[type="submit"]') ||
                Array.from(container.querySelectorAll('button')).find(el => {
                    const txt = el.textContent.trim();
                    return txt === '回复' || txt === '发送' || txt.includes('发表') || txt.includes('提交');
                });

            if (!submitBtn) {
                submitBtn = document.querySelector('button[type="submit"]') ||
                    Array.from(document.querySelectorAll('button')).find(el => {
                        const txt = el.textContent.trim();
                        return txt === '回复' || txt === '发送' || txt.includes('发表回复') || txt.includes('提交');
                    });
            }

            if (textarea && submitBtn) {
                let focusTarget = textarea.querySelector('p') || textarea;
                focusTarget.focus();
                await delay(300);

                try {
                    if (textarea.tagName === 'TEXTAREA') {
                        textarea.select();
                    } else {
                        const range = document.createRange();
                        range.selectNodeContents(textarea);
                        const sel = window.getSelection();
                        sel.removeAllRanges();
                        sel.addRange(range);
                    }
                } catch (e) {
                    this.addLog('⚠️ 模拟文本选择时遇到轻微异常,继续输入...');
                }

                await delay(200);

                if (textarea.tagName === 'TEXTAREA') {
                    setReactInputValue(textarea, randomText);
                } else {
                    try {
                        const beforeInputEvent = new InputEvent('beforeinput', {
                            inputType: 'insertText',
                            data: randomText,
                            bubbles: true,
                            cancelable: true
                        });
                        focusTarget.dispatchEvent(beforeInputEvent);
                    } catch (e) {
                        console.warn('beforeinput 尝试失败:', e);
                    }
                    await delay(100);

                    if (!textarea.textContent.includes(randomText)) {
                        try {
                            document.execCommand('insertText', false, randomText);
                        } catch (err) {
                            console.warn('execCommand 尝试失败:', err);
                        }
                        await delay(100);
                    }

                    if (!textarea.textContent.includes(randomText)) {
                        try {
                            const dataTransfer = new DataTransfer();
                            dataTransfer.setData('text/plain', randomText);
                            const pasteEvent = new ClipboardEvent('paste', {
                                clipboardData: dataTransfer,
                                bubbles: true,
                                cancelable: true
                            });
                            focusTarget.dispatchEvent(pasteEvent);
                        } catch (err) {
                            console.warn('Paste 事件模拟失败:', err);
                        }
                    }
                    
                    textarea.dispatchEvent(new Event('input', { bubbles: true }));
                    textarea.dispatchEvent(new Event('change', { bubbles: true }));
                }

                this.addLog(`📝 输入拟真词汇: "${randomText}"`);
                await delay(2000); 

                if (!this.isRunning) return false;

                if (submitBtn.disabled) {
                    this.addLog('⚠️ 发现发送按钮处于锁定状态,尝试激活...');
                    submitBtn.disabled = false;
                }

                this.addLog('📡 正在提交回复...');
                submitBtn.focus();
                submitBtn.click();
                submitBtn.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
                
                await delay(4000);
                return true;
            } else {
                this.addLog('❌ 定位回复框或提交按钮失败,可能该贴被锁定、已被禁言或未登录。');
                return false;
            }
        }
    }

    // ═══════════════════════════════════════════
    //  单 页 路 由 (SPA) 无 感 双 向 监 听
    // ═══════════════════════════════════════════
    let lastUrl = window.location.href;
    setInterval(() => {
        if (window.location.href !== lastUrl) {
            const oldUrl = lastUrl;
            lastUrl = window.location.href;
            
            const oldIsPost = oldUrl.includes('/posts/');
            const newIsPost = lastUrl.includes('/posts/');
            if (oldIsPost !== newIsPost) {
                console.error('🔄 [MaidUpgrade Pro] 检测到 Next.js SPA 路由切换,物理刷新重置油猴环境...');
                window.location.reload();
            }
        }
    }, 500);

    // ═══════════════════════════════════════════
    //  启 动 入 口
    // ═══════════════════════════════════════════
    function startPlugin() {
        if (window.nvUpgradeHelper) return; 
        window.nvUpgradeHelper = new UpgradeHelper();
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        startPlugin();
    } else {
        window.addEventListener('DOMContentLoaded', startPlugin);
        window.addEventListener('load', startPlugin);
    }
})();