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