// ==UserScript== // @name YouTube 聊天室管理 // @namespace http://tampermonkey.net/ // @version 12.8 // @description 16種預設可選色彩用以自動著色指定用戶訊息,其它功能包含封鎖用戶、簡化編輯儲存在瀏覽器的用戶清單、移除聊天室置頂消息,清理重複消息,添加功能切換開關。 // @match *://www.youtube.com/live_chat* // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; // 常數定義 const COLOR_OPTIONS = { "淺藍": "#ADD8E6", "深藍": "#0000FF", "淺綠": "#98FB98", "綠色": "#008000", "淺紅": "#F08080", "紅色": "#FF0000", "紫色": "#800080", "金色": "#FFD700", "粉紅": "#FFC0CB", "橙色": "#FFA500", "青色": "#00FFFF", "深綠": "#006400", "深紅": "#8B0000", "深紫": "#9400D3", "銀色": "#C0C0C0", "棕色": "#A52A2A" }; const MENU_AUTO_CLOSE_DELAY = 8000; const DUPLICATE_HIGHLIGHT_INTERVAL = 10000; const THROTTLE_DELAY = 150; const TEMP_USER_EXPIRE_TIME = 60000; // 1分鐘 = 60000毫秒 const MAX_MESSAGE_CACHE_SIZE = 200; const CLEANUP_INTERVAL = 30000; // 每30秒清理一次 // 高亮模式定義 const HIGHLIGHT_MODES = { BOTH: 0, NAME_ONLY: 1, MESSAGE_ONLY: 2 }; // 初始化設定 let userColorSettings = loadSettings('userColorSettings', {}); let keywordColorSettings = loadSettings('keywordColorSettings', {}); let blockedUsers = loadSettings('blockedUsers', []); let currentMenu = null; let menuTimeoutId = null; let lastDuplicateHighlightTime = 0; // 功能開關設定 let featureSettings = loadSettings('featureSettings', { pinEnabled: false, duplicateEnabled: true, highlightEnabled: true, blockEnabled: true, buttonsVisible: true, mentionHighlightEnabled: true }); // 高亮模式設定 let highlightSettings = loadSettings('highlightSettings', { defaultMode: HIGHLIGHT_MODES.BOTH, tempMode: HIGHLIGHT_MODES.BOTH }); // 新增臨時用戶清單 let tempUsers = loadSettings('tempUsers', {}); let lastTempUserCleanupTime = Date.now(); // 使用 Map 來管理快取 const userColorCache = new Map(Object.entries(userColorSettings)); const keywordColorCache = new Map(Object.entries(keywordColorSettings)); const blockedUsersSet = new Set(blockedUsers); const tempUserCache = new Map(Object.entries(tempUsers)); const processedMessages = new Set(); // 預先注入 CSS 樣式 const style = document.createElement('style'); style.textContent = ` .ytcm-menu { position: fixed; background-color: white; border: 1px solid black; padding: 5px; z-index: 9999; box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); border-radius: 5px; } .ytcm-color-item { cursor: pointer; padding: 5px; text-align: center; border-radius: 3px; margin: 2px; } .ytcm-list-item { cursor: pointer; padding: 5px; background-color: #f0f0f0; border-radius: 3px; margin: 2px; } .ytcm-button { cursor: pointer; padding: 5px 8px; margin: 5px 2px 0 2px; border-radius: 3px; border: 1px solid #ccc; background-color: #f8f8f8; } .ytcm-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; } .ytcm-button-row { display: flex; justify-content: space-between; margin-top: 5px; } .ytcm-flex-wrap { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; } .ytcm-control-panel { position: fixed; left: 0; bottom: 75px; z-index: 9998; display: flex; flex-direction: column; gap: 8px; padding: 0; } .ytcm-control-btn { padding: 5px 0 5px 5px; cursor: pointer; text-align: left; min-width: 40px; font-size: 14px; font-weight: bold; color: white; -webkit-text-stroke: 1px black; text-shadow: none; background: none; border: none; margin: 0; } .ytcm-control-btn.active { -webkit-text-stroke: 1px black; } .ytcm-control-btn.inactive { -webkit-text-stroke: 1px red; } .ytcm-toggle-btn { padding: 5px 0 5px 5px; cursor: pointer; text-align: left; min-width: 40px; font-size: 14px; font-weight: bold; color: white; -webkit-text-stroke: 1px black; text-shadow: none; background: none; border: none; margin: 0; } .ytcm-main-buttons { display: ${featureSettings.buttonsVisible ? 'flex' : 'none'}; flex-direction: column; gap: 8px; } .ytcm-duplicate-message { opacity: 0.5; transition: opacity 0.3s ease; } #author-name.ytcm-highlight { font-weight: bold; } #message.ytcm-highlight { font-weight: bold; } `; document.head.appendChild(style); // 創建控制面板 function createControlPanel() { const panel = document.createElement('div'); panel.className = 'ytcm-control-panel'; // 主按鈕容器 const mainButtons = document.createElement('div'); mainButtons.className = 'ytcm-main-buttons'; // 頂 - 清除置頂開關 const pinBtn = document.createElement('div'); pinBtn.className = `ytcm-control-btn ${featureSettings.pinEnabled ? 'active' : 'inactive'}`; pinBtn.textContent = '頂'; pinBtn.title = '切換清除置頂功能'; pinBtn.addEventListener('click', () => { featureSettings.pinEnabled = !featureSettings.pinEnabled; pinBtn.className = `ytcm-control-btn ${featureSettings.pinEnabled ? 'active' : 'inactive'}`; saveSettings('featureSettings', featureSettings); }); // 複 - 過濾重複發言開關 const duplicateBtn = document.createElement('div'); duplicateBtn.className = `ytcm-control-btn ${featureSettings.duplicateEnabled ? 'active' : 'inactive'}`; duplicateBtn.textContent = '複'; duplicateBtn.title = '切換過濾重複發言功能'; duplicateBtn.addEventListener('click', () => { featureSettings.duplicateEnabled = !featureSettings.duplicateEnabled; duplicateBtn.className = `ytcm-control-btn ${featureSettings.duplicateEnabled ? 'active' : 'inactive'}`; saveSettings('featureSettings', featureSettings); }); // 亮 - 高亮文字開關 const highlightBtn = document.createElement('div'); highlightBtn.className = `ytcm-control-btn ${featureSettings.highlightEnabled ? 'active' : 'inactive'}`; highlightBtn.textContent = '亮'; highlightBtn.title = getHighlightModeTooltip(highlightSettings.defaultMode); highlightBtn.addEventListener('click', (e) => { if (e.ctrlKey) { // Ctrl+左鍵點擊: 切換預設高亮模式 highlightSettings.defaultMode = (highlightSettings.defaultMode + 1) % 3; highlightBtn.title = getHighlightModeTooltip(highlightSettings.defaultMode); saveSettings('highlightSettings', highlightSettings); // 清除處理記錄以重新處理現有消息 processedMessages.clear(); const messages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer')).slice(-MAX_MESSAGE_CACHE_SIZE); messages.forEach(msg => processedMessages.delete(msg)); } else { // 普通點擊: 切換高亮功能開關 featureSettings.highlightEnabled = !featureSettings.highlightEnabled; highlightBtn.className = `ytcm-control-btn ${featureSettings.highlightEnabled ? 'active' : 'inactive'}`; saveSettings('featureSettings', featureSettings); } }); // 封 - 清理封鎖用戶開關 const blockBtn = document.createElement('div'); blockBtn.className = `ytcm-control-btn ${featureSettings.blockEnabled ? 'active' : 'inactive'}`; blockBtn.textContent = '封'; blockBtn.title = '切換清理封鎖用戶功能'; blockBtn.addEventListener('click', () => { featureSettings.blockEnabled = !featureSettings.blockEnabled; blockBtn.className = `ytcm-control-btn ${featureSettings.blockEnabled ? 'active' : 'inactive'}`; saveSettings('featureSettings', featureSettings); }); // @ - @高亮功能開關 const mentionBtn = document.createElement('div'); mentionBtn.className = `ytcm-control-btn ${featureSettings.mentionHighlightEnabled ? 'active' : 'inactive'}`; mentionBtn.textContent = '@'; mentionBtn.title = getHighlightModeTooltip(highlightSettings.tempMode); mentionBtn.addEventListener('click', (e) => { if (e.ctrlKey) { // Ctrl+左鍵點擊: 切換臨時高亮模式 highlightSettings.tempMode = (highlightSettings.tempMode + 1) % 3; mentionBtn.title = getHighlightModeTooltip(highlightSettings.tempMode); saveSettings('highlightSettings', highlightSettings); // 清除處理記錄以重新處理現有消息 processedMessages.clear(); const messages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer')).slice(-MAX_MESSAGE_CACHE_SIZE); messages.forEach(msg => processedMessages.delete(msg)); } else { // 普通點擊: 切換@高亮功能開關 featureSettings.mentionHighlightEnabled = !featureSettings.mentionHighlightEnabled; mentionBtn.className = `ytcm-control-btn ${featureSettings.mentionHighlightEnabled ? 'active' : 'inactive'}`; saveSettings('featureSettings', featureSettings); } }); // 將五個主按鈕添加到容器 mainButtons.appendChild(pinBtn); mainButtons.appendChild(duplicateBtn); mainButtons.appendChild(highlightBtn); mainButtons.appendChild(blockBtn); mainButtons.appendChild(mentionBtn); // ☑ - 切換按鈕顯示 const toggleBtn = document.createElement('div'); toggleBtn.className = 'ytcm-toggle-btn'; toggleBtn.textContent = '☑'; toggleBtn.title = '顯示/隱藏控制按鈕'; toggleBtn.addEventListener('click', () => { featureSettings.buttonsVisible = !featureSettings.buttonsVisible; mainButtons.style.display = featureSettings.buttonsVisible ? 'flex' : 'none'; saveSettings('featureSettings', featureSettings); }); // 將所有元素添加到面板 panel.appendChild(mainButtons); panel.appendChild(toggleBtn); document.body.appendChild(panel); return panel; } // 獲取高亮模式提示文字 function getHighlightModeTooltip(mode) { switch (mode) { case HIGHLIGHT_MODES.BOTH: return "當前模式: 高亮暱稱和對話 (Ctrl+左鍵切換)"; case HIGHLIGHT_MODES.NAME_ONLY: return "當前模式: 只高亮暱稱 (Ctrl+左鍵切換)"; case HIGHLIGHT_MODES.MESSAGE_ONLY: return "當前模式: 只高亮對話 (Ctrl+左鍵切換)"; default: return "高亮模式"; } } // 節流函數 function throttle(func, limit) { let lastFunc; let lastRan; return function() { const context = this; const args = arguments; if (!lastRan) { func.apply(context, args); lastRan = Date.now(); } else { clearTimeout(lastFunc); lastFunc = setTimeout(function() { if ((Date.now() - lastRan) >= limit) { func.apply(context, args); lastRan = Date.now(); } }, limit - (Date.now() - lastRan)); } }; } // 加載設定 function loadSettings(key, defaultValue) { try { const value = JSON.parse(localStorage.getItem(key)); return value !== null ? value : defaultValue; } catch (error) { console.error(`Failed to load ${key}:`, error); try { localStorage.removeItem(key); // 嘗試清除損壞的數據 } catch (e) { console.error('Failed to remove corrupted data:', e); } return defaultValue; } } // 保存設定 (批次處理) let settingsSaveQueue = {}; function saveSettings(key, value) { settingsSaveQueue[key] = value; if (!window.settingsSaveTimeout) { window.settingsSaveTimeout = setTimeout(() => { try { Object.keys(settingsSaveQueue).forEach(k => { try { localStorage.setItem(k, JSON.stringify(settingsSaveQueue[k])); } catch (error) { console.error(`Failed to save ${k}:`, error); // 嘗試清理空間 if (error.name === 'QuotaExceededError') { try { localStorage.clear(); localStorage.setItem(k, JSON.stringify(settingsSaveQueue[k])); } catch (e) { console.error('Failed after clearing storage:', e); } } } }); settingsSaveQueue = {}; } catch (error) { console.error('Batch save failed:', error); } window.settingsSaveTimeout = null; }, 1000); } } // 清理已不存在的消息 function cleanupProcessedMessages() { const allMessages = new Set(document.querySelectorAll('yt-live-chat-text-message-renderer')); for (const msg of processedMessages) { if (!allMessages.has(msg)) { processedMessages.delete(msg); } } } // 處理@提及的用戶 (改進版) function processMentionedUsers(messageText, authorName, authorColor) { if (!featureSettings.mentionHighlightEnabled || !authorColor) return; // 改進的@匹配邏輯,支持包含空格的用戶名 const mentionRegex = /@([^\s].*?(?=\s|$|@|[\u200b]))/g; let match; const mentionedUsers = new Set(); // 收集所有被提及的用戶 while ((match = mentionRegex.exec(messageText)) !== null) { const mentionedUser = match[1].trim(); if (mentionedUser) { mentionedUsers.add(mentionedUser); } } // 檢查每個被提及的用戶是否在現有用戶列表中 const allUsers = Array.from(document.querySelectorAll('#author-name')); const existingUsers = allUsers.map(el => el.textContent.trim()); mentionedUsers.forEach(mentionedUser => { // 檢查是否是完全匹配的現有用戶 const isExistingUser = existingUsers.some(user => user.toLowerCase() === mentionedUser.toLowerCase() ); if (isExistingUser && !userColorCache.has(mentionedUser) && !tempUserCache.has(mentionedUser)) { tempUsers[mentionedUser] = { color: authorColor, expireTime: Date.now() + TEMP_USER_EXPIRE_TIME }; tempUserCache.set(mentionedUser, { color: authorColor, expireTime: Date.now() + TEMP_USER_EXPIRE_TIME }); // 立即處理現有消息 const messages = document.querySelectorAll('yt-live-chat-text-message-renderer'); messages.forEach(msg => { const nameElement = msg.querySelector('#author-name'); if (nameElement && nameElement.textContent.trim() === mentionedUser) { processedMessages.delete(msg); // 允許重新處理這條消息 } }); } }); // 保存臨時用戶清單 if (mentionedUsers.size > 0) { saveSettings('tempUsers', tempUsers); } } // 清理過期的臨時用戶 function cleanupExpiredTempUsers() { const now = Date.now(); if (now - lastTempUserCleanupTime < CLEANUP_INTERVAL) return; lastTempUserCleanupTime = now; let changed = false; for (const [user, data] of tempUserCache.entries()) { if (data.expireTime <= now) { tempUserCache.delete(user); delete tempUsers[user]; changed = true; // 清除相關消息的處理狀態 const messages = document.querySelectorAll('yt-live-chat-text-message-renderer'); messages.forEach(msg => { const nameElement = msg.querySelector('#author-name'); if (nameElement && nameElement.textContent.trim() === user) { processedMessages.delete(msg); } }); } } if (changed) { saveSettings('tempUsers', tempUsers); } } // 高亮訊息 function highlightMessages(mutations) { if (!featureSettings.highlightEnabled) return; cleanupProcessedMessages(); // 清理已不存在的消息 const messages = []; mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1 && node.matches('yt-live-chat-text-message-renderer') && !processedMessages.has(node)) { messages.push(node); processedMessages.add(node); } }); }); // 如果沒有新增消息,檢查最近的消息 if (messages.length === 0) { const allMessages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer')).slice(-MAX_MESSAGE_CACHE_SIZE); allMessages.forEach(msg => { if (!processedMessages.has(msg)) { messages.push(msg); processedMessages.add(msg); } }); } messages.forEach(msg => { const authorName = msg.querySelector('#author-name'); const messageElement = msg.querySelector('#message'); if (!authorName || !messageElement) return; const userName = authorName.textContent.trim(); const messageText = messageElement.textContent.trim(); // 重置樣式 authorName.style.color = ''; authorName.classList.remove('ytcm-highlight'); messageElement.style.color = ''; messageElement.classList.remove('ytcm-highlight'); // 檢查是否在臨時用戶清單中 if (tempUserCache.has(userName)) { const color = tempUserCache.get(userName).color; applyHighlightMode(msg, color, highlightSettings.tempMode); return; } // 檢查是否在用戶顏色清單中 if (userColorCache.has(userName)) { const color = userColorCache.get(userName); applyHighlightMode(msg, color, highlightSettings.defaultMode); // 處理@提及的用戶 if (featureSettings.mentionHighlightEnabled) { processMentionedUsers(messageText, userName, color); } return; } // 檢查關鍵字 for (const [keyword, color] of keywordColorCache) { if (messageText.includes(keyword)) { applyHighlightMode(msg, color, highlightSettings.defaultMode); break; } } }); // 清理過期的臨時用戶 cleanupExpiredTempUsers(); } // 根據高亮模式應用樣式 function applyHighlightMode(msg, color, mode) { const authorName = msg.querySelector('#author-name'); const messageElement = msg.querySelector('#message'); // 先移除所有樣式 authorName.style.color = ''; authorName.classList.remove('ytcm-highlight'); messageElement.style.color = ''; messageElement.classList.remove('ytcm-highlight'); switch (mode) { case HIGHLIGHT_MODES.BOTH: authorName.style.color = color; authorName.classList.add('ytcm-highlight'); messageElement.style.color = color; messageElement.classList.add('ytcm-highlight'); break; case HIGHLIGHT_MODES.NAME_ONLY: authorName.style.color = color; authorName.classList.add('ytcm-highlight'); break; case HIGHLIGHT_MODES.MESSAGE_ONLY: messageElement.style.color = color; messageElement.classList.add('ytcm-highlight'); break; } } // 標記重複訊息 (改為淡化處理) function markDuplicateMessages() { if (!featureSettings.duplicateEnabled) return; const currentTime = Date.now(); if (currentTime - lastDuplicateHighlightTime < DUPLICATE_HIGHLIGHT_INTERVAL) return; lastDuplicateHighlightTime = currentTime; const messages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer')).slice(-MAX_MESSAGE_CACHE_SIZE); const messageMap = new Map(); messages.forEach(msg => { const authorName = msg.querySelector('#author-name'); const messageElement = msg.querySelector('#message'); if (!authorName || !messageElement) return; const userName = authorName.textContent.trim(); const messageText = messageElement.textContent.trim(); const key = `${userName}:${messageText}`; if (messageMap.has(key)) { messageElement.classList.add('ytcm-duplicate-message'); } else { messageMap.set(key, msg); messageElement.classList.remove('ytcm-duplicate-message'); } }); } // 處理封鎖用戶 function handleBlockedUsers(mutations) { if (!featureSettings.blockEnabled) return; cleanupProcessedMessages(); // 清理已不存在的消息 const messages = []; mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1 && node.matches('yt-live-chat-text-message-renderer') && !processedMessages.has(node)) { messages.push(node); processedMessages.add(node); } }); }); messages.forEach(msg => { const authorName = msg.querySelector('#author-name'); if (!authorName) return; const userName = authorName.textContent.trim(); if (blockedUsersSet.has(userName)) { const messageElement = msg.querySelector('#message'); if (messageElement) { messageElement.textContent = ''; } } }); } // 移除置頂訊息 function removePinnedMessage() { if (!featureSettings.pinEnabled) return; const pinnedMessage = document.querySelector('yt-live-chat-banner-renderer'); if (pinnedMessage) { pinnedMessage.style.display = 'none'; } } // 關閉選單函數 function closeMenu() { if (currentMenu) { document.body.removeChild(currentMenu); currentMenu = null; clearTimeout(menuTimeoutId); } } // 創建顏色選單 function createColorMenu(targetElement, event) { closeMenu(); const menu = document.createElement('div'); menu.className = 'ytcm-menu'; menu.style.top = `${event.clientY}px`; menu.style.left = `${event.clientX}px`; menu.style.width = '220px'; const colorGrid = document.createElement('div'); colorGrid.className = 'ytcm-grid'; Object.entries(COLOR_OPTIONS).forEach(([colorName, colorValue]) => { const colorItem = document.createElement('div'); colorItem.className = 'ytcm-color-item'; colorItem.textContent = colorName; colorItem.style.backgroundColor = colorValue; colorItem.addEventListener('click', () => { if (targetElement.type === 'user') { userColorSettings[targetElement.name] = colorValue; userColorCache.set(targetElement.name, colorValue); // 立即處理現有消息 const messages = document.querySelectorAll('yt-live-chat-text-message-renderer'); messages.forEach(msg => { const nameElement = msg.querySelector('#author-name'); if (nameElement && nameElement.textContent.trim() === targetElement.name) { processedMessages.delete(msg); // 允許重新處理這條消息 } }); } else if (targetElement.type === 'keyword') { keywordColorSettings[targetElement.keyword] = colorValue; keywordColorCache.set(targetElement.keyword, colorValue); } saveSettings('userColorSettings', userColorSettings); saveSettings('keywordColorSettings', keywordColorSettings); closeMenu(); }); colorGrid.appendChild(colorItem); }); const buttonRow = document.createElement('div'); buttonRow.className = 'ytcm-button-row'; const blockButton = document.createElement('button'); blockButton.className = 'ytcm-button'; blockButton.textContent = '封鎖'; blockButton.addEventListener('click', () => { if (targetElement.type === 'user') { blockedUsers.push(targetElement.name); blockedUsersSet.add(targetElement.name); saveSettings('blockedUsers', blockedUsers); } closeMenu(); }); const editButton = document.createElement('button'); editButton.className = 'ytcm-button'; editButton.textContent = '編輯'; editButton.addEventListener('click', (e) => { e.stopPropagation(); createEditMenu(event); }); const deleteButton = document.createElement('button'); deleteButton.className = 'ytcm-button'; deleteButton.textContent = '刪除'; deleteButton.addEventListener('click', () => { if (targetElement.type === 'user' && userColorSettings[targetElement.name]) { delete userColorSettings[targetElement.name]; userColorCache.delete(targetElement.name); saveSettings('userColorSettings', userColorSettings); } closeMenu(); }); buttonRow.appendChild(blockButton); buttonRow.appendChild(editButton); buttonRow.appendChild(deleteButton); menu.appendChild(colorGrid); menu.appendChild(buttonRow); document.body.appendChild(menu); currentMenu = menu; menuTimeoutId = setTimeout(closeMenu, MENU_AUTO_CLOSE_DELAY); } // 創建編輯選單 function createEditMenu(event) { closeMenu(); const menu = document.createElement('div'); menu.className = 'ytcm-menu'; menu.style.top = `${event.clientY}px`; menu.style.left = `${event.clientX}px`; menu.style.maxWidth = '600px'; const closeButton = document.createElement('button'); closeButton.className = 'ytcm-button'; closeButton.textContent = '關閉'; closeButton.style.width = '100%'; closeButton.style.marginBottom = '10px'; closeButton.addEventListener('click', closeMenu); menu.appendChild(closeButton); // 封鎖用戶名單 const blockedUserList = document.createElement('div'); blockedUserList.textContent = '封鎖用戶名單:'; blockedUserList.className = 'ytcm-flex-wrap'; blockedUsers.forEach(user => { const userItem = document.createElement('div'); userItem.className = 'ytcm-list-item'; userItem.textContent = user; userItem.addEventListener('click', () => { blockedUsers = blockedUsers.filter(u => u !== user); blockedUsersSet.delete(user); saveSettings('blockedUsers', blockedUsers); userItem.remove(); }); blockedUserList.appendChild(userItem); }); menu.appendChild(blockedUserList); // 關鍵字名單 const keywordList = document.createElement('div'); keywordList.textContent = '關鍵字名單:'; keywordList.className = 'ytcm-flex-wrap'; Object.keys(keywordColorSettings).forEach(keyword => { const keywordItem = document.createElement('div'); keywordItem.className = 'ytcm-list-item'; keywordItem.textContent = keyword; keywordItem.addEventListener('click', () => { delete keywordColorSettings[keyword]; keywordColorCache.delete(keyword); saveSettings('keywordColorSettings', keywordColorSettings); keywordItem.remove(); }); keywordList.appendChild(keywordItem); }); menu.appendChild(keywordList); // 被上色用戶名單 const coloredUserList = document.createElement('div'); coloredUserList.textContent = '被上色用戶名單:'; coloredUserList.className = 'ytcm-flex-wrap'; Object.keys(userColorSettings).forEach(user => { const userItem = document.createElement('div'); userItem.className = 'ytcm-list-item'; userItem.textContent = user; userItem.addEventListener('click', () => { delete userColorSettings[user]; userColorCache.delete(user); saveSettings('userColorSettings', userColorSettings); userItem.remove(); }); coloredUserList.appendChild(userItem); }); menu.appendChild(coloredUserList); document.body.appendChild(menu); currentMenu = menu; menuTimeoutId = setTimeout(closeMenu, MENU_AUTO_CLOSE_DELAY); } // 點擊事件處理 function handleClick(event) { if (currentMenu && !currentMenu.contains(event.target)) { closeMenu(); } if (event.target.id === 'author-name') { const userName = event.target.textContent.trim(); createColorMenu({ type: 'user', name: userName }, event); } else { const selectedText = window.getSelection().toString(); if (selectedText) { createColorMenu({ type: 'keyword', keyword: selectedText }, event); } } } // 初始化函數 function init() { // 清理舊的事件監聽器 document.removeEventListener('click', handleClick); // 添加新的事件監聽器 document.addEventListener('click', handleClick); // 創建控制面板 const controlPanel = createControlPanel(); // 初始化 MutationObserver const observer = new MutationObserver(throttle((mutations) => { highlightMessages(mutations); markDuplicateMessages(); handleBlockedUsers(mutations); removePinnedMessage(); }, THROTTLE_DELAY)); const chatContainer = document.querySelector('#chat'); if (chatContainer) { observer.observe(chatContainer, { childList: true, subtree: true }); } // 設置定期清理任務 const cleanupIntervalId = setInterval(() => { cleanupProcessedMessages(); cleanupExpiredTempUsers(); }, CLEANUP_INTERVAL); // 返回清理函數 return () => { observer.disconnect(); document.removeEventListener('click', handleClick); clearInterval(cleanupIntervalId); if (controlPanel && controlPanel.parentNode) { controlPanel.parentNode.removeChild(controlPanel); } if (currentMenu && currentMenu.parentNode) { currentMenu.parentNode.removeChild(currentMenu); } clearTimeout(menuTimeoutId); }; } // 啟動腳本 let cleanup = init(); // 如果頁面是動態加載的,可能需要重新初始化 const checkChatContainer = setInterval(() => { if (document.querySelector('#chat') && !cleanup) { cleanup = init(); } }, 1000); // 提供清理方法以防需要重新加載 window.addEventListener('beforeunload', () => { clearInterval(checkChatContainer); if (cleanup) cleanup(); }); })();