您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
洗版模式切換版
当前为
// ==UserScript== // @name YouTube 聊天室管理 // @namespace http://tampermonkey.net/ // @version 12.33 // @description 洗版模式切換版 // @match *://www.youtube.com/live_chat* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; const COLOR_OPTIONS = { "淺藍": "#ADD8E6", "深藍": "#0000FF", "淺綠": "#98FB98", "綠色": "#008000", "淺紅": "#F08080", "紅色": "#FF0000", "紫色": "#800080", "金色": "#FFD700", "粉紅": "#FFC0CB", "橙色": "#FFA500", "青色": "#00FFFF", "深綠": "#006400", "深紅": "#8B0000", "深紫": "#9400D3", "銀色": "#C0C0C0", "棕色": "#A52A2A" }; const MENU_AUTO_CLOSE_DELAY = 8000; const THROTTLE_DELAY = 150; const TEMP_USER_EXPIRE_TIME = 40000; const MAX_MESSAGE_CACHE_SIZE = 200; const CLEANUP_INTERVAL = 40000; const SPAM_CHECK_INTERVAL = 500; const SPAM_TEXT_LENGTH = 10; const HIGHLIGHT_MODES = { BOTH: 0, NAME_ONLY: 1, MESSAGE_ONLY: 2 }; const STYLE_MODES = { BASIC: 0, BOLD: 1, BACKGROUND: 2 }; const SPAM_MODES = { MARK: 0, REMOVE: 1 }; let userColorSettings = JSON.parse(localStorage.getItem('userColorSettings')) || {}; let blockedUsers = JSON.parse(localStorage.getItem('blockedUsers')) || []; let currentMenu = null; let menuTimeoutId = null; let featureSettings = JSON.parse(localStorage.getItem('featureSettings')) || { pinEnabled: false, highlightEnabled: true, blockEnabled: true, buttonsVisible: true, mentionHighlightEnabled: true, spamFilterEnabled: true, counterEnabled: true, spamMode: SPAM_MODES.MARK }; let highlightSettings = JSON.parse(localStorage.getItem('highlightSettings')) || { defaultMode: HIGHLIGHT_MODES.BOTH, tempMode: HIGHLIGHT_MODES.BOTH }; let styleMode = JSON.parse(localStorage.getItem('styleMode')) || STYLE_MODES.BASIC; let tempUsers = JSON.parse(localStorage.getItem('tempUsers')) || {}; let lastTempUserCleanupTime = Date.now(); let messageCache = new Set(); let userMessageCounts = {}; let lastSpamCheckTime = 0; const userColorCache = new Map(); const blockedUsersSet = new Set(blockedUsers); const tempUserCache = new Map(); const processedMessages = new Set(); const styleCache = new WeakMap(); const style = document.createElement('style'); style.textContent = ` .ytcm-menu { position: fixed; background-color: white; border: 1px solid black; padding: 5px; z-index: 9999; box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); border-radius: 5px; } .ytcm-color-item { cursor: pointer; padding: 5px; text-align: center; border-radius: 3px; margin: 2px; } .ytcm-list-item { cursor: pointer; padding: 5px; background-color: #f0f0f0; border-radius: 3px; margin: 2px; } .ytcm-button { cursor: pointer; padding: 5px 8px; margin: 5px 2px 0 2px; border-radius: 3px; border: 1px solid #ccc; background-color: #f8f8f8; } .ytcm-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; } .ytcm-button-row { display: flex; justify-content: space-between; margin-top: 5px; } .ytcm-flex-wrap { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; } .ytcm-control-panel { position: fixed; left: 0; bottom: 75px; z-index: 9998; display: flex; flex-direction: column; gap: 8px; padding: 0; } .ytcm-control-btn { padding: 5px 0 5px 5px; cursor: pointer; text-align: left; min-width: 40px; font-size: 14px; font-weight: bold; color: white; -webkit-text-stroke: 1px black; text-shadow: none; background: none; border: none; margin: 0; } .ytcm-control-btn.active { -webkit-text-stroke: 1px black; } .ytcm-control-btn.inactive { -webkit-text-stroke: 1px red; } .ytcm-toggle-btn { padding: 5px 0 5px 5px; cursor: pointer; text-align: left; min-width: 40px; font-size: 14px; font-weight: bold; color: white; -webkit-text-stroke: 1px black; text-shadow: none; background: none; border: none; margin: 0; } .ytcm-main-buttons { display: ${featureSettings.buttonsVisible ? 'flex' : 'none'}; flex-direction: column; gap: 8px; } .ytcm-bg-highlight { padding: 2px 4px; border-radius: 3px; } .ytcm-message-count { font-size: 0.8em; opacity: 0.7; vertical-align: super; } .ytcm-highlight-name { color: var(--highlight-color) !important; font-weight: var(--name-weight, normal) !important; background-color: var(--name-bg, transparent) !important; } .ytcm-highlight-msg { color: var(--highlight-color) !important; font-weight: var(--msg-weight, normal) !important; background-color: var(--msg-bg, transparent) !important; } .ytcm-bg-mode { color: #000 !important; background-color: var(--highlight-color) !important; } [data-blocked="true"] #message { content: "<封鎖>" !important; } [data-spam="true"] #message { content: "<洗版>" !important; } `; document.head.appendChild(style); function initializeCaches() { Object.entries(userColorSettings).forEach(([user, color]) => userColorCache.set(user, color)); Object.entries(tempUsers).forEach(([user, data]) => tempUserCache.set(user, data)); } function createControlPanel() { const panel = document.createElement('div'); panel.className = 'ytcm-control-panel'; const mainButtons = document.createElement('div'); mainButtons.className = 'ytcm-main-buttons'; const pinBtn = document.createElement('div'); pinBtn.className = `ytcm-control-btn ${featureSettings.pinEnabled ? 'active' : 'inactive'}`; pinBtn.textContent = '頂'; pinBtn.title = '切換清除置頂功能 (Ctrl+左鍵切換樣式模式)'; pinBtn.addEventListener('click', (e) => { if (e.ctrlKey) { styleMode = (styleMode + 1) % 3; localStorage.setItem('styleMode', JSON.stringify(styleMode)); updateStyleMode(); } else { featureSettings.pinEnabled = !featureSettings.pinEnabled; pinBtn.className = `ytcm-control-btn ${featureSettings.pinEnabled ? 'active' : 'inactive'}`; localStorage.setItem('featureSettings', JSON.stringify(featureSettings)); } }); const highlightBtn = document.createElement('div'); highlightBtn.className = `ytcm-control-btn ${featureSettings.highlightEnabled ? 'active' : 'inactive'}`; highlightBtn.textContent = '亮'; highlightBtn.title = getHighlightModeTooltip(highlightSettings.defaultMode); highlightBtn.addEventListener('click', (e) => { if (e.ctrlKey) { highlightSettings.defaultMode = (highlightSettings.defaultMode + 1) % 3; highlightBtn.title = getHighlightModeTooltip(highlightSettings.defaultMode); localStorage.setItem('highlightSettings', JSON.stringify(highlightSettings)); clearProcessedMessages(); } else { featureSettings.highlightEnabled = !featureSettings.highlightEnabled; highlightBtn.className = `ytcm-control-btn ${featureSettings.highlightEnabled ? 'active' : 'inactive'}`; localStorage.setItem('featureSettings', JSON.stringify(featureSettings)); } }); const blockBtn = document.createElement('div'); blockBtn.className = `ytcm-control-btn ${featureSettings.blockEnabled ? 'active' : 'inactive'}`; blockBtn.textContent = '封'; blockBtn.title = '切換清理封鎖用戶功能'; blockBtn.addEventListener('click', () => { featureSettings.blockEnabled = !featureSettings.blockEnabled; blockBtn.className = `ytcm-control-btn ${featureSettings.blockEnabled ? 'active' : 'inactive'}`; localStorage.setItem('featureSettings', JSON.stringify(featureSettings)); clearProcessedMessages(); }); const mentionBtn = document.createElement('div'); mentionBtn.className = `ytcm-control-btn ${featureSettings.mentionHighlightEnabled ? 'active' : 'inactive'}`; mentionBtn.textContent = '@'; mentionBtn.title = getHighlightModeTooltip(highlightSettings.tempMode); mentionBtn.addEventListener('click', (e) => { if (e.ctrlKey) { highlightSettings.tempMode = (highlightSettings.tempMode + 1) % 3; mentionBtn.title = getHighlightModeTooltip(highlightSettings.tempMode); localStorage.setItem('highlightSettings', JSON.stringify(highlightSettings)); clearProcessedMessages(); } else { featureSettings.mentionHighlightEnabled = !featureSettings.mentionHighlightEnabled; mentionBtn.className = `ytcm-control-btn ${featureSettings.mentionHighlightEnabled ? 'active' : 'inactive'}`; localStorage.setItem('featureSettings', JSON.stringify(featureSettings)); if (!featureSettings.mentionHighlightEnabled) { tempUsers = {}; tempUserCache.clear(); localStorage.setItem('tempUsers', JSON.stringify(tempUsers)); } } }); const spamBtn = document.createElement('div'); spamBtn.className = `ytcm-control-btn ${featureSettings.spamFilterEnabled ? 'active' : 'inactive'}`; spamBtn.textContent = '洗'; spamBtn.title = getSpamModeTooltip(featureSettings.spamMode); spamBtn.addEventListener('click', (e) => { if (e.ctrlKey) { featureSettings.spamMode = (featureSettings.spamMode + 1) % 2; spamBtn.title = getSpamModeTooltip(featureSettings.spamMode); localStorage.setItem('featureSettings', JSON.stringify(featureSettings)); } else { featureSettings.spamFilterEnabled = !featureSettings.spamFilterEnabled; spamBtn.className = `ytcm-control-btn ${featureSettings.spamFilterEnabled ? 'active' : 'inactive'}`; localStorage.setItem('featureSettings', JSON.stringify(featureSettings)); if (!featureSettings.spamFilterEnabled) messageCache.clear(); } }); const counterBtn = document.createElement('div'); counterBtn.className = `ytcm-control-btn ${featureSettings.counterEnabled ? 'active' : 'inactive'}`; counterBtn.textContent = '數'; counterBtn.title = '切換留言計數功能'; counterBtn.addEventListener('click', () => { featureSettings.counterEnabled = !featureSettings.counterEnabled; counterBtn.className = `ytcm-control-btn ${featureSettings.counterEnabled ? 'active' : 'inactive'}`; localStorage.setItem('featureSettings', JSON.stringify(featureSettings)); if (!featureSettings.counterEnabled) { document.querySelectorAll('.ytcm-message-count').forEach(el => el.remove()); } }); mainButtons.appendChild(pinBtn); mainButtons.appendChild(highlightBtn); mainButtons.appendChild(blockBtn); mainButtons.appendChild(mentionBtn); mainButtons.appendChild(spamBtn); mainButtons.appendChild(counterBtn); const toggleBtn = document.createElement('div'); toggleBtn.className = 'ytcm-toggle-btn'; toggleBtn.textContent = '☑'; toggleBtn.title = '顯示/隱藏控制按鈕'; toggleBtn.addEventListener('click', () => { featureSettings.buttonsVisible = !featureSettings.buttonsVisible; mainButtons.style.display = featureSettings.buttonsVisible ? 'flex' : 'none'; localStorage.setItem('featureSettings', JSON.stringify(featureSettings)); }); panel.appendChild(mainButtons); panel.appendChild(toggleBtn); document.body.appendChild(panel); return panel; } function clearProcessedMessages() { processedMessages.clear(); } function updateStyleMode() { styleCache.clear(); clearProcessedMessages(); } function getHighlightModeTooltip(mode) { switch (mode) { case HIGHLIGHT_MODES.BOTH: return "當前模式: 高亮暱稱和對話 (Ctrl+左鍵切換)"; case HIGHLIGHT_MODES.NAME_ONLY: return "當前模式: 只高亮暱稱 (Ctrl+左鍵切換)"; case HIGHLIGHT_MODES.MESSAGE_ONLY: return "當前模式: 只高亮對話 (Ctrl+左鍵切換)"; default: return "高亮模式"; } } function getSpamModeTooltip(mode) { switch (mode) { case SPAM_MODES.MARK: return "當前模式: 註記洗版 (Ctrl+左鍵切換為移除模式)"; case SPAM_MODES.REMOVE: return "當前模式: 移除洗版 (Ctrl+左鍵切換為註記模式)"; default: return "洗版處理模式"; } } function throttle(func, limit) { let lastFunc, lastRan; return function() { const context = this, args = arguments; if (!lastRan) { func.apply(context, args); lastRan = Date.now(); } else { clearTimeout(lastFunc); lastFunc = setTimeout(function() { if ((Date.now() - lastRan) >= limit) { func.apply(context, args); lastRan = Date.now(); } }, limit - (Date.now() - lastRan)); } }; } function cleanupProcessedMessages() { const allMessages = new Set(document.querySelectorAll('yt-live-chat-text-message-renderer')); for (const msg of processedMessages) { if (!allMessages.has(msg)) { processedMessages.delete(msg); styleCache.delete(msg); } } if (processedMessages.size > MAX_MESSAGE_CACHE_SIZE) { const messages = Array.from(processedMessages).slice(-MAX_MESSAGE_CACHE_SIZE); processedMessages.clear(); messages.forEach(msg => processedMessages.add(msg)); } } function processMentionedUsers(messageText, authorName, authorColor) { if (!featureSettings.mentionHighlightEnabled || !authorColor) return; const mentionRegex = /@([^\s].*?(?=\s|$|@|[\u200b]))/g; let match; const mentionedUsers = new Set(); while ((match = mentionRegex.exec(messageText)) !== null) { const mentionedUser = match[1].trim(); if (mentionedUser) mentionedUsers.add(mentionedUser); } const allUsers = Array.from(document.querySelectorAll('#author-name')); const existingUsers = allUsers.map(el => el.textContent.trim()); mentionedUsers.forEach(mentionedUser => { const isExistingUser = existingUsers.some(user => user.toLowerCase() === mentionedUser.toLowerCase()); if (isExistingUser && !userColorCache.has(mentionedUser) && !tempUserCache.has(mentionedUser)) { tempUsers[mentionedUser] = { color: authorColor, expireTime: Date.now() + TEMP_USER_EXPIRE_TIME }; tempUserCache.set(mentionedUser, { color: authorColor, expireTime: Date.now() + TEMP_USER_EXPIRE_TIME }); const messages = document.querySelectorAll('yt-live-chat-text-message-renderer'); messages.forEach(msg => { const nameElement = msg.querySelector('#author-name'); if (nameElement && nameElement.textContent.trim() === mentionedUser) { processedMessages.delete(msg); styleCache.delete(msg); } }); } }); if (mentionedUsers.size > 0) localStorage.setItem('tempUsers', JSON.stringify(tempUsers)); } function cleanupExpiredTempUsers() { const now = Date.now(); if (now - lastTempUserCleanupTime < CLEANUP_INTERVAL) return; lastTempUserCleanupTime = now; let changed = false; for (const [user, data] of tempUserCache.entries()) { if (data.expireTime <= now) { tempUserCache.delete(user); if (tempUsers.hasOwnProperty(user)) { delete tempUsers[user]; } changed = true; const messages = document.querySelectorAll('yt-live-chat-text-message-renderer'); messages.forEach(msg => { const nameElement = msg.querySelector('#author-name'); if (nameElement && nameElement.textContent.trim() === user) { processedMessages.delete(msg); styleCache.delete(msg); } }); } } if (changed) localStorage.setItem('tempUsers', JSON.stringify(tempUsers)); } function removePinnedMessage() { if (!featureSettings.pinEnabled) return; requestAnimationFrame(() => { const pinnedMessage = document.querySelector('yt-live-chat-banner-renderer'); if (pinnedMessage) pinnedMessage.style.display = 'none'; }); } function closeMenu() { if (currentMenu) { document.body.removeChild(currentMenu); currentMenu = null; clearTimeout(menuTimeoutId); } } function createColorMenu(targetElement, event) { closeMenu(); const menu = document.createElement('div'); menu.className = 'ytcm-menu'; menu.style.top = `${event.clientY}px`; menu.style.left = `${event.clientX}px`; menu.style.width = '220px'; const colorGrid = document.createElement('div'); colorGrid.className = 'ytcm-grid'; Object.entries(COLOR_OPTIONS).forEach(([colorName, colorValue]) => { const colorItem = document.createElement('div'); colorItem.className = 'ytcm-color-item'; colorItem.textContent = colorName; colorItem.style.backgroundColor = colorValue; colorItem.addEventListener('click', () => { if (targetElement.type === 'user') { userColorSettings[targetElement.name] = colorValue; userColorCache.set(targetElement.name, colorValue); const messages = document.querySelectorAll('yt-live-chat-text-message-renderer'); messages.forEach(msg => { const nameElement = msg.querySelector('#author-name'); if (nameElement && nameElement.textContent.trim() === targetElement.name) { processedMessages.delete(msg); styleCache.delete(msg); } }); } localStorage.setItem('userColorSettings', JSON.stringify(userColorSettings)); closeMenu(); }); colorGrid.appendChild(colorItem); }); const buttonRow = document.createElement('div'); buttonRow.className = 'ytcm-button-row'; const blockButton = document.createElement('button'); blockButton.className = 'ytcm-button'; blockButton.textContent = '封鎖'; blockButton.addEventListener('click', () => { if (targetElement.type === 'user') { blockedUsers.push(targetElement.name); blockedUsersSet.add(targetElement.name); localStorage.setItem('blockedUsers', JSON.stringify(blockedUsers)); const messages = document.querySelectorAll('yt-live-chat-text-message-renderer'); messages.forEach(msg => { const nameElement = msg.querySelector('#author-name'); if (nameElement && nameElement.textContent.trim() === targetElement.name) { msg.setAttribute('data-blocked', 'true'); const messageElement = msg.querySelector('#message'); if (messageElement) messageElement.textContent = '<封鎖>'; styleCache.delete(msg); } }); } closeMenu(); }); const editButton = document.createElement('button'); editButton.className = 'ytcm-button'; editButton.textContent = '編輯'; editButton.addEventListener('click', (e) => { e.stopPropagation(); createEditMenu(targetElement, event); }); const deleteButton = document.createElement('button'); deleteButton.className = 'ytcm-button'; deleteButton.textContent = '刪除'; deleteButton.addEventListener('click', () => { if (targetElement.type === 'user' && userColorSettings[targetElement.name]) { delete userColorSettings[targetElement.name]; userColorCache.delete(targetElement.name); localStorage.setItem('userColorSettings', JSON.stringify(userColorSettings)); const messages = document.querySelectorAll('yt-live-chat-text-message-renderer'); messages.forEach(msg => { const nameElement = msg.querySelector('#author-name'); if (nameElement && nameElement.textContent.trim() === targetElement.name) { processedMessages.delete(msg); styleCache.delete(msg); } }); } closeMenu(); }); buttonRow.appendChild(blockButton); buttonRow.appendChild(editButton); buttonRow.appendChild(deleteButton); menu.appendChild(colorGrid); menu.appendChild(buttonRow); document.body.appendChild(menu); currentMenu = menu; menuTimeoutId = setTimeout(closeMenu, MENU_AUTO_CLOSE_DELAY); } function createEditMenu(targetElement, event) { closeMenu(); const menu = document.createElement('div'); menu.className = 'ytcm-menu'; menu.style.top = `${event.clientY}px`; menu.style.left = `${event.clientX}px`; menu.style.maxWidth = '600px'; const closeButton = document.createElement('button'); closeButton.className = 'ytcm-button'; closeButton.textContent = '關閉'; closeButton.style.width = '100%'; closeButton.style.marginBottom = '10px'; closeButton.addEventListener('click', closeMenu); menu.appendChild(closeButton); const blockedUserList = document.createElement('div'); blockedUserList.textContent = '封鎖用戶名單:'; blockedUserList.className = 'ytcm-flex-wrap'; blockedUsers.forEach(user => { const userItem = document.createElement('div'); userItem.className = 'ytcm-list-item'; userItem.textContent = user; userItem.addEventListener('click', () => { blockedUsers = blockedUsers.filter(u => u !== user); blockedUsersSet.delete(user); localStorage.setItem('blockedUsers', JSON.stringify(blockedUsers)); userItem.remove(); const messages = document.querySelectorAll('yt-live-chat-text-message-renderer'); messages.forEach(msg => { const nameElement = msg.querySelector('#author-name'); if (nameElement && nameElement.textContent.trim() === user) { processedMessages.delete(msg); styleCache.delete(msg); } }); }); blockedUserList.appendChild(userItem); }); menu.appendChild(blockedUserList); const coloredUserList = document.createElement('div'); coloredUserList.textContent = '被上色用戶名單:'; coloredUserList.className = 'ytcm-flex-wrap'; Object.keys(userColorSettings).forEach(user => { const userItem = document.createElement('div'); userItem.className = 'ytcm-list-item'; userItem.textContent = user; userItem.addEventListener('click', () => { delete userColorSettings[user]; userColorCache.delete(user); localStorage.setItem('userColorSettings', JSON.stringify(userColorSettings)); userItem.remove(); const messages = document.querySelectorAll('yt-live-chat-text-message-renderer'); messages.forEach(msg => { const nameElement = msg.querySelector('#author-name'); if (nameElement && nameElement.textContent.trim() === user) { processedMessages.delete(msg); styleCache.delete(msg); } }); }); coloredUserList.appendChild(userItem); }); menu.appendChild(coloredUserList); document.body.appendChild(menu); currentMenu = menu; menuTimeoutId = setTimeout(closeMenu, MENU_AUTO_CLOSE_DELAY); } function checkForSpam(msg) { if (!featureSettings.spamFilterEnabled) return; const messageElement = msg.querySelector('#message'); if (!messageElement || messageElement.textContent === '<封鎖>' || messageElement.textContent === '<洗版>') return; const messageText = messageElement.textContent.trim().substring(0, SPAM_TEXT_LENGTH); if (messageCache.has(messageText)) { if (featureSettings.spamMode === SPAM_MODES.MARK) { messageElement.textContent = '<洗版>'; msg.setAttribute('data-spam', 'true'); styleCache.delete(msg); } else { msg.style.display = 'none'; } return; } messageCache.add(messageText); } function updateMessageCounter(msg) { if (!featureSettings.counterEnabled) return; const nameElement = msg.querySelector('#author-name'); if (!nameElement) return; const userName = nameElement.textContent.trim(); if (!userMessageCounts[userName]) userMessageCounts[userName] = 0; userMessageCounts[userName]++; const existingCounter = msg.querySelector('.ytcm-message-count'); if (existingCounter) existingCounter.remove(); const counterSpan = document.createElement('span'); counterSpan.className = 'ytcm-message-count'; counterSpan.textContent = userMessageCounts[userName]; const messageElement = msg.querySelector('#message'); if (messageElement) messageElement.appendChild(counterSpan); } function processMessage(msg) { if (styleCache.has(msg)) return; const authorName = msg.querySelector('#author-name'); const messageElement = msg.querySelector('#message'); if (!authorName || !messageElement) return; const userName = authorName.textContent.trim(); const messageText = messageElement.textContent.trim(); if (featureSettings.blockEnabled && blockedUsersSet.has(userName)) { msg.setAttribute('data-blocked', 'true'); messageElement.textContent = '<封鎖>'; styleCache.set(msg, true); return; } if (msg.hasAttribute('data-blocked') || msg.hasAttribute('data-spam')) { styleCache.set(msg, true); return; } if (featureSettings.spamFilterEnabled) { const now = Date.now(); if (now - lastSpamCheckTime >= SPAM_CHECK_INTERVAL) { checkForSpam(msg); lastSpamCheckTime = now; } } updateMessageCounter(msg); authorName.className = authorName.className.replace('ytcm-highlight-name', '').replace('ytcm-bg-highlight', '').replace('ytcm-bg-mode', '').trim(); messageElement.className = messageElement.className.replace('ytcm-highlight-msg', '').replace('ytcm-bg-highlight', '').replace('ytcm-bg-mode', '').trim(); if (featureSettings.highlightEnabled && (tempUserCache.has(userName) || userColorCache.has(userName))) { const color = tempUserCache.has(userName) ? tempUserCache.get(userName).color : userColorCache.get(userName); const mode = tempUserCache.has(userName) ? highlightSettings.tempMode : highlightSettings.defaultMode; authorName.style.setProperty('--highlight-color', color); messageElement.style.setProperty('--highlight-color', color); switch (styleMode) { case STYLE_MODES.BASIC: authorName.style.setProperty('--name-weight', 'normal'); authorName.style.setProperty('--name-bg', 'transparent'); messageElement.style.setProperty('--msg-weight', 'normal'); messageElement.style.setProperty('--msg-bg', 'transparent'); break; case STYLE_MODES.BOLD: authorName.style.setProperty('--name-weight', 'bold'); authorName.style.setProperty('--name-bg', 'transparent'); messageElement.style.setProperty('--msg-weight', 'bold'); messageElement.style.setProperty('--msg-bg', 'transparent'); break; case STYLE_MODES.BACKGROUND: authorName.style.setProperty('--name-bg', color); messageElement.style.setProperty('--msg-bg', color); break; } if (mode === HIGHLIGHT_MODES.BOTH || mode === HIGHLIGHT_MODES.NAME_ONLY) { authorName.classList.add('ytcm-highlight-name'); if (styleMode === STYLE_MODES.BACKGROUND) authorName.classList.add('ytcm-bg-mode'); } if (mode === HIGHLIGHT_MODES.BOTH || mode === HIGHLIGHT_MODES.MESSAGE_ONLY) { messageElement.classList.add('ytcm-highlight-msg'); if (styleMode === STYLE_MODES.BACKGROUND) messageElement.classList.add('ytcm-bg-mode'); } if (featureSettings.mentionHighlightEnabled) processMentionedUsers(messageText, userName, color); } styleCache.set(msg, true); } function highlightMessages(mutations) { cleanupProcessedMessages(); const messages = []; mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1 && node.matches('yt-live-chat-text-message-renderer') && !processedMessages.has(node)) { messages.push(node); processedMessages.add(node); } }); }); if (messages.length === 0) { const allMessages = Array.from(document.querySelectorAll('yt-live-chat-text-message-renderer')).slice(-MAX_MESSAGE_CACHE_SIZE); allMessages.forEach(msg => { if (!processedMessages.has(msg)) { messages.push(msg); processedMessages.add(msg); } }); } requestAnimationFrame(() => { messages.forEach(msg => processMessage(msg)); }); cleanupExpiredTempUsers(); } function handleClick(event) { if (currentMenu && !currentMenu.contains(event.target)) closeMenu(); if (event.target.id === 'author-name') { const userName = event.target.textContent.trim(); createColorMenu({ type: 'user', name: userName }, event); } } function init() { initializeCaches(); document.addEventListener('click', handleClick); const controlPanel = createControlPanel(); const observer = new MutationObserver(throttle((mutations) => { highlightMessages(mutations); removePinnedMessage(); }, THROTTLE_DELAY)); const chatContainer = document.querySelector('#chat'); if (chatContainer) observer.observe(chatContainer, { childList: true, subtree: true }); const cleanupIntervalId = setInterval(() => { cleanupProcessedMessages(); cleanupExpiredTempUsers(); }, CLEANUP_INTERVAL); return () => { observer.disconnect(); document.removeEventListener('click', handleClick); clearInterval(cleanupIntervalId); if (controlPanel) controlPanel.remove(); closeMenu(); }; } let cleanup = init(); const checkChatContainer = setInterval(() => { if (document.querySelector('#chat') && !cleanup) cleanup = init(); }, 1000); window.addEventListener('beforeunload', () => { clearInterval(checkChatContainer); cleanup?.(); }); })();