搜索结果

×

搜索结果将在这里显示。

抖音评论区监控脚本

直接上脚本


// ==UserScript==
// @name         抖音评论监控 · 弹幕版 (历史记录+导出)
// @namespace    https://www.douyin.com/
// @version      2.7.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_v7';
    const HISTORY_KEY = 'dy_monitor_history_v7'; // 新增版本号避免冲突

    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 // 新增:右侧最大显示数量
    };

    // 从存储加载配置
    let config = GM_getValue(STORAGE_KEY, DEFAULT_CONFIG);

    // 转换为运行时变量
    let keywords = new Set(config.keywords);
    let monitorRegions = new Set(config.regions);
    let enableRegionFilter = config.enableRegionFilter;
    let alertMode = config.alertMode;
    let autoOpenComment = config.autoOpenComment;
    let autoScrollComments = config.autoScrollComments;
    let scrollInterval = config.scrollInterval;
    let maxMarquees = config.maxMarquees;
    let showVideoLink = config.showVideoLink;
    let showVideoInfo = config.showVideoInfo;
    let maxHistoryItems = config.maxHistoryItems;
    let maxSideAlerts = config.maxSideAlerts || 2; // 默认2条

    const triggeredComments = new Set();

    // 历史记录
    let historyRecords = GM_getValue(HISTORY_KEY, []);

    // 状态变量
    let isCommentPanelOpen = false;
    let scrollTimer = null;
    let commentContainer = null;

    // 弹幕容器
    let marqueeContainer = null;

    // 侧边弹窗数组 - 用于管理显示数量
    let sideAlerts = [];

    // ==================== 保存配置 ====================
    function saveConfig() {
        config = {
            keywords: Array.from(keywords),
            regions: Array.from(monitorRegions),
            enableRegionFilter: enableRegionFilter,
            alertMode: alertMode,
            autoOpenComment: autoOpenComment,
            autoScrollComments: autoScrollComments,
            scrollInterval: scrollInterval,
            maxMarquees: maxMarquees,
            showVideoLink: showVideoLink,
            showVideoInfo: showVideoInfo,
            maxHistoryItems: maxHistoryItems,
            maxSideAlerts: maxSideAlerts
        };

        GM_setValue(STORAGE_KEY, config);
        showToast('💾 配置已保存', 1000);
    }

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

        historyRecords.unshift(record); // 新记录插入开头

        // 限制数量
        if (historyRecords.length > maxHistoryItems) {
            historyRecords = historyRecords.slice(0, maxHistoryItems);
        }

        GM_setValue(HISTORY_KEY, historyRecords);
    }

    function clearHistory() {
        if (confirm('确定要清空所有历史记录吗?')) {
            historyRecords = [];
            GM_setValue(HISTORY_KEY, []);
            showToast('🗑️ 历史记录已清空', 1500);
        }
    }

    // ==================== 导出功能 ====================
    function exportHistoryToCSV() {
        if (historyRecords.length === 0) {
            showToast('📭 没有历史记录可导出', 1500);
            return;
        }

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

        // 转换数据 - 新增评论内容
        const rows = historyRecords.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
            ];
        });

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

        // 创建下载链接
        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.setAttribute('download', `抖音评论监控_${new Date().toLocaleDateString()}.csv`);
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);

        showToast('📥 历史记录已导出', 1500);
    }

    // ==================== 工具函数 ====================
    function showToast(msg, duration = 2000, isWarning = false) {
        const toast = document.createElement('div');
        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.2s',
            pointerEvents: 'none'
        });
        toast.textContent = msg;
        document.body.appendChild(toast);
        setTimeout(() => {
            toast.style.opacity = '0';
            setTimeout(() => toast.remove(), 300);
        }, duration);
    }

    function formatTime(timestamp) {
        if (!timestamp) return '未知时间';
        let date = new Date(timestamp * 1000);
        if (isNaN(date.getTime())) {
            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())}`;
    }

    function generateCommentId(comment) {
        return `${comment.text || comment.content}_${comment.user?.nickname || ''}_${comment.create_time || comment.time || ''}`;
    }

    function isRegionMatched(ipLocation) {
        if (!enableRegionFilter) return true;
        if (!ipLocation || ipLocation === '未知') return false;
        if (monitorRegions.has('全部')) return true;

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

    // ==================== 提取视频信息 ====================
    function 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.log('提取视频信息出错:', e);
        }

        return info;
    }

    function formatVideoInfo(info) {
        return {
            author: info.author,
            publishTime: info.publishTime,
            title: info.title,
            fullTitle: info.fullTitle,
            videoUrl: info.videoUrl
        };
    }

    function copyFullComment(commentData, videoInfo) {
        const { text, author, time, ipLocation, matchedKeywords } = commentData;

        const timeStr = formatTime(time);
        const keywordStr = matchedKeywords.length > 0 ? `[关键词: ${matchedKeywords.join('、')}]` : '';

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

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

💬 评论内容:${text}

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

        if (typeof GM_setClipboard !== 'undefined') {
            GM_setClipboard(fullInfo, 'text');
            showToast('✅ 已复制完整信息', 1500);
        } else {
            const textarea = document.createElement('textarea');
            textarea.value = fullInfo;
            document.body.appendChild(textarea);
            textarea.select();
            document.execCommand('copy');
            document.body.removeChild(textarea);
            showToast('📋 已复制完整信息', 1500);
        }
    }

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

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

        if (!marqueeContainer) {
            marqueeContainer = document.createElement('div');
            marqueeContainer.id = 'marquee-container';
            Object.assign(marqueeContainer.style, {
                position: 'fixed',
                top: '70px',
                left: '0',
                right: '0',
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'center',
                gap: '8px',
                zIndex: 999999999,
                pointerEvents: 'none'
            });
            document.body.appendChild(marqueeContainer);
        }

        const marquee = document.createElement('div');
        marquee.className = 'dy-comment-marquee';
        Object.assign(marquee.style, {
            width: '700px',
            maxWidth: '90%',
            background: '#ffffff',
            borderRadius: '8px',
            padding: '16px 20px',
            color: '#333',
            fontSize: '14px',
            boxShadow: '0 4px 16px rgba(0,0,0,0.15)',
            border: '1px solid #eaeaea',
            pointerEvents: 'auto',
            marginBottom: '4px',
            transition: 'opacity 0.2s',
            fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
            lineHeight: '1.5'
        });

        if (!document.getElementById('marquee-styles')) {
            const style = document.createElement('style');
            style.id = 'marquee-styles';
            style.textContent = `
                @keyframes marqueeFadeIn {
                    from { opacity: 0; transform: translateY(-10px); }
                    to { opacity: 1; transform: translateY(0); }
                }
                .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;
                }
                .video-meta {
                    color: #666;
                    font-size: 12px;
                }
                .keyword-tag {
                    background: #ff4d4d;
                    color: #fff;
                    padding: 2px 8px;
                    border-radius: 4px;
                    font-size: 12px;
                    font-weight: 500;
                }
            `;
            document.head.appendChild(style);
        }

        // 视频信息区域
        if (showVideoInfo) {
            const videoInfoDiv = document.createElement('div');
            videoInfoDiv.style.marginBottom = '12px';
            videoInfoDiv.style.padding = '10px 12px';
            videoInfoDiv.style.backgroundColor = '#f5f9ff';
            videoInfoDiv.style.borderRadius = '6px';
            videoInfoDiv.style.borderLeft = '3px solid #4CAF50';

            const authorLine = document.createElement('div');
            authorLine.style.display = 'flex';
            authorLine.style.alignItems = 'center';
            authorLine.style.gap = '12px';
            authorLine.style.marginBottom = '6px';
            authorLine.style.flexWrap = '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.marginBottom = '4px';
            titleLine.style.fontSize = '13px';
            titleLine.style.color = '#333';
            titleLine.innerHTML = `📝 ${videoInfo.title}`;

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

            if (showVideoLink) {
                const linkLine = document.createElement('div');
                linkLine.style.marginTop = '6px';
                linkLine.style.fontSize = '12px';
                linkLine.style.display = 'flex';
                linkLine.style.alignItems = 'center';
                linkLine.style.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.marginBottom = '12px';
        commentInfoDiv.style.padding = '10px 12px';
        commentInfoDiv.style.backgroundColor = '#f8f8f8';
        commentInfoDiv.style.borderRadius = '6px';
        commentInfoDiv.style.borderLeft = '3px solid #ff4d4d';

        const metaLine = document.createElement('div');
        metaLine.style.display = 'flex';
        metaLine.style.alignItems = 'center';
        metaLine.style.gap = '12px';
        metaLine.style.marginBottom = '8px';
        metaLine.style.flexWrap = '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;">🕒 ${formatTime(time)}</span>
        `;

        const keywordsBar = document.createElement('div');
        keywordsBar.style.display = 'flex';
        keywordsBar.style.alignItems = 'center';
        keywordsBar.style.gap = '6px';
        keywordsBar.style.marginBottom = '8px';
        keywordsBar.style.flexWrap = '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.fontSize = '14px';
        contentDiv.style.lineHeight = '1.6';
        contentDiv.style.wordBreak = 'break-all';
        contentDiv.style.padding = '4px 0';
        contentDiv.style.color = '#444';
        contentDiv.textContent = text;

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

        const actionsBar = document.createElement('div');
        actionsBar.style.display = 'flex';
        actionsBar.style.justifyContent = 'flex-end';
        actionsBar.style.gap = '10px';
        actionsBar.style.marginTop = '8px';

        const copyBtn = document.createElement('button');
        copyBtn.textContent = '📋 复制完整信息';
        Object.assign(copyBtn.style, {
            background: '#4CAF50',
            border: 'none',
            color: '#fff',
            padding: '6px 14px',
            borderRadius: '4px',
            fontSize: '12px',
            cursor: 'pointer',
            transition: 'all 0.2s',
            fontWeight: '500'
        });
        copyBtn.onmouseenter = () => copyBtn.style.background = '#66BB6A';
        copyBtn.onmouseleave = () => copyBtn.style.background = '#4CAF50';
        copyBtn.onclick = (e) => {
            e.stopPropagation();
            copyFullComment(commentData, videoInfo);
        };

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '✕ 关闭';
        Object.assign(closeBtn.style, {
            background: '#ff4d4d',
            border: 'none',
            color: '#fff',
            padding: '6px 14px',
            borderRadius: '4px',
            fontSize: '12px',
            cursor: 'pointer',
            transition: 'all 0.2s',
            fontWeight: '500'
        });
        closeBtn.onmouseenter = () => closeBtn.style.background = '#ff6666';
        closeBtn.onmouseleave = () => closeBtn.style.background = '#ff4d4d';
        closeBtn.onclick = (e) => {
            e.stopPropagation();
            marquee.remove();
            if (marqueeContainer.children.length === 0) {
                marqueeContainer.remove();
                marqueeContainer = null;
            }
        };

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

        marquee.addEventListener('click', (e) => {
            if (e.target === copyBtn || e.target === closeBtn || 
                copyBtn.contains(e.target) || closeBtn.contains(e.target) ||
                e.target.tagName === 'A') {
                return;
            }
            copyFullComment(commentData, videoInfo);
        });

        marqueeContainer.appendChild(marquee);

        while (marqueeContainer.children.length > maxMarquees) {
            marqueeContainer.removeChild(marqueeContainer.firstChild);
        }
    }

    // ==================== 侧边弹窗(修改为始终显示最新两条)====================
    function showSideAlert(commentData, side = 'right') {
        const { text, author, time, ipLocation, matchedKeywords } = commentData;
        const videoInfo = formatVideoInfo(getVideoInfo());

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

        // 创建新弹窗
        const overlay = document.createElement('div');
        overlay.className = 'dy-comment-alert';
        overlay.dataset.timestamp = Date.now(); // 用于排序
        Object.assign(overlay.style, {
            position: 'fixed',
            top: '20px',
            [side]: '20px',
            width: '380px',
            background: '#1e1e1e',
            borderRadius: '12px',
            boxShadow: '0 8px 20px rgba(0,0,0,0.5)',
            color: '#fff',
            zIndex: 999999999,
            border: '1px solid #ff4d4d',
            animation: `sideSlideIn-${side} 0.2s ease-out`,
            marginBottom: '10px',
            fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif'
        });

        if (!document.getElementById('side-styles')) {
            const style = document.createElement('style');
            style.id = 'side-styles';
            style.textContent = `
                @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; }
                }
                .video-link-dark {
                    color: #64B5F6;
                    text-decoration: none;
                }
                .video-link-dark:hover {
                    text-decoration: underline;
                    color: #90CAF9;
                }
            `;
            document.head.appendChild(style);
        }

        const card = document.createElement('div');
        card.style.padding = '16px';

        const header = document.createElement('div');
        header.style.display = 'flex';
        header.style.justifyContent = 'space-between';
        header.style.alignItems = 'center';
        header.style.marginBottom = '16px';

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

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '×';
        Object.assign(closeBtn.style, {
            background: 'none',
            border: 'none',
            color: '#aaa',
            fontSize: '24px',
            cursor: 'pointer',
            padding: '0',
            lineHeight: '1'
        });
        closeBtn.onclick = () => {
            overlay.remove();
            // 从数组中移除
            sideAlerts = sideAlerts.filter(el => el !== overlay);
        };

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

        // 视频信息区域
        if (showVideoInfo) {
            const videoInfoDiv = document.createElement('div');
            videoInfoDiv.style.marginBottom = '16px';
            videoInfoDiv.style.padding = '12px';
            videoInfoDiv.style.background = '#2a2a2a';
            videoInfoDiv.style.borderRadius = '8px';
            videoInfoDiv.style.borderLeft = '3px solid #4CAF50';

            const authorLine = document.createElement('div');
            authorLine.style.display = 'flex';
            authorLine.style.alignItems = 'center';
            authorLine.style.gap = '10px';
            authorLine.style.marginBottom = '6px';
            authorLine.style.flexWrap = '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.fontSize = '13px';
            titleLine.style.color = '#ddd';
            titleLine.style.marginBottom = '6px';
            titleLine.style.lineHeight = '1.5';
            titleLine.textContent = videoInfo.title;

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

            if (showVideoLink) {
                const linkLine = document.createElement('div');
                linkLine.style.marginTop = '8px';
                linkLine.style.fontSize = '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.marginBottom = '16px';
        commentDiv.style.padding = '12px';
        commentDiv.style.background = '#2a2a2a';
        commentDiv.style.borderRadius = '8px';
        commentDiv.style.borderLeft = '3px solid #ff4d4d';

        const metaLine = document.createElement('div');
        metaLine.style.display = 'flex';
        metaLine.style.flexWrap = 'wrap';
        metaLine.style.gap = '10px';
        metaLine.style.marginBottom = '8px';
        metaLine.style.color = '#aaa';
        metaLine.style.fontSize = '12px';

        const isMatchedRegion = isRegionMatched(ipLocation);
        const ipStyle = isMatchedRegion ? 'color: #4CAF50; font-weight: bold;' : '';

        metaLine.innerHTML = `
            <span>👤 ${author || '匿名'}</span>
            <span>🕒 ${formatTime(time)}</span>
            <span style="${ipStyle}">📍 ${ipLocation || '未知'}</span>
        `;

        const tagsContainer = document.createElement('div');
        tagsContainer.style.display = 'flex';
        tagsContainer.style.flexWrap = 'wrap';
        tagsContainer.style.gap = '6px';
        tagsContainer.style.marginBottom = '8px';

        matchedKeywords.forEach(kw => {
            const tag = document.createElement('span');
            tag.style.background = '#ff4d4d';
            tag.style.padding = '4px 10px';
            tag.style.borderRadius = '16px';
            tag.style.fontSize = '11px';
            tag.textContent = `# ${kw}`;
            tagsContainer.appendChild(tag);
        });

        const contentDiv = document.createElement('div');
        contentDiv.style.fontSize = '13px';
        contentDiv.style.lineHeight = '1.6';
        contentDiv.style.wordBreak = 'break-all';
        contentDiv.style.maxHeight = '100px';
        contentDiv.style.overflowY = 'auto';
        contentDiv.style.color = '#eee';
        contentDiv.textContent = text;

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

        const actions = document.createElement('div');
        actions.style.display = 'flex';
        actions.style.gap = '8px';
        actions.style.justifyContent = 'flex-end';

        const copyBtn = document.createElement('button');
        copyBtn.textContent = '📋 复制完整信息';
        Object.assign(copyBtn.style, {
            background: '#4CAF50',
            border: 'none',
            color: '#fff',
            padding: '6px 14px',
            borderRadius: '6px',
            fontSize: '12px',
            cursor: 'pointer',
            transition: 'background 0.2s'
        });
        copyBtn.onmouseenter = () => copyBtn.style.background = '#66BB6A';
        copyBtn.onmouseleave = () => copyBtn.style.background = '#4CAF50';
        copyBtn.onclick = () => copyFullComment(commentData, videoInfo);

        const closeCardBtn = document.createElement('button');
        closeCardBtn.textContent = '关闭';
        Object.assign(closeCardBtn.style, {
            background: '#ff4d4d',
            border: 'none',
            color: '#fff',
            padding: '6px 14px',
            borderRadius: '6px',
            fontSize: '12px',
            cursor: 'pointer',
            transition: 'background 0.2s'
        });
        closeCardBtn.onmouseenter = () => closeCardBtn.style.background = '#ff6666';
        closeCardBtn.onmouseleave = () => closeCardBtn.style.background = '#ff4d4d';
        closeCardBtn.onclick = () => {
            overlay.remove();
            sideAlerts = sideAlerts.filter(el => el !== overlay);
        };

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

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

        // 添加到文档
        document.body.appendChild(overlay);

        // 添加到管理数组
        sideAlerts.push(overlay);

        // 按时间戳排序(最新的在最后)
        sideAlerts.sort((a, b) => (a.dataset.timestamp || 0) - (b.dataset.timestamp || 0));

        // 如果超过最大显示数量,移除最早的
        while (sideAlerts.length > maxSideAlerts) {
            const oldest = sideAlerts.shift();
            if (oldest && oldest.parentNode) {
                oldest.remove();
            }
        }

        // 重新计算所有弹窗的位置(从下往上排列,最新的在底部)
        updateSideAlertPositions(side);
    }

    // 更新侧边弹窗位置(从下往上排列,最新的在底部)
    function updateSideAlertPositions(side) {
        const alerts = document.querySelectorAll('.dy-comment-alert');
        const baseTop = 20;
        const offset = 10;
        let currentTop = baseTop;

        // 转为数组并排序(老的在上,新的在下)
        const sorted = Array.from(alerts).sort((a, b) => 
            (a.dataset.timestamp || 0) - (b.dataset.timestamp || 0)
        );

        sorted.forEach((el, index) => {
            el.style.top = currentTop + 'px';
            currentTop += el.offsetHeight + offset;
        });
    }

    // ==================== 根据模式显示弹窗 ====================
    function showAlert(commentData) {
        switch(alertMode) {
            case 'left':
                showSideAlert(commentData, 'left');
                break;
            case 'right':
                showSideAlert(commentData, 'right');
                break;
            case 'marquee':
                showMarquee(commentData);
                break;
            default:
                showSideAlert(commentData, 'right');
        }
    }

    // ==================== 历史记录表格弹窗(新增评论内容列)====================
    function showHistoryWindow() {
        // 创建遮罩层
        const overlay = document.createElement('div');
        overlay.id = 'history-window-overlay';
        Object.assign(overlay.style, {
            position: 'fixed',
            top: '0',
            left: '0',
            right: '0',
            bottom: '0',
            background: 'rgba(0,0,0,0.7)',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            zIndex: 999999999,
            backdropFilter: 'blur(4px)'
        });

        // 创建窗口
        const windowDiv = document.createElement('div');
        Object.assign(windowDiv.style, {
            width: '90%',
            maxWidth: '1300px', // 加宽以适应新列
            height: '80%',
            background: '#1e1e1e',
            borderRadius: '12px',
            boxShadow: '0 20px 40px rgba(0,0,0,0.5)',
            color: '#fff',
            display: 'flex',
            flexDirection: 'column',
            overflow: 'hidden',
            border: '1px solid #333'
        });

        // 标题栏
        const titleBar = document.createElement('div');
        Object.assign(titleBar.style, {
            padding: '16px 20px',
            borderBottom: '1px solid #333',
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            background: '#252525'
        });

        const title = document.createElement('h3');
        title.style.margin = '0';
        title.style.fontSize = '16px';
        title.style.fontWeight = '500';
        title.innerHTML = `📋 历史记录 (${historyRecords.length})`;

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '×';
        Object.assign(closeBtn.style, {
            background: 'none',
            border: 'none',
            color: '#aaa',
            fontSize: '24px',
            cursor: 'pointer',
            padding: '0 8px'
        });
        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');
        Object.assign(toolbar.style, {
            padding: '12px 20px',
            borderBottom: '1px solid #333',
            display: 'flex',
            gap: '10px',
            background: '#1a1a1a'
        });

        const exportBtn = document.createElement('button');
        exportBtn.textContent = '📥 导出CSV';
        Object.assign(exportBtn.style, {
            background: '#4CAF50',
            border: 'none',
            borderRadius: '4px',
            padding: '6px 16px',
            color: '#fff',
            fontSize: '13px',
            cursor: 'pointer'
        });
        exportBtn.onclick = exportHistoryToCSV;

        const clearBtn = document.createElement('button');
        clearBtn.textContent = '🗑️ 清空记录';
        Object.assign(clearBtn.style, {
            background: '#ff4d4d',
            border: 'none',
            borderRadius: '4px',
            padding: '6px 16px',
            color: '#fff',
            fontSize: '13px',
            cursor: 'pointer'
        });
        clearBtn.onclick = () => {
            clearHistory();
            overlay.remove();
        };

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

        // 表格容器
        const tableContainer = document.createElement('div');
        Object.assign(tableContainer.style, {
            flex: '1',
            overflow: 'auto',
            padding: '20px'
        });

        if (historyRecords.length === 0) {
            const emptyMsg = document.createElement('div');
            emptyMsg.style.textAlign = 'center';
            emptyMsg.style.padding = '50px';
            emptyMsg.style.color = '#666';
            emptyMsg.textContent = '暂无历史记录';
            tableContainer.appendChild(emptyMsg);
        } else {
            const table = document.createElement('table');
            Object.assign(table.style, {
                width: '100%',
                borderCollapse: 'collapse',
                fontSize: '13px',
                minWidth: '1200px' // 加宽
            });

            // 表头 - 新增评论内容列
            const thead = document.createElement('thead');
            const headerRow = document.createElement('tr');
            headerRow.style.background = '#2a2a2a';

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

            headers.forEach(text => {
                const th = document.createElement('th');
                th.textContent = text;
                Object.assign(th.style, {
                    padding: '12px 8px',
                    textAlign: 'left',
                    borderBottom: '2px solid #444',
                    color: '#aaa',
                    fontWeight: '500'
                });
                headerRow.appendChild(th);
            });
            thead.appendChild(headerRow);
            table.appendChild(thead);

            // 表体 - 新增评论内容
            const tbody = document.createElement('tbody');
            historyRecords.forEach((record, index) => {
                const row = document.createElement('tr');
                row.style.borderBottom = '1px solid #333';
                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');
                    Object.assign(td.style, {
                        padding: '10px 8px',
                        color: i === 8 ? '#ffb3b3' : '#e0e0e0', // 关键词列索引变为8
                        maxWidth: i === 2 ? '200px' : (i === 7 ? '250px' : 'none'), // 标题和评论内容列宽限制
                        overflow: 'hidden',
                        textOverflow: 'ellipsis',
                        whiteSpace: 'nowrap'
                    });

                    if (i === 9) { // 视频链接列索引变为9
                        const link = document.createElement('a');
                        link.href = cell;
                        link.target = '_blank';
                        link.textContent = '打开';
                        link.style.color = '#4CAF50';
                        link.style.textDecoration = 'none';
                        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);
    }

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

        const eventUp = new KeyboardEvent('keyup', {
            key: 'x',
            keyCode: 88,
            which: 88,
            code: 'KeyX',
            bubbles: true,
            cancelable: true
        });
        document.dispatchEvent(eventUp);
    }

    function openCommentPanel() {
        if (isCommentPanelOpen) return;

        simulateKeyX();

        isCommentPanelOpen = true;
        showToast('💬 尝试打开评论区 (模拟X键)', 1500);

        setTimeout(() => {
            const commentEl = findCommentContainer();
            if (commentEl) {
                showToast('✅ 评论区已打开', 1000);
                if (autoScrollComments) {
                    startCommentScroll();
                }
            } else {
                isCommentPanelOpen = false;
            }
        }, 2000);
    }

    // ==================== 查找评论区容器 ====================
    function findCommentContainer() {
        const selectors = [
            '[class*="comment-list"]',
            '[class*="CommentList"]',
            '[class*="commentContainer"]',
            '[class*="comments-container"]',
            '[class*="comment_list"]',
            '.LWSPvSJk',
            '#comment-container',
            '[data-e2e="comment-list"]'
        ];

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

    // ==================== 开始滚动评论区 ====================
    function startCommentScroll() {
        if (scrollTimer) {
            clearInterval(scrollTimer);
            scrollTimer = null;
        }

        commentContainer = findCommentContainer();
        if (!commentContainer) {
            showToast('⚠️ 未找到评论区', 1500);
            return;
        }

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

        scrollTimer = setInterval(() => {
            if (commentContainer) {
                commentContainer.scrollTop = commentContainer.scrollHeight;
                commentContainer.scrollBy(0, 200);
            }
        }, scrollInterval);
    }

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

    // ==================== 劫持评论接口 ====================
    function 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 => {
                const commentId = generateCommentId(comment);
                if (triggeredComments.has(commentId)) return;

                let text = comment.text || comment.content || '';
                let user = comment.user?.nickname || comment.user?.unique_id || comment.author || '匿名';
                let 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 || '未知';
                }

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

                    if (matchedKeywords.length > 0 && isRegionMatched(ipLabel)) {
                        triggeredComments.add(commentId);

                        showAlert({
                            text: text,
                            author: user,
                            time: createTime,
                            ipLocation: ipLabel,
                            matchedKeywords: matchedKeywords
                        });
                    }
                }
            });
        } catch (e) {}
    }

    // 劫持 fetch
    const originalFetch = window.fetch;
    window.fetch = function (...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 => inspectResponseBody(url, body)).catch(() => {});
            return response;
        });
    };

    // 劫持 XHR
    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) {
                    inspectResponseBody(url, this.responseText);
                }
                if (originalOnLoad) originalOnLoad.call(this, e);
            };
        }
        return originalXHRSend.apply(this, args);
    };

    // ==================== 悬浮配置面板 ====================
    let panel = null;
    let isDragging = false;
    let offsetX = 0, offsetY = 0;
    let isMinimized = false;

    function createPanel() {
        if (panel) panel.remove();

        panel = document.createElement('div');
        panel.id = 'comment-monitor-panel';
        Object.assign(panel.style, {
            position: 'fixed',
            bottom: '20px',
            right: '20px',
            width: isMinimized ? '160px' : '420px',
            background: '#1f1f1f',
            borderRadius: '12px',
            boxShadow: '0 8px 20px rgba(0,0,0,0.5)',
            color: '#e0e0e0',
            fontSize: '13px',
            zIndex: 9999999,
            border: '1px solid #333',
            fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
            cursor: 'default',
            userSelect: 'none',
            transition: 'width 0.2s ease'
        });

        const header = document.createElement('div');
        header.id = 'panel-header';
        Object.assign(header.style, {
            padding: '10px 14px',
            borderBottom: isMinimized ? 'none' : '1px solid #333',
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            background: '#252525',
            borderRadius: '12px 12px 0 0',
            cursor: 'move',
            userSelect: 'none'
        });

        const titleArea = document.createElement('div');
        titleArea.style.display = 'flex';
        titleArea.style.alignItems = 'center';
        titleArea.style.gap = '6px';

        const minimizeBtn = document.createElement('span');
        minimizeBtn.textContent = isMinimized ? '□' : '—';
        minimizeBtn.title = isMinimized ? '展开' : '最小化';
        Object.assign(minimizeBtn.style, {
            cursor: 'pointer',
            color: '#888',
            fontSize: '14px',
            padding: '2px 6px',
            borderRadius: '4px',
            transition: 'all 0.2s'
        });
        minimizeBtn.onmouseenter = () => minimizeBtn.style.color = '#fff';
        minimizeBtn.onmouseleave = () => minimizeBtn.style.color = '#888';
        minimizeBtn.onclick = (e) => {
            e.stopPropagation();
            isMinimized = !isMinimized;
            createPanel();
        };

        const title = document.createElement('div');
        title.style.fontWeight = '500';
        title.style.fontSize = isMinimized ? '13px' : '14px';
        title.innerHTML = isMinimized ? '🎯 监控' : '弹幕监控 v2.7';

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

        const historyBtn = document.createElement('span');
        historyBtn.textContent = '📋';
        historyBtn.title = '查看历史记录';
        Object.assign(historyBtn.style, {
            cursor: 'pointer',
            color: '#4CAF50',
            fontSize: '14px',
            padding: '2px 6px',
            borderRadius: '4px',
            marginLeft: '4px',
            transition: 'all 0.2s'
        });
        historyBtn.onmouseenter = () => { historyBtn.style.background = '#333'; };
        historyBtn.onmouseleave = () => { historyBtn.style.background = 'transparent'; };
        historyBtn.onclick = (e) => {
            e.stopPropagation();
            showHistoryWindow();
        };
        titleArea.appendChild(historyBtn);

        const saveBtn = document.createElement('span');
        saveBtn.textContent = '💾';
        saveBtn.title = '保存配置';
        Object.assign(saveBtn.style, {
            cursor: 'pointer',
            color: '#4CAF50',
            fontSize: '14px',
            padding: '2px 6px',
            borderRadius: '4px',
            marginLeft: '4px',
            transition: 'all 0.2s'
        });
        saveBtn.onmouseenter = () => { saveBtn.style.background = '#333'; };
        saveBtn.onmouseleave = () => { saveBtn.style.background = 'transparent'; };
        saveBtn.onclick = (e) => {
            e.stopPropagation();
            saveConfig();
        };
        titleArea.appendChild(saveBtn);

        const closeBtn = document.createElement('span');
        closeBtn.textContent = '×';
        Object.assign(closeBtn.style, {
            cursor: 'pointer',
            color: '#888',
            fontSize: '18px',
            fontWeight: '400',
            padding: '0 4px',
            borderRadius: '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 = () => panel.remove();

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

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

            const rect = panel.getBoundingClientRect();
            offsetX = e.clientX - rect.left;
            offsetY = e.clientY - rect.top;

            isDragging = true;
            panel.style.transition = 'none';
            panel.style.cursor = 'grabbing';
        });

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

            const newLeft = e.clientX - offsetX;
            const newTop = e.clientY - offsetY;

            const maxX = window.innerWidth - panel.offsetWidth;
            const maxY = window.innerHeight - panel.offsetHeight;

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

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

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

        panel.appendChild(header);

        if (!isMinimized) {
            const body = document.createElement('div');
            body.style.padding = '14px';
            body.style.maxHeight = '500px';
            body.style.overflowY = 'auto';

            // ========== 状态显示 ==========
            const statusSection = document.createElement('div');
            statusSection.style.marginBottom = '14px';
            statusSection.style.padding = '10px';
            statusSection.style.background = '#1a1a1a';
            statusSection.style.borderRadius = '8px';

            const recordCount = document.createElement('div');
            recordCount.style.marginBottom = '8px';
            recordCount.style.fontSize = '12px';
            recordCount.style.color = '#aaa';
            recordCount.innerHTML = `📋 历史记录: ${historyRecords.length} 条`;

            const controlBtns = document.createElement('div');
            controlBtns.style.display = 'flex';
            controlBtns.style.gap = '8px';
            controlBtns.style.marginTop = '4px';

            const openBtn = document.createElement('button');
            openBtn.textContent = '打开评论区 (X)';
            Object.assign(openBtn.style, {
                flex: '2',
                background: '#4CAF50',
                border: 'none',
                borderRadius: '4px',
                padding: '6px 0',
                color: '#fff',
                fontSize: '12px',
                cursor: 'pointer'
            });
            openBtn.onclick = openCommentPanel;

            const startScrollBtn = document.createElement('button');
            startScrollBtn.textContent = '开始滚动';
            Object.assign(startScrollBtn.style, {
                flex: '1',
                background: '#2196F3',
                border: 'none',
                borderRadius: '4px',
                padding: '6px 0',
                color: '#fff',
                fontSize: '12px',
                cursor: 'pointer'
            });
            startScrollBtn.onclick = startCommentScroll;

            const stopScrollBtn = document.createElement('button');
            stopScrollBtn.textContent = '停止滚动';
            Object.assign(stopScrollBtn.style, {
                flex: '1',
                background: '#ff4d4d',
                border: 'none',
                borderRadius: '4px',
                padding: '6px 0',
                color: '#fff',
                fontSize: '12px',
                cursor: 'pointer'
            });
            stopScrollBtn.onclick = stopCommentScroll;

            controlBtns.appendChild(openBtn);
            controlBtns.appendChild(startScrollBtn);
            controlBtns.appendChild(stopScrollBtn);

            statusSection.appendChild(recordCount);
            statusSection.appendChild(controlBtns);

            // ========== 弹窗模式切换 ==========
            const modeSection = document.createElement('div');
            modeSection.style.marginBottom = '14px';
            modeSection.style.padding = '10px';
            modeSection.style.background = '#1a1a1a';
            modeSection.style.borderRadius = '8px';

            const modeTitle = document.createElement('div');
            modeTitle.style.marginBottom = '8px';
            modeTitle.style.color = '#aaa';
            modeTitle.style.fontSize = '12px';
            modeTitle.innerHTML = '🎨 弹窗模式';

            const modeButtons = document.createElement('div');
            modeButtons.style.display = 'flex';
            modeButtons.style.gap = '6px';

            const leftBtn = document.createElement('button');
            leftBtn.textContent = '左侧';
            leftBtn.style.background = alertMode === 'left' ? '#4CAF50' : '#3a3a3a';
            Object.assign(leftBtn.style, {
                flex: '1',
                border: 'none',
                borderRadius: '4px',
                padding: '6px 0',
                color: '#fff',
                fontSize: '12px',
                cursor: 'pointer'
            });
            leftBtn.onclick = () => {
                alertMode = 'left';
                leftBtn.style.background = '#4CAF50';
                rightBtn.style.background = '#3a3a3a';
                marqueeBtn.style.background = '#3a3a3a';
                saveConfig();
                showToast('✅ 已切换到左侧弹窗', 1000);
            };

            const rightBtn = document.createElement('button');
            rightBtn.textContent = '右侧';
            rightBtn.style.background = alertMode === 'right' ? '#2196F3' : '#3a3a3a';
            Object.assign(rightBtn.style, {
                flex: '1',
                border: 'none',
                borderRadius: '4px',
                padding: '6px 0',
                color: '#fff',
                fontSize: '12px',
                cursor: 'pointer'
            });
            rightBtn.onclick = () => {
                alertMode = 'right';
                rightBtn.style.background = '#2196F3';
                leftBtn.style.background = '#3a3a3a';
                marqueeBtn.style.background = '#3a3a3a';
                saveConfig();
                showToast('✅ 已切换到右侧弹窗', 1000);
            };

            const marqueeBtn = document.createElement('button');
            marqueeBtn.textContent = '弹幕';
            marqueeBtn.style.background = alertMode === 'marquee' ? '#FF9800' : '#3a3a3a';
            Object.assign(marqueeBtn.style, {
                flex: '1',
                border: 'none',
                borderRadius: '4px',
                padding: '6px 0',
                color: '#fff',
                fontSize: '12px',
                cursor: 'pointer'
            });
            marqueeBtn.onclick = () => {
                alertMode = 'marquee';
                marqueeBtn.style.background = '#FF9800';
                leftBtn.style.background = '#3a3a3a';
                rightBtn.style.background = '#3a3a3a';
                saveConfig();
                showToast('✅ 已切换到弹幕模式', 1000);
            };

            modeButtons.appendChild(leftBtn);
            modeButtons.appendChild(rightBtn);
            modeButtons.appendChild(marqueeBtn);

            modeSection.appendChild(modeTitle);
            modeSection.appendChild(modeButtons);

            // ========== 右侧显示数量设置 ==========
            const countSection = document.createElement('div');
            countSection.style.marginBottom = '14px';
            countSection.style.padding = '10px';
            countSection.style.background = '#1a1a1a';
            countSection.style.borderRadius = '8px';

            const countTitle = document.createElement('div');
            countTitle.style.marginBottom = '8px';
            countTitle.style.color = '#aaa';
            countTitle.style.fontSize = '12px';
            countTitle.innerHTML = '📊 右侧弹窗数量';

            const countControl = document.createElement('div');
            countControl.style.display = 'flex';
            countControl.style.alignItems = 'center';
            countControl.style.gap = '8px';

            const countInput = document.createElement('input');
            countInput.type = 'number';
            countInput.min = '1';
            countInput.max = '5';
            countInput.value = maxSideAlerts;
            Object.assign(countInput.style, {
                width: '60px',
                background: '#2a2a2a',
                border: '1px solid #3a3a3a',
                borderRadius: '4px',
                padding: '4px',
                color: '#fff',
                textAlign: 'center'
            });

            const countLabel = document.createElement('span');
            countLabel.style.color = '#ccc';
            countLabel.style.fontSize = '11px';
            countLabel.textContent = '条 (1-5)';

            const countApply = document.createElement('button');
            countApply.textContent = '应用';
            Object.assign(countApply.style, {
                background: '#4CAF50',
                border: 'none',
                borderRadius: '4px',
                padding: '4px 12px',
                color: '#fff',
                fontSize: '11px',
                cursor: 'pointer',
                marginLeft: 'auto'
            });
            countApply.onclick = () => {
                const val = parseInt(countInput.value);
                if (val >= 1 && val <= 5) {
                    maxSideAlerts = val;
                    saveConfig();
                    showToast(`✅ 右侧弹窗数量已设为 ${val}`);
                } else {
                    showToast('❌ 请输入1-5之间的数字', 1500, true);
                }
            };

            countControl.appendChild(countInput);
            countControl.appendChild(countLabel);
            countControl.appendChild(countApply);
            countSection.appendChild(countTitle);
            countSection.appendChild(countControl);

            // ========== 视频信息开关 ==========
            const infoSection = document.createElement('div');
            infoSection.style.marginBottom = '14px';
            infoSection.style.padding = '10px';
            infoSection.style.background = '#1a1a1a';
            infoSection.style.borderRadius = '8px';

            const infoToggle = document.createElement('div');
            infoToggle.style.display = 'flex';
            infoToggle.style.justifyContent = 'space-between';
            infoToggle.style.alignItems = 'center';
            infoToggle.style.marginBottom = '8px';

            const infoLabel = document.createElement('span');
            infoLabel.style.color = '#aaa';
            infoLabel.style.fontSize = '12px';
            infoLabel.innerHTML = '📹 显示视频信息';

            const infoCheckbox = document.createElement('input');
            infoCheckbox.type = 'checkbox';
            infoCheckbox.checked = showVideoInfo;
            infoCheckbox.addEventListener('change', (e) => {
                showVideoInfo = e.target.checked;
                saveConfig();
                showToast(`📹 视频信息: ${e.target.checked ? '显示' : '隐藏'}`);
            });

            infoToggle.appendChild(infoLabel);
            infoToggle.appendChild(infoCheckbox);

            const linkToggle = document.createElement('div');
            linkToggle.style.display = 'flex';
            linkToggle.style.justifyContent = 'space-between';
            linkToggle.style.alignItems = 'center';

            const linkLabel = document.createElement('span');
            linkLabel.style.color = '#aaa';
            linkLabel.style.fontSize = '12px';
            linkLabel.innerHTML = '🔗 显示视频链接';

            const linkCheckbox = document.createElement('input');
            linkCheckbox.type = 'checkbox';
            linkCheckbox.checked = showVideoLink;
            linkCheckbox.addEventListener('change', (e) => {
                showVideoLink = e.target.checked;
                saveConfig();
                showToast(`🔗 视频链接: ${e.target.checked ? '显示' : '隐藏'}`);
            });

            linkToggle.appendChild(linkLabel);
            linkToggle.appendChild(linkCheckbox);

            infoSection.appendChild(infoToggle);
            infoSection.appendChild(linkToggle);

            // ========== 地区管理 ==========
            const regionSection = document.createElement('div');
            regionSection.style.marginBottom = '14px';
            regionSection.style.padding = '10px';
            regionSection.style.background = '#1a1a1a';
            regionSection.style.borderRadius = '8px';

            const regionHeader = document.createElement('div');
            regionHeader.style.display = 'flex';
            regionHeader.style.justifyContent = 'space-between';
            regionHeader.style.marginBottom = '8px';

            const regionTitle = document.createElement('div');
            regionTitle.style.color = '#aaa';
            regionTitle.style.fontSize = '12px';
            regionTitle.innerHTML = `📍 地区 (${monitorRegions.size})`;

            const regionToggle = document.createElement('label');
            regionToggle.style.display = 'flex';
            regionToggle.style.alignItems = 'center';
            regionToggle.style.gap = '4px';
            regionToggle.style.color = '#4CAF50';
            regionToggle.style.fontSize = '11px';

            const regionCheckbox = document.createElement('input');
            regionCheckbox.type = 'checkbox';
            regionCheckbox.checked = enableRegionFilter;
            regionCheckbox.addEventListener('change', (e) => {
                enableRegionFilter = e.target.checked;
                saveConfig();
                showToast(`📍 筛选: ${enableRegionFilter ? '开启' : '关闭'}`);
            });

            regionToggle.appendChild(regionCheckbox);
            regionToggle.appendChild(document.createTextNode('开启'));

            regionHeader.appendChild(regionTitle);
            regionHeader.appendChild(regionToggle);

            const regionList = document.createElement('div');
            regionList.style.marginBottom = '8px';
            regionList.style.maxHeight = '70px';
            regionList.style.overflowY = 'auto';
            regionList.style.background = '#252525';
            regionList.style.borderRadius = '4px';
            regionList.style.padding = '4px';

            function renderRegions() {
                regionList.innerHTML = '';
                if (monitorRegions.size === 0) {
                    regionList.innerHTML = '<div style="padding:6px; text-align:center; color:#666;">暂无</div>';
                    return;
                }
                monitorRegions.forEach(region => {
                    const item = document.createElement('div');
                    item.style.display = 'flex';
                    item.style.justifyContent = 'space-between';
                    item.style.padding = '4px 6px';
                    item.style.borderBottom = '1px solid #333';
                    item.style.fontSize = '11px';
                    item.innerHTML = `<span>${region}</span>`;

                    const delBtn = document.createElement('span');
                    delBtn.textContent = '✕';
                    delBtn.style.cursor = 'pointer';
                    delBtn.style.color = '#ff4d4d';
                    delBtn.style.opacity = '0.6';
                    delBtn.style.padding = '2px 6px';
                    delBtn.onmouseenter = () => delBtn.style.opacity = '1';
                    delBtn.onmouseleave = () => delBtn.style.opacity = '0.6';
                    delBtn.onclick = (e) => {
                        e.stopPropagation();
                        monitorRegions.delete(region);
                        saveConfig();
                        renderRegions();
                        regionTitle.innerHTML = `📍 地区 (${monitorRegions.size})`;
                        showToast(`❌ 已移除: ${region}`);
                    };
                    item.appendChild(delBtn);
                    regionList.appendChild(item);
                });
            }
            renderRegions();

            const regionAddArea = document.createElement('div');
            regionAddArea.style.display = 'flex';
            regionAddArea.style.gap = '4px';

            const regionInput = document.createElement('input');
            regionInput.type = 'text';
            regionInput.placeholder = '地区';
            Object.assign(regionInput.style, {
                flex: '1',
                background: '#2a2a2a',
                border: '1px solid #3a3a3a',
                borderRadius: '4px',
                padding: '4px 8px',
                color: '#fff',
                outline: 'none',
                fontSize: '11px'
            });

            const regionAddBtn = document.createElement('button');
            regionAddBtn.textContent = '+';
            Object.assign(regionAddBtn.style, {
                background: '#4CAF50',
                border: 'none',
                borderRadius: '4px',
                width: '24px',
                color: '#fff',
                cursor: 'pointer',
                fontSize: '14px',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center'
            });
            regionAddBtn.onclick = () => {
                const val = regionInput.value.trim();
                if (val) {
                    monitorRegions.add(val);
                    saveConfig();
                    renderRegions();
                    regionTitle.innerHTML = `📍 地区 (${monitorRegions.size})`;
                    regionInput.value = '';
                    showToast(`✅ 已添加: ${val}`);
                }
            };

            regionAddArea.appendChild(regionInput);
            regionAddArea.appendChild(regionAddBtn);

            regionSection.appendChild(regionHeader);
            regionSection.appendChild(regionList);
            regionSection.appendChild(regionAddArea);

            // ========== 关键词管理 ==========
            const keywordSection = document.createElement('div');
            keywordSection.style.marginBottom = '8px';
            keywordSection.style.padding = '10px';
            keywordSection.style.background = '#1a1a1a';
            keywordSection.style.borderRadius = '8px';

            const keywordHeader = document.createElement('div');
            keywordHeader.style.display = 'flex';
            keywordHeader.style.justifyContent = 'space-between';
            keywordHeader.style.marginBottom = '8px';
            keywordHeader.innerHTML = `
                <span style="color:#aaa; font-size:12px;">🔑 关键词 (${keywords.size})</span>
                <span style="color:#4CAF50; font-size:11px;">批量添加</span>
            `;

            const keywordList = document.createElement('div');
            keywordList.style.marginBottom = '8px';
            keywordList.style.maxHeight = '80px';
            keywordList.style.overflowY = 'auto';
            keywordList.style.background = '#252525';
            keywordList.style.borderRadius = '4px';
            keywordList.style.padding = '4px';

            function renderKeywords() {
                keywordList.innerHTML = '';
                if (keywords.size === 0) {
                    keywordList.innerHTML = '<div style="padding:6px; text-align:center; color:#666;">暂无</div>';
                    return;
                }
                keywords.forEach(kw => {
                    const item = document.createElement('div');
                    item.style.display = 'flex';
                    item.style.justifyContent = 'space-between';
                    item.style.padding = '4px 6px';
                    item.style.borderBottom = '1px solid #333';
                    item.style.fontSize = '11px';
                    item.innerHTML = `<span>${kw}</span>`;

                    const delBtn = document.createElement('span');
                    delBtn.textContent = '✕';
                    delBtn.style.cursor = 'pointer';
                    delBtn.style.color = '#ff4d4d';
                    delBtn.style.opacity = '0.6';
                    delBtn.style.padding = '2px 6px';
                    delBtn.onmouseenter = () => delBtn.style.opacity = '1';
                    delBtn.onmouseleave = () => delBtn.style.opacity = '0.6';
                    delBtn.onclick = (e) => {
                        e.stopPropagation();
                        keywords.delete(kw);
                        saveConfig();
                        renderKeywords();
                        keywordHeader.innerHTML = `
                            <span style="color:#aaa; font-size:12px;">🔑 关键词 (${keywords.size})</span>
                            <span style="color:#4CAF50; font-size:11px;">批量添加</span>
                        `;
                        showToast(`❌ 已删除: ${kw}`);
                    };
                    item.appendChild(delBtn);
                    keywordList.appendChild(item);
                });
            }
            renderKeywords();

            const keywordAddArea = document.createElement('div');
            keywordAddArea.style.display = 'flex';
            keywordAddArea.style.gap = '4px';
            keywordAddArea.style.marginBottom = '8px';

            const keywordInput = document.createElement('input');
            keywordInput.type = 'text';
            keywordInput.placeholder = '添加关键词';
            Object.assign(keywordInput.style, {
                flex: '1',
                background: '#2a2a2a',
                border: '1px solid #3a3a3a',
                borderRadius: '4px',
                padding: '4px 8px',
                color: '#fff',
                outline: 'none',
                fontSize: '11px'
            });

            const keywordAddBtn = document.createElement('button');
            keywordAddBtn.textContent = '+';
            Object.assign(keywordAddBtn.style, {
                background: '#4CAF50',
                border: 'none',
                borderRadius: '4px',
                width: '24px',
                color: '#fff',
                cursor: 'pointer',
                fontSize: '14px',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center'
            });
            keywordAddBtn.onclick = () => {
                const val = keywordInput.value.trim();
                if (val) {
                    keywords.add(val);
                    saveConfig();
                    renderKeywords();
                    keywordHeader.innerHTML = `
                        <span style="color:#aaa; font-size:12px;">🔑 关键词 (${keywords.size})</span>
                        <span style="color:#4CAF50; font-size:11px;">批量添加</span>
                    `;
                    keywordInput.value = '';
                    showToast(`✅ 已添加: ${val}`);
                }
            };

            keywordAddArea.appendChild(keywordInput);
            keywordAddArea.appendChild(keywordAddBtn);

            const batchTextarea = document.createElement('textarea');
            batchTextarea.placeholder = '批量添加(逗号/空格分隔)';
            Object.assign(batchTextarea.style, {
                width: '100%',
                background: '#2a2a2a',
                border: '1px solid #3a3a3a',
                borderRadius: '4px',
                padding: '6px',
                color: '#fff',
                fontSize: '11px',
                marginTop: '4px',
                resize: 'vertical',
                minHeight: '40px'
            });

            const batchBtn = document.createElement('button');
            batchBtn.textContent = '批量添加';
            Object.assign(batchBtn.style, {
                width: '100%',
                background: '#3a3a3a',
                border: 'none',
                borderRadius: '4px',
                padding: '6px 0',
                color: '#fff',
                fontSize: '11px',
                cursor: 'pointer',
                marginTop: '4px'
            });
            batchBtn.onclick = () => {
                const raw = batchTextarea.value.trim();
                if (!raw) return;
                const parts = raw.split(/[,\s]+/).filter(p => p.length > 0);
                let added = 0;
                parts.forEach(p => {
                    if (!keywords.has(p)) {
                        keywords.add(p);
                        added++;
                    }
                });
                if (added > 0) {
                    saveConfig();
                    renderKeywords();
                    keywordHeader.innerHTML = `
                        <span style="color:#aaa; font-size:12px;">🔑 关键词 (${keywords.size})</span>
                        <span style="color:#4CAF50; font-size:11px;">批量添加</span>
                    `;
                    showToast(`✅ 批量添加 ${added} 个`);
                }
                batchTextarea.value = '';
            };

            keywordSection.appendChild(keywordHeader);
            keywordSection.appendChild(keywordList);
            keywordSection.appendChild(keywordAddArea);
            keywordSection.appendChild(batchTextarea);
            keywordSection.appendChild(batchBtn);

            body.appendChild(statusSection);
            body.appendChild(modeSection);
            body.appendChild(countSection);
            body.appendChild(infoSection);
            body.appendChild(regionSection);
            body.appendChild(keywordSection);

            panel.appendChild(body);
        }

        document.body.appendChild(panel);
    }

    // ==================== 初始化 ====================
    function init() {
        console.log('抖音评论监控插件 v2.7 已启动 (右侧最新两条)');
        createPanel();

        if (autoOpenComment) {
            setTimeout(openCommentPanel, 2000);
        }

        setInterval(() => {
            if (!commentContainer) {
                commentContainer = findCommentContainer();
            }
        }, 3000);
    }

    // 注册菜单
    if (typeof GM_registerMenuCommand !== 'undefined') {
        GM_registerMenuCommand('📋 打开监控面板', createPanel);
        GM_registerMenuCommand('📋 查看历史记录', showHistoryWindow);
        GM_registerMenuCommand('📥 导出历史记录', exportHistoryToCSV);
        GM_registerMenuCommand('💬 打开评论区 (X)', openCommentPanel);
        GM_registerMenuCommand('▶️ 开始滚动', startCommentScroll);
        GM_registerMenuCommand('⏸️ 停止滚动', stopCommentScroll);
        GM_registerMenuCommand('💾 保存配置', saveConfig);
    }

    // 启动
    if (document.body) {
        setTimeout(init, 1500);
    } else {
        document.addEventListener('DOMContentLoaded', () => setTimeout(init, 1500));
    }

})();
发布时间: