// ==UserScript==
// @name YouTube 聊天室管理
// @namespace http://tampermonkey.net/
// @version 12.8
// @description 16種預設可選色彩用以自動著色指定用戶訊息,其它功能包含封鎖用戶、簡化編輯儲存在瀏覽器的用戶清單、移除聊天室置頂消息,清理重複消息,添加功能切換開關。
// @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 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();
});
})();