搜索结果

×

搜索结果将在这里显示。

抖音评论监控 · 3.0版 (历史记录+导出+自动播放下一个)

// ==UserScript==
// @name         抖音评论监控 · 3.0版 (历史记录+导出+自动播放下一个)
// @namespace    https://www.douyin.com/
// @version      3.0.0
// @description  监控评论关键词,右侧悬浮窗始终显示最新两条,历史记录包含评论内容,评论区加载完成后自动播放下一个视频
// @author       You
// @match        *://*.douyin.com/*
// @grant        GM_setClipboard
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    // ==================== 配置管理 ====================
    const STORAGE_KEY = 'dy_monitor_config_v8';
    const HISTORY_KEY = 'dy_monitor_history_v8';

    const DEFAULT_CONFIG = {
        keywords: ['福利', '加我', '微信', 'qq', '进群', '兼职', '赚钱', '秒杀'],
        regions: ['新疆', '全部'],
        enableRegionFilter: true,
        alertMode: 'right',
        autoOpenComment: true,
        autoScrollComments: true,
        scrollInterval: 2000,
        maxMarquees: 5,
        showVideoLink: true,
        showVideoInfo: true,
        maxHistoryItems: 100,
        maxSideAlerts: 2,
        autoNextVideo: false,
        nextVideoDelay: 5000,
        openCommentDelay: 5000
    };

    class ConfigManager {
        constructor() {
            this.config = null;
            this.load();
        }

        load() {
            const saved = GM_getValue(STORAGE_KEY);
            if (saved) {
                try {
                    this.config = JSON.parse(saved);
                    // 合并默认配置,确保所有字段都存在
                    this.config = { ...DEFAULT_CONFIG, ...this.config };
                } catch (e) {
                    this.config = { ...DEFAULT_CONFIG };
                }
            } else {
                this.config = { ...DEFAULT_CONFIG };
            }
            return this.config;
        }

        save() {
            GM_setValue(STORAGE_KEY, JSON.stringify(this.config));
        }

        get(key) {
            return this.config[key];
        }

        set(key, value) {
            this.config[key] = value;
            this.save();
        }

        getAll() {
            return { ...this.config };
        }

        update(updates) {
            Object.assign(this.config, updates);
            this.save();
        }

        getKeywords() {
            return new Set(this.config.keywords || []);
        }

        getRegions() {
            return new Set(this.config.regions || []);
        }

        setKeywords(keywordsSet) {
            this.config.keywords = Array.from(keywordsSet);
            this.save();
        }

        setRegions(regionsSet) {
            this.config.regions = Array.from(regionsSet);
            this.save();
        }
    }

    // ==================== 状态管理 ====================
    class StateManager {
        constructor() {
            this.state = {
                isCommentPanelOpen: false,
                commentContainer: null,
                currentVideoId: null,
                isWaitingForNext: false,
                lastCommentCount: 0,
                noIncreaseCount: 0,
                lastScrollTop: 0,
                scrollStableCount: 0,
                isMinimized: false,
                isDragging: false
            };
            this.triggeredComments = new Set();
            this.listeners = [];
            this.historyRecords = this.loadHistory();
        }

        loadHistory() {
            try {
                return GM_getValue(HISTORY_KEY, []);
            } catch (e) {
                return [];
            }
        }

        saveHistory() {
            try {
                GM_setValue(HISTORY_KEY, this.historyRecords);
            } catch (e) {
                console.error('保存历史记录失败:', e);
            }
        }

        getHistory() {
            return this.historyRecords;
        }

        addHistoryRecord(record) {
            this.historyRecords.unshift(record);
            const maxItems = 100; // 从配置读取
            if (this.historyRecords.length > maxItems) {
                this.historyRecords = this.historyRecords.slice(0, maxItems);
            }
            this.saveHistory();
        }

        clearHistory() {
            this.historyRecords = [];
            this.saveHistory();
        }

        get(key) {
            return this.state[key];
        }

        set(key, value) {
            this.state[key] = value;
            this.notifyListeners(key, value);
        }

        subscribe(listener) {
            this.listeners.push(listener);
            return () => {
                this.listeners = this.listeners.filter(l => l !== listener);
            };
        }

        notifyListeners(key, value) {
            this.listeners.forEach(listener => listener(key, value));
        }

        reset() {
            this.state = {
                ...this.state,
                isCommentPanelOpen: false,
                commentContainer: null,
                isWaitingForNext: false,
                lastCommentCount: 0,
                noIncreaseCount: 0,
                lastScrollTop: 0,
                scrollStableCount: 0
            };
        }

        isCommentTriggered(commentId) {
            return this.triggeredComments.has(commentId);
        }

        markCommentTriggered(commentId) {
            this.triggeredComments.add(commentId);
            // 限制集合大小,防止内存泄漏
            if (this.triggeredComments.size > 10000) {
                const entries = Array.from(this.triggeredComments);
                this.triggeredComments = new Set(entries.slice(-5000));
            }
        }

        getVideoId() {
            const url = window.location.href;
            const match = url.match(/video\/(\d+)/);
            return match ? match[1] : null;
        }
    }

    // ==================== 工具类 ====================
    class Utils {
        static showToast(msg, duration = 2000, isWarning = false) {
            const existing = document.querySelector('#dy-monitor-toast');
            if (existing) existing.remove();

            const toast = document.createElement('div');
            toast.id = 'dy-monitor-toast';
            Object.assign(toast.style, {
                position: 'fixed',
                left: '50%',
                top: '30%',
                transform: 'translateX(-50%)',
                background: isWarning ? 'rgba(255, 80, 80, 0.95)' : 'rgba(0,0,0,0.8)',
                color: '#fff',
                padding: '10px 20px',
                borderRadius: '40px',
                fontSize: '15px',
                fontWeight: '500',
                zIndex: 99999999,
                boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
                transition: 'opacity 0.3s ease',
                pointerEvents: 'none',
                maxWidth: '90%',
                textAlign: 'center'
            });
            toast.textContent = msg;
            document.body.appendChild(toast);

            setTimeout(() => {
                toast.style.opacity = '0';
                setTimeout(() => toast.remove(), 300);
            }, duration);
        }

        static formatTime(timestamp) {
            if (!timestamp) return '未知时间';
            let date;
            if (typeof timestamp === 'number') {
                date = timestamp > 10000000000 ? new Date(timestamp) : new Date(timestamp * 1000);
            } else {
                date = new Date(timestamp);
            }
            if (isNaN(date.getTime())) return '未知时间';

            const pad = (n) => n.toString().padStart(2, '0');
            return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
        }

        static generateCommentId(comment) {
            const text = comment.text || comment.content || '';
            const author = comment.user?.nickname || comment.user?.unique_id || comment.author || '';
            const time = comment.create_time || comment.time || '';
            return `${text}_${author}_${time}`.substring(0, 200);
        }

        static isRegionMatched(ipLocation, configManager) {
            if (!configManager.get('enableRegionFilter')) return true;
            if (!ipLocation || ipLocation === '未知') return false;

            const regions = configManager.getRegions();
            if (regions.has('全部')) return true;

            for (let region of regions) {
                if (region && region !== '全部' && ipLocation.includes(region)) {
                    return true;
                }
            }
            return false;
        }

        static debounce(func, wait) {
            let timeout;
            return function executedFunction(...args) {
                const later = () => {
                    clearTimeout(timeout);
                    func(...args);
                };
                clearTimeout(timeout);
                timeout = setTimeout(later, wait);
            };
        }

        static throttle(func, limit) {
            let inThrottle;
            return function(...args) {
                if (!inThrottle) {
                    func.apply(this, args);
                    inThrottle = true;
                    setTimeout(() => inThrottle = false, limit);
                }
            };
        }

        static copyToClipboard(text) {
            if (typeof GM_setClipboard !== 'undefined') {
                GM_setClipboard(text, 'text');
                return true;
            }

            try {
                const textarea = document.createElement('textarea');
                textarea.value = text;
                textarea.style.position = 'fixed';
                textarea.style.opacity = '0';
                document.body.appendChild(textarea);
                textarea.select();
                document.execCommand('copy');
                document.body.removeChild(textarea);
                return true;
            } catch (e) {
                return false;
            }
        }
    }

    // ==================== 监控核心类 ====================
    class CommentMonitor {
        constructor(configManager, stateManager) {
            this.configManager = configManager;
            this.stateManager = stateManager;
            this.scrollTimer = null;
            this.checkInterval = null;
            this.nextVideoTimer = null;
            this.openCommentTimer = null;
            this.marqueeContainer = null;
            this.sideAlerts = [];
            this.panel = null;
            this.isPanelMinimized = false;
            this.isDragging = false;
            this.dragOffsetX = 0;
            this.dragOffsetY = 0;
            this.stylesInjected = false;
        }

        // ====== 初始化 ======
        init() {
            console.log('抖音评论监控插件 v3.0 已启动');
            this.injectStyles();
            this.createPanel();
            this.setupEventListeners();

            if (this.configManager.get('autoOpenComment')) {
                this.scheduleOpenComment();
            }

            // 定期检查视频切换
            setInterval(() => {
                this.checkVideoChange();
            }, 2000);

            // 劫持评论接口
            this.hijackCommentAPIs();
        }

        // ====== 样式注入 ======
        injectStyles() {
            if (this.stylesInjected) return;

            const styles = `
                @keyframes sideSlideIn-right {
                    from { transform: translateX(100%); opacity: 0; }
                    to { transform: translateX(0); opacity: 1; }
                }
                @keyframes sideSlideIn-left {
                    from { transform: translateX(-100%); opacity: 0; }
                    to { transform: translateX(0); opacity: 1; }
                }
                @keyframes marqueeFadeIn {
                    from { opacity: 0; transform: translateY(-10px); }
                    to { opacity: 1; transform: translateY(0); }
                }
                @keyframes panelPulse {
                    0%, 100% { box-shadow: 0 8px 20px rgba(0,0,0,0.5); }
                    50% { box-shadow: 0 8px 30px rgba(76, 175, 80, 0.3); }
                }
                .video-link {
                    color: #2196F3;
                    text-decoration: none;
                    font-size: 12px;
                    word-break: break-all;
                }
                .video-link:hover {
                    text-decoration: underline;
                    color: #4CAF50;
                }
                .video-title {
                    font-weight: 500;
                    color: #1a1a1a;
                    line-height: 1.4;
                }
                .keyword-tag {
                    background: #ff4d4d;
                    color: #fff;
                    padding: 2px 8px;
                    border-radius: 4px;
                    font-size: 12px;
                    font-weight: 500;
                    display: inline-block;
                }
                .video-link-dark {
                    color: #64B5F6;
                    text-decoration: none;
                }
                .video-link-dark:hover {
                    text-decoration: underline;
                    color: #90CAF9;
                }
                .dy-comment-alert {
                    animation-duration: 0.3s;
                }
                #comment-monitor-panel {
                    transition: width 0.3s ease, height 0.3s ease, box-shadow 0.3s ease;
                }
                #comment-monitor-panel.minimized {
                    width: 160px !important;
                    cursor: pointer;
                }
                #comment-monitor-panel .panel-content {
                    overflow: hidden;
                    transition: max-height 0.3s ease, opacity 0.3s ease;
                }
                #comment-monitor-panel .panel-content.hidden {
                    max-height: 0 !important;
                    opacity: 0;
                    padding: 0 14px !important;
                }
                .dy-monitor-scrollbar::-webkit-scrollbar {
                    width: 4px;
                }
                .dy-monitor-scrollbar::-webkit-scrollbar-track {
                    background: #1a1a1a;
                }
                .dy-monitor-scrollbar::-webkit-scrollbar-thumb {
                    background: #4CAF50;
                    border-radius: 2px;
                }
            `;

            const styleEl = document.createElement('style');
            styleEl.id = 'dy-monitor-styles';
            styleEl.textContent = styles;
            document.head.appendChild(styleEl);
            this.stylesInjected = true;
        }

        // ====== 视频信息获取 ======
        getVideoInfo() {
            const info = {
                author: '未知作者',
                publishTime: '未知时间',
                title: '未知标题',
                fullTitle: '未知标题',
                videoUrl: window.location.href
            };

            try {
                const authorElement = document.querySelector('[data-e2e="feed-video-nickname"] span, .account-name-text span');
                if (authorElement) {
                    info.author = authorElement.textContent.trim().replace('@', '');
                }

                const timeElement = document.querySelector('.video-create-time .time, [class*="time"]');
                if (timeElement) {
                    info.publishTime = timeElement.textContent.trim();
                }

                const titleContainer = document.querySelector('.title .FJhgcCvF, [data-e2e="video-desc"]');
                if (titleContainer) {
                    const titleSpans = titleContainer.querySelectorAll('span');
                    let fullTitle = '';
                    titleSpans.forEach(span => {
                        const text = span.textContent.trim();
                        if (text && !text.includes('展开') && !text.includes('收起')) {
                            fullTitle += text + ' ';
                        }
                    });
                    info.fullTitle = fullTitle.trim() || titleContainer.textContent.trim();
                    info.title = info.fullTitle.length > 80 ? info.fullTitle.substring(0, 80) + '...' : info.fullTitle;
                }

                info.videoUrl = window.location.href;
            } catch (e) {
                console.debug('提取视频信息出错:', e);
            }

            return info;
        }

        // ====== 评论检测 ======
        findCommentContainer() {
            const selectors = [
                '[class*="comment-list"]',
                '[class*="CommentList"]',
                '[class*="commentContainer"]',
                '[data-e2e="comment-list"]',
                '.LWSPvSJk',
                '[class*="comments-container"]',
                '#comment-container'
            ];

            for (const selector of selectors) {
                const el = document.querySelector(selector);
                if (el && el.children.length > 0) {
                    return el;
                }
            }
            return null;
        }

        getCommentCount() {
            const container = this.stateManager.get('commentContainer');
            if (!container) return 0;

            const items = container.querySelectorAll([
                '[class*="comment-item"]',
                '[data-e2e="comment-item"]',
                '[class*="CommentItem"]'
            ].join(','));

            return items.length;
        }

        matchKeywords(text) {
            const keywords = this.configManager.getKeywords();
            if (!keywords || keywords.size === 0) return [];

            const matched = [];
            for (const kw of keywords) {
                if (text.includes(kw)) {
                    matched.push(kw);
                }
            }
            return matched;
        }

        // ====== 评论接口劫持 ======
        hijackCommentAPIs() {
            // 劫持 fetch
            const originalFetch = window.fetch;
            window.fetch = (...args) => {
                const url = args[0] instanceof Request ? args[0].url : args[0];
                return originalFetch.apply(this, args).then(response => {
                    const cloned = response.clone();
                    cloned.text().then(body => this.inspectResponseBody(url, body)).catch(() => {});
                    return response;
                });
            };

            // 劫持 XMLHttpRequest
            const originalXHROpen = XMLHttpRequest.prototype.open;
            const originalXHRSend = XMLHttpRequest.prototype.send;

            XMLHttpRequest.prototype.open = function(method, url) {
                this._monitorUrl = url;
                return originalXHROpen.apply(this, arguments);
            };

            XMLHttpRequest.prototype.send = function(...args) {
                if (this._monitorUrl) {
                    const url = this._monitorUrl;
                    const originalOnLoad = this.onload;
                    this.onload = function(e) {
                        if (this.readyState === 4 && this.status === 200) {
                            this.inspectResponseBody(url, this.responseText);
                        }
                        if (originalOnLoad) originalOnLoad.call(this, e);
                    };
                    // 绑定 inspectResponseBody 方法
                    this.inspectResponseBody = (url, body) => {
                        // 使用外部的 inspectResponseBody
                        window._dyMonitorInstance.inspectResponseBody(url, body);
                    };
                }
                return originalXHRSend.apply(this, args);
            };

            // 保存实例引用
            window._dyMonitorInstance = this;
        }

        inspectResponseBody(url, bodyText) {
            if (!bodyText || typeof bodyText !== 'string') return;

            const commentAPIs = [
                '/comment/',
                '/v2/comment/',
                '/aweme/v1/comment/',
                '/aweme/v1/web/comment/list/'
            ];

            if (!commentAPIs.some(api => url.includes(api))) return;

            try {
                const data = JSON.parse(bodyText);
                let comments = [];

                if (data.comments) comments = data.comments;
                else if (data.data && Array.isArray(data.data)) comments = data.data;
                else if (data.data && data.data.comments) comments = data.data.comments;
                else if (data.comment_list) comments = data.comment_list;
                else return;

                comments.forEach(comment => {
                    this.processComment(comment);
                });
            } catch (e) {
                // 解析失败忽略
            }
        }

        processComment(comment) {
            const commentId = Utils.generateCommentId(comment);
            if (this.stateManager.isCommentTriggered(commentId)) return;

            let text = comment.text || comment.content || '';
            if (!text) return;

            const user = comment.user?.nickname || comment.user?.unique_id || comment.author || '匿名';
            const createTime = comment.create_time || comment.time || null;
            let ipLabel = comment.ip_label || comment.ip_location || comment.region || '未知';

            if (!ipLabel || ipLabel === '未知') {
                ipLabel = comment.user?.ip_location || comment.ip || '未知';
            }

            const matchedKeywords = this.matchKeywords(text);
            if (matchedKeywords.length > 0 && Utils.isRegionMatched(ipLabel, this.configManager)) {
                this.stateManager.markCommentTriggered(commentId);
                this.showAlert({
                    text: text,
                    author: user,
                    time: createTime,
                    ipLocation: ipLabel,
                    matchedKeywords: matchedKeywords
                });
            }
        }

        // ====== 显示提醒 ======
        showAlert(commentData) {
            const alertMode = this.configManager.get('alertMode');
            const videoInfo = this.getVideoInfo();

            // 添加到历史记录
            this.addHistoryRecord(commentData, videoInfo);

            // 根据模式显示
            switch(alertMode) {
                case 'left':
                    this.showSideAlert(commentData, 'left');
                    break;
                case 'right':
                    this.showSideAlert(commentData, 'right');
                    break;
                case 'marquee':
                    this.showMarquee(commentData);
                    break;
                default:
                    this.showSideAlert(commentData, 'right');
            }

            // 重置自动切换计时器
            if (this.configManager.get('autoNextVideo') && this.stateManager.get('isWaitingForNext')) {
                this.resetNextVideoTimer();
            }
        }

        // ====== 侧边弹窗 ======
        showSideAlert(commentData, side = 'right') {
            const videoInfo = this.getVideoInfo();
            const { text, author, time, ipLocation, matchedKeywords } = commentData;

            const overlay = document.createElement('div');
            overlay.className = 'dy-comment-alert';
            overlay.dataset.timestamp = Date.now();

            overlay.style.cssText = `
                position: fixed;
                top: 20px;
                ${side}: 20px;
                width: 380px;
                max-height: 60vh;
                background: #1e1e1e;
                border-radius: 12px;
                box-shadow: 0 8px 20px rgba(0,0,0,0.5);
                color: #fff;
                z-index: 999999999;
                border: 1px solid #ff4d4d;
                animation: sideSlideIn-${side} 0.3s ease-out;
                margin-bottom: 10px;
                font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
                overflow: hidden;
                display: flex;
                flex-direction: column;
            `;

            const card = document.createElement('div');
            card.style.cssText = `
                padding: 16px;
                overflow-y: auto;
                flex: 1;
            `;

            // 头部
            const header = document.createElement('div');
            header.style.cssText = `
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 12px;
            `;

            const title = document.createElement('div');
            title.style.cssText = `
                display: flex;
                align-items: center;
                gap: 8px;
                font-weight: 600;
                font-size: 14px;
            `;
            title.innerHTML = `
                <span style="background:#ff4d4d; width:10px; height:10px; border-radius:50%; display:inline-block;"></span>
                🔔 触发 ${matchedKeywords.length} 个关键词
            `;

            const closeBtn = document.createElement('button');
            closeBtn.textContent = '×';
            closeBtn.style.cssText = `
                background: none;
                border: none;
                color: #aaa;
                font-size: 24px;
                cursor: pointer;
                padding: 0 4px;
                line-height: 1;
                transition: color 0.2s;
            `;
            closeBtn.onmouseenter = () => closeBtn.style.color = '#fff';
            closeBtn.onmouseleave = () => closeBtn.style.color = '#aaa';
            closeBtn.onclick = () => {
                overlay.remove();
                this.sideAlerts = this.sideAlerts.filter(el => el !== overlay);
            };

            header.appendChild(title);
            header.appendChild(closeBtn);

            // 视频信息
            if (this.configManager.get('showVideoInfo')) {
                const videoInfoDiv = document.createElement('div');
                videoInfoDiv.style.cssText = `
                    margin-bottom: 12px;
                    padding: 12px;
                    background: #2a2a2a;
                    border-radius: 8px;
                    border-left: 3px solid #4CAF50;
                `;

                const authorLine = document.createElement('div');
                authorLine.style.cssText = `
                    display: flex;
                    align-items: center;
                    gap: 10px;
                    margin-bottom: 6px;
                    flex-wrap: wrap;
                `;
                authorLine.innerHTML = `
                    <span style="font-weight:600; color:#4CAF50;">👤 ${videoInfo.author}</span>
                    <span style="color:#aaa; font-size:12px;">🕒 ${videoInfo.publishTime}</span>
                `;

                const titleLine = document.createElement('div');
                titleLine.style.cssText = `
                    font-size: 13px;
                    color: #ddd;
                    margin-bottom: 6px;
                    line-height: 1.5;
                `;
                titleLine.textContent = videoInfo.title;

                videoInfoDiv.appendChild(authorLine);
                videoInfoDiv.appendChild(titleLine);

                if (this.configManager.get('showVideoLink')) {
                    const linkLine = document.createElement('div');
                    linkLine.style.cssText = `
                        margin-top: 8px;
                        font-size: 12px;
                    `;
                    linkLine.innerHTML = `
                        <span style="color:#aaa;">🔗</span>
                        <a href="${videoInfo.videoUrl}" target="_blank" class="video-link-dark" style="margin-left:4px;">打开视频</a>
                    `;
                    videoInfoDiv.appendChild(linkLine);
                }

                card.appendChild(videoInfoDiv);
            }

            // 评论内容
            const commentDiv = document.createElement('div');
            commentDiv.style.cssText = `
                margin-bottom: 12px;
                padding: 12px;
                background: #2a2a2a;
                border-radius: 8px;
                border-left: 3px solid #ff4d4d;
            `;

            const metaLine = document.createElement('div');
            metaLine.style.cssText = `
                display: flex;
                flex-wrap: wrap;
                gap: 10px;
                margin-bottom: 8px;
                color: #aaa;
                font-size: 12px;
            `;
            metaLine.innerHTML = `
                <span>👤 ${author || '匿名'}</span>
                <span>🕒 ${Utils.formatTime(time)}</span>
                <span style="color: #4CAF50; font-weight: bold;">📍 ${ipLocation || '未知'}</span>
            `;

            const tagsContainer = document.createElement('div');
            tagsContainer.style.cssText = `
                display: flex;
                flex-wrap: wrap;
                gap: 6px;
                margin-bottom: 8px;
            `;
            matchedKeywords.forEach(kw => {
                const tag = document.createElement('span');
                tag.style.cssText = `
                    background: #ff4d4d;
                    padding: 2px 10px;
                    border-radius: 12px;
                    font-size: 11px;
                    color: #fff;
                `;
                tag.textContent = `# ${kw}`;
                tagsContainer.appendChild(tag);
            });

            const contentDiv = document.createElement('div');
            contentDiv.style.cssText = `
                font-size: 13px;
                line-height: 1.6;
                word-break: break-all;
                max-height: 100px;
                overflow-y: auto;
                color: #eee;
            `;
            contentDiv.textContent = text;

            commentDiv.appendChild(metaLine);
            commentDiv.appendChild(tagsContainer);
            commentDiv.appendChild(contentDiv);
            card.appendChild(commentDiv);

            // 操作按钮
            const actions = document.createElement('div');
            actions.style.cssText = `
                display: flex;
                gap: 8px;
                justify-content: flex-end;
                margin-top: 4px;
            `;

            const copyBtn = this.createActionButton('📋 复制完整信息', '#4CAF50', () => {
                this.copyFullComment(commentData, videoInfo);
            });

            const closeCardBtn = this.createActionButton('关闭', '#ff4d4d', () => {
                overlay.remove();
                this.sideAlerts = this.sideAlerts.filter(el => el !== overlay);
            });

            actions.appendChild(copyBtn);
            actions.appendChild(closeCardBtn);

            card.appendChild(header);
            card.appendChild(actions);
            overlay.appendChild(card);

            document.body.appendChild(overlay);
            this.sideAlerts.push(overlay);

            // 限制数量
            const maxAlerts = this.configManager.get('maxSideAlerts');
            while (this.sideAlerts.length > maxAlerts) {
                const oldest = this.sideAlerts.shift();
                if (oldest && oldest.parentNode) {
                    oldest.remove();
                }
            }

            // 更新位置
            this.updateSideAlertPositions(side);

            // 自动清理(5分钟后)
            setTimeout(() => {
                if (overlay.parentNode) {
                    overlay.style.opacity = '0';
                    setTimeout(() => {
                        overlay.remove();
                        this.sideAlerts = this.sideAlerts.filter(el => el !== overlay);
                    }, 300);
                }
            }, 300000);
        }

        createActionButton(text, color, onClick) {
            const btn = document.createElement('button');
            btn.textContent = text;
            btn.style.cssText = `
                background: ${color};
                border: none;
                color: #fff;
                padding: 6px 14px;
                border-radius: 6px;
                font-size: 12px;
                cursor: pointer;
                transition: all 0.2s;
                font-weight: 500;
            `;
            btn.onmouseenter = () => {
                btn.style.transform = 'scale(1.05)';
                btn.style.opacity = '0.9';
            };
            btn.onmouseleave = () => {
                btn.style.transform = 'scale(1)';
                btn.style.opacity = '1';
            };
            btn.onclick = onClick;
            return btn;
        }

        updateSideAlertPositions(side) {
            const alerts = document.querySelectorAll('.dy-comment-alert');
            let currentTop = 20;
            const gap = 10;

            const sorted = Array.from(alerts).sort((a, b) => 
                (parseInt(a.dataset.timestamp) || 0) - (parseInt(b.dataset.timestamp) || 0)
            );

            // 使用 requestAnimationFrame 优化
            requestAnimationFrame(() => {
                sorted.forEach((el, index) => {
                    el.style.top = currentTop + 'px';
                    // 移除旧的过渡,避免闪烁
                    if (index === sorted.length - 1) {
                        el.style.transition = 'top 0.3s ease';
                    }
                    currentTop += el.offsetHeight + gap;
                });
            });
        }

        // ====== 弹幕模式 ======
        showMarquee(commentData) {
            const videoInfo = this.getVideoInfo();
            const { text, author, time, ipLocation, matchedKeywords } = commentData;

            if (!this.marqueeContainer) {
                this.marqueeContainer = document.createElement('div');
                this.marqueeContainer.id = 'marquee-container';
                this.marqueeContainer.style.cssText = `
                    position: fixed;
                    top: 70px;
                    left: 0;
                    right: 0;
                    display: flex;
                    flex-direction: column;
                    align-items: center;
                    gap: 8px;
                    z-index: 999999999;
                    pointer-events: none;
                `;
                document.body.appendChild(this.marqueeContainer);
            }

            const marquee = document.createElement('div');
            marquee.className = 'dy-comment-marquee';
            marquee.style.cssText = `
                width: 700px;
                max-width: 90%;
                background: #ffffff;
                border-radius: 8px;
                padding: 16px 20px;
                color: #333;
                font-size: 14px;
                box-shadow: 0 4px 16px rgba(0,0,0,0.15);
                border: 1px solid #eaeaea;
                pointer-events: auto;
                margin-bottom: 4px;
                animation: marqueeFadeIn 0.3s ease;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                line-height: 1.5;
            `;

            // 视频信息
            if (this.configManager.get('showVideoInfo')) {
                const videoInfoDiv = document.createElement('div');
                videoInfoDiv.style.cssText = `
                    margin-bottom: 12px;
                    padding: 10px 12px;
                    background-color: #f5f9ff;
                    border-radius: 6px;
                    border-left: 3px solid #4CAF50;
                `;

                const authorLine = document.createElement('div');
                authorLine.style.cssText = `
                    display: flex;
                    align-items: center;
                    gap: 12px;
                    margin-bottom: 6px;
                    flex-wrap: wrap;
                `;
                authorLine.innerHTML = `
                    <span style="font-weight:600; color:#1a1a1a;">👤 ${videoInfo.author}</span>
                    <span style="color:#999; font-size:12px;">🕒 ${videoInfo.publishTime}</span>
                `;

                const titleLine = document.createElement('div');
                titleLine.className = 'video-title';
                titleLine.style.cssText = `
                    margin-bottom: 4px;
                    font-size: 13px;
                    color: #333;
                `;
                titleLine.innerHTML = `📝 ${videoInfo.title}`;

                videoInfoDiv.appendChild(authorLine);
                videoInfoDiv.appendChild(titleLine);

                if (this.configManager.get('showVideoLink')) {
                    const linkLine = document.createElement('div');
                    linkLine.style.cssText = `
                        margin-top: 6px;
                        font-size: 12px;
                        display: flex;
                        align-items: center;
                        gap: 6px;
                    `;
                    linkLine.innerHTML = `
                        <span style="color:#666;">🔗</span>
                        <a href="${videoInfo.videoUrl}" target="_blank" class="video-link">打开视频</a>
                    `;
                    videoInfoDiv.appendChild(linkLine);
                }

                marquee.appendChild(videoInfoDiv);
            }

            // 评论信息
            const commentInfoDiv = document.createElement('div');
            commentInfoDiv.style.cssText = `
                margin-bottom: 12px;
                padding: 10px 12px;
                background-color: #f8f8f8;
                border-radius: 6px;
                border-left: 3px solid #ff4d4d;
            `;

            const metaLine = document.createElement('div');
            metaLine.style.cssText = `
                display: flex;
                align-items: center;
                gap: 12px;
                margin-bottom: 8px;
                flex-wrap: wrap;
            `;
            metaLine.innerHTML = `
                <span style="font-weight:600; color:#1a1a1a;">👤 ${author || '匿名'}</span>
                <span style="color:#666;">📍 ${ipLocation || '未知'}</span>
                <span style="color:#999; font-size:12px;">🕒 ${Utils.formatTime(time)}</span>
            `;

            const keywordsBar = document.createElement('div');
            keywordsBar.style.cssText = `
                display: flex;
                align-items: center;
                gap: 6px;
                margin-bottom: 8px;
                flex-wrap: wrap;
            `;
            matchedKeywords.forEach(kw => {
                const tag = document.createElement('span');
                tag.className = 'keyword-tag';
                tag.textContent = `# ${kw}`;
                keywordsBar.appendChild(tag);
            });

            const contentDiv = document.createElement('div');
            contentDiv.style.cssText = `
                font-size: 14px;
                line-height: 1.6;
                word-break: break-all;
                padding: 4px 0;
                color: #444;
            `;
            contentDiv.textContent = text;

            commentInfoDiv.appendChild(metaLine);
            commentInfoDiv.appendChild(keywordsBar);
            commentInfoDiv.appendChild(contentDiv);
            marquee.appendChild(commentInfoDiv);

            // 操作按钮
            const actionsBar = document.createElement('div');
            actionsBar.style.cssText = `
                display: flex;
                justify-content: flex-end;
                gap: 10px;
                margin-top: 8px;
            `;

            const copyBtn = this.createActionButton('📋 复制完整信息', '#4CAF50', () => {
                this.copyFullComment(commentData, videoInfo);
            });

            const closeBtn = this.createActionButton('✕ 关闭', '#ff4d4d', () => {
                marquee.remove();
                if (this.marqueeContainer && this.marqueeContainer.children.length === 0) {
                    this.marqueeContainer.remove();
                    this.marqueeContainer = null;
                }
            });

            actionsBar.appendChild(copyBtn);
            actionsBar.appendChild(closeBtn);
            marquee.appendChild(actionsBar);

            // 点击弹幕复制完整信息
            marquee.addEventListener('click', (e) => {
                if (e.target.closest('button') || e.target.closest('a')) return;
                this.copyFullComment(commentData, videoInfo);
            });

            this.marqueeContainer.appendChild(marquee);

            // 限制数量
            const maxMarquees = this.configManager.get('maxMarquees');
            while (this.marqueeContainer.children.length > maxMarquees) {
                this.marqueeContainer.removeChild(this.marqueeContainer.firstChild);
            }
        }

        // ====== 复制功能 ======
        copyFullComment(commentData, videoInfo) {
            const { text, author, time, ipLocation, matchedKeywords } = commentData;
            const timeStr = Utils.formatTime(time);
            const keywordStr = matchedKeywords.length > 0 ? `[关键词: ${matchedKeywords.join('、')}]` : '';

            const fullInfo = `👤 评论用户:${author || '匿名'}
📍 评论地区:${ipLocation || '未知'}
🕒 评论时间:${timeStr}
🔑 ${keywordStr}

📹 视频信息:
   👤 发布者:${videoInfo.author}
   🕒 发布时间:${videoInfo.publishTime}
   📝 标题:${videoInfo.fullTitle}
   🔗 链接:${videoInfo.videoUrl}

💬 评论内容:${text}

-----
来自抖音评论监控插件 v3.0`;

            if (Utils.copyToClipboard(fullInfo)) {
                Utils.showToast('✅ 已复制完整信息', 1500);
            } else {
                Utils.showToast('❌ 复制失败,请手动复制', 1500, true);
            }
        }

        // ====== 历史记录 ======
        addHistoryRecord(commentData, videoInfo) {
            const record = {
                id: `${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
                timestamp: Date.now(),
                date: new Date().toLocaleString(),
                comment: {
                    text: commentData.text,
                    author: commentData.author,
                    time: commentData.time,
                    timeStr: Utils.formatTime(commentData.time),
                    ipLocation: commentData.ipLocation,
                    matchedKeywords: commentData.matchedKeywords
                },
                video: {
                    author: videoInfo.author,
                    publishTime: videoInfo.publishTime,
                    title: videoInfo.fullTitle,
                    url: videoInfo.videoUrl
                }
            };

            this.stateManager.addHistoryRecord(record);
        }

        showHistoryWindow() {
            const history = this.stateManager.getHistory();

            const overlay = document.createElement('div');
            overlay.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: rgba(0,0,0,0.7);
                display: flex;
                align-items: center;
                justify-content: center;
                z-index: 999999999;
                backdrop-filter: blur(4px);
                animation: fadeIn 0.3s ease;
            `;

            const windowDiv = document.createElement('div');
            windowDiv.style.cssText = `
                width: 90%;
                max-width: 1300px;
                height: 80%;
                background: #1e1e1e;
                border-radius: 12px;
                box-shadow: 0 20px 40px rgba(0,0,0,0.5);
                color: #fff;
                display: flex;
                flex-direction: column;
                overflow: hidden;
                border: 1px solid #333;
                animation: slideUp 0.3s ease;
            `;

            // 标题栏
            const titleBar = document.createElement('div');
            titleBar.style.cssText = `
                padding: 16px 20px;
                border-bottom: 1px solid #333;
                display: flex;
                justify-content: space-between;
                align-items: center;
                background: #252525;
                flex-shrink: 0;
            `;

            const title = document.createElement('h3');
            title.style.cssText = `
                margin: 0;
                font-size: 16px;
                font-weight: 500;
            `;
            title.innerHTML = `📋 历史记录 (${history.length})`;

            const closeBtn = document.createElement('button');
            closeBtn.textContent = '×';
            closeBtn.style.cssText = `
                background: none;
                border: none;
                color: #aaa;
                font-size: 24px;
                cursor: pointer;
                padding: 0 8px;
                transition: color 0.2s;
            `;
            closeBtn.onmouseenter = () => closeBtn.style.color = '#fff';
            closeBtn.onmouseleave = () => closeBtn.style.color = '#aaa';
            closeBtn.onclick = () => overlay.remove();

            titleBar.appendChild(title);
            titleBar.appendChild(closeBtn);

            // 工具栏
            const toolbar = document.createElement('div');
            toolbar.style.cssText = `
                padding: 12px 20px;
                border-bottom: 1px solid #333;
                display: flex;
                gap: 10px;
                background: #1a1a1a;
                flex-shrink: 0;
                flex-wrap: wrap;
            `;

            const exportBtn = this.createActionButton('📥 导出CSV', '#4CAF50', () => {
                this.exportHistoryToCSV();
            });

            const clearBtn = this.createActionButton('🗑️ 清空记录', '#ff4d4d', () => {
                if (confirm('确定要清空所有历史记录吗?')) {
                    this.stateManager.clearHistory();
                    Utils.showToast('🗑️ 历史记录已清空', 1500);
                    overlay.remove();
                }
            });

            toolbar.appendChild(exportBtn);
            toolbar.appendChild(clearBtn);

            // 表格容器
            const tableContainer = document.createElement('div');
            tableContainer.style.cssText = `
                flex: 1;
                overflow: auto;
                padding: 20px;
            `;

            if (history.length === 0) {
                const emptyMsg = document.createElement('div');
                emptyMsg.style.cssText = `
                    text-align: center;
                    padding: 50px;
                    color: #666;
                    font-size: 16px;
                `;
                emptyMsg.textContent = '暂无历史记录';
                tableContainer.appendChild(emptyMsg);
            } else {
                const table = document.createElement('table');
                table.style.cssText = `
                    width: 100%;
                    border-collapse: collapse;
                    font-size: 13px;
                    min-width: 1000px;
                `;

                const thead = document.createElement('thead');
                const headerRow = document.createElement('tr');
                headerRow.style.cssText = `
                    background: #2a2a2a;
                `;

                const headers = [
                    '序号', '视频作者', '视频标题', '视频发布时间',
                    '评论人', '评论时间', '评论地区', '评论内容',
                    '命中关键词', '视频链接'
                ];

                headers.forEach(text => {
                    const th = document.createElement('th');
                    th.textContent = text;
                    th.style.cssText = `
                        padding: 12px 8px;
                        text-align: left;
                        border-bottom: 2px solid #444;
                        color: #aaa;
                        font-weight: 500;
                        position: sticky;
                        top: 0;
                        background: #2a2a2a;
                    `;
                    headerRow.appendChild(th);
                });
                thead.appendChild(headerRow);
                table.appendChild(thead);

                const tbody = document.createElement('tbody');
                history.forEach((record, index) => {
                    const row = document.createElement('tr');
                    row.style.cssText = `
                        border-bottom: 1px solid #333;
                        transition: background 0.2s;
                    `;
                    row.onmouseenter = () => row.style.background = '#2a2a2a';
                    row.onmouseleave = () => row.style.background = 'transparent';

                    const cells = [
                        index + 1,
                        record.video.author,
                        record.video.title,
                        record.video.publishTime,
                        record.comment.author,
                        record.comment.timeStr,
                        record.comment.ipLocation,
                        record.comment.text,
                        record.comment.matchedKeywords.join('、'),
                        record.video.url
                    ];

                    cells.forEach((cell, i) => {
                        const td = document.createElement('td');
                        td.style.cssText = `
                            padding: 10px 8px;
                            color: ${i === 8 ? '#ffb3b3' : '#e0e0e0'};
                            max-width: ${i === 2 ? '200px' : (i === 7 ? '250px' : 'none')};
                            overflow: hidden;
                            text-overflow: ellipsis;
                            white-space: nowrap;
                        `;

                        if (i === 9) {
                            const link = document.createElement('a');
                            link.href = cell;
                            link.target = '_blank';
                            link.textContent = '打开';
                            link.style.cssText = `
                                color: #4CAF50;
                                text-decoration: none;
                                transition: color 0.2s;
                            `;
                            link.onmouseenter = () => link.style.textDecoration = 'underline';
                            link.onmouseleave = () => link.style.textDecoration = 'none';
                            td.appendChild(link);
                        } else {
                            td.textContent = cell;
                            if (i === 8 && cell) {
                                td.style.fontWeight = '500';
                            }
                        }

                        row.appendChild(td);
                    });

                    tbody.appendChild(row);
                });
                table.appendChild(tbody);
                tableContainer.appendChild(table);
            }

            windowDiv.appendChild(titleBar);
            windowDiv.appendChild(toolbar);
            windowDiv.appendChild(tableContainer);
            overlay.appendChild(windowDiv);
            document.body.appendChild(overlay);
        }

        exportHistoryToCSV() {
            const history = this.stateManager.getHistory();
            if (history.length === 0) {
                Utils.showToast('📭 没有历史记录可导出', 1500);
                return;
            }

            const headers = [
                '序号', '视频作者', '视频标题', '视频发布时间',
                '评论人', '评论时间', '评论地区', '评论内容',
                '命中关键词', '视频链接', '记录时间'
            ];

            const rows = history.map((record, index) => {
                return [
                    index + 1,
                    record.video.author,
                    record.video.title.replace(/,/g, ','),
                    record.video.publishTime,
                    record.comment.author,
                    record.comment.timeStr,
                    record.comment.ipLocation,
                    record.comment.text.replace(/,/g, ','),
                    record.comment.matchedKeywords.join('、'),
                    record.video.url,
                    record.date
                ];
            });

            const csvContent = [
                headers.join(','),
                ...rows.map(row => row.map(cell => `"${cell}"`).join(','))
            ].join('\n');

            try {
                const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
                const url = URL.createObjectURL(blob);
                const link = document.createElement('a');
                link.href = url;
                link.download = `抖音评论监控_${new Date().toLocaleDateString()}.csv`;
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                URL.revokeObjectURL(url);
                Utils.showToast('📥 历史记录已导出', 1500);
            } catch (e) {
                Utils.showToast('❌ 导出失败: ' + e.message, 2000, true);
            }
        }

        // ====== 评论区控制 ======
        openCommentPanel() {
            if (this.stateManager.get('isCommentPanelOpen')) return;

            // 模拟 X 键
            const event = new KeyboardEvent('keydown', {
                key: 'x',
                keyCode: 88,
                which: 88,
                code: 'KeyX',
                bubbles: true,
                cancelable: true
            });
            document.dispatchEvent(event);

            this.stateManager.set('isCommentPanelOpen', true);
            Utils.showToast('💬 尝试打开评论区 (模拟X键)', 1500);

            setTimeout(() => {
                const commentEl = this.findCommentContainer();
                if (commentEl) {
                    Utils.showToast('✅ 评论区已打开', 1000);
                    this.stateManager.set('commentContainer', commentEl);
                    this.stateManager.set('lastCommentCount', this.getCommentCount());

                    if (this.configManager.get('autoScrollComments')) {
                        this.startCommentScroll();
                    }

                    if (this.configManager.get('autoNextVideo')) {
                        this.startMonitoring();
                    }
                } else {
                    this.stateManager.set('isCommentPanelOpen', false);
                }
            }, 2000);
        }

        scheduleOpenComment() {
            if (this.openCommentTimer) {
                clearTimeout(this.openCommentTimer);
                this.openCommentTimer = null;
            }
            const delay = this.configManager.get('openCommentDelay');
            this.openCommentTimer = setTimeout(() => {
                this.openCommentTimer = null;
                this.openCommentPanel();
            }, delay);
            Utils.showToast(`⏰ ${delay/1000}秒后打开评论区`, 1500);
        }

        // ====== 评论滚动 ======
        startCommentScroll() {
            if (this.scrollTimer) {
                clearInterval(this.scrollTimer);
                this.scrollTimer = null;
            }

            let container = this.stateManager.get('commentContainer');
            if (!container) {
                container = this.findCommentContainer();
                this.stateManager.set('commentContainer', container);
            }

            if (!container) {
                Utils.showToast('⚠️ 未找到评论区', 1500);
                return;
            }

            Utils.showToast('🔄 开始滚动加载评论', 1500);

            this.scrollTimer = setInterval(() => {
                const currentContainer = this.stateManager.get('commentContainer');
                if (currentContainer && this.configManager.get('autoScrollComments') && 
                    !this.stateManager.get('isWaitingForNext')) {
                    currentContainer.scrollTop = currentContainer.scrollHeight;
                    currentContainer.scrollBy(0, 200);
                }
            }, this.configManager.get('scrollInterval'));
        }

        stopCommentScroll() {
            if (this.scrollTimer) {
                clearInterval(this.scrollTimer);
                this.scrollTimer = null;
                Utils.showToast('⏸️ 停止滚动', 1000);
            }
        }

        // ====== 自动切换视频 ======
        startMonitoring() {
            if (this.checkInterval) {
                clearInterval(this.checkInterval);
                this.checkInterval = null;
            }

            this.checkInterval = setInterval(() => {
                if (!this.stateManager.get('isCommentPanelOpen')) return;
                if (this.stateManager.get('isWaitingForNext')) return;
                if (this.shouldSwitchVideo()) {
                    this.startNextVideoTimer();
                }
            }, 1500);
        }

        shouldSwitchVideo() {
            if (!this.configManager.get('autoNextVideo')) return false;
            if (!this.stateManager.get('isCommentPanelOpen')) return false;
            if (this.stateManager.get('isWaitingForNext')) return false;

            const container = this.stateManager.get('commentContainer');
            if (!container) return false;

            // 检查是否滚动到底部
            const scrollTop = container.scrollTop;
            const scrollHeight = container.scrollHeight;
            const clientHeight = container.clientHeight;

            if (scrollTop + clientHeight >= scrollHeight - 10) {
                const lastScrollTop = this.stateManager.get('lastScrollTop');
                const scrollStableCount = this.stateManager.get('scrollStableCount');

                if (scrollTop === lastScrollTop) {
                    this.stateManager.set('scrollStableCount', scrollStableCount + 1);
                    if (scrollStableCount + 1 >= 2) return true;
                } else {
                    this.stateManager.set('scrollStableCount', 0);
                }
                this.stateManager.set('lastScrollTop', scrollTop);
            }

            // 检查评论是否停滞
            const currentCount = this.getCommentCount();
            const lastCount = this.stateManager.get('lastCommentCount');
            const noIncreaseCount = this.stateManager.get('noIncreaseCount');

            if (currentCount === lastCount) {
                this.stateManager.set('noIncreaseCount', noIncreaseCount + 1);
                if (noIncreaseCount + 1 >= 3) return true;
            } else {
                this.stateManager.set('noIncreaseCount', 0);
            }
            this.stateManager.set('lastCommentCount', currentCount);

            return false;
        }

        startNextVideoTimer() {
            if (!this.configManager.get('autoNextVideo') || this.stateManager.get('isWaitingForNext')) return;
            if (this.nextVideoTimer) {
                clearTimeout(this.nextVideoTimer);
                this.nextVideoTimer = null;
            }

            const delay = this.configManager.get('nextVideoDelay');
            Utils.showToast(`⏰ ${delay/1000}秒后切换视频`, 2000);

            this.stateManager.set('isWaitingForNext', true);
            this.nextVideoTimer = setTimeout(() => {
                this.nextVideoTimer = null;
                this.stateManager.set('isWaitingForNext', false);
                this.triggerNextVideo();
            }, delay);
        }

        resetNextVideoTimer() {
            if (!this.stateManager.get('isWaitingForNext')) return;
            if (this.nextVideoTimer) {
                clearTimeout(this.nextVideoTimer);
                this.nextVideoTimer = null;
            }

            Utils.showToast('🔄 检测到新评论,重置计时器', 1500);
            this.stateManager.set('isWaitingForNext', false);
            this.startNextVideoTimer();
        }

        triggerNextVideo() {
            const event = new KeyboardEvent('keydown', {
                key: 'ArrowDown',
                code: 'ArrowDown',
                keyCode: 40,
                bubbles: true,
                cancelable: true
            });
            document.dispatchEvent(event);
            Utils.showToast('⏩ 切换到下一个视频', 1000);

            // 重置状态
            this.stateManager.reset();
            this.stateManager.set('commentContainer', null);
            this.stopCommentScroll();
            if (this.checkInterval) {
                clearInterval(this.checkInterval);
                this.checkInterval = null;
            }
        }

        // ====== 视频切换检测 ======
        checkVideoChange() {
            const newId = this.stateManager.getVideoId();
            const currentId = this.stateManager.get('currentVideoId');

            if (newId && currentId && newId !== currentId) {
                console.log('检测到视频切换');
                this.stateManager.set('currentVideoId', newId);

                // 重置状态
                this.stateManager.reset();
                this.stateManager.set('commentContainer', null);
                this.stopCommentScroll();
                if (this.checkInterval) {
                    clearInterval(this.checkInterval);
                    this.checkInterval = null;
                }
                if (this.nextVideoTimer) {
                    clearTimeout(this.nextVideoTimer);
                    this.nextVideoTimer = null;
                }
                this.stateManager.set('isWaitingForNext', false);

                // 重新打开评论区
                if (this.configManager.get('autoOpenComment')) {
                    this.scheduleOpenComment();
                }
            }
            this.stateManager.set('currentVideoId', newId);
        }

        // ====== 事件监听 ======
        setupEventListeners() {
            // 键盘快捷键
            document.addEventListener('keydown', (e) => {
                // Ctrl+Shift+H 打开历史记录
                if (e.ctrlKey && e.shiftKey && (e.key === 'h' || e.key === 'H')) {
                    e.preventDefault();
                    this.showHistoryWindow();
                }
                // Alt+X 打开评论区
                if (e.altKey && (e.key === 'x' || e.key === 'X')) {
                    e.preventDefault();
                    this.openCommentPanel();
                }
            });
        }

        // ====== 控制面板 ======
        createPanel() {
            if (this.panel) {
                this.panel.remove();
                this.panel = null;
            }

            const isMinimized = this.isPanelMinimized;
            this.panel = document.createElement('div');
            this.panel.id = 'comment-monitor-panel';
            this.panel.className = isMinimized ? 'minimized' : '';
            this.panel.style.cssText = `
                position: fixed;
                bottom: 20px;
                right: 20px;
                width: ${isMinimized ? '160px' : '420px'};
                max-height: 80vh;
                background: #1f1f1f;
                border-radius: 12px;
                box-shadow: 0 8px 20px rgba(0,0,0,0.5);
                color: #e0e0e0;
                font-size: 13px;
                z-index: 9999999;
                border: 1px solid #333;
                font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
                cursor: default;
                user-select: none;
                display: flex;
                flex-direction: column;
            `;

            // 头部
            const header = document.createElement('div');
            header.id = 'panel-header';
            header.style.cssText = `
                padding: 10px 14px;
                border-bottom: ${isMinimized ? 'none' : '1px solid #333'};
                display: flex;
                justify-content: space-between;
                align-items: center;
                background: #252525;
                border-radius: 12px 12px 0 0;
                cursor: move;
                user-select: none;
                flex-shrink: 0;
            `;

            const titleArea = document.createElement('div');
            titleArea.style.cssText = `
                display: flex;
                align-items: center;
                gap: 6px;
            `;

            const minimizeBtn = document.createElement('span');
            minimizeBtn.textContent = isMinimized ? '□' : '—';
            minimizeBtn.title = isMinimized ? '展开' : '最小化';
            minimizeBtn.style.cssText = `
                cursor: pointer;
                color: #888;
                font-size: 14px;
                padding: 2px 6px;
                border-radius: 4px;
                transition: all 0.2s;
            `;
            minimizeBtn.onmouseenter = () => minimizeBtn.style.color = '#fff';
            minimizeBtn.onmouseleave = () => minimizeBtn.style.color = '#888';
            minimizeBtn.onclick = (e) => {
                e.stopPropagation();
                this.isPanelMinimized = !this.isPanelMinimized;
                this.createPanel();
            };

            const title = document.createElement('div');
            title.style.cssText = `
                font-weight: 500;
                font-size: ${isMinimized ? '13px' : '14px'};
            `;
            title.innerHTML = isMinimized ? '🎯 监控' : '弹幕监控 v3.0';

            const historyBtn = document.createElement('span');
            historyBtn.textContent = '📋';
            historyBtn.title = '查看历史记录 (Ctrl+Shift+H)';
            historyBtn.style.cssText = `
                cursor: pointer;
                color: #4CAF50;
                font-size: 14px;
                padding: 2px 6px;
                border-radius: 4px;
                transition: all 0.2s;
            `;
            historyBtn.onmouseenter = () => { historyBtn.style.background = '#333'; };
            historyBtn.onmouseleave = () => { historyBtn.style.background = 'transparent'; };
            historyBtn.onclick = (e) => {
                e.stopPropagation();
                this.showHistoryWindow();
            };

            const saveBtn = document.createElement('span');
            saveBtn.textContent = '💾';
            saveBtn.title = '保存配置';
            saveBtn.style.cssText = `
                cursor: pointer;
                color: #4CAF50;
                font-size: 14px;
                padding: 2px 6px;
                border-radius: 4px;
                transition: all 0.2s;
            `;
            saveBtn.onmouseenter = () => { saveBtn.style.background = '#333'; };
            saveBtn.onmouseleave = () => { saveBtn.style.background = 'transparent'; };
            saveBtn.onclick = (e) => {
                e.stopPropagation();
                this.configManager.save();
                Utils.showToast('💾 配置已保存', 1000);
            };

            titleArea.appendChild(minimizeBtn);
            titleArea.appendChild(title);
            titleArea.appendChild(historyBtn);
            titleArea.appendChild(saveBtn);

            const closeBtn = document.createElement('span');
            closeBtn.textContent = '×';
            closeBtn.style.cssText = `
                cursor: pointer;
                color: #888;
                font-size: 18px;
                font-weight: 400;
                padding: 0 4px;
                border-radius: 4px;
                transition: all 0.2s;
            `;
            closeBtn.onmouseenter = () => { closeBtn.style.background = '#333'; closeBtn.style.color = '#fff'; };
            closeBtn.onmouseleave = () => { closeBtn.style.background = 'transparent'; closeBtn.style.color = '#888'; };
            closeBtn.onclick = () => {
                if (this.panel) {
                    this.panel.remove();
                    this.panel = null;
                }
            };

            header.appendChild(titleArea);
            header.appendChild(closeBtn);

            // 拖拽
            header.addEventListener('mousedown', (e) => {
                if (e.button !== 0) return;
                const target = e.target;
                if (target === closeBtn || target === minimizeBtn || 
                    target === historyBtn || target === saveBtn) return;
                e.preventDefault();

                const rect = this.panel.getBoundingClientRect();
                this.dragOffsetX = e.clientX - rect.left;
                this.dragOffsetY = e.clientY - rect.top;
                this.isDragging = true;
                this.panel.style.transition = 'none';
                this.panel.style.cursor = 'grabbing';
            });

            document.addEventListener('mousemove', (e) => {
                if (!this.isDragging || !this.panel) return;
                e.preventDefault();

                const newLeft = e.clientX - this.dragOffsetX;
                const newTop = e.clientY - this.dragOffsetY;
                const maxX = window.innerWidth - this.panel.offsetWidth;
                const maxY = window.innerHeight - this.panel.offsetHeight;

                this.panel.style.left = Math.min(Math.max(0, newLeft), maxX) + 'px';
                this.panel.style.top = Math.min(Math.max(0, newTop), maxY) + 'px';
                this.panel.style.right = 'auto';
                this.panel.style.bottom = 'auto';
            });

            document.addEventListener('mouseup', () => {
                if (this.isDragging && this.panel) {
                    this.isDragging = false;
                    this.panel.style.cursor = 'default';
                    this.panel.style.transition = 'width 0.3s ease, height 0.3s ease';
                }
            });

            header.addEventListener('dragstart', (e) => e.preventDefault());

            this.panel.appendChild(header);

            // 内容区域
            if (!isMinimized) {
                const body = document.createElement('div');
                body.className = 'panel-content dy-monitor-scrollbar';
                body.style.cssText = `
                    padding: 14px;
                    overflow-y: auto;
                    flex: 1;
                `;

                this.buildPanelContent(body);
                this.panel.appendChild(body);
            }

            document.body.appendChild(this.panel);
        }

        buildPanelContent(container) {
            const historyCount = this.stateManager.getHistory().length;

            // 状态信息
            const statusSection = this.createSection('状态', `
                <div style="margin-bottom:8px;font-size:12px;color:#aaa;">📋 历史记录: ${historyCount} 条</div>
                <div style="display:flex;gap:8px;">
                    <button class="dy-panel-btn primary" data-action="openComment">打开评论区 (X)</button>
                    <button class="dy-panel-btn info" data-action="startScroll">开始滚动</button>
                    <button class="dy-panel-btn danger" data-action="stopScroll">停止滚动</button>
                </div>
            `);
            container.appendChild(statusSection);

            // 弹窗模式
            const alertMode = this.configManager.get('alertMode');
            const modeSection = this.createSection('🎨 弹窗模式', `
                <div style="display:flex;gap:6px;">
                    <button class="dy-panel-btn ${alertMode === 'left' ? 'primary' : 'secondary'}" data-action="modeLeft">左侧</button>
                    <button class="dy-panel-btn ${alertMode === 'right' ? 'info' : 'secondary'}" data-action="modeRight">右侧</button>
                    <button class="dy-panel-btn ${alertMode === 'marquee' ? 'warning' : 'secondary'}" data-action="modeMarquee">弹幕</button>
                </div>
            `);
            container.appendChild(modeSection);

            // 显示数量
            const maxAlerts = this.configManager.get('maxSideAlerts');
            const countSection = this.createSection('📊 右侧弹窗数量', `
                <div style="display:flex;align-items:center;gap:8px;">
                    <input type="number" id="alertCountInput" min="1" max="5" value="${maxAlerts}" 
                           style="width:60px;background:#2a2a2a;border:1px solid #3a3a3a;border-radius:4px;padding:4px;color:#fff;text-align:center;">
                    <span style="color:#ccc;font-size:11px;">条 (1-5)</span>
                    <button class="dy-panel-btn primary" style="margin-left:auto;" data-action="applyCount">应用</button>
                </div>
            `);
            container.appendChild(countSection);

            // 自动播放下一个
            const autoNext = this.configManager.get('autoNextVideo');
            const nextDelay = this.configManager.get('nextVideoDelay');
            const openDelay = this.configManager.get('openCommentDelay');

            const autoSection = this.createSection('⏩ 自动播放下一个', `
                <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
                    <span style="color:#aaa;font-size:12px;">自动播放下一个视频</span>
                    <label style="position:relative;display:inline-block;width:40px;height:22px;">
                        <input type="checkbox" id="autoNextToggle" ${autoNext ? 'checked' : ''} 
                               style="opacity:0;width:0;height:0;">
                        <span style="position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:${autoNext ? '#4CAF50' : '#555'};
                             transition:0.3s;border-radius:22px;">
                            <span style="position:absolute;content:'';height:16px;width:16px;left:3px;bottom:3px;
                                 background:white;transition:0.3s;border-radius:50%;transform:${autoNext ? 'translateX(18px)' : 'none'};"></span>
                        </span>
                    </label>
                </div>
                <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
                    <span style="color:#aaa;font-size:11px;">切换延迟:</span>
                    <input type="number" id="nextDelayInput" min="1000" max="10000" step="500" value="${nextDelay}"
                           style="width:80px;background:#2a2a2a;border:1px solid #3a3a3a;border-radius:4px;padding:4px;color:#fff;text-align:center;">
                    <span style="color:#ccc;font-size:11px;">毫秒</span>
                    <button class="dy-panel-btn primary" style="margin-left:auto;" data-action="applyDelay">应用</button>
                </div>
                <div style="display:flex;align-items:center;gap:8px;padding-top:6px;border-top:1px solid #333;">
                    <span style="color:#aaa;font-size:11px;">打开评论区延迟:</span>
                    <input type="number" id="openDelayInput" min="1000" max="10000" step="500" value="${openDelay}"
                           style="width:80px;background:#2a2a2a;border:1px solid #3a3a3a;border-radius:4px;padding:4px;color:#fff;text-align:center;">
                    <span style="color:#ccc;font-size:11px;">毫秒</span>
                    <button class="dy-panel-btn info" style="margin-left:auto;" data-action="applyOpenDelay">应用</button>
                </div>
            `);
            container.appendChild(autoSection);

            // 视频信息开关
            const showVideo = this.configManager.get('showVideoInfo');
            const showLink = this.configManager.get('showVideoLink');
            const infoSection = this.createSection('📹 显示选项', `
                <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
                    <span style="color:#aaa;font-size:12px;">显示视频信息</span>
                    <label style="position:relative;display:inline-block;width:40px;height:22px;">
                        <input type="checkbox" id="showVideoInfo" ${showVideo ? 'checked' : ''}
                               style="opacity:0;width:0;height:0;">
                        <span style="position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:${showVideo ? '#4CAF50' : '#555'};
                             transition:0.3s;border-radius:22px;">
                            <span style="position:absolute;content:'';height:16px;width:16px;left:3px;bottom:3px;
                                 background:white;transition:0.3s;border-radius:50%;transform:${showVideo ? 'translateX(18px)' : 'none'};"></span>
                        </span>
                    </label>
                </div>
                <div style="display:flex;justify-content:space-between;align-items:center;">
                    <span style="color:#aaa;font-size:12px;">显示视频链接</span>
                    <label style="position:relative;display:inline-block;width:40px;height:22px;">
                        <input type="checkbox" id="showVideoLink" ${showLink ? 'checked' : ''}
                               style="opacity:0;width:0;height:0;">
                        <span style="position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:${showLink ? '#4CAF50' : '#555'};
                             transition:0.3s;border-radius:22px;">
                            <span style="position:absolute;content:'';height:16px;width:16px;left:3px;bottom:3px;
                                 background:white;transition:0.3s;border-radius:50%;transform:${showLink ? 'translateX(18px)' : 'none'};"></span>
                        </span>
                    </label>
                </div>
            `);
            container.appendChild(infoSection);

            // 地区管理
            const regions = this.configManager.getRegions();
            const enableRegion = this.configManager.get('enableRegionFilter');
            const regionSection = this.createSection(`📍 地区 (${regions.size})`, `
                <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
                    <span style="color:#aaa;font-size:12px;">地区筛选</span>
                    <label style="display:flex;align-items:center;gap:4px;color:#4CAF50;font-size:11px;">
                        <input type="checkbox" id="enableRegionFilter" ${enableRegion ? 'checked' : ''}>
                        开启
                    </label>
                </div>
                <div id="regionList" style="margin-bottom:8px;max-height:70px;overflow-y:auto;background:#252525;border-radius:4px;padding:4px;"></div>
                <div style="display:flex;gap:4px;">
                    <input type="text" id="regionInput" placeholder="地区" 
                           style="flex:1;background:#2a2a2a;border:1px solid #3a3a3a;border-radius:4px;padding:4px 8px;color:#fff;outline:none;font-size:11px;">
                    <button class="dy-panel-btn primary" data-action="addRegion" style="width:24px;font-size:14px;">+</button>
                </div>
            `);
            container.appendChild(regionSection);

            // 关键词管理
            const keywords = this.configManager.getKeywords();
            const keywordSection = this.createSection(`🔑 关键词 (${keywords.size})`, `
                <div id="keywordList" style="margin-bottom:8px;max-height:80px;overflow-y:auto;background:#252525;border-radius:4px;padding:4px;"></div>
                <div style="display:flex;gap:4px;margin-bottom:8px;">
                    <input type="text" id="keywordInput" placeholder="添加关键词"
                           style="flex:1;background:#2a2a2a;border:1px solid #3a3a3a;border-radius:4px;padding:4px 8px;color:#fff;outline:none;font-size:11px;">
                    <button class="dy-panel-btn primary" data-action="addKeyword" style="width:24px;font-size:14px;">+</button>
                </div>
                <textarea id="batchKeywordInput" placeholder="批量添加(逗号/空格分隔)"
                          style="width:100%;background:#2a2a2a;border:1px solid #3a3a3a;border-radius:4px;padding:6px;color:#fff;font-size:11px;resize:vertical;min-height:40px;"></textarea>
                <button class="dy-panel-btn secondary" data-action="batchAdd" style="width:100%;margin-top:4px;">批量添加</button>
            `);
            container.appendChild(keywordSection);

            // 绑定事件
            this.bindPanelEvents(container);

            // 渲染列表
            this.renderRegionList(container);
            this.renderKeywordList(container);
        }

        createSection(title, content) {
            const section = document.createElement('div');
            section.style.cssText = `
                margin-bottom: 14px;
                padding: 10px;
                background: #1a1a1a;
                border-radius: 8px;
            `;

            const titleDiv = document.createElement('div');
            titleDiv.style.cssText = `
                margin-bottom: 8px;
                color: #aaa;
                font-size: 12px;
            `;
            titleDiv.textContent = title;
            section.appendChild(titleDiv);

            const contentDiv = document.createElement('div');
            contentDiv.innerHTML = content;
            section.appendChild(contentDiv);

            return section;
        }

        bindPanelEvents(container) {
            // 按钮事件
            container.querySelectorAll('[data-action]').forEach(el => {
                el.addEventListener('click', () => {
                    const action = el.dataset.action;
                    this.handlePanelAction(action, container);
                });
            });

            // 开关事件
            const autoNextToggle = container.querySelector('#autoNextToggle');
            if (autoNextToggle) {
                autoNextToggle.addEventListener('change', (e) => {
                    const checked = e.target.checked;
                    this.configManager.set('autoNextVideo', checked);
                    Utils.showToast(`⏩ 自动播放下一个: ${checked ? '开启' : '关闭'}`);
                    if (!checked && this.nextVideoTimer) {
                        clearTimeout(this.nextVideoTimer);
                        this.nextVideoTimer = null;
                        this.stateManager.set('isWaitingForNext', false);
                    }
                    if (checked && this.stateManager.get('isCommentPanelOpen')) {
                        this.startMonitoring();
                    }
                });
            }

            const showVideoInfo = container.querySelector('#showVideoInfo');
            if (showVideoInfo) {
                showVideoInfo.addEventListener('change', (e) => {
                    this.configManager.set('showVideoInfo', e.target.checked);
                    Utils.showToast(`📹 视频信息: ${e.target.checked ? '显示' : '隐藏'}`);
                });
            }

            const showVideoLink = container.querySelector('#showVideoLink');
            if (showVideoLink) {
                showVideoLink.addEventListener('change', (e) => {
                    this.configManager.set('showVideoLink', e.target.checked);
                    Utils.showToast(`🔗 视频链接: ${e.target.checked ? '显示' : '隐藏'}`);
                });
            }

            const enableRegionFilter = container.querySelector('#enableRegionFilter');
            if (enableRegionFilter) {
                enableRegionFilter.addEventListener('change', (e) => {
                    this.configManager.set('enableRegionFilter', e.target.checked);
                    Utils.showToast(`📍 筛选: ${e.target.checked ? '开启' : '关闭'}`);
                });
            }

            // Enter键支持
            const regionInput = container.querySelector('#regionInput');
            if (regionInput) {
                regionInput.addEventListener('keydown', (e) => {
                    if (e.key === 'Enter') {
                        this.handlePanelAction('addRegion', container);
                    }
                });
            }

            const keywordInput = container.querySelector('#keywordInput');
            if (keywordInput) {
                keywordInput.addEventListener('keydown', (e) => {
                    if (e.key === 'Enter') {
                        this.handlePanelAction('addKeyword', container);
                    }
                });
            }
        }

        handlePanelAction(action, container) {
            switch(action) {
                case 'openComment':
                    this.openCommentPanel();
                    break;
                case 'startScroll':
                    this.startCommentScroll();
                    break;
                case 'stopScroll':
                    this.stopCommentScroll();
                    break;
                case 'modeLeft':
                    this.configManager.set('alertMode', 'left');
                    Utils.showToast('✅ 已切换到左侧弹窗', 1000);
                    this.createPanel();
                    break;
                case 'modeRight':
                    this.configManager.set('alertMode', 'right');
                    Utils.showToast('✅ 已切换到右侧弹窗', 1000);
                    this.createPanel();
                    break;
                case 'modeMarquee':
                    this.configManager.set('alertMode', 'marquee');
                    Utils.showToast('✅ 已切换到弹幕模式', 1000);
                    this.createPanel();
                    break;
                case 'applyCount': {
                    const input = container.querySelector('#alertCountInput');
                    if (input) {
                        const val = parseInt(input.value);
                        if (val >= 1 && val <= 5) {
                            this.configManager.set('maxSideAlerts', val);
                            Utils.showToast(`✅ 右侧弹窗数量已设为 ${val}`);
                        } else {
                            Utils.showToast('❌ 请输入1-5之间的数字', 1500, true);
                        }
                    }
                    break;
                }
                case 'applyDelay': {
                    const input = container.querySelector('#nextDelayInput');
                    if (input) {
                        const val = parseInt(input.value);
                        if (val >= 1000 && val <= 10000) {
                            this.configManager.set('nextVideoDelay', val);
                            Utils.showToast(`⏱️ 切换延迟已设为 ${val}ms`);
                        } else {
                            Utils.showToast('❌ 请输入1000-10000之间的数字', 1500, true);
                        }
                    }
                    break;
                }
                case 'applyOpenDelay': {
                    const input = container.querySelector('#openDelayInput');
                    if (input) {
                        const val = parseInt(input.value);
                        if (val >= 1000 && val <= 10000) {
                            this.configManager.set('openCommentDelay', val);
                            Utils.showToast(`⏱️ 打开评论区延迟已设为 ${val}ms`);
                        } else {
                            Utils.showToast('❌ 请输入1000-10000之间的数字', 1500, true);
                        }
                    }
                    break;
                }
                case 'addRegion': {
                    const input = container.querySelector('#regionInput');
                    if (input) {
                        const val = input.value.trim();
                        if (val) {
                            const regions = this.configManager.getRegions();
                            regions.add(val);
                            this.configManager.setRegions(regions);
                            this.renderRegionList(container);
                            input.value = '';
                            Utils.showToast(`✅ 已添加: ${val}`);
                            this.createPanel();
                        }
                    }
                    break;
                }
                case 'addKeyword': {
                    const input = container.querySelector('#keywordInput');
                    if (input) {
                        const val = input.value.trim();
                        if (val) {
                            const keywords = this.configManager.getKeywords();
                            keywords.add(val);
                            this.configManager.setKeywords(keywords);
                            this.renderKeywordList(container);
                            input.value = '';
                            Utils.showToast(`✅ 已添加: ${val}`);
                            this.createPanel();
                        }
                    }
                    break;
                }
                case 'batchAdd': {
                    const textarea = container.querySelector('#batchKeywordInput');
                    if (textarea) {
                        const raw = textarea.value.trim();
                        if (raw) {
                            const parts = raw.split(/[,\s]+/).filter(p => p.length > 0);
                            const keywords = this.configManager.getKeywords();
                            let added = 0;
                            parts.forEach(p => {
                                if (!keywords.has(p)) {
                                    keywords.add(p);
                                    added++;
                                }
                            });
                            if (added > 0) {
                                this.configManager.setKeywords(keywords);
                                this.renderKeywordList(container);
                                Utils.showToast(`✅ 批量添加 ${added} 个`);
                            }
                            textarea.value = '';
                            this.createPanel();
                        }
                    }
                    break;
                }
            }
        }

        renderRegionList(container) {
            const list = container.querySelector('#regionList');
            if (!list) return;

            const regions = this.configManager.getRegions();
            list.innerHTML = '';

            if (regions.size === 0) {
                list.innerHTML = '<div style="padding:6px;text-align:center;color:#666;">暂无</div>';
                return;
            }

            regions.forEach(region => {
                const item = document.createElement('div');
                item.style.cssText = `
                    display: flex;
                    justify-content: space-between;
                    padding: 4px 6px;
                    border-bottom: 1px solid #333;
                    font-size: 11px;
                `;
                item.innerHTML = `<span>${region}</span>`;

                const delBtn = document.createElement('span');
                delBtn.textContent = '✕';
                delBtn.style.cssText = `
                    cursor: pointer;
                    color: #ff4d4d;
                    opacity: 0.6;
                    padding: 2px 6px;
                    transition: opacity 0.2s;
                `;
                delBtn.onmouseenter = () => delBtn.style.opacity = '1';
                delBtn.onmouseleave = () => delBtn.style.opacity = '0.6';
                delBtn.onclick = () => {
                    const regionsSet = this.configManager.getRegions();
                    regionsSet.delete(region);
                    this.configManager.setRegions(regionsSet);
                    this.renderRegionList(container);
                    Utils.showToast(`❌ 已移除: ${region}`);
                    this.createPanel();
                };
                item.appendChild(delBtn);
                list.appendChild(item);
            });
        }

        renderKeywordList(container) {
            const list = container.querySelector('#keywordList');
            if (!list) return;

            const keywords = this.configManager.getKeywords();
            list.innerHTML = '';

            if (keywords.size === 0) {
                list.innerHTML = '<div style="padding:6px;text-align:center;color:#666;">暂无</div>';
                return;
            }

            keywords.forEach(kw => {
                const item = document.createElement('div');
                item.style.cssText = `
                    display: flex;
                    justify-content: space-between;
                    padding: 4px 6px;
                    border-bottom: 1px solid #333;
                    font-size: 11px;
                `;
                item.innerHTML = `<span>${kw}</span>`;

                const delBtn = document.createElement('span');
                delBtn.textContent = '✕';
                delBtn.style.cssText = `
                    cursor: pointer;
                    color: #ff4d4d;
                    opacity: 0.6;
                    padding: 2px 6px;
                    transition: opacity 0.2s;
                `;
                delBtn.onmouseenter = () => delBtn.style.opacity = '1';
                delBtn.onmouseleave = () => delBtn.style.opacity = '0.6';
                delBtn.onclick = () => {
                    const keywordsSet = this.configManager.getKeywords();
                    keywordsSet.delete(kw);
                    this.configManager.setKeywords(keywordsSet);
                    this.renderKeywordList(container);
                    Utils.showToast(`❌ 已删除: ${kw}`);
                    this.createPanel();
                };
                item.appendChild(delBtn);
                list.appendChild(item);
            });
        }

        // ====== 清理资源 ======
        cleanup() {
            if (this.scrollTimer) {
                clearInterval(this.scrollTimer);
                this.scrollTimer = null;
            }
            if (this.checkInterval) {
                clearInterval(this.checkInterval);
                this.checkInterval = null;
            }
            if (this.nextVideoTimer) {
                clearTimeout(this.nextVideoTimer);
                this.nextVideoTimer = null;
            }
            if (this.openCommentTimer) {
                clearTimeout(this.openCommentTimer);
                this.openCommentTimer = null;
            }
            if (this.panel) {
                this.panel.remove();
                this.panel = null;
            }
            if (this.marqueeContainer) {
                this.marqueeContainer.remove();
                this.marqueeContainer = null;
            }
            this.sideAlerts.forEach(el => el.remove());
            this.sideAlerts = [];

            // 移除样式
            const styles = document.querySelector('#dy-monitor-styles');
            if (styles) styles.remove();

            // 移除全局引用
            if (window._dyMonitorInstance) {
                delete window._dyMonitorInstance;
            }
        }
    }

    // ==================== 主程序入口 ====================
    class Application {
        constructor() {
            this.configManager = null;
            this.stateManager = null;
            this.monitor = null;
            this.initialized = false;
        }

        init() {
            if (this.initialized) return;

            try {
                this.configManager = new ConfigManager();
                this.stateManager = new StateManager();
                this.monitor = new CommentMonitor(this.configManager, this.stateManager);
                this.monitor.init();
                this.initialized = true;

                // 注册菜单命令
                this.registerMenuCommands();

                console.log('抖音评论监控插件 v3.0 初始化完成');
            } catch (e) {
                console.error('初始化失败:', e);
                Utils.showToast('❌ 插件初始化失败: ' + e.message, 3000, true);
            }
        }

        registerMenuCommands() {
            if (typeof GM_registerMenuCommand === 'undefined') return;

            GM_registerMenuCommand('📋 打开监控面板', () => {
                if (this.monitor) this.monitor.createPanel();
            });
            GM_registerMenuCommand('📋 查看历史记录', () => {
                if (this.monitor) this.monitor.showHistoryWindow();
            });
            GM_registerMenuCommand('📥 导出历史记录', () => {
                if (this.monitor) this.monitor.exportHistoryToCSV();
            });
            GM_registerMenuCommand('💬 打开评论区 (X)', () => {
                if (this.monitor) this.monitor.openCommentPanel();
            });
            GM_registerMenuCommand('▶️ 开始滚动', () => {
                if (this.monitor) this.monitor.startCommentScroll();
            });
            GM_registerMenuCommand('⏸️ 停止滚动', () => {
                if (this.monitor) this.monitor.stopCommentScroll();
            });
            GM_registerMenuCommand('⏩ 手动切换视频', () => {
                if (this.monitor) this.monitor.triggerNextVideo();
            });
            GM_registerMenuCommand('💾 保存配置', () => {
                if (this.configManager) {
                    this.configManager.save();
                    Utils.showToast('💾 配置已保存', 1000);
                }
            });
        }

        destroy() {
            if (this.monitor) {
                this.monitor.cleanup();
            }
            this.initialized = false;
        }
    }

    // ==================== 启动 ====================
    let app = null;

    function startApp() {
        if (app) {
            app.destroy();
        }
        app = new Application();
        app.init();
    }

    // 等待页面加载完成
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            setTimeout(startApp, 1500);
        });
    } else {
        setTimeout(startApp, 1500);
    }

    // 页面关闭时清理
    window.addEventListener('beforeunload', () => {
        if (app) {
            app.destroy();
            app = null;
        }
    });

    // 暴露调试接口
    window.__dyMonitorDebug = {
        app: () => app,
        config: () => app ? app.configManager.getAll() : null,
        state: () => app ? app.stateManager.state : null,
        history: () => app ? app.stateManager.getHistory() : null,
        monitor: () => app ? app.monitor : null
    };

})();
发布时间: