YouTube Chat Message Manager

A one-of-a-kind chat management design that automatically applies the following to specified users: color-coded usernames and messages, real-time chat history, message count statistics, user blacklist, and enhanced @mentions. For other users: replaces @handles with display names, spam detection (hideable), donation/chat history overview, and auto-scroll to the latest messages.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         YouTube Chat Message Manager
// @name:zh-TW   Youtube 聊天消息管理者
// @author       Dxzy
// @namespace    http://tampermonkey.net/
// @version      21.3.11
// @description:zh-TW  僅此一家的消息管理設計,指定用戶後自動運作以下行為:用戶名稱和消息上色、發言履歷即時一覽、次數統計,用戶黑名單,@改進。對於非特定用戶:以用戶暱稱取代@帳號,檢測洗版(可隱藏),抖內消息/聊天室對話歷史一覽,自動回捲最新進度。
// @description A one-of-a-kind chat management design that automatically applies the following to specified users: color-coded usernames and messages, real-time chat history, message count statistics, user blacklist, and enhanced @mentions. For other users: replaces @handles with display names, spam detection (hideable), donation/chat history overview, and auto-scroll to the latest messages.
// @match        https://www.youtube.com/live_chat*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==
(function(){'use strict';
            const CALLOUT_USER_EXPIRE_TIME = 30000,
                  EPHEMERAL_USER_DURATION = Infinity,
                  MAX_MESSAGE_CACHE_SIZE = 300,
                  CACHE_CLEANUP_INTERVAL = 60000,
                  DOUBLE_CLICK_DELAY = 350,
                  STATS_UPDATE_INTERVAL = 10000,
                  DISPLAY_NAME_FETCH_TIMEOUT = 20000,
                  DISPLAY_NAME_RETRY_INTERVAL = 60000,
                  DISPLAY_NAME_REQUEST_LIMIT = 10,
                  DISPLAY_NAME_REQUEST_WINDOW = 1000,
                  MAX_DISPLAY_NAME_CACHE_SIZE = 500,
                  MAX_SPAM_CACHE_SIZE = 4000,
                  AUTO_RESUME_THRESHOLD_PX = 24,
                  AUTO_RESUME_DELAY_MS = 2500,
                  AUTO_RESUME_COOLDOWN_MS = 2000,
                  AUTO_RESUME_CHECK_INTERVAL_MS = 5000;
            const LANG = {
                'zh-TW': {
                    buttons: {'封鎖':'封鎖','編輯':'編輯','刪除':'刪除','清除':'清除', '對話':'對話'},
                    tooltips: {
                        ephemeral: '短暫模式:切換至短暫用戶上色,開啟時上色不儲存',
                        pin: '清除置頂:開啟/關閉自動移除置頂訊息',
                        highlight: mode => `高亮模式:${mode} (雙擊切換模式)`,
                        block: mode => `封鎖模式:${mode} (雙擊切換模式)`,
                        callout: mode => `呼叫高亮:${mode} (雙擊切換模式)`,
                        spam: mode => `洗版過濾:${mode} (雙擊切換模式)`,
                        counter: '留言計數:顯示/隱藏用戶留言計數',
                        clearConfirm: '確定清除所有設定?',
                        clearButton: '確認',
                        pauseSingle: '隱藏控制按鈕',
                        pauseDouble: '雙擊暫停腳本運作',
                        statsTotal: '點擊切換臨時視圖:僅顯示高亮、抖內',
                        statsFilter: '點擊切換臨時視圖:僅顯示過濾消息'
                    }
                },
                'en': {
                    buttons: {'封鎖':'Block','編輯':'Edit','刪除':'Delete','清除':'Clear', '對話':'Convo'},
                    tooltips: {
                        ephemeral: 'Ephemeral mode: Switch to ephemeral user coloring, colors are not saved when enabled',
                        pin: 'Pin removal: Toggle auto-remove pinned messages',
                        highlight: mode => `Highlight mode: ${mode} (Double-click to switch)`,
                        block: mode => `Block mode: ${mode} (Double-click to switch)`,
                        callout: mode => `Callout highlight: ${mode} (Double-click to switch)`,
                        spam: mode => `Spam filter: ${mode} (Double-click to switch)`,
                        counter: 'Message counter: Show/hide user message counts',
                        clearConfirm: 'Confirm reset all settings?',
                        clearButton: 'Confirm',
                        pauseSingle: 'Hide control buttons',
                        pauseDouble: 'Double-click to pause script',
                        statsTotal: 'Click for temp view: Show Highlight & SC only',
                        statsFilter: 'Click for temp view: Show filtered messages only'
                    }
                }
            };
            const currentLang = navigator.language.startsWith('zh') ? 'zh-TW' : 'en';
            const COLOR_OPTIONS = {
                "淺藍":"#A5CDF3", "藍色":"#62A8EA", "深藍":"#1C76CA", "紫色":"#FF00FF",
                "淺綠":"#98FB98", "綠色":"#00FF00", "深綠":"#00B300", "青色":"#00FFFF",
                "粉紅":"#FFC0CB", "淺紅":"#F08080", "紅色":"#FF0000", "深紅":"#8B0000",
                "橙色":"#FFA500", "金色":"#FFD700", "灰色":"#BDBDBD", "深灰":"#404040"
            };
            const HIGHLIGHT_MODES = { BOTH:0, NAME_ONLY:1, MESSAGE_ONLY:2 },
                  SPAM_MODES = { MARK:0, REMOVE:1 },
                  BLOCK_MODES = { MARK:0, HIDE:1 };
            const STORAGE_KEYS = {
                USER_COLOR_SETTINGS: 'ytcm_userColorSettings',
                BLOCKED_USERS: 'ytcm_blockedUsers',
                FEATURE_SETTINGS: 'ytcm_featureSettings'
            };
            let userColorSettings = JSON.parse(localStorage.getItem(STORAGE_KEYS.USER_COLOR_SETTINGS)) || {},
                blockedUsers = JSON.parse(localStorage.getItem(STORAGE_KEYS.BLOCKED_USERS)) || [],
                featureSettings = JSON.parse(localStorage.getItem(STORAGE_KEYS.FEATURE_SETTINGS)) || {};
            let localEphemeralMode = false;
            let currentMenu = null,
                ephemeralUsers = {},
                ephemeralColorSettings = {},
                ephemeralBlockedUsers = new Set(),
                lastClickTime = 0,
                clickCount = 0,
                isUpdatingFromStorage = false,
                mainButtonsElement = null,
                controlButtonRefs = {},
                isScriptPaused = false;
            let statsBox = null,
                frequencyDisplay = null,
                spamStatsDisplay = null,
                statsTotalSpan = null,
                statsFilterSpan = null,
                authorHoverTimer = null,
                currentTooltip = null;
            let cacheSizeHistory = [],
                lastCacheSize = 0,
                lastTotalMessageCount = 0;
            let statsUpdaterInterval = null,
                cacheCleanupInterval = null,
                displayNameRetryInterval = null,
                autoResumeInterval = null;
            let totalMessageCount = 0,
                totalSpamCount = 0;
            let tempViewMode = null;
            let lastAutoResumeTime = 0;
            let autoResumePending = false;
            let autoResumeDeviationStartTime = 0;
            let isMouseInChat = false;
            let cachedScroller = null;
            let lastScrollerCheckTime = 0;
            let isConfirmedNearBottom = true;
            const userColorCache = new Map(),
                  blockedUsersSet = new Set(blockedUsers),
                  calloutUserCache = new Map(),
                  spamCache = new Map(),
                  processedMessages = new Map(),
                  processedMessageIds = new Set();
            const displayNameCache = new Map(),
                  displayNamePending = new Map(),
                  displayNameFailed = new Map(),
                  displayNameRequestTimes = [];
            const cleanupTasks = [];
            function registerCleanup(fn) {
                cleanupTasks.push(fn);
            }
            function setWithLimit(map, key, value, limit) {
                if (map.size >= limit) {
                    const firstKey = map.keys().next().value;
                    map.delete(firstKey);
                }
                map.set(key, value);
            }
            function decodeHtmlEntities(text) {
                if (!text) return text;
                const entities = {
                    '&': '&',
                    '&lt;': '<',
                    '&gt;': '>',
                    '&quot;': '"',
                    '&#39;': "'",
                    '&#x27;': "'",
                    '&apos;': "'",
                    '&nbsp;': ' '
                };
                return text.replace(/&(#(?:x[0-9a-f]+|\d+)|[a-z]+);/gi, (match) => {
                    return entities[match.toLowerCase()] || match;
                });
            }
            function extractDisplayName(html) {
                try {
                    const patterns = [
                        /<title>([^<]+?)\s*-\s*YouTube<\/title>/i,
                        /<title>([^<]+?)<\/title>/i
                    ];
                    for (const pattern of patterns) {
                        const m = html.match(pattern);
                        if (m && m[1]) {
                            const name = decodeHtmlEntities(m[1].trim());
                            if (name && !name.includes('404') && !name.includes('Not Found') && name.length > 0 && name !== 'YouTube') {
                                return name;
                            }
                        }
                    }
                    return null;
                } catch (e) {
                    return null;
                }
            }
            function canMakeDisplayNameRequest() {
                const now = Date.now();
                while (displayNameRequestTimes.length > 0 && now - displayNameRequestTimes[0] > DISPLAY_NAME_REQUEST_WINDOW) {
                    displayNameRequestTimes.shift();
                }
                return displayNameRequestTimes.length < DISPLAY_NAME_REQUEST_LIMIT;
            }
            function recordDisplayNameRequest() {
                displayNameRequestTimes.push(Date.now());
            }
            function fetchWithTimeout(url, timeout = DISPLAY_NAME_FETCH_TIMEOUT) {
                return Promise.race([
                    fetch(url, { credentials: 'omit', cache: 'force-cache' }),
                    new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), timeout))
                ]);
            }
            function batchUpdateDisplayNames(handle, displayName) {
                const normalizedHandle = normalizeUserName(handle);
                const messages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer'));
                messages.forEach(msg => {
                    const authorNameElement = msg.querySelector('#author-name') || msg.querySelector('.author-name');
                    if (!authorNameElement) return;
                    const msgHandle = getMessageHandle(msg);
                    if (msgHandle && msgHandle === normalizedHandle) {
                        if (authorNameElement.textContent.trim() !== displayName) {
                            authorNameElement.textContent = displayName;
                            authorNameElement.setAttribute('data-original-handle', handle);
                            authorNameElement.setAttribute('data-ytcm-name-replaced', 'true');
                        }
                    }
                });
            }
            function fetchDisplayName(handle) {
                const normalizedHandle = handle.startsWith('@') ? handle : `@${handle}`;
                if (displayNameCache.has(normalizedHandle)) {
                    batchUpdateDisplayNames(normalizedHandle, displayNameCache.get(normalizedHandle));
                    return Promise.resolve(displayNameCache.get(normalizedHandle));
                }
                if (displayNamePending.has(normalizedHandle)) {
                    return displayNamePending.get(normalizedHandle);
                }
                if (!canMakeDisplayNameRequest()) {
                    return new Promise(resolve => {
                        setTimeout(() => {
                            fetchDisplayName(handle).then(resolve);
                        }, DISPLAY_NAME_REQUEST_WINDOW);
                    });
                }
                recordDisplayNameRequest();
                const p = fetchWithTimeout(`https://www.youtube.com/${normalizedHandle}`)
                .then(r => {
                    if (!r.ok) {
                        displayNameFailed.set(normalizedHandle, Date.now());
                        return null;
                    }
                    return r.text();
                })
                .then(html => {
                    if (!html) {
                        displayNameFailed.set(normalizedHandle, Date.now());
                        return null;
                    }
                    const name = extractDisplayName(html);
                    if (name) {
                        setWithLimit(displayNameCache, normalizedHandle, name, MAX_DISPLAY_NAME_CACHE_SIZE);
                        displayNameFailed.delete(normalizedHandle);
                        batchUpdateDisplayNames(normalizedHandle, name);
                        return name;
                    }
                    displayNameFailed.set(normalizedHandle, Date.now());
                    return null;
                })
                .catch(() => {
                    displayNameFailed.set(normalizedHandle, Date.now());
                    return null;
                })
                .finally(() => {
                    displayNamePending.delete(normalizedHandle);
                });
                displayNamePending.set(normalizedHandle, p);
                return p;
            }
            function retryFailedDisplayNames() {
                const now = Date.now();
                for (const [handle, lastFailTime] of displayNameFailed.entries()) {
                    if (now - lastFailTime >= DISPLAY_NAME_RETRY_INTERVAL) {
                        fetchDisplayName(handle);
                    }
                }
            }
            function getChatScroller() {
                const now = Date.now();
                if (cachedScroller && now - lastScrollerCheckTime < 5000) return cachedScroller;
                cachedScroller = document.querySelector('yt-live-chat-item-list-renderer #item-scroller, #chat #item-scroller') ||
                    document.querySelector('yt-live-chat-item-list-renderer #items, #chat #items');
                lastScrollerCheckTime = now;
                return cachedScroller;
            }
            function isNearBottom() {
                const scroller = getChatScroller();
                if (!scroller) return true;
                return scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight <= AUTO_RESUME_THRESHOLD_PX;
            }
            function isMenuOrDialogOpen() {
                return !!currentMenu || !!currentTooltip ||
                    document.querySelector('.ytcm-menu') ||
                    document.querySelector('.ytcm-full-convo-window') ||
                    document.querySelector('.ytcm-notification');
            }
            function forceScrollToBottom() {
                const scroller = getChatScroller();
                if (scroller) {
                    scroller.scrollTop = scroller.scrollHeight;
                }
            }
            function resetAutoResumeTimer() {
                autoResumeDeviationStartTime = 0;
                autoResumePending = false;
                isConfirmedNearBottom = true;
            }
            function checkAutoResume() {
                if (isScriptPaused) return;
                if (isConfirmedNearBottom) {
                    if (!isNearBottom()) {
                        isConfirmedNearBottom = false;
                        autoResumeDeviationStartTime = Date.now();
                    }
                    return;
                }
                if (Date.now() - autoResumeDeviationStartTime > 30000) {
                    forceScrollToBottom();
                    resetAutoResumeTimer();
                    return;
                }
                if (isMouseInChat || isMenuOrDialogOpen()) {
                    autoResumePending = false;
                    return;
                }
                if (autoResumePending) return;
                const now = Date.now();
                if (now - lastAutoResumeTime < AUTO_RESUME_COOLDOWN_MS) return;
                autoResumePending = true;
                setTimeout(() => {
                    if (!isScriptPaused && !isNearBottom() && !isMouseInChat && !isMenuOrDialogOpen()) {
                        forceScrollToBottom();
                        lastAutoResumeTime = Date.now();
                    }
                    autoResumePending = false;
                }, AUTO_RESUME_DELAY_MS);
                if (isNearBottom()) {
                    resetAutoResumeTimer();
                }
            }
            function startTimers() {
                if (!displayNameRetryInterval) {
                    displayNameRetryInterval = setInterval(retryFailedDisplayNames, DISPLAY_NAME_RETRY_INTERVAL);
                    registerCleanup(() => { if (displayNameRetryInterval) clearInterval(displayNameRetryInterval); displayNameRetryInterval = null; });
                }
                if (!cacheCleanupInterval) {
                    cacheCleanupInterval = setInterval(() => {
                        const now = Date.now();
                        for (const user in ephemeralUsers) { if (EPHEMERAL_USER_DURATION !== Infinity && ephemeralUsers[user] <= now) { delete ephemeralUsers[user]; delete ephemeralColorSettings[user]; updateAllMessages(user); } }
                        for (const [user, data] of calloutUserCache.entries()) { if (data.expireTime <= now) { calloutUserCache.delete(user); updateAllMessages(user); } }
                        if (featureSettings.pinRemovalEnabled) { requestAnimationFrame(() => { const pinnedMessage = document.querySelector('yt-live-chat-banner-renderer'); if (pinnedMessage) pinnedMessage.style.display = 'none'; }); }
                        if (processedMessageIds.size > 10000) processedMessageIds.clear();
                    }, CACHE_CLEANUP_INTERVAL);
                    registerCleanup(() => { if (cacheCleanupInterval) { clearInterval(cacheCleanupInterval); cacheCleanupInterval = null; } });
                }
                if (!statsUpdaterInterval) {
                    statsUpdaterInterval = setInterval(() => {
                        const delta = totalMessageCount - lastTotalMessageCount;
                        cacheSizeHistory.push(delta);
                        if (cacheSizeHistory.length > 6) cacheSizeHistory.shift();
                        lastTotalMessageCount = totalMessageCount;
                        updateStatsDisplay();
                    }, STATS_UPDATE_INTERVAL);
                    registerCleanup(() => { if (statsUpdaterInterval) { clearInterval(statsUpdaterInterval); statsUpdaterInterval = null; } });
                }
                if (!autoResumeInterval) {
                    autoResumeInterval = setInterval(checkAutoResume, AUTO_RESUME_CHECK_INTERVAL_MS);
                    registerCleanup(() => { if (autoResumeInterval) { clearInterval(autoResumeInterval); autoResumeInterval = null; } });
                }
            }
            function stopTimers() {
                if (displayNameRetryInterval) { clearInterval(displayNameRetryInterval); displayNameRetryInterval = null; }
                if (cacheCleanupInterval) { clearInterval(cacheCleanupInterval); cacheCleanupInterval = null; }
                if (statsUpdaterInterval) { clearInterval(statsUpdaterInterval); statsUpdaterInterval = null; }
                if (autoResumeInterval) { clearInterval(autoResumeInterval); autoResumeInterval = null; }
            }
            function updateMessageDisplayName(msg) {
                const authorNameElement = msg.querySelector('#author-name') || msg.querySelector('.author-name');
                if (!authorNameElement) return;
                const originalText = authorNameElement.textContent.trim();
                if (!originalText) return;
                const handle = originalText.startsWith('@') ? originalText : `@${originalText}`;
                if (authorNameElement.hasAttribute('data-ytcm-name-replaced')) {
                    if (displayNameCache.has(handle)) {
                        const cachedName = displayNameCache.get(handle);
                        if (authorNameElement.textContent !== cachedName) {
                            authorNameElement.textContent = cachedName;
                            authorNameElement.setAttribute('data-original-handle', handle);
                        }
                    }
                    return;
                }
                if (displayNameCache.has(handle)) {
                    batchUpdateDisplayNames(handle, displayNameCache.get(handle));
                    return;
                }
                if (!displayNamePending.has(handle)) {
                    fetchDisplayName(handle);
                }
            }
            function getMessageHandle(msg) {
                const authorNameElement = msg.querySelector('#author-name') || msg.querySelector('.author-name');
                if (!authorNameElement) return null;
                const originalHandle = authorNameElement.getAttribute('data-original-handle');
                if (originalHandle) return normalizeUserName(originalHandle);
                const text = authorNameElement.textContent.trim();
                return normalizeUserName(text.startsWith('@') ? text : `@${text}`);
            }
            const defaultFeatureSettings = {
                pinRemovalEnabled: true,
                highlightEnabled: true,
                blockEnabled: true,
                buttonsVisible: true,
                calloutHighlightEnabled: true,
                spamFilterEnabled: true,
                counterEnabled: true,
                spamMode: SPAM_MODES.MARK,
                blockMode: BLOCK_MODES.MARK,
                defaultMode: HIGHLIGHT_MODES.BOTH,
                calloutMode: HIGHLIGHT_MODES.BOTH
            };
            for (const [key, value] of Object.entries(defaultFeatureSettings)) {
                if (featureSettings[key] === undefined) {
                    featureSettings[key] = value;
                }
            }
            localStorage.setItem(STORAGE_KEYS.FEATURE_SETTINGS, JSON.stringify(featureSettings));
            Object.entries(userColorSettings).forEach(([user, color]) => userColorCache.set(user, color));
            function updateControlButtonStyles() {
                if (!featureSettings.buttonsVisible || isScriptPaused) return;
                const btnConfigs = [
                    { action: '臨', enabled: localEphemeralMode },
                    { action: '頂', enabled: featureSettings.pinRemovalEnabled },
                    { action: '亮', enabled: featureSettings.highlightEnabled },
                    { action: '封', enabled: featureSettings.blockEnabled },
                    { action: '@', enabled: featureSettings.calloutHighlightEnabled },
                    { action: '洗', enabled: featureSettings.spamFilterEnabled },
                    { action: '數', enabled: featureSettings.counterEnabled }
                ];
                btnConfigs.forEach(cfg => {
                    const btn = controlButtonRefs[cfg.action];
                    if (btn) {
                        btn.className = `ytcm-control-btn ${cfg.enabled ? 'active' : 'inactive'}`;
                        if (cfg.action === '亮') {
                            btn.title = LANG[currentLang].tooltips.highlight(
                                featureSettings.defaultMode === HIGHLIGHT_MODES.BOTH ? (currentLang === 'zh-TW' ? "全部高亮" : "Both") :
                                featureSettings.defaultMode === HIGHLIGHT_MODES.NAME_ONLY ? (currentLang === 'zh-TW' ? "僅暱稱" : "Name Only") :
                                (currentLang === 'zh-TW' ? "僅對話" : "Message Only")
                            );
                        } else if (cfg.action === '封') {
                            btn.title = LANG[currentLang].tooltips.block(
                                featureSettings.blockMode === BLOCK_MODES.MARK
                                ? (currentLang === 'zh-TW' ? '標記' : 'Mark')
                                : (currentLang === 'zh-TW' ? '清除' : 'Clear')
                            );
                        } else if (cfg.action === '@') {
                            btn.title = LANG[currentLang].tooltips.callout(
                                featureSettings.calloutMode === HIGHLIGHT_MODES.BOTH ? (currentLang === 'zh-TW' ? "全部高亮" : "Both") :
                                featureSettings.calloutMode === HIGHLIGHT_MODES.NAME_ONLY ? (currentLang === 'zh-TW' ? "僅暱稱" : "Name Only") :
                                (currentLang === 'zh-TW' ? "僅對話" : "Message Only")
                            );
                        } else if (cfg.action === '洗') {
                            btn.title = LANG[currentLang].tooltips.spam(
                                featureSettings.spamMode === SPAM_MODES.MARK
                                ? (currentLang === 'zh-TW' ? '標記' : 'Mark')
                                : (currentLang === 'zh-TW' ? '清除' : 'Clear')
                            );
                        }
                    }
                });
            }
            function updateStatsDisplay() {
                if (statsBox && frequencyDisplay) {
                    const msgsPerMin = cacheSizeHistory.reduce((a, b) => a + b, 0);
                    frequencyDisplay.textContent = `Msgs: ${msgsPerMin}`;
                }
                if (statsTotalSpan) statsTotalSpan.textContent = `Total: ${totalMessageCount}`;
                if (statsFilterSpan) statsFilterSpan.textContent = ` Filter: ${totalSpamCount}`;
            }
            function syncStorage(key, skipUpdate = false) {
                if (isUpdatingFromStorage) return;
                isUpdatingFromStorage = true;
                try {
                    if (key === STORAGE_KEYS.USER_COLOR_SETTINGS || key === null) {
                        const data = JSON.parse(localStorage.getItem(STORAGE_KEYS.USER_COLOR_SETTINGS)) || {};
                        userColorSettings = data;
                        userColorCache.clear();
                        Object.entries(userColorSettings).forEach(([user, color]) => userColorCache.set(user, color));
                    }
                    if (key === STORAGE_KEYS.BLOCKED_USERS || key === null) {
                        const data = JSON.parse(localStorage.getItem(STORAGE_KEYS.BLOCKED_USERS)) || [];
                        blockedUsers = data;
                        blockedUsersSet.clear();
                        blockedUsers.forEach(user => blockedUsersSet.add(user));
                    }
                    if (key === STORAGE_KEYS.FEATURE_SETTINGS || key === null) {
                        const data = JSON.parse(localStorage.getItem(STORAGE_KEYS.FEATURE_SETTINGS)) || {};
                        featureSettings = data;
                        if (mainButtonsElement) {
                            mainButtonsElement.classList.toggle('hidden', !featureSettings.buttonsVisible || isScriptPaused);
                        }
                        updateControlButtonStyles();
                    }
                    if (!skipUpdate && (key === STORAGE_KEYS.USER_COLOR_SETTINGS || key === STORAGE_KEYS.BLOCKED_USERS)) {
                        updateAllMessages();
                    }
                } finally {
                    isUpdatingFromStorage = false;
                }
            }
            function saveAndApplyFeatures(needUpdateMessages = true) {
                localStorage.setItem(STORAGE_KEYS.FEATURE_SETTINGS, JSON.stringify(featureSettings));
                updateControlButtonStyles();
                updateStatsDisplay();
                if (needUpdateMessages) {
                    updateAllMessages();
                }
            }
            window.addEventListener('storage', (e) => {
                if (e.key === STORAGE_KEYS.USER_COLOR_SETTINGS || e.key === STORAGE_KEYS.BLOCKED_USERS || e.key === STORAGE_KEYS.FEATURE_SETTINGS) {
                    syncStorage(e.key, true);
                    if (e.key === STORAGE_KEYS.USER_COLOR_SETTINGS || e.key === STORAGE_KEYS.BLOCKED_USERS) {
                        updateAllMessages();
                    }
                    if (e.key === STORAGE_KEYS.FEATURE_SETTINGS) {
                        if (mainButtonsElement) {
                            mainButtonsElement.classList.toggle('hidden', !featureSettings.buttonsVisible || isScriptPaused);
                        }
                        updateControlButtonStyles();
                        updateAllMessages();
                        updateStatsDisplay();
                    }
                }
            });
            GM_addStyle(`
:root{--highlight-color:inherit}
.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;contain:content}
.ytcm-color-item{cursor:pointer;padding:0;border-radius:3px;margin:2px;border:1px solid #ddd;transition:transform 0.1s;min-width:40px;height:25px}
.ytcm-color-item:hover{transform:scale(1.1);box-shadow:0 0 5px rgba(0,0,0,0.3)}
.ytcm-list-item{cursor:pointer;padding:3px;background-color:#f0f0f0;border-radius:3px;margin:2px;font-size:12px}
.ytcm-button{cursor:pointer;padding:5px 8px;margin:5px 2px 0 2px;border-radius:3px;border:1px solid #ccc;background-color:#f8f8f8;font-size:12px}
.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:0;padding:0}
.ytcm-control-btn{padding:5px 0;cursor:pointer;text-align:left;min-width:20px;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;cursor:pointer;text-align:left;min-width:20px;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:flex;flex-direction:column;gap:0}
.ytcm-main-buttons.hidden{display:none!important}
.ytcm-message-count{position:absolute;top:4px;right:4px;font-size:8px;background:#000;color:#fff!important;font-weight:normal!important;padding:1px 4px;border-radius:3px;z-index:10;line-height:1.2}
.ytcm-stats-box{position:fixed;top:-4px;left:100px;z-index:9999;font-size:12px;font-weight:bold;color:white;background:rgba(0,0,0,0.7);padding:4px 8px;border-radius:4px;cursor:default;contain:content;pointer-events:none;white-space:nowrap}
.ytcm-stats-box div{margin: 2px 0;}
.ytcm-stat-clickable{cursor:pointer;text-decoration:none;pointer-events:auto;}
.ytcm-stat-clickable:hover, .ytcm-stat-clickable.active{color:#ffd700;}
.ytcm-temp-hide{display:none!important;}
.ytcm-temp-view-filter[data-blocked="true"][data-block-mode="hide"]{display:flex!important}
.ytcm-temp-view-filter[data-blocked="true"][data-block-mode="mark"]{background-color:transparent!important}
.ytcm-temp-view-filter[data-spam="true"].spam-marked #author-name,
.ytcm-temp-view-filter[data-spam="true"].spam-marked #message{color:inherit!important}
.ytcm-temp-view-filter[data-highlight] #author-name, .ytcm-temp-view-filter[data-highlight] #message { color: inherit !important; font-weight: normal !important; }
.ytcm-temp-view-filter[data-ephemeral] #author-name, .ytcm-temp-view-filter[data-ephemeral] #message { color: inherit !important; font-weight: normal !important; }
.ytcm-temp-view-filter[data-callout-highlight] #author-name, .ytcm-temp-view-filter[data-callout-highlight] #message { color: inherit !important; font-weight: normal !important; }
yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer{position:relative}
#message{position:relative}
[data-blocked="true"][data-block-mode="mark"]{background-color:#303030 !important}
[data-blocked="true"][data-block-mode="hide"]{display:none!important}
[data-spam="true"]{display:none!important}
[data-spam="true"].spam-marked{display:flex!important;}
[data-spam="true"].spam-marked #author-name,[data-spam="true"].spam-marked #message{color:#404040!important}
[data-highlight="name"] #author-name,[data-highlight="both"] #author-name{color:var(--highlight-color)!important;font-weight:bold!important}
[data-highlight="message"] #message,[data-highlight="both"] #message{color:var(--highlight-color)!important;font-weight:bold!important}
[data-ephemeral="true"] #author-name,[data-ephemeral="true"] #message{color:var(--ephemeral-color)!important;font-weight:bold!important;opacity:var(--ephemeral-opacity,1)}
[data-callout-highlight="name"] #author-name,[data-callout-highlight="both"] #author-name{color:var(--highlight-color)!important;font-weight:bold!important}
[data-callout-highlight="message"] #message,[data-callout-highlight="both"] #message{color:var(--highlight-color)!important;font-weight:bold!important}
.ytcm-spam-tooltip{position:absolute;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;font-size:12px;max-width:90vw;max-height:80vh;overflow-y:auto;word-wrap:break-word;white-space:normal;box-sizing:border-box;contain:content}
.ytcm-notification{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background-color:rgba(0,0,0,0.7);color:white;padding:10px 20px;border-radius:5px;z-index:10000;font-size:14px;contain:content}
.ytcm-full-convo-window{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:10001;color:white;font-size:14px;overflow:hidden;display:flex;flex-direction:column;padding:20px;box-sizing:border-box;contain:layout}
.ytcm-full-convo-header{display:flex;justify-content:flex-end;margin-bottom:10px;align-items:center;}
.ytcm-full-convo-btn{margin-left:10px;padding:5px 10px;background-color:#333;color:white;border:none;cursor:pointer;border-radius:3px}
.ytcm-full-convo-content{overflow-y:auto;flex-grow:1;line-height:1.4;white-space:pre-wrap}
.ytcm-convo-filter{margin-left:10px;padding:5px;background-color:#333;color:white;border:none;border-radius:3px;}
`);
            function normalizeUserName(userName) {
                return userName.startsWith('@') ? userName.substring(1) : userName;
            }
            function parseSpamKey(key) {
                const firstColonIndex = key.indexOf(':');
                if (firstColonIndex === -1) {
                    return { user: key, message: '' };
                }
                const user = key.substring(0, firstColonIndex);
                const message = key.substring(firstColonIndex + 1);
                return { user, message };
            }
            function getContrastColor(hexColor) {
                const r = parseInt(hexColor.substr(1, 2), 16);
                const g = parseInt(hexColor.substr(3, 2), 16);
                const b = parseInt(hexColor.substr(5, 2), 16);
                const brightness = (r * 299 + g * 587 + b * 114) / 1000;
                return brightness > 128 ? '#000000' : '#ffffff';
            }
            function updateAllMessages(userName) {
                if (isScriptPaused) return;
                const normalizedUserName = userName ? normalizeUserName(userName) : null;
                const messages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer'))
                .filter(msg => {
                    const nameElement = msg.querySelector('#author-name') || msg.querySelector('.author-name');
                    if (!nameElement) return false;
                    if (normalizedUserName) {
                        const currentText = nameElement.textContent.trim();
                        const originalHandle = nameElement.getAttribute('data-original-handle');
                        const msgHandle = originalHandle ? normalizeUserName(originalHandle) : normalizeUserName(currentText);
                        return msgHandle === normalizedUserName;
                    }
                    return true;
                });
                messages.forEach(msg => {
                    processedMessages.delete(msg);
                    const counterEl = msg.querySelector('.ytcm-message-count');
                    if (counterEl) counterEl.remove();
                });
                requestAnimationFrame(() => {
                    messages.forEach(msg => processMessage(msg, false));
                });
            }
            function ensureStatsDisplay() {
                if (!statsBox) {
                    statsBox = document.createElement('div');
                    statsBox.className = 'ytcm-stats-box';
                    frequencyDisplay = document.createElement('div');
                    frequencyDisplay.textContent = `Msgs: 0`;
                    spamStatsDisplay = document.createElement('div');
                    statsTotalSpan = document.createElement('span');
                    statsTotalSpan.className = 'ytcm-stat-clickable ytcm-stat-total';
                    statsTotalSpan.textContent = `Total: 0`;
                    statsTotalSpan.title = LANG[currentLang].tooltips.statsTotal;
                    statsTotalSpan.onclick = () => {
                        if (tempViewMode === 'total') {
                            tempViewMode = null;
                        } else {
                            tempViewMode = 'total';
                        }
                        if (tempViewMode === 'total') {
                            statsTotalSpan.classList.add('active');
                            statsFilterSpan.classList.remove('active');
                        } else {
                            statsTotalSpan.classList.remove('active');
                            if (tempViewMode === 'filter') statsFilterSpan.classList.add('active');
                            else statsFilterSpan.classList.remove('active');
                        }
                        updateAllMessages();
                    };
                    statsFilterSpan = document.createElement('span');
                    statsFilterSpan.className = 'ytcm-stat-clickable ytcm-stat-filter';
                    statsFilterSpan.textContent = ` Filter: 0`;
                    statsFilterSpan.title = LANG[currentLang].tooltips.statsFilter;
                    statsFilterSpan.onclick = () => {
                        if (tempViewMode === 'filter') {
                            tempViewMode = null;
                        } else {
                            tempViewMode = 'filter';
                        }
                        if (tempViewMode === 'filter') {
                            statsFilterSpan.classList.add('active');
                            statsTotalSpan.classList.remove('active');
                        } else {
                            statsFilterSpan.classList.remove('active');
                            if (tempViewMode === 'total') statsTotalSpan.classList.add('active');
                            else statsTotalSpan.classList.remove('active');
                        }
                        updateAllMessages();
                    };
                    spamStatsDisplay.appendChild(statsTotalSpan);
                    spamStatsDisplay.appendChild(statsFilterSpan);
                    statsBox.appendChild(frequencyDisplay);
                    statsBox.appendChild(spamStatsDisplay);
                    document.body.appendChild(statsBox);
                }
                startTimers();
            }
            function createControlPanel() {
                const panel = document.createElement('div');
                panel.className = 'ytcm-control-panel';
                const mainButtons = document.createElement('div');
                mainButtons.className = 'ytcm-main-buttons';
                if (!featureSettings.buttonsVisible) mainButtons.classList.add('hidden');
                mainButtonsElement = mainButtons;
                const buttons = [
                    {
                        text: '臨',
                        className: `ytcm-control-btn ${localEphemeralMode ? 'active' : 'inactive'}`,
                        title: LANG[currentLang].tooltips.ephemeral,
                        onClick: () => handleButtonClick('臨', () => {
                            localEphemeralMode = !localEphemeralMode;
                            updateControlButtonStyles();
                        })
                    },
                    {
                        text: '頂',
                        className: `ytcm-control-btn ${featureSettings.pinRemovalEnabled ? 'active' : 'inactive'}`,
                        title: LANG[currentLang].tooltips.pin,
                        onClick: () => handleButtonClick('頂', () => {
                            featureSettings.pinRemovalEnabled = !featureSettings.pinRemovalEnabled;
                            saveAndApplyFeatures(false);
                            const pinnedMessage = document.querySelector('yt-live-chat-banner-renderer');
                            if (pinnedMessage) pinnedMessage.style.display = featureSettings.pinRemovalEnabled ? 'none' : '';
                        })
                    },
                    {
                        text: '亮',
                        className: `ytcm-control-btn ${featureSettings.highlightEnabled ? 'active' : 'inactive'}`,
                        title: LANG[currentLang].tooltips.highlight(
                            featureSettings.defaultMode === HIGHLIGHT_MODES.BOTH ? (currentLang === 'zh-TW' ? "全部高亮" : "Both") :
                            featureSettings.defaultMode === HIGHLIGHT_MODES.NAME_ONLY ? (currentLang === 'zh-TW' ? "僅暱稱" : "Name Only") :
                            (currentLang === 'zh-TW' ? "僅對話" : "Message Only")
                        ),
                        onClick: () => handleButtonClick(
                            '亮',
                            () => {
                                featureSettings.highlightEnabled = !featureSettings.highlightEnabled;
                                saveAndApplyFeatures(true);
                            },
                            () => {
                                featureSettings.defaultMode = (featureSettings.defaultMode + 1) % 3;
                                saveAndApplyFeatures(true);
                            }
                        )
                    },
                    {
                        text: '封',
                        className: `ytcm-control-btn ${featureSettings.blockEnabled ? 'active' : 'inactive'}`,
                        title: LANG[currentLang].tooltips.block(
                            featureSettings.blockMode === BLOCK_MODES.MARK
                            ? (currentLang === 'zh-TW' ? '標記' : 'Mark')
                            : (currentLang === 'zh-TW' ? '清除' : 'Clear')
                        ),
                        onClick: () => handleButtonClick(
                            '封',
                            () => {
                                const wasEnabled = featureSettings.blockEnabled;
                                const wasHideMode = featureSettings.blockMode === BLOCK_MODES.HIDE;
                                featureSettings.blockEnabled = !featureSettings.blockEnabled;
                                saveAndApplyFeatures(true);
                                if (wasEnabled && !featureSettings.blockEnabled && wasHideMode) {
                                    document.querySelectorAll('[data-blocked="true"][data-block-mode="hide"]').forEach(msg => {
                                        msg.style.display = '';
                                        msg.setAttribute('data-block-mode', 'mark');
                                    });
                                }
                            },
                            () => {
                                const oldMode = featureSettings.blockMode;
                                featureSettings.blockMode = (featureSettings.blockMode + 1) % 2;
                                saveAndApplyFeatures(true);
                                if (featureSettings.blockEnabled) {
                                    const selector = oldMode === BLOCK_MODES.MARK ? '[data-block-mode="mark"]' : '[data-block-mode="hide"]';
                                    const newMode = featureSettings.blockMode === BLOCK_MODES.MARK ? 'mark' : 'hide';
                                    document.querySelectorAll(`[data-blocked="true"]${selector}`).forEach(msg => {
                                        msg.setAttribute('data-block-mode', newMode);
                                    });
                                }
                            }
                        )
                    },
                    {
                        text: '@',
                        className: `ytcm-control-btn ${featureSettings.calloutHighlightEnabled ? 'active' : 'inactive'}`,
                        title: LANG[currentLang].tooltips.callout(
                            featureSettings.calloutMode === HIGHLIGHT_MODES.BOTH ? (currentLang === 'zh-TW' ? "全部高亮" : "Both") :
                            featureSettings.calloutMode === HIGHLIGHT_MODES.NAME_ONLY ? (currentLang === 'zh-TW' ? "僅暱稱" : "Name Only") :
                            (currentLang === 'zh-TW' ? "僅對話" : "Message Only")
                        ),
                        onClick: () => handleButtonClick(
                            '@',
                            () => {
                                featureSettings.calloutHighlightEnabled = !featureSettings.calloutHighlightEnabled;
                                saveAndApplyFeatures(true);
                                if (!featureSettings.calloutHighlightEnabled) calloutUserCache.clear();
                            },
                            () => {
                                const oldMode = featureSettings.calloutMode;
                                featureSettings.calloutMode = (featureSettings.calloutMode + 1) % 3;
                                saveAndApplyFeatures(true);
                                if (featureSettings.calloutHighlightEnabled) {
                                    let newAttr = 'both';
                                    if (oldMode === HIGHLIGHT_MODES.BOTH) newAttr = 'name';
                                    else if (oldMode === HIGHLIGHT_MODES.NAME_ONLY) newAttr = 'message';
                                    document.querySelectorAll('[data-callout-highlight]').forEach(msg => {
                                        msg.setAttribute('data-callout-highlight', newAttr);
                                        msg.setAttribute('data-highlight', newAttr);
                                    });
                                }
                            }
                        )
                    },
                    {
                        text: '洗',
                        className: `ytcm-control-btn ${featureSettings.spamFilterEnabled ? 'active' : 'inactive'}`,
                        title: LANG[currentLang].tooltips.spam(
                            featureSettings.spamMode === SPAM_MODES.MARK
                            ? (currentLang === 'zh-TW' ? '標記' : 'Mark')
                            : (currentLang === 'zh-TW' ? '清除' : 'Clear')
                        ),
                        onClick: () => handleButtonClick(
                            '洗',
                            () => {
                                const wasEnabled = featureSettings.spamFilterEnabled;
                                featureSettings.spamFilterEnabled = !featureSettings.spamFilterEnabled;
                                saveAndApplyFeatures(true);
                                if (wasEnabled && !featureSettings.spamFilterEnabled) {
                                    document.querySelectorAll('[data-spam="true"]').forEach(msg => {
                                        msg.removeAttribute('data-spam');
                                        msg.classList.remove('spam-marked');
                                    });
                                }
                            },
                            () => {
                                featureSettings.spamMode = (featureSettings.spamMode + 1) % 2;
                                saveAndApplyFeatures(true);
                            }
                        )
                    },
                    {
                        text: '數',
                        className: `ytcm-control-btn ${featureSettings.counterEnabled ? 'active' : 'inactive'}`,
                        title: LANG[currentLang].tooltips.counter,
                        onClick: () => handleButtonClick('數', () => {
                            featureSettings.counterEnabled = !featureSettings.counterEnabled;
                            saveAndApplyFeatures(true);
                            if (!featureSettings.counterEnabled) {
                                document.querySelectorAll('.ytcm-message-count').forEach(el => el.remove());
                            }
                        })
                    }
                ];
                buttons.forEach(btn => {
                    const button = document.createElement('div');
                    button.className = btn.className;
                    button.textContent = btn.text;
                    button.title = btn.title;
                    button.dataset.action = btn.text;
                    button.addEventListener('click', btn.onClick);
                    controlButtonRefs[btn.text] = button;
                    mainButtons.appendChild(button);
                });
                const toggleBtn = document.createElement('div');
                toggleBtn.className = 'ytcm-toggle-btn';
                toggleBtn.textContent = '☑';
                toggleBtn.title = currentLang === 'zh-TW' ? LANG[currentLang].tooltips.pauseSingle + '\n' + LANG[currentLang].tooltips.pauseDouble : LANG.en.tooltips.pauseSingle + '\n' + LANG.en.tooltips.pauseDouble;
                toggleBtn.addEventListener('click', () => {
                    if (isScriptPaused) {
                        isScriptPaused = false;
                        mainButtons.classList.toggle('hidden', !featureSettings.buttonsVisible);
                        if (statsBox) statsBox.style.display = '';
                        updateControlButtonStyles();
                        updateStatsDisplay();
                        startTimers();
                        document.querySelectorAll('yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer').forEach(msg => {
                            if (!processedMessages.has(msg)) {
                                processMessage(msg, true);
                            }
                        });
                        return;
                    }
                    const now = Date.now();
                    if (now - lastClickTime < DOUBLE_CLICK_DELAY) {
                        clickCount++;
                        if (clickCount === 2) {
                            isScriptPaused = true;
                            mainButtons.classList.add('hidden');
                            if (statsBox) statsBox.style.display = 'none';
                            stopTimers();
                            clickCount = 0;
                        }
                    } else {
                        clickCount = 1;
                        setTimeout(() => {
                            if (clickCount === 1) {
                                featureSettings.buttonsVisible = !featureSettings.buttonsVisible;
                                localStorage.setItem(STORAGE_KEYS.FEATURE_SETTINGS, JSON.stringify(featureSettings));
                                mainButtons.classList.toggle('hidden', !featureSettings.buttonsVisible);
                            }
                            clickCount = 0;
                        }, DOUBLE_CLICK_DELAY);
                    }
                    lastClickTime = now;
                });
                panel.appendChild(mainButtons);
                panel.appendChild(toggleBtn);
                document.body.appendChild(panel);
                return panel;
            }
            function handleButtonClick(btnText, toggleAction, modeAction) {
                if (isScriptPaused) return;
                const now = Date.now();
                if (now - lastClickTime < DOUBLE_CLICK_DELAY) {
                    clickCount++;
                    if (clickCount === 2 && modeAction) {
                        modeAction();
                        clickCount = 0;
                    }
                } else {
                    clickCount = 1;
                    setTimeout(() => {
                        if (clickCount === 1) toggleAction();
                        clickCount = 0;
                    }, DOUBLE_CLICK_DELAY);
                }
                lastClickTime = now;
            }
            function cleanupProcessedMessages() {
                const allMessages = new Set(document.querySelectorAll('yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer'));
                const toDelete = [];
                processedMessages.forEach((_, msg) => {
                    if (!allMessages.has(msg)) toDelete.push(msg);
                });
                toDelete.forEach(msg => {
                    processedMessages.delete(msg);
                });
            }
            function processCalloutUsers(messageText, authorName, authorColor) {
                if (!featureSettings.calloutHighlightEnabled || !authorColor) return;
                const mentionRegex = /@([^\s].*?(?=\s|$|@|[\u200b]))/g;
                let match;
                const mentionedUsers = new Set();
                while ((match = mentionRegex.exec(messageText)) !== null) {
                    if (match[1]) mentionedUsers.add(match[1].trim());
                }
                if (mentionedUsers.size !== 1) return;
                const mentionedUser = Array.from(mentionedUsers)[0];
                const allUsers = Array.from(document.querySelectorAll('#author-name, .author-name'));
                const existingUsers = allUsers.map(el => {
                    const originalHandle = el.getAttribute('data-original-handle');
                    return originalHandle ? normalizeUserName(originalHandle) : normalizeUserName(el.textContent.trim());
                });
                const cleanMentionedUser = normalizeUserName(mentionedUser);
                const isExistingUser = existingUsers.some(user => user.toLowerCase() === cleanMentionedUser.toLowerCase());
                if (isExistingUser && !userColorCache.has(cleanMentionedUser) && !calloutUserCache.has(cleanMentionedUser)) {
                    calloutUserCache.set(cleanMentionedUser, {
                        color: authorColor,
                        expireTime: Date.now() + CALLOUT_USER_EXPIRE_TIME,
                        highlightMode: featureSettings.calloutMode
                    });
                    updateAllMessages(cleanMentionedUser);
                }
            }
            function closeMenu() {
                if (currentMenu) {
                    document.body.removeChild(currentMenu);
                    currentMenu = null;
                }
            }
            function createFullConvoWindow() {
                closeMenu();
                const windowEl = document.createElement('div');
                windowEl.className = 'ytcm-full-convo-window';
                const header = document.createElement('div');
                header.className = 'ytcm-full-convo-header';
                const closeBtn = document.createElement('button');
                closeBtn.className = 'ytcm-full-convo-btn';
                closeBtn.textContent = currentLang === 'zh-TW' ? '關閉' : 'Close';
                closeBtn.onclick = () => document.body.removeChild(windowEl);
                const copyBtn = document.createElement('button');
                copyBtn.className = 'ytcm-full-convo-btn';
                copyBtn.textContent = currentLang === 'zh-TW' ? '複製' : 'Copy';
                copyBtn.onclick = () => {
                    const filter = convoFilterSelect.value;
                    const textToCopy = Array.from(spamCache.entries())
                    .filter(([key, val]) => {
                        if (filter === 'superchat') return val.type === 'superchat';
                        return true;
                    })
                    .sort((a, b) => a[1].t - b[1].t)
                    .map(([key, val]) => {
                        const { user, message } = parseSpamKey(key);
                        return `[${new Date(val.t).toLocaleString()}] ${normalizeUserName(user)}: ${message}`;
                    })
                    .join('\n');
                    navigator.clipboard.writeText(textToCopy).then(() => {
                        showTemporaryNotification(currentLang === 'zh-TW' ? '對話記錄已複製' : 'Conversation copied');
                    }).catch(err => console.error('Failed to copy conversation:', err));
                };
                const exportBtn = document.createElement('button');
                exportBtn.className = 'ytcm-full-convo-btn';
                exportBtn.textContent = currentLang === 'zh-TW' ? '匯出' : 'Export';
                exportBtn.onclick = () => {
                    const filter = convoFilterSelect.value;
                    const textToExport = Array.from(spamCache.entries())
                    .filter(([key, val]) => {
                        if (filter === 'superchat') return val.type === 'superchat';
                        return true;
                    })
                    .sort((a, b) => a[1].t - b[1].t)
                    .map(([key, val]) => {
                        const { user, message } = parseSpamKey(key);
                        return `[${new Date(val.t).toLocaleString()}] ${normalizeUserName(user)}: ${message}`;
                    })
                    .join('\n');
                    const blob = new Blob([textToExport], { type: 'text/plain' });
                    const url = URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = url;
                    const now = new Date();
                    a.download = `yt_convo_${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}.txt`;
                    a.click();
                    URL.revokeObjectURL(url);
                };
                const convoFilterSelect = document.createElement('select');
                convoFilterSelect.className = 'ytcm-convo-filter';
                const options = [
                    { value: 'default', text: currentLang === 'zh-TW' ? '所有紀錄' : 'All Records' },
                    { value: 'superchat', text: currentLang === 'zh-TW' ? '抖內' : 'SuperChat' }
                ];
                options.forEach(opt => {
                    const option = document.createElement('option');
                    option.value = opt.value;
                    option.textContent = opt.text;
                    convoFilterSelect.appendChild(option);
                });
                convoFilterSelect.onchange = () => {
                    renderConvoContent(convoFilterSelect.value, content);
                };
                header.appendChild(convoFilterSelect);
                header.appendChild(exportBtn);
                header.appendChild(copyBtn);
                header.appendChild(closeBtn);
                const content = document.createElement('div');
                content.className = 'ytcm-full-convo-content';
                windowEl.appendChild(header);
                windowEl.appendChild(content);
                document.body.appendChild(windowEl);
                renderConvoContent('default', content);
                setTimeout(() => content.scrollTop = content.scrollHeight, 10);
            }
            function renderConvoContent(filter, contentEl) {
                contentEl.textContent = Array.from(spamCache.entries())
                    .filter(([key, val]) => {
                    if (filter === 'superchat') return val.type === 'superchat';
                    return true;
                })
                    .sort((a, b) => a[1].t - b[1].t)
                    .map(([key, val]) => {
                    const { user, message } = parseSpamKey(key);
                    return `${normalizeUserName(user)}: ${message}`;
                }).join('\n');
            }
            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.title = colorName;
                    colorItem.style.backgroundColor = colorValue;
                    colorItem.addEventListener('click', () => {
                        const userName = normalizeUserName(targetElement.name);
                        if (targetElement.type === 'user') {
                            userColorSettings[userName] = colorValue;
                            userColorCache.set(userName, colorValue);
                            updateAllMessages(userName);
                            localStorage.setItem(STORAGE_KEYS.USER_COLOR_SETTINGS, JSON.stringify(userColorSettings));
                        } else if (targetElement.type === 'temp') {
                            if (localEphemeralMode) {
                                ephemeralUsers[userName] = Date.now() + EPHEMERAL_USER_DURATION;
                                ephemeralColorSettings[userName] = colorValue;
                                calloutUserCache.set(userName, { color: colorValue, expireTime: Date.now() + EPHEMERAL_USER_DURATION, highlightMode: HIGHLIGHT_MODES.BOTH });
                            } else {
                                calloutUserCache.set(userName, { color: colorValue, expireTime: Date.now() + CALLOUT_USER_EXPIRE_TIME, highlightMode: HIGHLIGHT_MODES.BOTH });
                            }
                            updateAllMessages(userName);
                        }
                        closeMenu();
                    });
                    colorGrid.appendChild(colorItem);
                });
                const buttonRow = document.createElement('div');
                buttonRow.className = 'ytcm-button-row';
                const buttons = [
                    {
                        text: LANG[currentLang].buttons.封鎖,
                        className: 'ytcm-button',
                        onClick: () => {
                            const userName = normalizeUserName(targetElement.name);
                            if (targetElement.type === 'user') {
                                blockedUsers.push(userName);
                                blockedUsersSet.add(userName);
                                localStorage.setItem(STORAGE_KEYS.BLOCKED_USERS, JSON.stringify(blockedUsers));
                            } else if (targetElement.type === 'temp' && localEphemeralMode) {
                                ephemeralBlockedUsers.add(userName);
                            }
                            updateAllMessages(userName);
                            closeMenu();
                        }
                    },
                    {
                        text: LANG[currentLang].buttons.編輯,
                        className: 'ytcm-button',
                        onClick: (e) => { e.stopPropagation(); createEditMenu(targetElement, event); }
                    },
                    {
                        text: LANG[currentLang].buttons.刪除,
                        className: 'ytcm-button',
                        onClick: () => {
                            const userName = normalizeUserName(targetElement.name);
                            let foundInList = false;
                            if (userColorSettings[userName]) { delete userColorSettings[userName]; userColorCache.delete(userName); foundInList = true; localStorage.setItem(STORAGE_KEYS.USER_COLOR_SETTINGS, JSON.stringify(userColorSettings)); }
                            if (blockedUsersSet.has(userName)) { blockedUsers = blockedUsers.filter(u => normalizeUserName(u) !== userName); blockedUsersSet.delete(userName); foundInList = true; localStorage.setItem(STORAGE_KEYS.BLOCKED_USERS, JSON.stringify(blockedUsers)); }
                            if (calloutUserCache.has(userName)) { calloutUserCache.delete(userName); foundInList = true; }
                            if (ephemeralUsers[userName]) { delete ephemeralUsers[userName]; foundInList = true; }
                            if (ephemeralColorSettings[userName]) { delete ephemeralColorSettings[userName]; foundInList = true; }
                            if (ephemeralBlockedUsers.has(userName)) { ephemeralBlockedUsers.delete(userName); foundInList = true; }
                            const messages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer')).filter(msg => {
                                const nameElement = msg.querySelector('#author-name') || msg.querySelector('.author-name');
                                const msgHandle = getMessageHandle(msg);
                                return msgHandle && msgHandle === userName;
                            });
                            messages.forEach(msg => {
                                if (foundInList) {
                                    msg.removeAttribute('data-highlight'); msg.removeAttribute('data-ephemeral'); msg.removeAttribute('data-blocked'); msg.removeAttribute('data-spam');
                                    msg.classList.remove('spam-marked');
                                    msg.style.removeProperty('--highlight-color'); msg.style.removeProperty('--ephemeral-color');
                                    msg.querySelector('.ytcm-message-count')?.remove();
                                } else { msg.style.display = 'none'; }
                            });
                            closeMenu();
                        }
                    },
                    {
                        text: LANG[currentLang].buttons.對話,
                        className: 'ytcm-button',
                        onClick: () => createFullConvoWindow()
                    }
                ];
                buttons.forEach(btn => {
                    const button = document.createElement('button');
                    button.className = btn.className;
                    button.textContent = btn.text;
                    button.addEventListener('click', btn.onClick);
                    buttonRow.appendChild(button);
                });
                menu.appendChild(colorGrid);
                menu.appendChild(buttonRow);
                document.body.appendChild(menu);
                currentMenu = menu;
                const menuRect = menu.getBoundingClientRect();
                const viewportHeight = window.innerHeight;
                let adjustedTop = parseFloat(menu.style.top);
                if (menuRect.bottom > viewportHeight) adjustedTop = adjustedTop - (menuRect.bottom - viewportHeight) - 10;
                if (adjustedTop < 0) adjustedTop = 10;
                menu.style.top = `${adjustedTop}px`;
                let isMouseOverMenu = false;
                const mouseEnterHandler = () => { isMouseOverMenu = true; };
                const mouseLeaveHandler = () => {
                    isMouseOverMenu = false;
                    setTimeout(() => {
                        if (!isMouseOverMenu) {
                            closeMenu();
                        }
                    }, 100);
                };
                menu.addEventListener('mouseenter', mouseEnterHandler);
                menu.addEventListener('mouseleave', mouseLeaveHandler);
            }
            function showTemporaryNotification(text) {
                const notification = document.createElement('div');
                notification.textContent = text;
                notification.className = 'ytcm-notification';
                document.body.appendChild(notification);
                setTimeout(() => { if (notification.parentNode) notification.parentNode.removeChild(notification); }, 2000);
            }
            function createEditMenu(targetElement, event) {
                closeMenu();
                const menu = document.createElement('div');
                menu.className = 'ytcm-menu';
                menu.style.top = '10px'; menu.style.left = '10px'; menu.style.width = '90%'; menu.style.maxHeight = '80vh'; menu.style.overflowY = 'auto';
                const buttonRow = document.createElement('div');
                buttonRow.className = 'ytcm-button-row';
                const buttons = [
                    { text: currentLang === 'zh-TW' ? '關閉' : 'Close', className: 'ytcm-button', onClick: closeMenu },
                    {
                        text: currentLang === 'zh-TW' ? '匯出設定' : 'Export', className: 'ytcm-button',
                        onClick: () => {
                            const data = { userColorSettings, blockedUsers };
                            const blob = new Blob([JSON.stringify(data)], {type: 'application/json'});
                            const url = URL.createObjectURL(blob);
                            const a = document.createElement('a'); a.href = url; a.download = 'yt_chat_settings.json'; a.click(); URL.revokeObjectURL(url);
                        }
                    },
                    {
                        text: currentLang === 'zh-TW' ? '選擇檔案' : 'Import', className: 'ytcm-button',
                        onClick: () => {
                            const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = '.json';
                            fileInput.onchange = (e) => {
                                const file = e.target.files[0]; if (!file) return;
                                const reader = new FileReader();
                                reader.onload = (event) => {
                                    try {
                                        const data = JSON.parse(event.target.result);
                                        const newUserColorSettings = {};
                                        for (const [key, value] of Object.entries(data.userColorSettings)) newUserColorSettings[normalizeUserName(key)] = value;
                                        const newBlockedUsers = data.blockedUsers.map(normalizeUserName);
                                        localStorage.setItem(STORAGE_KEYS.USER_COLOR_SETTINGS, JSON.stringify(newUserColorSettings));
                                        localStorage.setItem(STORAGE_KEYS.BLOCKED_USERS, JSON.stringify(newBlockedUsers));
                                        localStorage.setItem(STORAGE_KEYS.FEATURE_SETTINGS, JSON.stringify(featureSettings));
                                        userColorSettings = newUserColorSettings; blockedUsers = newBlockedUsers;
                                        userColorCache.clear(); Object.entries(userColorSettings).forEach(([user, color]) => userColorCache.set(user, color));
                                        blockedUsersSet.clear(); blockedUsers.forEach(user => blockedUsersSet.add(user));
                                        updateAllMessages();
                                    } catch (err) { console.error("Invalid file format:", err); }
                                };
                                reader.readAsText(file); closeMenu(); showTemporaryNotification(currentLang === 'zh-TW' ? '名單已匯入' : 'Lists imported');
                            };
                            fileInput.click();
                        }
                    },
                    {
                        text: LANG[currentLang].buttons.清除, className: 'ytcm-button',
                        onClick: () => {
                            const confirmMenu = document.createElement('div'); confirmMenu.className = 'ytcm-menu'; confirmMenu.style.top = `${event.clientY}px`; confirmMenu.style.left = `${event.clientX}px`;
                            const confirmText = document.createElement('div'); confirmText.textContent = LANG[currentLang].tooltips.clearConfirm;
                            const confirmButton = document.createElement('button'); confirmButton.className = 'ytcm-button'; confirmButton.textContent = LANG[currentLang].tooltips.clearButton;
                            confirmButton.addEventListener('click', () => { localStorage.removeItem(STORAGE_KEYS.USER_COLOR_SETTINGS); localStorage.removeItem(STORAGE_KEYS.BLOCKED_USERS); localStorage.removeItem(STORAGE_KEYS.FEATURE_SETTINGS); window.location.reload(); });
                            confirmMenu.appendChild(confirmText); confirmMenu.appendChild(confirmButton); document.body.appendChild(confirmMenu);
                            setTimeout(() => { if (document.body.contains(confirmMenu)) document.body.removeChild(confirmMenu); }, 5000);
                        }
                    }
                ];
                buttons.forEach(btn => {
                    const button = document.createElement('button'); button.className = btn.className; button.textContent = btn.text; button.style.flex = '1'; button.style.margin = '0 1px'; button.addEventListener('click', btn.onClick); buttonRow.appendChild(button);
                });
                menu.appendChild(buttonRow);
                const ephemeralUserList = document.createElement('div');
                ephemeralUserList.textContent = currentLang === 'zh-TW' ? '臨時名單:' : 'Temporary List:';
                ephemeralUserList.className = 'ytcm-flex-wrap';
                const ephemeralUsersSet = new Set([...Object.keys(ephemeralColorSettings), ...ephemeralBlockedUsers]);
                ephemeralUsersSet.forEach(user => {
                    const userItem = document.createElement('div');
                    userItem.className = 'ytcm-list-item';
                    userItem.textContent = user;
                    const color = ephemeralColorSettings[user];
                    if (color) {
                        userItem.style.backgroundColor = color;
                        userItem.style.color = getContrastColor(color);
                    }
                    userItem.addEventListener('click', () => {
                        if (ephemeralBlockedUsers.has(user)) {
                            blockedUsers.push(user);
                            blockedUsersSet.add(user);
                            ephemeralBlockedUsers.delete(user);
                            localStorage.setItem(STORAGE_KEYS.BLOCKED_USERS, JSON.stringify(blockedUsers));
                        }
                        if (ephemeralColorSettings[user]) {
                            userColorSettings[user] = ephemeralColorSettings[user];
                            userColorCache.set(user, ephemeralColorSettings[user]);
                            delete ephemeralColorSettings[user];
                            localStorage.setItem(STORAGE_KEYS.USER_COLOR_SETTINGS, JSON.stringify(userColorSettings));
                        }
                        if (ephemeralUsers[user]) delete ephemeralUsers[user];
                        updateAllMessages();
                        userItem.remove();
                    });
                    ephemeralUserList.appendChild(userItem);
                });
                menu.appendChild(ephemeralUserList);
                const blockedUserList = document.createElement('div'); blockedUserList.textContent = currentLang === 'zh-TW' ? '封鎖名單:' : 'Blocked Users:'; 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 => normalizeUserName(u) !== user); blockedUsersSet.delete(user); localStorage.setItem(STORAGE_KEYS.BLOCKED_USERS, JSON.stringify(blockedUsers)); userItem.remove(); updateAllMessages(user); });
                    blockedUserList.appendChild(userItem);
                });
                menu.appendChild(blockedUserList);
                const coloredUserList = document.createElement('div'); coloredUserList.textContent = currentLang === 'zh-TW' ? '著色名單:' : 'Colored Users:'; coloredUserList.className = 'ytcm-flex-wrap';
                Object.keys(userColorSettings).forEach(user => {
                    const userItem = document.createElement('div'); userItem.className = 'ytcm-list-item'; userItem.textContent = user;
                    const userColor = userColorCache.get(user) || userColorSettings[user];
                    if (userColor) { userItem.style.backgroundColor = userColor; userItem.style.color = getContrastColor(userColor); }
                    userItem.addEventListener('click', () => { delete userColorSettings[user]; userColorCache.delete(user); localStorage.setItem(STORAGE_KEYS.USER_COLOR_SETTINGS, JSON.stringify(userColorSettings)); userItem.remove(); updateAllMessages(user); });
                    coloredUserList.appendChild(userItem);
                });
                menu.appendChild(coloredUserList);
                document.body.appendChild(menu); currentMenu = menu;
                let isMouseOverMenu = false;
                const mouseEnterHandler = () => { isMouseOverMenu = true; };
                const mouseLeaveHandler = () => {
                    isMouseOverMenu = false;
                    setTimeout(() => {
                        if (!isMouseOverMenu) {
                            closeMenu();
                        }
                    }, 100);
                };
                menu.addEventListener('mouseenter', mouseEnterHandler);
                menu.addEventListener('mouseleave', mouseLeaveHandler);
            }
            function updateMessageCounter(msg) {
                if (!featureSettings.counterEnabled) return;
                const userName = getMessageHandle(msg); if (!userName) return;
                let count = 0;
                for (const [key, val] of spamCache.entries()) {
                    if (typeof val === 'object' && key.startsWith(`${userName}:`)) count++;
                }
                const existingCounter = msg.querySelector('.ytcm-message-count'); if (existingCounter) existingCounter.remove();
                const counterSpan = document.createElement('span'); counterSpan.className = 'ytcm-message-count'; counterSpan.textContent = count; msg.appendChild(counterSpan);
            }
            function processMessage(msg, updateStats = true) {
                if (processedMessages.has(msg)) return;
                if (isScriptPaused) {
                    processedMessages.set(msg, true);
                    return;
                }
                if (updateStats) totalMessageCount++;
                const isSuperChatOrMembership = msg.matches('yt-live-chat-paid-message-renderer') || msg.matches('yt-live-chat-membership-item-renderer');
                const authorNameElement = msg.querySelector('#author-name') || msg.querySelector('.author-name');
                const messageElement = msg.querySelector('#message') || msg.querySelector('.message-text');
                const userName = authorNameElement ? (getMessageHandle(msg) || normalizeUserName(authorNameElement.textContent.trim())) : '';

                let isSpam = false;
                if (!isSuperChatOrMembership && featureSettings.spamFilterEnabled && authorNameElement && messageElement) {
                    isSpam = checkForSpam(msg, userName, messageElement);
                }
                if (!isSpam && msg.hasAttribute('data-spam')) isSpam = true;
                if (isSpam && featureSettings.spamFilterEnabled) {
                    msg.setAttribute('data-spam', 'true');
                    if (featureSettings.spamMode === SPAM_MODES.MARK) msg.classList.add('spam-marked'); else msg.classList.remove('spam-marked');
                    if (updateStats) totalSpamCount++;
                } else { msg.removeAttribute('data-spam'); msg.classList.remove('spam-marked'); }

                const isPermanentlyBlocked = blockedUsersSet.has(userName);
                const isEphemeralBlocked = ephemeralBlockedUsers.has(userName);
                const isBlocked = featureSettings.blockEnabled && (isPermanentlyBlocked || isEphemeralBlocked);
                if (isBlocked) {
                    msg.setAttribute('data-blocked', 'true');
                    msg.setAttribute('data-block-mode', featureSettings.blockMode === BLOCK_MODES.MARK ? 'mark' : 'hide');
                    if (!isSpam && updateStats) totalSpamCount++;
                }

                msg.removeAttribute('data-highlight'); msg.removeAttribute('data-ephemeral');
                let isHighlight = false;
                if (!isSuperChatOrMembership) {
                    if (localEphemeralMode && ephemeralUsers[userName]) {
                        msg.setAttribute('data-ephemeral', 'true');
                        isHighlight = true;
                        if (calloutUserCache.has(userName)) {
                            const tempData = calloutUserCache.get(userName);
                            msg.style.setProperty('--highlight-color', tempData.color); msg.style.setProperty('--ephemeral-color', tempData.color);
                            msg.setAttribute('data-highlight', tempData.highlightMode === HIGHLIGHT_MODES.BOTH ? 'both' : tempData.highlightMode === HIGHLIGHT_MODES.NAME_ONLY ? 'name' : 'message');
                        } else {
                            const color = userColorCache.get(userName) || ephemeralColorSettings[userName] || COLOR_OPTIONS.紅色;
                            msg.style.setProperty('--highlight-color', color); msg.style.setProperty('--ephemeral-color', color); msg.setAttribute('data-highlight', 'both');
                        }
                    } else if (featureSettings.highlightEnabled && (calloutUserCache.has(userName) || userColorCache.get(userName))) {
                        const color = calloutUserCache.has(userName) ? calloutUserCache.get(userName).color : userColorCache.get(userName);
                        const mode = calloutUserCache.has(userName) ? calloutUserCache.get(userName).highlightMode || featureSettings.defaultMode : featureSettings.defaultMode;
                        msg.style.setProperty('--highlight-color', color);
                        isHighlight = true;
                        if (mode !== HIGHLIGHT_MODES.BOTH && mode !== HIGHLIGHT_MODES.NAME_ONLY && mode !== HIGHLIGHT_MODES.MESSAGE_ONLY) return;
                        msg.setAttribute('data-highlight', mode === HIGHLIGHT_MODES.BOTH ? 'both' : mode === HIGHLIGHT_MODES.NAME_ONLY ? 'name' : 'message');
                    }
                }

                if (isSuperChatOrMembership) {
                    const amountElement = msg.querySelector('#purchase-amount');
                    if (authorNameElement && messageElement) {
                        const messageText = messageElement.textContent.trim();
                        const amountText = amountElement ? amountElement.textContent.trim() : '';
                        const combinedContent = (amountText ? `(${amountText})` : '') + `${userName}:${messageText}`;
                        setWithLimit(spamCache, `${userName}:${combinedContent}`, { t: Date.now(), type: 'superchat', spam: false }, MAX_SPAM_CACHE_SIZE);
                        const userColor = userColorCache.get(userName) || userColorSettings[userName];
                        if (userColor) {
                            msg.style.backgroundColor = userColor;
                        } else {
                            msg.style.removeProperty('background-color');
                        }
                    }
                }

                updateMessageCounter(msg);
                if (featureSettings.calloutHighlightEnabled && authorNameElement && messageElement && !isSuperChatOrMembership) {
                    const textNodes = Array.from(messageElement.childNodes).filter(node => node.nodeType === Node.TEXT_NODE && !node.parentElement.classList.contains('emoji'));
                    const messageText = textNodes.map(node => node.textContent.trim()).join(' ');
                    processCalloutUsers(messageText, userName, calloutUserCache.has(userName) ? calloutUserCache.get(userName).color : userColorCache.get(userName));
                }

                processedMessages.set(msg, true);
                updateMessageDisplayName(msg);

                if (tempViewMode === 'total') {
                    if (isBlocked || isSpam || (!isSuperChatOrMembership && !isHighlight)) {
                        msg.classList.add('ytcm-temp-hide');
                        msg.classList.remove('ytcm-temp-view-filter');
                    } else {
                        msg.classList.remove('ytcm-temp-hide');
                        msg.classList.remove('ytcm-temp-view-filter');
                    }
                } else if (tempViewMode === 'filter') {
                    if ((isBlocked || isSpam) && !isSuperChatOrMembership && !isHighlight) {
                        msg.classList.remove('ytcm-temp-hide');
                        msg.classList.add('ytcm-temp-view-filter');
                    } else {
                        msg.classList.add('ytcm-temp-hide');
                        msg.classList.remove('ytcm-temp-view-filter');
                    }
                } else {
                    msg.classList.remove('ytcm-temp-hide');
                    msg.classList.remove('ytcm-temp-view-filter');
                }
            }
            function checkForSpam(msg, userName, messageElement) {
                if (!featureSettings.spamFilterEnabled || !userName || !messageElement) return false;
                const messageId = msg.getAttribute('timestamp') || msg.id || '';
                if (messageId && processedMessageIds.has(messageId)) return false;
                if (messageId) {
                    processedMessageIds.add(messageId);
                }
                const textNodes = Array.from(messageElement.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
                let messageText = textNodes.map(node => node.textContent).join(' ').trim();
                if (!messageText) return false;
                messageText = messageText.replace(/\p{Extended_Pictographic}/gu, '').replace(/[\u200B-\u200D\uFEFF]/g, '').replace(/\s+/g, ' ').trim();
                if (!messageText) return false;
                const spamKey = `${userName}:${messageText}`;
                if (!spamCache.has(spamKey)) { setWithLimit(spamCache, spamKey, { t: Date.now(), type: 'normal', spam: false }, MAX_SPAM_CACHE_SIZE); return false; }
                setWithLimit(spamCache, spamKey, { t: Date.now(), type: 'normal', spam: true }, MAX_SPAM_CACHE_SIZE);
                return true;
            }
            function highlightMessages(mutations) {
                if (isScriptPaused) return;
                cleanupProcessedMessages();
                const messages = [];
                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === 1 && (node.matches('yt-live-chat-text-message-renderer') || node.matches('.super-fast-chat-message') || node.matches('yt-live-chat-paid-message-renderer') || node.matches('yt-live-chat-membership-item-renderer')) && !processedMessages.has(node)) {
                            messages.push(node);
                        }
                    });
                });
                if (messages.length === 0) {
                    const allMessages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer')).slice(-MAX_MESSAGE_CACHE_SIZE);
                    allMessages.forEach(msg => { if (!processedMessages.has(msg)) { messages.push(msg); } });
                }
                requestAnimationFrame(() => { messages.forEach(msg => processMessage(msg, true)); });
            }
            function attachMessageListeners(msg) {
                msg.addEventListener('click', handleClick, { capture: true });
                const authorImg = msg.querySelector('#author-photo img') || msg.querySelector('.author-photo img');
                if (authorImg) {
                    authorImg.addEventListener('mouseenter', handleAuthorPhotoEnter);
                    authorImg.addEventListener('mouseleave', handleAuthorPhotoLeave);
                }
            }
            function handleClick(event) {
                if (isScriptPaused) return;
                if (event.button !== 0) return;
                const msgElement = event.target.closest('yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer');
                if (!msgElement) return;
                const authorName = msgElement.querySelector('#author-name') || msgElement.querySelector('.author-name');
                const authorImg = msgElement.querySelector('#author-photo img') || msgElement.querySelector('.author-photo img');
                if (authorImg && authorImg.contains(event.target)) {
                    event.stopPropagation(); event.preventDefault();
                    if (event.ctrlKey) {
                        const URL = authorName?.parentNode?.parentNode?.parentNode?.data?.authorExternalChannelId || authorName?.parentNode?.parentNode?.parentNode?.parentNode?.parentNode?.parentNode?.parentNode?.parentNode?.data?.authorExternalChannelId;
                        URL && window.open("https://www.youtube.com/channel/" + URL + "/about", "_blank");
                    } else {
                        const userName = getMessageHandle(msgElement) || normalizeUserName(authorName.textContent.trim());
                        createColorMenu({ type: localEphemeralMode ? 'temp' : 'user', name: userName }, event);
                    }
                    return;
                }
                if (authorName && authorName.contains(event.target)) {
                    event.stopPropagation(); event.preventDefault();
                    const inputField = document.querySelector('yt-live-chat-text-message-renderer #text, .super-fast-chat-message #text, yt-live-chat-text-input-field-renderer [contenteditable]');
                    if (inputField) {
                        setTimeout(() => {
                            const userName = getMessageHandle(msgElement) || normalizeUserName(authorName.textContent.trim());
                            const currentText = inputField.textContent || inputField.innerText || '';
                            if (!currentText.includes('@' + userName)) {
                                const mentionText = `@${userName}\u2009`;
                                const range = document.createRange(); const selection = window.getSelection();
                                range.selectNodeContents(inputField); range.collapse(false); selection.removeAllRanges(); selection.addRange(range);
                                inputField.focus(); document.execCommand('insertText', false, mentionText);
                                range.setStartAfter(inputField.lastChild); range.collapse(true); selection.removeAllRanges(); selection.addRange(range);
                            }
                        }, 200);
                    }
                    return;
                }
            }
            function handleAuthorPhotoHover(event) {
                if (isScriptPaused) return;
                const imgElement = event.target;
                const msgElement = imgElement.closest('yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer');
                if (!msgElement) return;
                const authorNameElement = msgElement.querySelector('#author-name') || msgElement.querySelector('.author-name');
                if (!authorNameElement) return;
                const userName = getMessageHandle(msgElement) || normalizeUserName(authorNameElement.textContent.trim());
                const userSpamEntries = [];
                for (const [key, val] of spamCache.entries()) {
                    if (typeof val === 'object' && key.startsWith(`${userName}:`)) userSpamEntries.push(key.substring(userName.length + 1));
                }
                if (userSpamEntries.length === 0) return;
                if (currentTooltip) { document.body.removeChild(currentTooltip); currentTooltip = null; }
                const tooltip = document.createElement('div'); tooltip.className = 'ytcm-spam-tooltip'; tooltip.style.position = 'absolute'; tooltip.style.top = `${imgElement.getBoundingClientRect().top}px`; tooltip.style.left = `${imgElement.getBoundingClientRect().right + 10}px`;
                const title = document.createElement('div'); title.textContent = `${userName}:`; title.style.fontWeight = 'bold'; title.style.marginBottom = '4px'; tooltip.appendChild(title);
                const messagesDiv = document.createElement('div');
                userSpamEntries.reverse().forEach(text => { const msgSpan = document.createElement('div'); msgSpan.textContent = text; msgSpan.style.borderBottom = '1px solid #ccc'; msgSpan.style.paddingBottom = '2px'; msgSpan.style.marginBottom = '2px'; messagesDiv.appendChild(msgSpan); });
                tooltip.appendChild(messagesDiv);
                let isMouseOverTooltip = false, isMouseOverImg = true, isMouseOverExtendedArea = false, tooltipTimeoutId = null;
                const resetTooltipTimeout = () => { clearTimeout(tooltipTimeoutId); tooltipTimeoutId = setTimeout(() => { if (!isMouseOverTooltip && !isMouseOverImg && !isMouseOverExtendedArea) { if (document.body.contains(tooltip)) document.body.removeChild(tooltip); currentTooltip = null; } }, 100); };
                const handleMouseOverTooltip = () => { isMouseOverTooltip = true; };
                const handleMouseOutTooltip = () => { isMouseOverTooltip = false; resetTooltipTimeout(); };
                const handleMouseOverImg = () => { isMouseOverImg = true; };
                const handleMouseOutImg = () => { isMouseOverImg = false; resetTooltipTimeout(); };
                const handleMouseEnterExtendedArea = () => { isMouseOverExtendedArea = true; };
                const handleMouseLeaveExtendedArea = () => { isMouseOverExtendedArea = false; resetTooltipTimeout(); };
                tooltip.addEventListener('mouseenter', handleMouseOverTooltip); tooltip.addEventListener('mouseleave', handleMouseOutTooltip);
                imgElement.addEventListener('mouseenter', handleMouseOverImg); imgElement.addEventListener('mouseleave', handleMouseOutImg);
                const handleMouseMoveExtendedArea = (e) => {
                    const imgRect = imgElement.getBoundingClientRect(); const tooltipRect = tooltip.getBoundingClientRect();
                    const extendedAreaLeft = imgRect.right; const extendedAreaRight = tooltipRect.left;
                    const extendedAreaTop = Math.min(imgRect.top, tooltipRect.top); const extendedAreaBottom = Math.max(imgRect.bottom, tooltipRect.bottom);
                    const isWithinExtendedArea = localEphemeralMode && e.clientX >= extendedAreaLeft && e.clientX <= extendedAreaRight && e.clientY >= extendedAreaTop && e.clientY <= extendedAreaBottom;
                    if (isWithinExtendedArea) { if (!isMouseOverExtendedArea) { handleMouseEnterExtendedArea(); document.addEventListener('mouseleave', handleMouseLeaveExtendedArea, { once: true }); } } else { if (isMouseOverExtendedArea) handleMouseLeaveExtendedArea(); }
                };
                document.addEventListener('mousemove', handleMouseMoveExtendedArea); registerCleanup(() => { document.removeEventListener('mousemove', handleMouseMoveExtendedArea); });
                resetTooltipTimeout(); document.body.appendChild(tooltip); currentTooltip = tooltip;
                const _ = tooltip.offsetHeight; const tooltipRect = tooltip.getBoundingClientRect();
                const viewportHeight = window.innerHeight; const viewportWidth = window.innerWidth;
                let adjustedTop = tooltipRect.top; let adjustedLeft = tooltipRect.left;
                if (tooltipRect.bottom > viewportHeight) adjustedTop = viewportHeight - tooltipRect.height - 10;
                if (adjustedTop < 0) adjustedTop = 10;
                if (tooltipRect.right > viewportWidth) adjustedLeft = viewportWidth - tooltipRect.width - 10;
                if (adjustedLeft < 0) adjustedLeft = 10;
                tooltip.style.top = `${adjustedTop}px`; tooltip.style.left = `${adjustedLeft}px`;
                const observer = new MutationObserver(() => {
                    const updatedMsgElement = imgElement.closest('yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer');
                    if (!updatedMsgElement || updatedMsgElement.getAttribute('data-block-mode') === 'hide') { if (document.body.contains(tooltip)) document.body.removeChild(tooltip); currentTooltip = null; observer.disconnect(); }
                });
                observer.observe(document.body, { childList: true, subtree: true }); registerCleanup(() => { observer.disconnect(); });
            }
            function handleAuthorPhotoEnter(event) { if (isScriptPaused) return; if (authorHoverTimer) clearTimeout(authorHoverTimer); authorHoverTimer = setTimeout(() => { handleAuthorPhotoHover(event); }, 100); }
            function handleAuthorPhotoLeave(event) { if (authorHoverTimer) clearTimeout(authorHoverTimer); }
            function bindChatMouseEvents() {
                const chatContainer = document.querySelector('yt-live-chat-renderer, #chat');
                if (!chatContainer) return;
                chatContainer.addEventListener('mouseenter', () => { isMouseInChat = true; });
                chatContainer.addEventListener('mouseleave', () => { isMouseInChat = false; });
            }
            function bindInteractionReset() {
                const resetFn = () => { if (autoResumeDeviationStartTime > 0) resetAutoResumeTimer(); };
                const scrollHandler = (e) => {
                    if (e.target.closest('#chat, yt-live-chat-item-list-renderer')) resetFn();
                };
                const clickHandler = (e) => {
                    if (e.target.closest('#chat, yt-live-chat-renderer')) resetFn();
                };
                const keydownHandler = (e) => {
                    if (e.target.closest('yt-live-chat-text-input-field-renderer')) resetFn();
                };
                window.addEventListener('scroll', scrollHandler, { capture: true, passive: true });
                document.addEventListener('click', clickHandler);
                document.addEventListener('keydown', keydownHandler);
                registerCleanup(() => {
                    window.removeEventListener('scroll', scrollHandler, { capture: true });
                    document.removeEventListener('click', clickHandler);
                    document.removeEventListener('keydown', keydownHandler);
                });
            }
            function init() {
                document.querySelectorAll('yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer').forEach(msg => {
                    attachMessageListeners(msg);
                    msg.setAttribute('data-ytcm-handled', 'true');
                    if (!processedMessages.has(msg)) { processMessage(msg, true); }
                });
                const observer = new MutationObserver(mutations => {
                    highlightMessages(mutations);
                    document.querySelectorAll('yt-live-chat-text-message-renderer:not([data-ytcm-handled]), .super-fast-chat-message:not([data-ytcm-handled]), yt-live-chat-paid-message-renderer:not([data-ytcm-handled]), yt-live-chat-membership-item-renderer:not([data-ytcm-handled])').forEach(msg => {
                        msg.setAttribute('data-ytcm-handled', 'true');
                        attachMessageListeners(msg);
                        if (!processedMessages.has(msg)) { processMessage(msg, true); }
                    });
                });
                const chatContainer = document.querySelector('#chat');
                if (chatContainer) {
                    observer.observe(chatContainer, { childList: true, subtree: true });
                    const existingMessages = Array.from(chatContainer.querySelectorAll('yt-live-chat-text-message-renderer, .super-fast-chat-message, yt-live-chat-paid-message-renderer, yt-live-chat-membership-item-renderer'));
                    existingMessages.forEach(msg => {
                        msg.setAttribute('data-ytcm-handled', 'true');
                        attachMessageListeners(msg);
                        if (!processedMessages.has(msg)) { processMessage(msg, true); }
                    });
                }
                registerCleanup(() => { observer.disconnect(); });
                bindChatMouseEvents();
                bindInteractionReset();
                const controlPanel = createControlPanel();
                ensureStatsDisplay();
                return () => {
                    cleanupTasks.forEach(fn => { try { fn(); } catch (e) { console.warn('Cleanup task error:', e); } });
                    cleanupTasks.length = 0; if (controlPanel) controlPanel.remove(); closeMenu();
                    if (statsBox && statsBox.parentNode) statsBox.parentNode.removeChild(statsBox);
                };
            }
            let cleanup = null;
            function main() { if (window.location.pathname.includes('/live_chat')) cleanup = init(); }
            main();
            window.addEventListener('beforeunload', () => { cleanup?.(); });
           })();