Greasy Fork

来自缓存

Greasy Fork is available in English.

夸克资源助手

单面板多标签页设计,集成智能回帖和资源采集功能。支持快速回帖、批量回帖(1-50个)、智能去重、随机回复内容;支持内容提取、图片Base64转换、文章状态检测、多账号切换、服务器上传。可拖拽面板,可最小化为圆形按钮,标签页状态记忆。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         夸克资源助手
// @namespace    http://tampermonkey.net/
// @version      5.1.1
// @description  单面板多标签页设计,集成智能回帖和资源采集功能。支持快速回帖、批量回帖(1-50个)、智能去重、随机回复内容;支持内容提取、图片Base64转换、文章状态检测、多账号切换、服务器上传。可拖拽面板,可最小化为圆形按钮,标签页状态记忆。
// @match        https://kuafuzys.net/*
// @match        https://www.kuafuzy.com/*
// @match        https://www.kuakesou.com/*
// @match        https://www.kuakeq.com/*
// @match        https://kuakezy.cc/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @author       PYY
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    // ========================================
    // 配置模块
    // ========================================
    const CONFIG = {
        // 选择器配置
        selectors: {
            replyTextarea: '#message',
            replySubmitBtn: '#submit',
            threadList: 'ul.threadlist li.media.thread .style3_subject a[href^="thread-"]'
        },

        // 随机回复内容池
        replyTemplates: [
            "感谢分享,非常不错的资源!",
            "太棒了,正好需要这个!",
            "优秀的内容,支持楼主!",
            "收藏了,感谢分享!",
            "这个资源很实用,赞一个!",
            "好东西,感谢楼主的分享!",
            "非常感谢,辛苦了!",
            "很有帮助,支持一下!"
        ],

        // 延迟配置(毫秒)
        delays: {
            beforeSubmit: 800,      // 提交前等待
            afterSubmit: 2000,      // 提交后等待
            betweenPosts: 3000,     // 批量回帖间隔
            pageLoad: 1000          // 页面加载等待
        },

        // 限制配置
        limits: {
            maxBatchCount: 50,      // 单次批量最大数量
            maxLogEntries: 100,     // 最大日志条数
            maxPageAttempts: 30     // 最大翻页尝试
        },

        // 存储键名
        storageKeys: {
            repliedThreads: 'replied_threads_v5',
            batchQueue: 'batch_queue_v5',
            batchMode: 'batch_mode_v5',
            batchCount: 'batch_count_v5',
            logs: 'logs_v5',
            statusText: 'status_text_v5',
            failedAttempts: 'failed_attempts_v5',
            bindCookieId: 'quark_tool_bindCookieId',
            currentTab: 'current_tab_v5',
            panelMinimized: 'panel_minimized_v5'
        },

        // 采集配置
        collection: {
            serverUrl: "https://zys.52huahua.cn/api/biz/collection/save",
            checkUrl: "https://zys.52huahua.cn/api/biz/collection/isExist",
            platform: "kkwpzys",
            accounts: [
                { label: "我想我是海", value: "1896186752012374017" },
                { label: "书生", value: "1900922270486798338" },
                { label: "海海游戏社", value: "1900354501367640065" }
            ]
        }
    };

    // ========================================
    // 工具函数模块
    // ========================================
    const Utils = {
        // 延迟函数
        delay: (ms) => new Promise(resolve => setTimeout(resolve, ms)),

        // 随机延迟
        randomDelay: (min, max) => {
            const ms = min + Math.random() * (max - min);
            return Utils.delay(ms);
        },

        // 获取随机回复内容
        getRandomReply: () => {
            const templates = CONFIG.replyTemplates;
            return templates[Math.floor(Math.random() * templates.length)];
        },

        // 解析帖子ID
        parseThreadId: (url) => {
            const match = url.match(/thread-(\d+)(-\d+-\d+)?\.htm/);
            return match ? match[1] : null;
        },

        // 检查是否为帖子详情页
        isThreadPage: () => {
            return /\/thread-\d+(-\d+-\d+)?\.htm/.test(location.href);
        },

        // 检查是否为用户列表页
        isUserListPage: () => {
            return /\/user-thread-\d+(-\d+)?\.htm/.test(location.href);
        },

        // 格式化日期为 YYYY-MM-DD HH:mm:ss
        formatDateTime: (date) => {
            const pad = (n) => String(n).padStart(2, '0');
            return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
        },

        // XPath 辅助函数 - 获取单个元素
        getElementByXPath: (xpath) => {
            try {
                const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
                return result.singleNodeValue;
            } catch (e) {
                console.error("XPath 错误:", e);
                return null;
            }
        },

        // XPath 辅助函数 - 获取所有匹配的元素
        getElementsByXPath: (xpath) => {
            try {
                const result = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
                const elements = [];
                for (let i = 0; i < result.snapshotLength; i++) {
                    elements.push(result.snapshotItem(i));
                }
                return elements;
            } catch (e) {
                console.error("XPath 错误:", e);
                return [];
            }
        }
    };

    // ========================================
    // 存储管理模块
    // ========================================
    const Storage = {
        // 获取已回帖列表
        getRepliedThreads: () => {
            return GM_getValue(CONFIG.storageKeys.repliedThreads, []) || [];
        },

        // 添加已回帖记录
        addRepliedThread: (tid) => {
            const replied = Storage.getRepliedThreads();
            if (!replied.includes(tid)) {
                replied.push(tid);
                GM_setValue(CONFIG.storageKeys.repliedThreads, replied);
            }
        },

        // 检查是否已回帖
        isReplied: (tid) => {
            return Storage.getRepliedThreads().includes(tid);
        },

        // 清空已回帖记录
        clearRepliedThreads: () => {
            GM_setValue(CONFIG.storageKeys.repliedThreads, []);
        },

        // 获取批量队列
        getBatchQueue: () => {
            return GM_getValue(CONFIG.storageKeys.batchQueue, []) || [];
        },

        // 保存批量队列
        saveBatchQueue: (queue) => {
            GM_setValue(CONFIG.storageKeys.batchQueue, queue);
        },

        // 获取批量模式状态
        isBatchMode: () => {
            return GM_getValue(CONFIG.storageKeys.batchMode, false);
        },

        // 设置批量模式
        setBatchMode: (enabled) => {
            GM_setValue(CONFIG.storageKeys.batchMode, enabled);
        },

        // 获取批量剩余数量
        getBatchCount: () => {
            return GM_getValue(CONFIG.storageKeys.batchCount, 0);
        },

        // 设置批量剩余数量
        setBatchCount: (count) => {
            GM_setValue(CONFIG.storageKeys.batchCount, count);
        },

        // 获取日志
        getLogs: () => {
            return GM_getValue(CONFIG.storageKeys.logs, []) || [];
        },

        // 保存日志
        saveLogs: (logs) => {
            GM_setValue(CONFIG.storageKeys.logs, logs);
        },

        // 添加日志
        addLog: (message, type) => {
            const logs = Storage.getLogs();
            const time = new Date().toLocaleTimeString();
            logs.unshift({ time, message, type });
            // 限制日志数量
            if (logs.length > CONFIG.limits.maxLogEntries) {
                logs.pop();
            }
            Storage.saveLogs(logs);
        },

        // 清空日志
        clearLogs: () => {
            GM_setValue(CONFIG.storageKeys.logs, []);
        },

        // 获取状态文本
        getStatusText: () => {
            return GM_getValue(CONFIG.storageKeys.statusText, '待机中');
        },

        // 设置状态文本
        setStatusText: (text) => {
            GM_setValue(CONFIG.storageKeys.statusText, text);
        },

        // 获取失败尝试次数
        getFailedAttempts: () => {
            return GM_getValue(CONFIG.storageKeys.failedAttempts, 0);
        },

        // 设置失败尝试次数
        setFailedAttempts: (count) => {
            GM_setValue(CONFIG.storageKeys.failedAttempts, count);
        },

        // 重置失败尝试次数
        resetFailedAttempts: () => {
            GM_setValue(CONFIG.storageKeys.failedAttempts, 0);
        }
    };

    // ========================================
    // 采集数据模块
    // ========================================
    const CollectionData = {
        data: null,

        // 初始化采集数据
        init: () => {
            CollectionData.data = {
                collectionPlatform: CONFIG.collection.platform,
                resourceLink: null,
                title: null,
                username: null,
                uid: null,
                content: null,
                node: null,
                tags: null,
                quarkLink: null,
                status: "1",
                createTime: Utils.formatDateTime(new Date()),
                createUser: "1543837863788879871",
                deleteFlag: "NOT_DELETE",
                bindCookieId: localStorage.getItem(CONFIG.storageKeys.bindCookieId) || CONFIG.collection.accounts[0].value
            };
        },

        // 获取采集数据
        get: () => CollectionData.data,

        // 重置采集数据
        reset: () => CollectionData.init()
    };

    // ========================================
    // UI模块
    // ========================================
    const UI = {
        panel: null,
        logContainer: null,
        collectionLogArea: null,
        currentTab: 'reply', // 当前激活的标签页

        // 初始化样式
        initStyles: () => {
            GM_addStyle(`
                #unifiedPanel {
                    position: fixed;
                    top: 100px;
                    right: 20px;
                    width: 380px;
                    background: #ffffff;
                    border: 1px solid #e0e0e0;
                    border-radius: 8px;
                    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
                    z-index: 999999;
                    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
                    font-size: 14px;
                    max-height: 80vh;
                    display: flex;
                    flex-direction: column;
                    transition: all 0.3s ease;
                }

                #unifiedPanel.minimized {
                    width: 60px;
                    height: 60px;
                    border-radius: 50%;
                    max-height: none;
                    overflow: hidden;
                    box-shadow: 0 2px 8px rgba(0,0,0,0.2);
                }

                #unifiedPanel.minimized .panel-header {
                    border-radius: 50%;
                    padding: 0;
                    width: 60px;
                    height: 60px;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    cursor: pointer;
                }

                #unifiedPanel.minimized .panel-header h3 {
                    font-size: 24px;
                    margin: 0;
                }

                #unifiedPanel.minimized .panel-header > div {
                    display: none;
                }

                #unifiedPanel.minimized .tab-nav,
                #unifiedPanel.minimized .tab-content {
                    display: none;
                }
                
                #unifiedPanel .panel-header {
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    color: white;
                    padding: 12px 15px;
                    border-radius: 8px 8px 0 0;
                    cursor: move;
                    user-select: none;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                }
                
                #unifiedPanel .panel-header h3 {
                    margin: 0;
                    font-size: 16px;
                    font-weight: 600;
                }

                /* 标签页导航 */
                #unifiedPanel .tab-nav {
                    display: flex;
                    background: #f5f5f5;
                    border-bottom: 2px solid #e0e0e0;
                }

                #unifiedPanel .tab-nav button {
                    flex: 1;
                    padding: 12px 16px;
                    border: none;
                    background: transparent;
                    cursor: pointer;
                    font-size: 14px;
                    font-weight: 500;
                    color: #666;
                    transition: all 0.3s;
                    border-bottom: 3px solid transparent;
                }

                #unifiedPanel .tab-nav button:hover {
                    background: #e8e8e8;
                    color: #333;
                }

                #unifiedPanel .tab-nav button.active {
                    color: #667eea;
                    background: #fff;
                    border-bottom-color: #667eea;
                }

                /* 标签页内容 */
                #unifiedPanel .tab-content {
                    display: none;
                    padding: 15px;
                    overflow-y: auto;
                    flex: 1;
                }

                #unifiedPanel .tab-content.active {
                    display: block;
                }

                /* 通用样式 */
                #unifiedPanel .btn-group {
                    display: flex;
                    gap: 8px;
                    margin-bottom: 12px;
                }
                
                #unifiedPanel button:not(.tab-nav button) {
                    flex: 1;
                    padding: 8px 12px;
                    border: none;
                    border-radius: 5px;
                    cursor: pointer;
                    font-size: 13px;
                    font-weight: 500;
                    transition: all 0.2s;
                }
                
                #unifiedPanel button.primary {
                    background: #667eea;
                    color: white;
                }
                
                #unifiedPanel button.primary:hover {
                    background: #5568d3;
                }
                
                #unifiedPanel button.secondary {
                    background: #f5f5f5;
                    color: #333;
                }
                
                #unifiedPanel button.secondary:hover {
                    background: #e8e8e8;
                }
                
                #unifiedPanel button.danger {
                    background: #ef5350;
                    color: white;
                }
                
                #unifiedPanel button.danger:hover {
                    background: #e53935;
                }
                
                #unifiedPanel button:disabled {
                    opacity: 0.5;
                    cursor: not-allowed;
                }
                
                #unifiedPanel .input-group {
                    margin-bottom: 12px;
                }
                
                #unifiedPanel input, #unifiedPanel select {
                    width: 100%;
                    padding: 8px 12px;
                    border: 1px solid #ddd;
                    border-radius: 5px;
                    font-size: 13px;
                    box-sizing: border-box;
                }
                
                #unifiedPanel input:focus, #unifiedPanel select:focus {
                    outline: none;
                    border-color: #667eea;
                }
                
                #unifiedPanel .divider {
                    height: 1px;
                    background: #e0e0e0;
                    margin: 12px 0;
                }
                
                #unifiedPanel .log-container, #unifiedPanel .log-area {
                    max-height: 200px;
                    overflow-y: auto;
                    background: #f9f9f9;
                    border-radius: 5px;
                    padding: 8px;
                    font-size: 12px;
                }
                
                #unifiedPanel .log-entry {
                    margin: 4px 0;
                    padding: 4px 6px;
                    border-radius: 3px;
                    line-height: 1.4;
                }
                
                #unifiedPanel .log-entry.info {
                    color: #333;
                }
                
                #unifiedPanel .log-entry.success {
                    color: #2e7d32;
                    background: #e8f5e9;
                }
                
                #unifiedPanel .log-entry.error {
                    color: #c62828;
                    background: #ffebee;
                }
                
                #unifiedPanel .log-entry .time {
                    color: #999;
                    font-size: 11px;
                    margin-right: 6px;
                }
                
                #unifiedPanel .status-bar, #unifiedPanel .status-container {
                    padding: 8px 12px;
                    background: #f5f5f5;
                    border-radius: 5px;
                    margin-bottom: 12px;
                    font-size: 12px;
                    color: #666;
                }
                
                #unifiedPanel .status-bar .label {
                    font-weight: 600;
                    color: #333;
                }

                #unifiedPanel .status-light {
                    width: 12px;
                    height: 12px;
                    border-radius: 50%;
                    background: #ccc;
                    display: inline-block;
                    margin-left: 10px;
                    vertical-align: middle;
                    transition: background 0.3s ease;
                }

                #unifiedPanel .status-text {
                    margin-left: 5px;
                    vertical-align: middle;
                    font-size: 12px;
                    color: #666;
                }
            `);
        },

        // 创建统一面板
        createPanel: () => {
            const panel = document.createElement('div');
            panel.id = 'unifiedPanel';
            panel.innerHTML = `
                <div class="panel-header">
                    <h3>🚀 夸克资源助手</h3>
                    <div>
                        <span style="cursor: pointer; margin-right: 10px;" id="panelMinimize">−</span>
                        <span style="cursor: pointer;" id="panelClose">✕</span>
                    </div>
                </div>
                
                <!-- 标签页导航 -->
                <div class="tab-nav">
                    <button class="tab-btn active" data-tab="reply">💬 回帖</button>
                    <button class="tab-btn" data-tab="collection">📦 采集</button>
                </div>
                
                <!-- 回帖标签页 -->
                <div class="tab-content active" id="tab-reply">
                    <div class="status-bar">
                        <span class="label">状态:</span><span id="statusText">待机中</span>
                    </div>
                    
                    <div class="input-group">
                        <input type="number" id="userIdInput" placeholder="输入用户ID">
                    </div>
                    
                    <div class="btn-group">
                        <button class="secondary" id="btnGoToUser">跳转列表</button>
                        <button class="primary" id="btnQuickReply">快速回帖</button>
                    </div>
                    
                    <div class="divider"></div>
                    
                    <div class="btn-group">
                        <button class="secondary" id="btnBatchReply">批量回帖</button>
                    </div>
                    
                    <div class="input-group" id="batchInputGroup" style="display:none;">
                        <input type="number" id="batchCount" placeholder="输入批量回帖数量 (1-50)" min="1" max="50">
                    </div>
                    
                    <div class="btn-group" id="batchControlGroup" style="display:none;">
                        <button class="primary" id="btnStartBatch">开始批量</button>
                        <button class="danger" id="btnStopBatch">停止</button>
                    </div>
                    
                    <div class="divider"></div>
                    
                    <div class="btn-group">
                        <button class="secondary" id="btnClearHistory">清空记录</button>
                        <button class="secondary" id="btnViewStats">查看统计</button>
                    </div>
                    
                    <div class="divider"></div>
                    
                    <div class="log-container" id="logContainer"></div>
                </div>
                
                <!-- 采集标签页 -->
                <div class="tab-content" id="tab-collection">
                    <div class="status-container">
                        <strong>文章状态:</strong>
                        <div class="status-light" id="status-light"></div>
                        <span class="status-text" id="status-text">未检查</span>
                    </div>
                    
                    <div class="input-group">
                        <label style="display:block;margin-bottom:5px;font-weight:500;">绑定账号</label>
                        <select id="account-selector"></select>
                    </div>
                    
                    <div class="divider"></div>
                    
                    <div class="btn-group">
                        <button class="primary" id="btnQuickReply2">快速回帖</button>
                        <button class="primary" id="btnExtract">提取内容</button>
                    </div>
                    
                    <div class="btn-group">
                        <button class="secondary" id="btnShowData">查看数据</button>
                        <button class="secondary" id="btnUpload">上传服务器</button>
                    </div>
                    
                    <div class="divider"></div>
                    
                    <div class="log-area" id="collectionLogArea"></div>
                </div>
            `;
            
            document.body.appendChild(panel);
            UI.panel = panel;
            UI.logContainer = panel.querySelector('#logContainer');
            UI.collectionLogArea = panel.querySelector('#collectionLogArea');
            
            // 恢复上次使用的标签页
            const savedTab = GM_getValue(CONFIG.storageKeys.currentTab, 'reply');
            UI.currentTab = savedTab;
            
            // 如果保存的不是默认标签,需要切换
            if (savedTab !== 'reply') {
                const tabBtns = panel.querySelectorAll('.tab-nav button');
                const tabContents = panel.querySelectorAll('.tab-content');
                
                tabBtns.forEach(btn => {
                    if (btn.getAttribute('data-tab') === savedTab) {
                        btn.classList.add('active');
                    } else {
                        btn.classList.remove('active');
                    }
                });
                
                tabContents.forEach(content => {
                    if (content.id === `tab-${savedTab}`) {
                        content.classList.add('active');
                    } else {
                        content.classList.remove('active');
                    }
                });
            }
            
            // 恢复最小化状态
            const isMinimized = GM_getValue(CONFIG.storageKeys.panelMinimized, false);
            if (isMinimized) {
                panel.classList.add('minimized');
                // 最小化状态下按钮会被隐藏,所以不需要改变文本
            }
            
            // 初始化采集账号选择器
            UI.initAccountSelector();
            
            // 绑定事件
            UI.bindEvents();
            UI.bindTabEvents();
            
            // 使面板可拖拽
            UI.makeDraggable(panel, panel.querySelector('.panel-header'));
        },

        // 绑定事件
        bindEvents: () => {
            // 跳转到用户列表页
            document.getElementById('btnGoToUser').onclick = () => {
                const userId = document.getElementById('userIdInput').value.trim();
                if (!userId) {
                    UI.log('请输入用户ID', 'error');
                    return;
                }
                if (!/^\d+$/.test(userId)) {
                    UI.log('用户ID必须是数字', 'error');
                    return;
                }
                UI.log(`跳转到用户 ${userId} 的帖子列表`, 'info');
                // 使用当前域名而不是硬编码
                const currentOrigin = window.location.origin;
                location.href = `${currentOrigin}/user-thread-${userId}.htm`;
            };
            
            // 快速回帖按钮
            document.getElementById('btnQuickReply').onclick = () => {
                ReplyHandler.quickReply();
            };
            
            // 批量回帖按钮
            document.getElementById('btnBatchReply').onclick = () => {
                UI.toggleBatchMode();
            };
            
            // 开始批量
            document.getElementById('btnStartBatch').onclick = () => {
                const count = parseInt(document.getElementById('batchCount').value);
                if (!count || count < 1 || count > CONFIG.limits.maxBatchCount) {
                    UI.log(`请输入有效的数量 (1-${CONFIG.limits.maxBatchCount})`, 'error');
                    return;
                }
                ReplyHandler.startBatch(count);
            };
            
            // 停止批量
            document.getElementById('btnStopBatch').onclick = () => {
                ReplyHandler.stopBatch();
            };
            
            // 清空记录
            document.getElementById('btnClearHistory').onclick = () => {
                if (confirm('确定要清空所有回帖记录、日志和队列吗?')) {
                    Storage.clearRepliedThreads();
                    Storage.clearLogs();
                    Storage.saveBatchQueue([]);
                    Storage.setBatchMode(false);
                    Storage.setBatchCount(0);
                    Storage.resetFailedAttempts();
                    if (UI.logContainer) {
                        UI.logContainer.innerHTML = '';
                    }
                    UI.log('已清空所有记录', 'success');
                    UI.updateStatus('待机中');
                    UI.setButtonsDisabled(false);
                }
            };
            
            // 查看统计
            document.getElementById('btnViewStats').onclick = () => {
                const replied = Storage.getRepliedThreads();
                UI.log(`已回帖数量:${replied.length} 个`, 'info');
            };
            
            // 最小化/最大化面板
            const toggleMinimize = (forceRestore = false) => {
                const isMinimized = UI.panel.classList.contains('minimized');
                const minimizeBtn = document.getElementById('panelMinimize');
                
                if (isMinimized || forceRestore) {
                    // 恢复 - 重置位置到右上角避免溢出
                    UI.panel.classList.remove('minimized');
                    UI.panel.style.top = '100px';
                    UI.panel.style.right = '20px';
                    UI.panel.style.left = 'auto';
                    if (minimizeBtn) minimizeBtn.textContent = '−';
                    GM_setValue(CONFIG.storageKeys.panelMinimized, false);
                } else {
                    // 最小化
                    UI.panel.classList.add('minimized');
                    if (minimizeBtn) minimizeBtn.textContent = '−';
                    GM_setValue(CONFIG.storageKeys.panelMinimized, true);
                }
            };

            // 最小化按钮点击
            document.getElementById('panelMinimize').onclick = (e) => {
                e.stopPropagation();
                toggleMinimize();
            };

            // 最小化状态下点击面板头部恢复(但拖拽时不展开)
            UI.panel.querySelector('.panel-header').addEventListener('click', (e) => {
                if (UI.panel.classList.contains('minimized')) {
                    // 检查是否刚刚拖拽过
                    if (UI.panel._hasMoved && UI.panel._hasMoved()) {
                        return; // 如果是拖拽,不展开
                    }
                    e.stopPropagation();
                    toggleMinimize(true);
                }
            });

            // 关闭面板
            document.getElementById('panelClose').onclick = () => {
                UI.panel.style.display = 'none';
            };

            // 采集功能按钮
            document.getElementById('btnQuickReply2').onclick = () => ReplyHandler.quickReply();
            document.getElementById('btnExtract').onclick = () => Collector.extractAll();
            document.getElementById('btnUpload').onclick = () => Collector.uploadServer();
            document.getElementById('btnShowData').onclick = () => Collector.showData();
        },

        // 绑定标签页切换事件
        bindTabEvents: () => {
            const tabBtns = UI.panel.querySelectorAll('.tab-nav button');
            const tabContents = UI.panel.querySelectorAll('.tab-content');
            
            console.log('绑定标签页事件,找到按钮数量:', tabBtns.length);
            
            tabBtns.forEach((btn, index) => {
                console.log(`按钮${index}:`, btn.getAttribute('data-tab'));
                btn.addEventListener('click', (e) => {
                    e.preventDefault();
                    const targetTab = btn.getAttribute('data-tab');
                    console.log('点击标签:', targetTab);
                    
                    // 移除所有active类
                    tabBtns.forEach(b => b.classList.remove('active'));
                    tabContents.forEach(c => c.classList.remove('active'));
                    
                    // 添加active类到当前标签
                    btn.classList.add('active');
                    const targetContent = document.getElementById(`tab-${targetTab}`);
                    if (targetContent) {
                        targetContent.classList.add('active');
                        console.log('切换到标签:', targetTab);
                    } else {
                        console.error('未找到标签内容:', `tab-${targetTab}`);
                    }
                    
                    // 保存当前标签到本地存储
                    UI.currentTab = targetTab;
                    GM_setValue(CONFIG.storageKeys.currentTab, targetTab);
                });
            });
        },

        // 初始化账号选择器
        initAccountSelector: () => {
            const accountSelect = document.getElementById('account-selector');
            if (!accountSelect) return;
            
            CONFIG.collection.accounts.forEach(({ label, value }) => {
                const option = document.createElement('option');
                option.textContent = label;
                option.value = value;
                accountSelect.appendChild(option);
            });
            
            const savedBindCookieId = localStorage.getItem(CONFIG.storageKeys.bindCookieId);
            const isValid = CONFIG.collection.accounts.some(acc => acc.value === savedBindCookieId);
            accountSelect.value = isValid ? savedBindCookieId : CONFIG.collection.accounts[0].value;
            
            // 确保 CollectionData.data 已初始化
            if (CollectionData.data) {
                CollectionData.data.bindCookieId = accountSelect.value;
            }
            
            accountSelect.addEventListener('change', (e) => {
                if (CollectionData.data) {
                    CollectionData.data.bindCookieId = e.target.value;
                }
                localStorage.setItem(CONFIG.storageKeys.bindCookieId, e.target.value);
                UI.addCollectionLog('已切换到账号: ' + e.target.options[e.target.selectedIndex].text);
            });
        },

        // 切换批量模式UI
        toggleBatchMode: () => {
            const inputGroup = document.getElementById('batchInputGroup');
            const controlGroup = document.getElementById('batchControlGroup');
            const isVisible = inputGroup.style.display !== 'none';
            
            inputGroup.style.display = isVisible ? 'none' : 'block';
            controlGroup.style.display = isVisible ? 'none' : 'flex';
        },

        // 使面板可拖拽
        makeDraggable: () => {
            const header = UI.panel.querySelector('.panel-header');
            let isDragging = false;
            let hasMoved = false;
            let currentX, currentY, initialX, initialY;
            
            header.addEventListener('mousedown', (e) => {
                if (e.target.id === 'panelClose' || e.target.id === 'panelMinimize') return;
                isDragging = true;
                hasMoved = false;
                initialX = e.clientX - UI.panel.offsetLeft;
                initialY = e.clientY - UI.panel.offsetTop;
            });
            
            document.addEventListener('mousemove', (e) => {
                if (!isDragging) return;
                e.preventDefault();
                hasMoved = true;
                currentX = e.clientX - initialX;
                currentY = e.clientY - initialY;
                UI.panel.style.left = currentX + 'px';
                UI.panel.style.top = currentY + 'px';
                UI.panel.style.right = 'auto';
            });
            
            document.addEventListener('mouseup', () => {
                isDragging = false;
                // 重置移动标记,延迟一点以便点击事件能检测到
                setTimeout(() => {
                    hasMoved = false;
                }, 100);
            });
            
            // 保存 hasMoved 状态供点击事件使用
            UI.panel._hasMoved = () => hasMoved;
        },

        // 记录日志
        log: (message, type = 'info') => {
            // 保存到存储
            Storage.addLog(message, type);
            
            // 显示到UI
            if (UI.logContainer) {
                const entry = document.createElement('div');
                entry.className = `log-entry ${type}`;
                const time = new Date().toLocaleTimeString();
                entry.innerHTML = `<span class="time">${time}</span>${message}`;
                
                UI.logContainer.insertBefore(entry, UI.logContainer.firstChild);
                
                // 限制日志数量
                const entries = UI.logContainer.querySelectorAll('.log-entry');
                if (entries.length > CONFIG.limits.maxLogEntries) {
                    entries[entries.length - 1].remove();
                }
            }
            
            console.log(`[回帖助手] ${message}`);
        },

        // 更新状态
        updateStatus: (text) => {
            // 保存到存储
            Storage.setStatusText(text);
            
            // 显示到UI
            const statusText = document.getElementById('statusText');
            if (statusText) {
                statusText.textContent = text;
            }
        },

        // 恢复日志
        restoreLogs: () => {
            const logs = Storage.getLogs();
            if (UI.logContainer && logs.length > 0) {
                UI.logContainer.innerHTML = '';
                logs.forEach(log => {
                    const entry = document.createElement('div');
                    entry.className = `log-entry ${log.type}`;
                    entry.innerHTML = `<span class="time">${log.time}</span>${log.message}`;
                    UI.logContainer.appendChild(entry);
                });
            }
        },

        // 恢复状态
        restoreStatus: () => {
            const statusText = Storage.getStatusText();
            UI.updateStatus(statusText);
        },

        // 禁用/启用按钮
        setButtonsDisabled: (disabled) => {
            const buttons = UI.panel.querySelectorAll('button');
            buttons.forEach(btn => {
                if (btn.id !== 'btnStopBatch') {
                    btn.disabled = disabled;
                }
            });
        },

        // 添加采集日志
        addCollectionLog: (msg) => {
            console.log(`[采集工具] ${msg}`);
            if (UI.collectionLogArea) {
                const p = document.createElement('div');
                p.textContent = msg;
                UI.collectionLogArea.appendChild(p);
                UI.collectionLogArea.scrollTop = UI.collectionLogArea.scrollHeight;
            }
        },

        // 更新状态灯
        updateStatusLight: (color, text) => {
            const light = document.getElementById('status-light');
            const textSpan = document.getElementById('status-text');
            if (light) light.style.background = color;
            if (textSpan) textSpan.textContent = text;
        }
    };

    // ========================================
    // 回帖处理模块
    // ========================================
    const ReplyHandler = {
        // 快速回帖(当前页面)
        quickReply: async () => {
            if (!Utils.isThreadPage()) {
                UI.log('请在帖子详情页使用快速回帖功能', 'error');
                return;
            }
            
            const tid = Utils.parseThreadId(location.href);
            if (!tid) {
                UI.log('无法解析帖子ID', 'error');
                return;
            }
            
            if (Storage.isReplied(tid)) {
                UI.log('该帖子已回复过,跳过', 'error');
                return;
            }
            
            UI.updateStatus('正在回帖...');
            UI.setButtonsDisabled(true);
            
            try {
                await ReplyHandler.submitReply(tid);
                UI.log('回帖成功!', 'success');
                UI.updateStatus('回帖完成');
            } catch (error) {
                UI.log(`回帖失败:${error.message}`, 'error');
                UI.updateStatus('回帖失败');
            } finally {
                UI.setButtonsDisabled(false);
            }
        },

        // 提交回复
        submitReply: async (tid) => {
            const textarea = document.querySelector(CONFIG.selectors.replyTextarea);
            const submitBtn = document.querySelector(CONFIG.selectors.replySubmitBtn);
            
            if (!textarea || !submitBtn) {
                throw new Error('未找到回复框或提交按钮');
            }
            
            // 填充随机内容
            const replyText = Utils.getRandomReply();
            textarea.value = replyText;
            
            // 触发事件
            textarea.dispatchEvent(new Event('input', { bubbles: true }));
            textarea.dispatchEvent(new Event('change', { bubbles: true }));
            
            UI.log(`回复内容:${replyText}`, 'info');
            
            // 等待后提交
            await Utils.delay(CONFIG.delays.beforeSubmit);
            submitBtn.click();
            
            // 标记已回复
            Storage.addRepliedThread(tid);
            
            // 等待提交完成
            await Utils.delay(CONFIG.delays.afterSubmit);
        },

        // 开始批量回帖
        startBatch: async (count) => {
            if (!Utils.isUserListPage()) {
                UI.log('请在用户帖子列表页使用批量回帖功能', 'error');
                return;
            }
            
            // 获取所有未回复的帖子
            const threadLinks = document.querySelectorAll(CONFIG.selectors.threadList);
            const unrepliedLinks = Array.from(threadLinks)
                .map(link => ({
                    url: link.href,
                    tid: Utils.parseThreadId(link.href)
                }))
                .filter(item => item.tid && !Storage.isReplied(item.tid));
            
            if (unrepliedLinks.length === 0) {
                UI.log('当前页面没有未回复的帖子', 'error');
                return;
            }
            
            // 从所有未回复的帖子中随机选择 count 个
            const shuffled = unrepliedLinks.sort(() => Math.random() - 0.5);
            const targetLinks = shuffled.slice(0, Math.min(count, unrepliedLinks.length));
            const queue = targetLinks.map(item => item.url);
            
            // 保存队列
            Storage.saveBatchQueue(queue);
            Storage.setBatchMode(true);
            Storage.setBatchCount(queue.length);
            Storage.resetFailedAttempts();
            
            UI.log(`从 ${unrepliedLinks.length} 个未回复帖子中随机选择了 ${queue.length} 个`, 'success');
            UI.log(`开始批量回帖,队列中有 ${queue.length} 个帖子`, 'success');
            UI.updateStatus(`批量模式:剩余 ${queue.length} 个帖子`);
            UI.setButtonsDisabled(true);
            
            await ReplyHandler.processBatch();
        },

        // 处理批量回帖
        processBatch: async () => {
            if (!Storage.isBatchMode()) {
                return;
            }
            
            // 从队列获取下一个帖子
            let queue = Storage.getBatchQueue();
            
            if (queue.length === 0) {
                UI.log('🎉 批量回帖全部完成!', 'success');
                ReplyHandler.stopBatch();
                return;
            }
            
            // 取第一个(队列已在startBatch时随机打乱)
            const nextUrl = queue[0];
            const tid = Utils.parseThreadId(nextUrl);
            
            UI.log(`→ 准备回复帖子:${tid} (队列剩余 ${queue.length})`, 'info');
            UI.updateStatus(`批量模式:剩余 ${queue.length} 个帖子`);
            
            // 从队列中移除第一个(访问前就删除,避免重复)
            queue.shift();
            Storage.saveBatchQueue(queue);
            Storage.setBatchCount(queue.length);
            
            // 跳转到帖子页面
            location.href = nextUrl;
        },

        // 停止批量回帖
        stopBatch: () => {
            Storage.setBatchMode(false);
            Storage.setBatchCount(0);
            Storage.saveBatchQueue([]);
            Storage.resetFailedAttempts();
            UI.log('已停止批量回帖', 'success');
            UI.updateStatus('待机中');
            UI.setButtonsDisabled(false);
        },

        // 在帖子页面自动回帖(批量模式)
        autoReplyInThread: async () => {
            if (!Storage.isBatchMode()) return;
            
            const tid = Utils.parseThreadId(location.href);
            if (!tid) {
                UI.log('无法解析帖子ID', 'error');
                return;
            }
            
            if (Storage.isReplied(tid)) {
                UI.log(`帖子 ${tid} 已回复过,跳过`, 'info');
                await Utils.delay(1000);
                // 已经从队列中移除了,直接返回继续下一个
                history.back();
                return;
            }
            
            UI.updateStatus('正在自动回帖...');
            
            try {
                await Utils.delay(CONFIG.delays.pageLoad);
                await ReplyHandler.submitReply(tid);
                
                const remaining = Storage.getBatchCount();
                
                UI.log(`✓ 帖子 ${tid} 回复成功,剩余 ${remaining} 个帖子`, 'success');
                UI.updateStatus(`批量模式:剩余 ${remaining} 个帖子`);
                
                // 等待后返回列表
                await Utils.delay(CONFIG.delays.betweenPosts);
                history.back();
            } catch (error) {
                UI.log(`自动回帖失败:${error.message}`, 'error');
                // 出错也返回继续下一个(已从队列移除)
                await Utils.delay(2000);
                history.back();
            }
        }
    };

    // ========================================
    // 采集处理模块
    // ========================================
    const Collector = {
        // 检查文章是否已存在
        checkArticleExists: async () => {
            if (!CollectionData.data.title) {
                UI.updateStatusLight('gray', '未检查');
                return false;
            }
            UI.updateStatusLight('#FFA500', '检查中...');
            try {
                const response = await fetch(CONFIG.collection.checkUrl, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: CollectionData.data.title
                });
                const data = await response.json();
                const exists = data.data === true || data.data === 'true' || data.data === 1 || data.data === '1';
                if (exists) {
                    UI.updateStatusLight('#f44336', '文章已存在');
                    UI.addCollectionLog('⚠️ 该文章已在数据库中');
                    return true;
                } else {
                    UI.updateStatusLight('#4CAF50', '文章不存在');
                    UI.addCollectionLog('✅ 该文章为新内容');
                    return false;
                }
            } catch (err) {
                UI.updateStatusLight('#FF9800', '检查失败');
                UI.addCollectionLog('❌ 检查接口失败: ' + err.message);
                return false;
            }
        },

        // 统一提取所有内容
        extractAll: async () => {
            UI.addCollectionLog('开始提取所有内容...');
            
            // 第一步:检查夸克链接
            UI.addCollectionLog('1. 检查夸克链接...');
            const alertDiv = document.querySelector("div.alert.alert-success[role='alert']");
            if (alertDiv) {
                const allText = alertDiv.textContent || alertDiv.innerText || '';
                const quarkPattern = /https?:\/\/pan\.quark\.(cn|com)\/s\/[a-zA-Z0-9]+/g;
                const matches = allText.match(quarkPattern);
                if (matches && matches.length > 0) {
                    CollectionData.data.quarkLink = matches[0];
                    UI.addCollectionLog('✓ 夸克链接提取成功: ' + CollectionData.data.quarkLink);
                } else {
                    UI.addCollectionLog('❌ 未找到夸克链接。请确认已回帖。');
                    return;
                }
            } else {
                UI.addCollectionLog('❌ 未找到回帖提示框。请先回帖查看链接。');
                return;
            }
            
            // 第二步:提取基本信息
            UI.addCollectionLog('2. 提取标题、作者、节点和资源链接...');
            await Collector.extractMeta();
            
            // 第三步:提取标签
            UI.addCollectionLog('3. 提取标签...');
            Collector.extractTags();
            
            // 第四步:提取正文
            UI.addCollectionLog('4. 提取正文...');
            await Collector.extractContent();
            
            UI.addCollectionLog('✅ 所有内容提取完成!');
            UI.addCollectionLog('可以点击【查看数据】查看完整数据,然后点击【上传服务器】');
        },

        // 提取元数据
        extractMeta: async () => {
            const currentUrl = window.location.href;
            try {
                const urlObj = new URL(currentUrl);
                const pathParts = urlObj.pathname.split('/').filter(part => part);
                if (pathParts.length > 0) {
                    CollectionData.data.resourceLink = pathParts[pathParts.length - 1];
                }
                UI.addCollectionLog('资源链接: ' + CollectionData.data.resourceLink);
            } catch (e) {
                UI.addCollectionLog('URL 解析失败: ' + e.message);
            }
            
            // 提取标题
            const titleEl = document.querySelector("h4.break-all.font-weight-bold");
            if (titleEl) {
                CollectionData.data.title = titleEl.textContent.trim().replace(/\s+/g, " ");
                UI.addCollectionLog('标题: ' + CollectionData.data.title);
            } else {
                UI.addCollectionLog('未找到标题');
            }
            
            // 提取作者
            const userEl = document.querySelector("span.username.font-weight-bold.small a");
            if (userEl) {
                CollectionData.data.username = userEl.textContent.trim();
                UI.addCollectionLog('作者: ' + CollectionData.data.username);
            } else {
                UI.addCollectionLog('未找到作者');
            }
            
            // 提取节点
            const nodeEl = Utils.getElementByXPath("//*[@id='body']/div/div/div[2]/ol/li[2]/a");
            if (nodeEl) {
                CollectionData.data.node = nodeEl.textContent.trim();
                UI.addCollectionLog('节点: ' + CollectionData.data.node);
            } else {
                UI.addCollectionLog('未找到节点');
            }
        },

        // 提取标签
        extractTags: () => {
            const tagsXPath = "/html/body/main/div/div/div[2]/div[1]/div[2]/div[2]//a";
            const tagElements = Utils.getElementsByXPath(tagsXPath);
            if (tagElements && tagElements.length > 0) {
                const tagTexts = tagElements.map(tag => tag.textContent.trim()).filter(text => text);
                CollectionData.data.tags = tagTexts.join(",");
                UI.addCollectionLog('标签: ' + CollectionData.data.tags);
            } else {
                UI.addCollectionLog('未找到标签');
            }
        },

        // 提取正文内容
        extractContent: async () => {
            const contentXPath = "/html/body/main/div/div/div[2]/div[1]/div[2]";
            const contentEl = Utils.getElementByXPath(contentXPath);
            if (!contentEl) {
                UI.addCollectionLog('未找到正文区域');
                return;
            }
            
            const clonedContent = contentEl.cloneNode(true);
            
            // 删除多余元素
            try {
                let deleteCount = 0;
                const removeList = ['.tt-license', '.alert.alert-success', '.mt-3'];
                removeList.forEach(sel => {
                    const el = clonedContent.querySelector(sel);
                    if (el && el.parentNode) {
                        el.parentNode.removeChild(el);
                        deleteCount++;
                    }
                });
                UI.addCollectionLog(`已删除 ${deleteCount} 个指定元素`);
            } catch (e) {
                UI.addCollectionLog('删除元素时出错: ' + e.message);
            }
            
            // 处理图片转Base64
            const imgEls = clonedContent.querySelectorAll("img");
            let converted = 0;
            
            const convertToBase64 = async (url) => {
                try {
                    const response = await fetch(url);
                    const blob = await response.blob();
                    return await new Promise((resolve, reject) => {
                        const reader = new FileReader();
                        reader.onloadend = () => resolve(reader.result);
                        reader.onerror = reject;
                        reader.readAsDataURL(blob);
                    });
                } catch (err) {
                    console.error("图片转Base64失败:", err);
                    return url;
                }
            };
            
            const tasks = Array.from(imgEls).map(async (img) => {
                const src = img.getAttribute("src");
                if (!src) return;
                try {
                    const absoluteUrl = new URL(src, window.location.href).href;
                    const base64 = await convertToBase64(absoluteUrl);
                    img.setAttribute("src", base64);
                    converted++;
                } catch (e) {
                    console.warn("处理图片失败:", src, e);
                }
            });
            
            await Promise.all(tasks);
            UI.addCollectionLog(`共处理图片 ${imgEls.length} 张,成功转为Base64:${converted} 张`);
            
            CollectionData.data.content = clonedContent.outerHTML;
            UI.addCollectionLog('✅ 正文提取完成');
        },

        // 上传到服务器
        uploadServer: () => {
            if (!CONFIG.collection.serverUrl.startsWith("http")) {
                UI.addCollectionLog('❌ 请先设置服务器地址!');
                return;
            }
            UI.addCollectionLog('开始上传到服务器...');
            fetch(CONFIG.collection.serverUrl, {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(CollectionData.data)
            })
                .then(res => res.json())
                .then(data => UI.addCollectionLog('✅ 上传成功: ' + JSON.stringify(data)))
                .catch(err => UI.addCollectionLog('❌ 上传失败: ' + err));
        },

        // 查看数据
        showData: () => {
            UI.addCollectionLog('当前收集数据:');
            UI.addCollectionLog(JSON.stringify(CollectionData.data, null, 2));
        }
    };

    // ========================================
    // 主程序初始化
    // ========================================
    const App = {
        init: async () => {
            // 初始化采集数据(必须在创建面板之前)
            CollectionData.init();
            
            // 初始化UI
            UI.initStyles();
            UI.createPanel();
            
            // 恢复日志和状态
            UI.restoreLogs();
            UI.restoreStatus();
            
            // 如果是批量模式,显示批量控制按钮
            if (Storage.isBatchMode()) {
                const inputGroup = document.getElementById('batchInputGroup');
                const controlGroup = document.getElementById('batchControlGroup');
                if (inputGroup && controlGroup) {
                    inputGroup.style.display = 'block';
                    controlGroup.style.display = 'flex';
                }
                UI.setButtonsDisabled(true);
            }
            
            UI.log('夸克资源助手已启动 v5.1.1', 'success');
            
            // 检查当前页面类型
            if (Utils.isThreadPage()) {
                UI.log('检测到帖子详情页', 'info');
                UI.addCollectionLog('✅ 采集工具已就绪');
                
                // 自动检查文章状态
                App.autoCheckArticle();
                
                // 如果是批量模式,自动回帖
                if (Storage.isBatchMode()) {
                    await ReplyHandler.autoReplyInThread();
                } else {
                    UI.updateStatus('帖子详情页 - 可使用快速回帖');
                }
            } else if (Utils.isUserListPage()) {
                UI.log('检测到用户列表页', 'info');
                if (!Storage.isBatchMode()) {
                    UI.updateStatus('用户列表页 - 可使用批量回帖');
                } else {
                    // 批量模式下,在列表页继续处理
                    UI.log('批量模式中,准备处理下一个帖子...', 'info');
                    setTimeout(() => {
                        ReplyHandler.processBatch();
                    }, 1500);
                }
            } else {
                UI.log('当前页面类型未知', 'info');
                if (!Storage.isBatchMode()) {
                    UI.updateStatus('待机中');
                }
            }
            
        },
        
        // 自动检查文章
        autoCheckArticle: () => {
            const titleEl = document.querySelector("h4.break-all.font-weight-bold");
            if (titleEl) {
                const title = titleEl.textContent.trim().replace(/\s+/g, " ");
                CollectionData.data.title = title;
                Collector.checkArticleExists();
            } else {
                const checkObserver = new MutationObserver(() => {
                    const titleEl = document.querySelector("h4.break-all.font-weight-bold");
                    if (titleEl) {
                        const title = titleEl.textContent.trim().replace(/\s+/g, " ");
                        CollectionData.data.title = title;
                        Collector.checkArticleExists();
                        checkObserver.disconnect();
                    }
                });
                checkObserver.observe(document.body, { childList: true, subtree: true });
            }
        }
    };

    // 启动应用
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', App.init);
    } else {
        App.init();
    }

})();