YouTube 聊天室管理

16種預設可選色彩用以自動著色指定用戶訊息,其它功能包含封鎖用戶、簡化編輯儲存在瀏覽器的用戶清單、移除聊天室置頂消息,清理重複消息,添加功能切換開關。

目前為 2025-04-14 提交的版本,檢視 最新版本

// ==UserScript==
// @name         YouTube 聊天室管理
// @namespace    http://tampermonkey.net/
// @version      12.8
// @description  16種預設可選色彩用以自動著色指定用戶訊息,其它功能包含封鎖用戶、簡化編輯儲存在瀏覽器的用戶清單、移除聊天室置頂消息,清理重複消息,添加功能切換開關。
// @match        *://www.youtube.com/live_chat*
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    // 常數定義
    const COLOR_OPTIONS = {
        "淺藍": "#ADD8E6",
        "深藍": "#0000FF",
        "淺綠": "#98FB98",
        "綠色": "#008000",
        "淺紅": "#F08080",
        "紅色": "#FF0000",
        "紫色": "#800080",
        "金色": "#FFD700",
        "粉紅": "#FFC0CB",
        "橙色": "#FFA500",
        "青色": "#00FFFF",
        "深綠": "#006400",
        "深紅": "#8B0000",
        "深紫": "#9400D3",
        "銀色": "#C0C0C0",
        "棕色": "#A52A2A"
    };

    const MENU_AUTO_CLOSE_DELAY = 8000;
    const DUPLICATE_HIGHLIGHT_INTERVAL = 10000;
    const THROTTLE_DELAY = 150;
    const TEMP_USER_EXPIRE_TIME = 60000; // 1分鐘 = 60000毫秒
    const MAX_MESSAGE_CACHE_SIZE = 200;
    const CLEANUP_INTERVAL = 30000; // 每30秒清理一次

    // 高亮模式定義
    const HIGHLIGHT_MODES = {
        BOTH: 0,
        NAME_ONLY: 1,
        MESSAGE_ONLY: 2
    };

    // 初始化設定
    let userColorSettings = loadSettings('userColorSettings', {});
    let keywordColorSettings = loadSettings('keywordColorSettings', {});
    let blockedUsers = loadSettings('blockedUsers', []);
    let currentMenu = null;
    let menuTimeoutId = null;
    let lastDuplicateHighlightTime = 0;

    // 功能開關設定
    let featureSettings = loadSettings('featureSettings', {
        pinEnabled: false,
        duplicateEnabled: true,
        highlightEnabled: true,
        blockEnabled: true,
        buttonsVisible: true,
        mentionHighlightEnabled: true
    });

    // 高亮模式設定
    let highlightSettings = loadSettings('highlightSettings', {
        defaultMode: HIGHLIGHT_MODES.BOTH,
        tempMode: HIGHLIGHT_MODES.BOTH
    });

    // 新增臨時用戶清單
    let tempUsers = loadSettings('tempUsers', {});
    let lastTempUserCleanupTime = Date.now();

    // 使用 Map 來管理快取
    const userColorCache = new Map(Object.entries(userColorSettings));
    const keywordColorCache = new Map(Object.entries(keywordColorSettings));
    const blockedUsersSet = new Set(blockedUsers);
    const tempUserCache = new Map(Object.entries(tempUsers));
    const processedMessages = new Set();

    // 預先注入 CSS 樣式
    const style = document.createElement('style');
    style.textContent = `
        .ytcm-menu {
            position: fixed;
            background-color: white;
            border: 1px solid black;
            padding: 5px;
            z-index: 9999;
            box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
            border-radius: 5px;
        }
        .ytcm-color-item {
            cursor: pointer;
            padding: 5px;
            text-align: center;
            border-radius: 3px;
            margin: 2px;
        }
        .ytcm-list-item {
            cursor: pointer;
            padding: 5px;
            background-color: #f0f0f0;
            border-radius: 3px;
            margin: 2px;
        }
        .ytcm-button {
            cursor: pointer;
            padding: 5px 8px;
            margin: 5px 2px 0 2px;
            border-radius: 3px;
            border: 1px solid #ccc;
            background-color: #f8f8f8;
        }
        .ytcm-grid {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 5px;
        }
        .ytcm-button-row {
            display: flex;
            justify-content: space-between;
            margin-top: 5px;
        }
        .ytcm-flex-wrap {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
            margin-bottom: 10px;
        }
        .ytcm-control-panel {
            position: fixed;
            left: 0;
            bottom: 75px;
            z-index: 9998;
            display: flex;
            flex-direction: column;
            gap: 8px;
            padding: 0;
        }
        .ytcm-control-btn {
            padding: 5px 0 5px 5px;
            cursor: pointer;
            text-align: left;
            min-width: 40px;
            font-size: 14px;
            font-weight: bold;
            color: white;
            -webkit-text-stroke: 1px black;
            text-shadow: none;
            background: none;
            border: none;
            margin: 0;
        }
        .ytcm-control-btn.active {
            -webkit-text-stroke: 1px black;
        }
        .ytcm-control-btn.inactive {
            -webkit-text-stroke: 1px red;
        }
        .ytcm-toggle-btn {
            padding: 5px 0 5px 5px;
            cursor: pointer;
            text-align: left;
            min-width: 40px;
            font-size: 14px;
            font-weight: bold;
            color: white;
            -webkit-text-stroke: 1px black;
            text-shadow: none;
            background: none;
            border: none;
            margin: 0;
        }
        .ytcm-main-buttons {
            display: ${featureSettings.buttonsVisible ? 'flex' : 'none'};
            flex-direction: column;
            gap: 8px;
        }
        .ytcm-duplicate-message {
            opacity: 0.5;
            transition: opacity 0.3s ease;
        }
        #author-name.ytcm-highlight {
            font-weight: bold;
        }
        #message.ytcm-highlight {
            font-weight: bold;
        }
    `;
    document.head.appendChild(style);

    // 創建控制面板
    function createControlPanel() {
        const panel = document.createElement('div');
        panel.className = 'ytcm-control-panel';

        // 主按鈕容器
        const mainButtons = document.createElement('div');
        mainButtons.className = 'ytcm-main-buttons';

        // 頂 - 清除置頂開關
        const pinBtn = document.createElement('div');
        pinBtn.className = `ytcm-control-btn ${featureSettings.pinEnabled ? 'active' : 'inactive'}`;
        pinBtn.textContent = '頂';
        pinBtn.title = '切換清除置頂功能';
        pinBtn.addEventListener('click', () => {
            featureSettings.pinEnabled = !featureSettings.pinEnabled;
            pinBtn.className = `ytcm-control-btn ${featureSettings.pinEnabled ? 'active' : 'inactive'}`;
            saveSettings('featureSettings', featureSettings);
        });

        // 複 - 過濾重複發言開關
        const duplicateBtn = document.createElement('div');
        duplicateBtn.className = `ytcm-control-btn ${featureSettings.duplicateEnabled ? 'active' : 'inactive'}`;
        duplicateBtn.textContent = '複';
        duplicateBtn.title = '切換過濾重複發言功能';
        duplicateBtn.addEventListener('click', () => {
            featureSettings.duplicateEnabled = !featureSettings.duplicateEnabled;
            duplicateBtn.className = `ytcm-control-btn ${featureSettings.duplicateEnabled ? 'active' : 'inactive'}`;
            saveSettings('featureSettings', featureSettings);
        });

        // 亮 - 高亮文字開關
        const highlightBtn = document.createElement('div');
        highlightBtn.className = `ytcm-control-btn ${featureSettings.highlightEnabled ? 'active' : 'inactive'}`;
        highlightBtn.textContent = '亮';
        highlightBtn.title = getHighlightModeTooltip(highlightSettings.defaultMode);
        highlightBtn.addEventListener('click', (e) => {
            if (e.ctrlKey) {
                // Ctrl+左鍵點擊: 切換預設高亮模式
                highlightSettings.defaultMode = (highlightSettings.defaultMode + 1) % 3;
                highlightBtn.title = getHighlightModeTooltip(highlightSettings.defaultMode);
                saveSettings('highlightSettings', highlightSettings);
                // 清除處理記錄以重新處理現有消息
                processedMessages.clear();
                const messages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer')).slice(-MAX_MESSAGE_CACHE_SIZE);
                messages.forEach(msg => processedMessages.delete(msg));
            } else {
                // 普通點擊: 切換高亮功能開關
                featureSettings.highlightEnabled = !featureSettings.highlightEnabled;
                highlightBtn.className = `ytcm-control-btn ${featureSettings.highlightEnabled ? 'active' : 'inactive'}`;
                saveSettings('featureSettings', featureSettings);
            }
        });

        // 封 - 清理封鎖用戶開關
        const blockBtn = document.createElement('div');
        blockBtn.className = `ytcm-control-btn ${featureSettings.blockEnabled ? 'active' : 'inactive'}`;
        blockBtn.textContent = '封';
        blockBtn.title = '切換清理封鎖用戶功能';
        blockBtn.addEventListener('click', () => {
            featureSettings.blockEnabled = !featureSettings.blockEnabled;
            blockBtn.className = `ytcm-control-btn ${featureSettings.blockEnabled ? 'active' : 'inactive'}`;
            saveSettings('featureSettings', featureSettings);
        });

        // @ - @高亮功能開關
        const mentionBtn = document.createElement('div');
        mentionBtn.className = `ytcm-control-btn ${featureSettings.mentionHighlightEnabled ? 'active' : 'inactive'}`;
        mentionBtn.textContent = '@';
        mentionBtn.title = getHighlightModeTooltip(highlightSettings.tempMode);
        mentionBtn.addEventListener('click', (e) => {
            if (e.ctrlKey) {
                // Ctrl+左鍵點擊: 切換臨時高亮模式
                highlightSettings.tempMode = (highlightSettings.tempMode + 1) % 3;
                mentionBtn.title = getHighlightModeTooltip(highlightSettings.tempMode);
                saveSettings('highlightSettings', highlightSettings);
                // 清除處理記錄以重新處理現有消息
                processedMessages.clear();
                const messages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer')).slice(-MAX_MESSAGE_CACHE_SIZE);
                messages.forEach(msg => processedMessages.delete(msg));
            } else {
                // 普通點擊: 切換@高亮功能開關
                featureSettings.mentionHighlightEnabled = !featureSettings.mentionHighlightEnabled;
                mentionBtn.className = `ytcm-control-btn ${featureSettings.mentionHighlightEnabled ? 'active' : 'inactive'}`;
                saveSettings('featureSettings', featureSettings);
            }
        });

        // 將五個主按鈕添加到容器
        mainButtons.appendChild(pinBtn);
        mainButtons.appendChild(duplicateBtn);
        mainButtons.appendChild(highlightBtn);
        mainButtons.appendChild(blockBtn);
        mainButtons.appendChild(mentionBtn);

        // ☑ - 切換按鈕顯示
        const toggleBtn = document.createElement('div');
        toggleBtn.className = 'ytcm-toggle-btn';
        toggleBtn.textContent = '☑';
        toggleBtn.title = '顯示/隱藏控制按鈕';
        toggleBtn.addEventListener('click', () => {
            featureSettings.buttonsVisible = !featureSettings.buttonsVisible;
            mainButtons.style.display = featureSettings.buttonsVisible ? 'flex' : 'none';
            saveSettings('featureSettings', featureSettings);
        });

        // 將所有元素添加到面板
        panel.appendChild(mainButtons);
        panel.appendChild(toggleBtn);

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

    // 獲取高亮模式提示文字
    function getHighlightModeTooltip(mode) {
        switch (mode) {
            case HIGHLIGHT_MODES.BOTH:
                return "當前模式: 高亮暱稱和對話 (Ctrl+左鍵切換)";
            case HIGHLIGHT_MODES.NAME_ONLY:
                return "當前模式: 只高亮暱稱 (Ctrl+左鍵切換)";
            case HIGHLIGHT_MODES.MESSAGE_ONLY:
                return "當前模式: 只高亮對話 (Ctrl+左鍵切換)";
            default:
                return "高亮模式";
        }
    }

    // 節流函數
    function throttle(func, limit) {
        let lastFunc;
        let lastRan;
        return function() {
            const context = this;
            const args = arguments;
            if (!lastRan) {
                func.apply(context, args);
                lastRan = Date.now();
            } else {
                clearTimeout(lastFunc);
                lastFunc = setTimeout(function() {
                    if ((Date.now() - lastRan) >= limit) {
                        func.apply(context, args);
                        lastRan = Date.now();
                    }
                }, limit - (Date.now() - lastRan));
            }
        };
    }

    // 加載設定
    function loadSettings(key, defaultValue) {
        try {
            const value = JSON.parse(localStorage.getItem(key));
            return value !== null ? value : defaultValue;
        } catch (error) {
            console.error(`Failed to load ${key}:`, error);
            try {
                localStorage.removeItem(key); // 嘗試清除損壞的數據
            } catch (e) {
                console.error('Failed to remove corrupted data:', e);
            }
            return defaultValue;
        }
    }

    // 保存設定 (批次處理)
    let settingsSaveQueue = {};
    function saveSettings(key, value) {
        settingsSaveQueue[key] = value;
        if (!window.settingsSaveTimeout) {
            window.settingsSaveTimeout = setTimeout(() => {
                try {
                    Object.keys(settingsSaveQueue).forEach(k => {
                        try {
                            localStorage.setItem(k, JSON.stringify(settingsSaveQueue[k]));
                        } catch (error) {
                            console.error(`Failed to save ${k}:`, error);
                            // 嘗試清理空間
                            if (error.name === 'QuotaExceededError') {
                                try {
                                    localStorage.clear();
                                    localStorage.setItem(k, JSON.stringify(settingsSaveQueue[k]));
                                } catch (e) {
                                    console.error('Failed after clearing storage:', e);
                                }
                            }
                        }
                    });
                    settingsSaveQueue = {};
                } catch (error) {
                    console.error('Batch save failed:', error);
                }
                window.settingsSaveTimeout = null;
            }, 1000);
        }
    }

    // 清理已不存在的消息
    function cleanupProcessedMessages() {
        const allMessages = new Set(document.querySelectorAll('yt-live-chat-text-message-renderer'));
        for (const msg of processedMessages) {
            if (!allMessages.has(msg)) {
                processedMessages.delete(msg);
            }
        }
    }

    // 處理@提及的用戶 (改進版)
    function processMentionedUsers(messageText, authorName, authorColor) {
        if (!featureSettings.mentionHighlightEnabled || !authorColor) return;

        // 改進的@匹配邏輯,支持包含空格的用戶名
        const mentionRegex = /@([^\s].*?(?=\s|$|@|[\u200b]))/g;
        let match;
        const mentionedUsers = new Set();

        // 收集所有被提及的用戶
        while ((match = mentionRegex.exec(messageText)) !== null) {
            const mentionedUser = match[1].trim();
            if (mentionedUser) {
                mentionedUsers.add(mentionedUser);
            }
        }

        // 檢查每個被提及的用戶是否在現有用戶列表中
        const allUsers = Array.from(document.querySelectorAll('#author-name'));
        const existingUsers = allUsers.map(el => el.textContent.trim());

        mentionedUsers.forEach(mentionedUser => {
            // 檢查是否是完全匹配的現有用戶
            const isExistingUser = existingUsers.some(user =>
                user.toLowerCase() === mentionedUser.toLowerCase()
            );

            if (isExistingUser &&
                !userColorCache.has(mentionedUser) &&
                !tempUserCache.has(mentionedUser)) {

                tempUsers[mentionedUser] = {
                    color: authorColor,
                    expireTime: Date.now() + TEMP_USER_EXPIRE_TIME
                };
                tempUserCache.set(mentionedUser, {
                    color: authorColor,
                    expireTime: Date.now() + TEMP_USER_EXPIRE_TIME
                });

                // 立即處理現有消息
                const messages = document.querySelectorAll('yt-live-chat-text-message-renderer');
                messages.forEach(msg => {
                    const nameElement = msg.querySelector('#author-name');
                    if (nameElement && nameElement.textContent.trim() === mentionedUser) {
                        processedMessages.delete(msg); // 允許重新處理這條消息
                    }
                });
            }
        });

        // 保存臨時用戶清單
        if (mentionedUsers.size > 0) {
            saveSettings('tempUsers', tempUsers);
        }
    }

    // 清理過期的臨時用戶
    function cleanupExpiredTempUsers() {
        const now = Date.now();
        if (now - lastTempUserCleanupTime < CLEANUP_INTERVAL) return;

        lastTempUserCleanupTime = now;
        let changed = false;

        for (const [user, data] of tempUserCache.entries()) {
            if (data.expireTime <= now) {
                tempUserCache.delete(user);
                delete tempUsers[user];
                changed = true;

                // 清除相關消息的處理狀態
                const messages = document.querySelectorAll('yt-live-chat-text-message-renderer');
                messages.forEach(msg => {
                    const nameElement = msg.querySelector('#author-name');
                    if (nameElement && nameElement.textContent.trim() === user) {
                        processedMessages.delete(msg);
                    }
                });
            }
        }

        if (changed) {
            saveSettings('tempUsers', tempUsers);
        }
    }

    // 高亮訊息
    function highlightMessages(mutations) {
        if (!featureSettings.highlightEnabled) return;

        cleanupProcessedMessages(); // 清理已不存在的消息

        const messages = [];

        mutations.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                if (node.nodeType === 1 && node.matches('yt-live-chat-text-message-renderer') && !processedMessages.has(node)) {
                    messages.push(node);
                    processedMessages.add(node);
                }
            });
        });

        // 如果沒有新增消息,檢查最近的消息
        if (messages.length === 0) {
            const allMessages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer')).slice(-MAX_MESSAGE_CACHE_SIZE);
            allMessages.forEach(msg => {
                if (!processedMessages.has(msg)) {
                    messages.push(msg);
                    processedMessages.add(msg);
                }
            });
        }

        messages.forEach(msg => {
            const authorName = msg.querySelector('#author-name');
            const messageElement = msg.querySelector('#message');
            if (!authorName || !messageElement) return;

            const userName = authorName.textContent.trim();
            const messageText = messageElement.textContent.trim();

            // 重置樣式
            authorName.style.color = '';
            authorName.classList.remove('ytcm-highlight');
            messageElement.style.color = '';
            messageElement.classList.remove('ytcm-highlight');

            // 檢查是否在臨時用戶清單中
            if (tempUserCache.has(userName)) {
                const color = tempUserCache.get(userName).color;
                applyHighlightMode(msg, color, highlightSettings.tempMode);
                return;
            }

            // 檢查是否在用戶顏色清單中
            if (userColorCache.has(userName)) {
                const color = userColorCache.get(userName);
                applyHighlightMode(msg, color, highlightSettings.defaultMode);

                // 處理@提及的用戶
                if (featureSettings.mentionHighlightEnabled) {
                    processMentionedUsers(messageText, userName, color);
                }
                return;
            }

            // 檢查關鍵字
            for (const [keyword, color] of keywordColorCache) {
                if (messageText.includes(keyword)) {
                    applyHighlightMode(msg, color, highlightSettings.defaultMode);
                    break;
                }
            }
        });

        // 清理過期的臨時用戶
        cleanupExpiredTempUsers();
    }

    // 根據高亮模式應用樣式
    function applyHighlightMode(msg, color, mode) {
        const authorName = msg.querySelector('#author-name');
        const messageElement = msg.querySelector('#message');

        // 先移除所有樣式
        authorName.style.color = '';
        authorName.classList.remove('ytcm-highlight');
        messageElement.style.color = '';
        messageElement.classList.remove('ytcm-highlight');

        switch (mode) {
            case HIGHLIGHT_MODES.BOTH:
                authorName.style.color = color;
                authorName.classList.add('ytcm-highlight');
                messageElement.style.color = color;
                messageElement.classList.add('ytcm-highlight');
                break;
            case HIGHLIGHT_MODES.NAME_ONLY:
                authorName.style.color = color;
                authorName.classList.add('ytcm-highlight');
                break;
            case HIGHLIGHT_MODES.MESSAGE_ONLY:
                messageElement.style.color = color;
                messageElement.classList.add('ytcm-highlight');
                break;
        }
    }

    // 標記重複訊息 (改為淡化處理)
    function markDuplicateMessages() {
        if (!featureSettings.duplicateEnabled) return;

        const currentTime = Date.now();
        if (currentTime - lastDuplicateHighlightTime < DUPLICATE_HIGHLIGHT_INTERVAL) return;
        lastDuplicateHighlightTime = currentTime;

        const messages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer')).slice(-MAX_MESSAGE_CACHE_SIZE);
        const messageMap = new Map();

        messages.forEach(msg => {
            const authorName = msg.querySelector('#author-name');
            const messageElement = msg.querySelector('#message');
            if (!authorName || !messageElement) return;

            const userName = authorName.textContent.trim();
            const messageText = messageElement.textContent.trim();
            const key = `${userName}:${messageText}`;

            if (messageMap.has(key)) {
                messageElement.classList.add('ytcm-duplicate-message');
            } else {
                messageMap.set(key, msg);
                messageElement.classList.remove('ytcm-duplicate-message');
            }
        });
    }

    // 處理封鎖用戶
    function handleBlockedUsers(mutations) {
        if (!featureSettings.blockEnabled) return;

        cleanupProcessedMessages(); // 清理已不存在的消息

        const messages = [];

        mutations.forEach(mutation => {
            mutation.addedNodes.forEach(node => {
                if (node.nodeType === 1 && node.matches('yt-live-chat-text-message-renderer') && !processedMessages.has(node)) {
                    messages.push(node);
                    processedMessages.add(node);
                }
            });
        });

        messages.forEach(msg => {
            const authorName = msg.querySelector('#author-name');
            if (!authorName) return;

            const userName = authorName.textContent.trim();
            if (blockedUsersSet.has(userName)) {
                const messageElement = msg.querySelector('#message');
                if (messageElement) {
                    messageElement.textContent = '';
                }
            }
        });
    }

    // 移除置頂訊息
    function removePinnedMessage() {
        if (!featureSettings.pinEnabled) return;

        const pinnedMessage = document.querySelector('yt-live-chat-banner-renderer');
        if (pinnedMessage) {
            pinnedMessage.style.display = 'none';
        }
    }

    // 關閉選單函數
    function closeMenu() {
        if (currentMenu) {
            document.body.removeChild(currentMenu);
            currentMenu = null;
            clearTimeout(menuTimeoutId);
        }
    }

    // 創建顏色選單
    function createColorMenu(targetElement, event) {
        closeMenu();

        const menu = document.createElement('div');
        menu.className = 'ytcm-menu';
        menu.style.top = `${event.clientY}px`;
        menu.style.left = `${event.clientX}px`;
        menu.style.width = '220px';

        const colorGrid = document.createElement('div');
        colorGrid.className = 'ytcm-grid';

        Object.entries(COLOR_OPTIONS).forEach(([colorName, colorValue]) => {
            const colorItem = document.createElement('div');
            colorItem.className = 'ytcm-color-item';
            colorItem.textContent = colorName;
            colorItem.style.backgroundColor = colorValue;
            colorItem.addEventListener('click', () => {
                if (targetElement.type === 'user') {
                    userColorSettings[targetElement.name] = colorValue;
                    userColorCache.set(targetElement.name, colorValue);

                    // 立即處理現有消息
                    const messages = document.querySelectorAll('yt-live-chat-text-message-renderer');
                    messages.forEach(msg => {
                        const nameElement = msg.querySelector('#author-name');
                        if (nameElement && nameElement.textContent.trim() === targetElement.name) {
                            processedMessages.delete(msg); // 允許重新處理這條消息
                        }
                    });
                } else if (targetElement.type === 'keyword') {
                    keywordColorSettings[targetElement.keyword] = colorValue;
                    keywordColorCache.set(targetElement.keyword, colorValue);
                }
                saveSettings('userColorSettings', userColorSettings);
                saveSettings('keywordColorSettings', keywordColorSettings);
                closeMenu();
            });
            colorGrid.appendChild(colorItem);
        });

        const buttonRow = document.createElement('div');
        buttonRow.className = 'ytcm-button-row';

        const blockButton = document.createElement('button');
        blockButton.className = 'ytcm-button';
        blockButton.textContent = '封鎖';
        blockButton.addEventListener('click', () => {
            if (targetElement.type === 'user') {
                blockedUsers.push(targetElement.name);
                blockedUsersSet.add(targetElement.name);
                saveSettings('blockedUsers', blockedUsers);
            }
            closeMenu();
        });

        const editButton = document.createElement('button');
        editButton.className = 'ytcm-button';
        editButton.textContent = '編輯';
        editButton.addEventListener('click', (e) => {
            e.stopPropagation();
            createEditMenu(event);
        });

        const deleteButton = document.createElement('button');
        deleteButton.className = 'ytcm-button';
        deleteButton.textContent = '刪除';
        deleteButton.addEventListener('click', () => {
            if (targetElement.type === 'user' && userColorSettings[targetElement.name]) {
                delete userColorSettings[targetElement.name];
                userColorCache.delete(targetElement.name);
                saveSettings('userColorSettings', userColorSettings);
            }
            closeMenu();
        });

        buttonRow.appendChild(blockButton);
        buttonRow.appendChild(editButton);
        buttonRow.appendChild(deleteButton);

        menu.appendChild(colorGrid);
        menu.appendChild(buttonRow);
        document.body.appendChild(menu);
        currentMenu = menu;

        menuTimeoutId = setTimeout(closeMenu, MENU_AUTO_CLOSE_DELAY);
    }

    // 創建編輯選單
    function createEditMenu(event) {
        closeMenu();

        const menu = document.createElement('div');
        menu.className = 'ytcm-menu';
        menu.style.top = `${event.clientY}px`;
        menu.style.left = `${event.clientX}px`;
        menu.style.maxWidth = '600px';

        const closeButton = document.createElement('button');
        closeButton.className = 'ytcm-button';
        closeButton.textContent = '關閉';
        closeButton.style.width = '100%';
        closeButton.style.marginBottom = '10px';
        closeButton.addEventListener('click', closeMenu);
        menu.appendChild(closeButton);

        // 封鎖用戶名單
        const blockedUserList = document.createElement('div');
        blockedUserList.textContent = '封鎖用戶名單:';
        blockedUserList.className = 'ytcm-flex-wrap';

        blockedUsers.forEach(user => {
            const userItem = document.createElement('div');
            userItem.className = 'ytcm-list-item';
            userItem.textContent = user;
            userItem.addEventListener('click', () => {
                blockedUsers = blockedUsers.filter(u => u !== user);
                blockedUsersSet.delete(user);
                saveSettings('blockedUsers', blockedUsers);
                userItem.remove();
            });
            blockedUserList.appendChild(userItem);
        });
        menu.appendChild(blockedUserList);

        // 關鍵字名單
        const keywordList = document.createElement('div');
        keywordList.textContent = '關鍵字名單:';
        keywordList.className = 'ytcm-flex-wrap';

        Object.keys(keywordColorSettings).forEach(keyword => {
            const keywordItem = document.createElement('div');
            keywordItem.className = 'ytcm-list-item';
            keywordItem.textContent = keyword;
            keywordItem.addEventListener('click', () => {
                delete keywordColorSettings[keyword];
                keywordColorCache.delete(keyword);
                saveSettings('keywordColorSettings', keywordColorSettings);
                keywordItem.remove();
            });
            keywordList.appendChild(keywordItem);
        });
        menu.appendChild(keywordList);

        // 被上色用戶名單
        const coloredUserList = document.createElement('div');
        coloredUserList.textContent = '被上色用戶名單:';
        coloredUserList.className = 'ytcm-flex-wrap';

        Object.keys(userColorSettings).forEach(user => {
            const userItem = document.createElement('div');
            userItem.className = 'ytcm-list-item';
            userItem.textContent = user;
            userItem.addEventListener('click', () => {
                delete userColorSettings[user];
                userColorCache.delete(user);
                saveSettings('userColorSettings', userColorSettings);
                userItem.remove();
            });
            coloredUserList.appendChild(userItem);
        });
        menu.appendChild(coloredUserList);

        document.body.appendChild(menu);
        currentMenu = menu;

        menuTimeoutId = setTimeout(closeMenu, MENU_AUTO_CLOSE_DELAY);
    }

    // 點擊事件處理
    function handleClick(event) {
        if (currentMenu && !currentMenu.contains(event.target)) {
            closeMenu();
        }

        if (event.target.id === 'author-name') {
            const userName = event.target.textContent.trim();
            createColorMenu({ type: 'user', name: userName }, event);
        } else {
            const selectedText = window.getSelection().toString();
            if (selectedText) {
                createColorMenu({ type: 'keyword', keyword: selectedText }, event);
            }
        }
    }

    // 初始化函數
    function init() {
        // 清理舊的事件監聽器
        document.removeEventListener('click', handleClick);

        // 添加新的事件監聽器
        document.addEventListener('click', handleClick);

        // 創建控制面板
        const controlPanel = createControlPanel();

        // 初始化 MutationObserver
        const observer = new MutationObserver(throttle((mutations) => {
            highlightMessages(mutations);
            markDuplicateMessages();
            handleBlockedUsers(mutations);
            removePinnedMessage();
        }, THROTTLE_DELAY));

        const chatContainer = document.querySelector('#chat');
        if (chatContainer) {
            observer.observe(chatContainer, { childList: true, subtree: true });
        }

        // 設置定期清理任務
        const cleanupIntervalId = setInterval(() => {
            cleanupProcessedMessages();
            cleanupExpiredTempUsers();
        }, CLEANUP_INTERVAL);

        // 返回清理函數
        return () => {
            observer.disconnect();
            document.removeEventListener('click', handleClick);
            clearInterval(cleanupIntervalId);
            if (controlPanel && controlPanel.parentNode) {
                controlPanel.parentNode.removeChild(controlPanel);
            }
            if (currentMenu && currentMenu.parentNode) {
                currentMenu.parentNode.removeChild(currentMenu);
            }
            clearTimeout(menuTimeoutId);
        };
    }

    // 啟動腳本
    let cleanup = init();

    // 如果頁面是動態加載的,可能需要重新初始化
    const checkChatContainer = setInterval(() => {
        if (document.querySelector('#chat') && !cleanup) {
            cleanup = init();
        }
    }, 1000);

    // 提供清理方法以防需要重新加載
    window.addEventListener('beforeunload', () => {
        clearInterval(checkChatContainer);
        if (cleanup) cleanup();
    });
})();