YouTube 聊天室管理

洗版模式切換版

// ==UserScript==
// @name         YouTube 聊天室管理
// @namespace    http://tampermonkey.net/
// @version      12.33
// @description  洗版模式切換版
// @match        *://www.youtube.com/live_chat*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const COLOR_OPTIONS = {
        "淺藍": "#5FA6E8","藍色": "#2463D1","深藍": "#0000FF","紫色": "#FF00FF",
        "淺綠": "#98FB98","綠色": "#00FF00","深綠": "#006400","青色": "#00FFFF",
        "粉紅": "#FFC0CB","淺紅": "#F08080","紅色": "#FF0000","深紅": "#8B0000",
        "橙色": "#FFA500","金色": "#FFD700","灰色": "#BDBDBD","深灰": "#404040"
    };

    const MENU_AUTO_CLOSE_DELAY = 8000;
    const THROTTLE_DELAY = 150;
    const TEMP_USER_EXPIRE_TIME = 40000;
    const MAX_MESSAGE_CACHE_SIZE = 200;
    const CLEANUP_INTERVAL = 40000;
    const SPAM_CHECK_INTERVAL = 500;
    const SPAM_TEXT_LENGTH = 10;

    const HIGHLIGHT_MODES = { BOTH: 0, NAME_ONLY: 1, MESSAGE_ONLY: 2 };
    const STYLE_MODES = { BASIC: 0, BOLD: 1, BACKGROUND: 2 };
    const SPAM_MODES = { MARK: 0, REMOVE: 1 };

    let userColorSettings = JSON.parse(localStorage.getItem('userColorSettings')) || {};
    let blockedUsers = JSON.parse(localStorage.getItem('blockedUsers')) || [];
    let currentMenu = null;
    let menuTimeoutId = null;
    let featureSettings = JSON.parse(localStorage.getItem('featureSettings')) || {
        pinEnabled: false, highlightEnabled: true,
        blockEnabled: true, buttonsVisible: true,
        mentionHighlightEnabled: true, spamFilterEnabled: true,
        counterEnabled: true, spamMode: SPAM_MODES.MARK
    };
    let highlightSettings = JSON.parse(localStorage.getItem('highlightSettings')) || {
        defaultMode: HIGHLIGHT_MODES.BOTH, tempMode: HIGHLIGHT_MODES.BOTH
    };
    let styleMode = JSON.parse(localStorage.getItem('styleMode')) || STYLE_MODES.BASIC;
    let tempUsers = JSON.parse(localStorage.getItem('tempUsers')) || {};
    let lastTempUserCleanupTime = Date.now();
    let messageCache = new Set();
    let userMessageCounts = {};
    let lastSpamCheckTime = 0;

    const userColorCache = new Map();
    const blockedUsersSet = new Set(blockedUsers);
    const tempUserCache = new Map();
    const processedMessages = new Set();
    const styleCache = new WeakMap();

    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-bg-highlight {
            padding: 2px 4px;
            border-radius: 3px;
        }
        .ytcm-message-count {
            font-size: 0.8em;
            opacity: 0.7;
            vertical-align: super;
        }
        .ytcm-highlight-name {
            color: var(--highlight-color) !important;
            font-weight: var(--name-weight, normal) !important;
            background-color: var(--name-bg, transparent) !important;
        }
        .ytcm-highlight-msg {
            color: var(--highlight-color) !important;
            font-weight: var(--msg-weight, normal) !important;
            background-color: var(--msg-bg, transparent) !important;
        }
        .ytcm-bg-mode {
            color: #000 !important;
            background-color: var(--highlight-color) !important;
        }
        [data-blocked="true"] #message {
            content: "<封鎖>" !important;
        }
        [data-spam="true"] #message {
            content: "<洗版>" !important;
        }
    `;
    document.head.appendChild(style);

    function initializeCaches() {
        Object.entries(userColorSettings).forEach(([user, color]) => userColorCache.set(user, color));
        Object.entries(tempUsers).forEach(([user, data]) => tempUserCache.set(user, data));
    }

    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 = '切換清除置頂功能 (Ctrl+左鍵切換樣式模式)';
        pinBtn.addEventListener('click', (e) => {
            if (e.ctrlKey) {
                styleMode = (styleMode + 1) % 3;
                localStorage.setItem('styleMode', JSON.stringify(styleMode));
                updateStyleMode();
            } else {
                featureSettings.pinEnabled = !featureSettings.pinEnabled;
                pinBtn.className = `ytcm-control-btn ${featureSettings.pinEnabled ? 'active' : 'inactive'}`;
                localStorage.setItem('featureSettings', JSON.stringify(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) {
                highlightSettings.defaultMode = (highlightSettings.defaultMode + 1) % 3;
                highlightBtn.title = getHighlightModeTooltip(highlightSettings.defaultMode);
                localStorage.setItem('highlightSettings', JSON.stringify(highlightSettings));
                clearProcessedMessages();
            } else {
                featureSettings.highlightEnabled = !featureSettings.highlightEnabled;
                highlightBtn.className = `ytcm-control-btn ${featureSettings.highlightEnabled ? 'active' : 'inactive'}`;
                localStorage.setItem('featureSettings', JSON.stringify(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'}`;
            localStorage.setItem('featureSettings', JSON.stringify(featureSettings));
            clearProcessedMessages();
        });

        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) {
                highlightSettings.tempMode = (highlightSettings.tempMode + 1) % 3;
                mentionBtn.title = getHighlightModeTooltip(highlightSettings.tempMode);
                localStorage.setItem('highlightSettings', JSON.stringify(highlightSettings));
                clearProcessedMessages();
            } else {
                featureSettings.mentionHighlightEnabled = !featureSettings.mentionHighlightEnabled;
                mentionBtn.className = `ytcm-control-btn ${featureSettings.mentionHighlightEnabled ? 'active' : 'inactive'}`;
                localStorage.setItem('featureSettings', JSON.stringify(featureSettings));
                if (!featureSettings.mentionHighlightEnabled) {
                    tempUsers = {};
                    tempUserCache.clear();
                    localStorage.setItem('tempUsers', JSON.stringify(tempUsers));
                }
            }
        });

        const spamBtn = document.createElement('div');
        spamBtn.className = `ytcm-control-btn ${featureSettings.spamFilterEnabled ? 'active' : 'inactive'}`;
        spamBtn.textContent = '洗';
        spamBtn.title = getSpamModeTooltip(featureSettings.spamMode);
        spamBtn.addEventListener('click', (e) => {
            if (e.ctrlKey) {
                featureSettings.spamMode = (featureSettings.spamMode + 1) % 2;
                spamBtn.title = getSpamModeTooltip(featureSettings.spamMode);
                localStorage.setItem('featureSettings', JSON.stringify(featureSettings));
            } else {
                featureSettings.spamFilterEnabled = !featureSettings.spamFilterEnabled;
                spamBtn.className = `ytcm-control-btn ${featureSettings.spamFilterEnabled ? 'active' : 'inactive'}`;
                localStorage.setItem('featureSettings', JSON.stringify(featureSettings));
                if (!featureSettings.spamFilterEnabled) messageCache.clear();
            }
        });

        const counterBtn = document.createElement('div');
        counterBtn.className = `ytcm-control-btn ${featureSettings.counterEnabled ? 'active' : 'inactive'}`;
        counterBtn.textContent = '數';
        counterBtn.title = '切換留言計數功能';
        counterBtn.addEventListener('click', () => {
            featureSettings.counterEnabled = !featureSettings.counterEnabled;
            counterBtn.className = `ytcm-control-btn ${featureSettings.counterEnabled ? 'active' : 'inactive'}`;
            localStorage.setItem('featureSettings', JSON.stringify(featureSettings));
            if (!featureSettings.counterEnabled) {
                document.querySelectorAll('.ytcm-message-count').forEach(el => el.remove());
            }
        });

        mainButtons.appendChild(pinBtn);
        mainButtons.appendChild(highlightBtn);
        mainButtons.appendChild(blockBtn);
        mainButtons.appendChild(mentionBtn);
        mainButtons.appendChild(spamBtn);
        mainButtons.appendChild(counterBtn);

        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';
            localStorage.setItem('featureSettings', JSON.stringify(featureSettings));
        });

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

    function clearProcessedMessages() {
        processedMessages.clear();
    }

    function updateStyleMode() {
        styleCache.clear();
        clearProcessedMessages();
    }

    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 getSpamModeTooltip(mode) {
        switch (mode) {
            case SPAM_MODES.MARK: return "當前模式: 註記洗版 (Ctrl+左鍵切換為移除模式)";
            case SPAM_MODES.REMOVE: return "當前模式: 移除洗版 (Ctrl+左鍵切換為註記模式)";
            default: return "洗版處理模式";
        }
    }

    function throttle(func, limit) {
        let lastFunc, lastRan;
        return function() {
            const context = this, 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 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);
                styleCache.delete(msg);
            }
        }
        if (processedMessages.size > MAX_MESSAGE_CACHE_SIZE) {
            const messages = Array.from(processedMessages).slice(-MAX_MESSAGE_CACHE_SIZE);
            processedMessages.clear();
            messages.forEach(msg => processedMessages.add(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);
                        styleCache.delete(msg);
                    }
                });
            }
        });
        if (mentionedUsers.size > 0) localStorage.setItem('tempUsers', JSON.stringify(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);
                if (tempUsers.hasOwnProperty(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);
                        styleCache.delete(msg);
                    }
                });
            }
        }
        if (changed) localStorage.setItem('tempUsers', JSON.stringify(tempUsers));
    }

    function removePinnedMessage() {
        if (!featureSettings.pinEnabled) return;
        requestAnimationFrame(() => {
            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);
                            styleCache.delete(msg);
                        }
                    });
                }
                localStorage.setItem('userColorSettings', JSON.stringify(userColorSettings));
                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);
                localStorage.setItem('blockedUsers', JSON.stringify(blockedUsers));
                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) {
                        msg.setAttribute('data-blocked', 'true');
                        const messageElement = msg.querySelector('#message');
                        if (messageElement) messageElement.textContent = '<封鎖>';
                        styleCache.delete(msg);
                    }
                });
            }
            closeMenu();
        });
        const editButton = document.createElement('button');
        editButton.className = 'ytcm-button';
        editButton.textContent = '編輯';
        editButton.addEventListener('click', (e) => {
            e.stopPropagation();
            createEditMenu(targetElement, 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);
                localStorage.setItem('userColorSettings', JSON.stringify(userColorSettings));
                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);
                        styleCache.delete(msg);
                    }
                });
            }
            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(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.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);
                localStorage.setItem('blockedUsers', JSON.stringify(blockedUsers));
                userItem.remove();
                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);
                        styleCache.delete(msg);
                    }
                });
            });
            blockedUserList.appendChild(userItem);
        });
        menu.appendChild(blockedUserList);

        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);
                localStorage.setItem('userColorSettings', JSON.stringify(userColorSettings));
                userItem.remove();
                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);
                        styleCache.delete(msg);
                    }
                });
            });
            coloredUserList.appendChild(userItem);
        });
        menu.appendChild(coloredUserList);

        document.body.appendChild(menu);
        currentMenu = menu;
        menuTimeoutId = setTimeout(closeMenu, MENU_AUTO_CLOSE_DELAY);
    }

    function checkForSpam(msg) {
        if (!featureSettings.spamFilterEnabled) return;

        const messageElement = msg.querySelector('#message');
        if (!messageElement || messageElement.textContent === '<封鎖>' || messageElement.textContent === '<洗版>') return;

        const messageText = messageElement.textContent.trim().substring(0, SPAM_TEXT_LENGTH);

        if (messageCache.has(messageText)) {
            if (featureSettings.spamMode === SPAM_MODES.MARK) {
                messageElement.textContent = '<洗版>';
                msg.setAttribute('data-spam', 'true');
                styleCache.delete(msg);
            } else {
                msg.style.display = 'none';
            }
            return;
        }

        messageCache.add(messageText);
    }

    function updateMessageCounter(msg) {
        if (!featureSettings.counterEnabled) return;

        const nameElement = msg.querySelector('#author-name');
        if (!nameElement) return;

        const userName = nameElement.textContent.trim();
        if (!userMessageCounts[userName]) userMessageCounts[userName] = 0;
        userMessageCounts[userName]++;

        const existingCounter = msg.querySelector('.ytcm-message-count');
        if (existingCounter) existingCounter.remove();

        const counterSpan = document.createElement('span');
        counterSpan.className = 'ytcm-message-count';
        counterSpan.textContent = userMessageCounts[userName];
        const messageElement = msg.querySelector('#message');
        if (messageElement) messageElement.appendChild(counterSpan);
    }

    function processMessage(msg) {
        if (styleCache.has(msg)) return;

        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();

        if (featureSettings.blockEnabled && blockedUsersSet.has(userName)) {
            msg.setAttribute('data-blocked', 'true');
            messageElement.textContent = '<封鎖>';
            styleCache.set(msg, true);
            return;
        }

        if (msg.hasAttribute('data-blocked') || msg.hasAttribute('data-spam')) {
            styleCache.set(msg, true);
            return;
        }

        if (featureSettings.spamFilterEnabled) {
            const now = Date.now();
            if (now - lastSpamCheckTime >= SPAM_CHECK_INTERVAL) {
                checkForSpam(msg);
                lastSpamCheckTime = now;
            }
        }

        updateMessageCounter(msg);

        authorName.className = authorName.className.replace('ytcm-highlight-name', '').replace('ytcm-bg-highlight', '').replace('ytcm-bg-mode', '').trim();
        messageElement.className = messageElement.className.replace('ytcm-highlight-msg', '').replace('ytcm-bg-highlight', '').replace('ytcm-bg-mode', '').trim();

        if (featureSettings.highlightEnabled && (tempUserCache.has(userName) || userColorCache.has(userName))) {
            const color = tempUserCache.has(userName) ? tempUserCache.get(userName).color : userColorCache.get(userName);
            const mode = tempUserCache.has(userName) ? highlightSettings.tempMode : highlightSettings.defaultMode;

            authorName.style.setProperty('--highlight-color', color);
            messageElement.style.setProperty('--highlight-color', color);

            switch (styleMode) {
                case STYLE_MODES.BASIC:
                    authorName.style.setProperty('--name-weight', 'normal');
                    authorName.style.setProperty('--name-bg', 'transparent');
                    messageElement.style.setProperty('--msg-weight', 'normal');
                    messageElement.style.setProperty('--msg-bg', 'transparent');
                    break;
                case STYLE_MODES.BOLD:
                    authorName.style.setProperty('--name-weight', 'bold');
                    authorName.style.setProperty('--name-bg', 'transparent');
                    messageElement.style.setProperty('--msg-weight', 'bold');
                    messageElement.style.setProperty('--msg-bg', 'transparent');
                    break;
                case STYLE_MODES.BACKGROUND:
                    authorName.style.setProperty('--name-bg', color);
                    messageElement.style.setProperty('--msg-bg', color);
                    break;
            }

            if (mode === HIGHLIGHT_MODES.BOTH || mode === HIGHLIGHT_MODES.NAME_ONLY) {
                authorName.classList.add('ytcm-highlight-name');
                if (styleMode === STYLE_MODES.BACKGROUND) authorName.classList.add('ytcm-bg-mode');
            }
            if (mode === HIGHLIGHT_MODES.BOTH || mode === HIGHLIGHT_MODES.MESSAGE_ONLY) {
                messageElement.classList.add('ytcm-highlight-msg');
                if (styleMode === STYLE_MODES.BACKGROUND) messageElement.classList.add('ytcm-bg-mode');
            }

            if (featureSettings.mentionHighlightEnabled) processMentionedUsers(messageText, userName, color);
        }

        styleCache.set(msg, true);
    }

    function highlightMessages(mutations) {
        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); } });
        }

        requestAnimationFrame(() => {
            messages.forEach(msg => processMessage(msg));
        });
        cleanupExpiredTempUsers();
    }

    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);
        }
    }

    function init() {
        initializeCaches();
        document.addEventListener('click', handleClick);
        const controlPanel = createControlPanel();
        const observer = new MutationObserver(throttle((mutations) => {
            highlightMessages(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.remove();
            closeMenu();
        };
    }

    let cleanup = init();
    const checkChatContainer = setInterval(() => {
        if (document.querySelector('#chat') && !cleanup) cleanup = init();
    }, 1000);
    window.addEventListener('beforeunload', () => {
        clearInterval(checkChatContainer);
        cleanup?.();
    });
})();