您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
YouTubeのチャット欄にワードミュート機能を追加します
// ==UserScript== // @name YouTube Live - Word mute // @namespace https://twitter.com/4chouyou // @version 0.1.2 // @description YouTubeのチャット欄にワードミュート機能を追加します // @author mufuuuu // @match https://www.youtube.com/live_chat* // @grant GM_getValue // @grant GM_setValue // ==/UserScript== /* jshint esversion: 6 */ (() => { const TIMEOUT = 10000; const STRINGS = { ja:{ enterMuteWord: '"/"で括ると正規表現', inputButton: '追加', toggleMuteWordsList: 'ミュートワードリストの表示/非表示', excludeOwner: 'チャンネル所有者を除く', excludePaidMessage: 'スーパーチャットを除く', showMuteWordOnHover: 'ホバー時にミュートワードを表示', showMutedContentOnHover: 'ツールチップで本文を表示', selectionEnabled: '選択ポップアップを有効化', hideChatCompletely: 'ミュートしたチャットを非表示' }, en:{ enterMuteWord: 'Use "/" for RegExp.', inputButton: 'Add', toggleMuteWordsList: 'Show/Hide mute word list', excludeOwner: 'Exclude channel owner', excludePaidMessage: 'Exclude Super Chat', showMuteWordOnHover: 'Show mute word on hover', showMutedContentOnHover: 'Show content tooltip on hover', selectionEnabled: 'Enable selection popup', hideChatCompletely: 'Hide muted chat completely' } }; var YTChatWordMute = {}; (YTChatWordMute => { class Main { constructor() { this.muteWords = []; this.strings = {}; this.config = { excludeOwner: true, excludePaidMessage: false, showMuteWordOnHover: true, showMutedContentOnHover: true, hideChatCompletely: false, selectionEnabled: false, reverseList: true, openList: true }; } load() { let language = ((window.navigator.languages && window.navigator.languages[0]) || window.navigator.language || window.navigator.userLanguage || window.navigator.browserLanguage).substring(0, 2); if(Object.keys(STRINGS).indexOf(language) != -1) { this.strings = STRINGS[language]; }else { this.strings = STRINGS.en; } this.muteWords = JSON.parse(GM_getValue('muteWords', false)) || JSON.parse(localStorage.getItem('muteWord')) || []; this.config = JSON.parse(GM_getValue('config', false)) || this.config; let promise = new Promise(resolve => { let intervalid = window.setInterval(() => { if(document.querySelector('#chat #items')) { window.clearTimeout(timeoutid); window.clearInterval(intervalid); resolve(true); } }, 100); let timeoutid = window.setTimeout(() => { window.clearInterval(intervalid); }, TIMEOUT); }); promise.then(() => { YTChatWordMute.wordMute.load(); YTChatWordMute.selection.load(); YTChatWordMute.popup.load(); }); } save() { GM_setValue('muteWords', JSON.stringify(this.muteWords)); GM_setValue('config', JSON.stringify(this.config)); } } class WordMute { refreshStylesheet() { let style = document.getElementById('styleWordMute'); if(!style) { style = document.createElement('style'); style.id = 'styleWordMute'; style.type = 'text/css'; document.head.appendChild(style); } const CSS = [ '.muted #message, .muted #author-name, .muted #chat-badges { display: none; }', '.muted yt-live-chat-author-chip.yt-live-chat-text-message-renderer { margin-right: 0px !important; }', '.muted #author-photo { visibility: collapse; }', '.muted #content::after { content: "[Muted]"; display:inline-block; opacity: .25; width: 100%; line-height: 1em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }', '.muted:hover #content::after { content: "[Muted] "attr(muteword); }' ]; if(YTChatWordMute.main.config.hideChatCompletely) { style.innerHTML = CSS.join('\n') + '\n.muted { display: none; }'; }else { style.innerHTML = CSS.join('\n'); } } load() { this.refreshStylesheet(); this.applyMuteAll(); let observer = new MutationObserver((mutations) => { let chatItems; if(mutations.length == 1) { chatItems = Array.from(mutations[0].addedNodes); }else { chatItems = Array.from(document.querySelectorAll('#chat #items > .yt-live-chat-item-list-renderer')); } this.applyMute(chatItems); }); let chat = document.querySelector('#chat #items'); observer.observe(chat, {childList: true}); } addMuteWord(muteWord) { YTChatWordMute.main.muteWords.push(muteWord); YTChatWordMute.main.save(); } removeMuteWord(muteWord) { if(YTChatWordMute.main.muteWords.indexOf(muteWord) >= 0) { YTChatWordMute.main.muteWords.splice(YTChatWordMute.main.muteWords.indexOf(muteWord), 1); } YTChatWordMute.main.save(); } applyMuteAll() { let chatItems = Array.from(document.querySelectorAll('#chat #items > .yt-live-chat-item-list-renderer')); this.applyMute(chatItems); } applyMute(chatItems) { chatItems = chatItems.filter(node => node.tagName == 'YT-LIVE-CHAT-TEXT-MESSAGE-RENDERER' || node.tagName == 'YT-LIVE-CHAT-PAID-MESSAGE-RENDERER'); chatItems.forEach(node => { let content = node.querySelector('#content'); content.removeAttribute('muteword'); content.removeAttribute('title'); node.classList.remove('muted'); }); if(YTChatWordMute.main.config.excludePaidMessage) { chatItems = chatItems.filter(node => node.tagName != 'YT-LIVE-CHAT-PAID-MESSAGE-RENDERER'); } if(YTChatWordMute.main.config.excludeOwner) { chatItems = chatItems.filter(node => !node.hasAttribute('author-is-owner')); } chatItems.forEach(node => { let content = node.querySelector('#content'); let message = node.querySelector('#message'); /*console.log('message.innerHTML: "' + message.innerHTML + '"');*/ let text = message.innerHTML.replace(/\r?\n/g, '').replace(/<paper-tooltip.*\/paper-tooltip>/g, '').replace(/<[^<]*alt=\"([^"]+)\"[^>]*>/g, '$1'); /*console.log('text: "' + text + '"');*/ for(let i = 0; i < YTChatWordMute.main.muteWords.length; i++) { let muteWord = YTChatWordMute.main.muteWords[i]; if(muteWord.substring(0, 1) == '/' && muteWord.substring(muteWord.length - 1) == '/') { muteWord = muteWord.substring(1, muteWord.length - 1); }else { muteWord = muteWord.replace(/[-.*+^|[\]()?${}\\]/g, '\\$&'); } let regex = new RegExp(muteWord, 'u'); if(regex.test(text)) { /*console.log('match: "' + YTChatWordMute.main.muteWords[i] + '", text: "' + text + '"');*/ if(YTChatWordMute.main.config.showMuteWordOnHover) { content.setAttribute('muteword', YTChatWordMute.main.muteWords[i]); } if(YTChatWordMute.main.config.showMutedContentOnHover) { content.setAttribute('title', text); } node.classList.add('muted'); break; } } }); } } class Selection { refreshStylesheet() { let style = document.getElementById('styleSelection'); if(!style) { style = document.createElement('style'); style.id = 'styleSelection'; style.type = 'text/css'; document.head.appendChild(style); } const CSS = [ '#selectionPopup {position: absolute; padding: 8px; background-color: var(--yt-spec-general-background-a); color: var(--yt-live-chat-primary-text-color, var(--yt-primary-text-color)); font-size: 1.2em; box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); border-radius: 4px; cursor: pointer;}', '#selectionPopup:hover { background-color: var(--yt-spec-general-background-b); }' ]; style.innerHTML = CSS.join('\n'); } load() { this.refreshStylesheet(); this.addEventListener(); } addEventListener() { document.documentElement.addEventListener('click', onClick, false); function onClick(e) { if(YTChatWordMute.main.config.selectionEnabled) { if(e.target.id == 'selectionPopup') { let selectionPopup = e.target; let selectionText = selectionPopup.getAttribute('muteWord'); selectionPopup.remove(); YTChatWordMute.popup.addMuteWord(selectionText); }else if(e.target.classList.contains('yt-live-chat-text-message-renderer') || e.target.classList.contains('yt-live-chat-paid-message-renderer') || e.target.classList.contains('yt-live-chat-item-list-renderer')) { let selectionPopup = document.getElementById('selectionPopup'); if(selectionPopup) selectionPopup.remove(); if(document.getSelection() && !document.getSelection().isCollapsed) { let selectionText = document.getSelection().toString(); selectionPopup = document.createElement('div'); selectionPopup.id = 'selectionPopup'; selectionPopup.style.top = e.pageY + 'px'; selectionPopup.style.left = e.pageX + 'px'; selectionPopup.textContent = 'Mute "' + selectionText + '"'; selectionPopup.setAttribute('muteWord', selectionText); document.body.appendChild(selectionPopup); } } }else { document.documentElement.removeEventListener('click', onClick, false); } } } } class Popup { refreshStylesheet() { let style = document.getElementById('stylePopup'); if(!style) { style = document.createElement('style'); style.id = 'stylePopup'; style.type = 'text/css'; document.head.appendChild(style); } const CSS = [ '#muteMenu { display: inline-block; position: relative; width: 40px; height: 40px; text-align: center; line-height: 40px; font-size: 0.9em; color: var(--yt-spec-icon-inactive); cursor: pointer; }', '#muteMenu:hover, #muteMenu[active="true"] { color: var(--yt-spec-icon-active-other); }', '#muteMenu::after { content: ""; display: block; position: absolute; width: 0px; height: 0px; left: 20px; top: 20px; border-radius: 50%; background-color: var(--yt-spec-icon-active-other); opacity: .25; transition-duration: .2s; }', '#muteMenu[active="true"]::after { width: 40px; height: 40px; left: 0px; top: 0px; }', '#mutePopupContainer { position: fixed; width: 220px; top: 44px; right: 14px; padding: 12px; background-color: var(--yt-spec-brand-background-solid); color: var(--paper-listbox-color, #212121); box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); border-radius: 4px; }', '#muteWordInput { width: 100%; border: none; outline: none; background-color: transparent; color: var(--yt-live-chat-primary-text-color); }', '#muteWordInput::placeholder { color: var(--yt-live-chat-text-input-field-placeholder-color, hsla(0, 0%, 6.7%, 0.6)); }', '#muteWordInput + #underline { position: relative; height: 2px; }', '#muteWordInput + #underline #unfocused { position: absolute; top: 0; left: 0; right: 0; height: 1px; background-color: var(--yt-live-chat-text-input-field-inactive-underline-color, #b8b8b8); }', '#muteWordInput + #underline #focused { position: absolute; top: 0; left: 0; right: 0; height: 2px; background-color: var(--yt-live-chat-text-input-field-active-underline-color, #2793e6); transform-origin: center center; transform: scale3d(0,1,1); transition: transform; transition-duration: 0.25s; }', '#muteWordInput:focus + #underline #focused { transform: none; }', '#muteWordAdd { display: block; margin: 8px 0 0 auto; padding: 8px; border: none; background-color: transparent; outline: none; font-size: 1.4rem; font-weight: 500; letter-spacing: .007px; color: var(--yt-spec-call-to-action); cursor: pointer; }', '.openButton { margin-top: 8px; padding: 4px; background-color: var(--yt-spec-general-background-a); border-radius: 4px; text-align: center; cursor: pointer; }', '.openButton:hover {background-color: var(--yt-spec-general-background-b); }', '.listContainer { padding: 0; margin: 0; max-height: 270px; overflow: auto; }', '.listContainer:not([empty="true"]) { margin-top: 12px; }', '#muteWordListContainer .listContainer:not([open]) { display: none; }', '.listItem { display: flex; font-size: 1.2em; line-height: 18px; }', '.listItem:nth-child(even) { background-color: var(--yt-spec-general-background-a); }', '.title { flex: 1; padding-left: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }', '.removeButton { width: 16px; cursor: pointer }', '.removeButton:hover { text-decoration: underline; }' ]; style.innerHTML = CSS.join('\n'); } load() { this.refreshStylesheet(); let menu = document.querySelector('#overflow'); let muteMenu = document.createElement('a'); muteMenu.id = 'muteMenu'; muteMenu.innerText = 'Mute'; menu.parentNode.insertBefore(muteMenu, menu); muteMenu.addEventListener('click', () => { this.toggle(); }, false); let mutePopupContainer = document.createElement('div'); mutePopupContainer.id = 'mutePopupContainer'; mutePopupContainer.className = 'hidden'; let addMuteWordContainer = document.createElement('div'); addMuteWordContainer.id = 'addMuteWordContainer'; let inputText = document.createElement('input'); inputText.id = 'muteWordInput'; inputText.type = 'text'; inputText.setAttribute('autocomplete', 'off'); inputText.placeholder = YTChatWordMute.main.strings.enterMuteWord; addMuteWordContainer.appendChild(inputText); inputText.addEventListener('keypress', (e) => { if(e.keyCode == '13') { this.addMuteWord(); } }, false); let underline = document.createElement('div'); underline.id = 'underline'; let unfocused = document.createElement('div'); unfocused.id = 'unfocused'; underline.appendChild(unfocused); let focused = document.createElement('div'); focused.id = 'focused'; underline.appendChild(focused); addMuteWordContainer.appendChild(underline); let inputButton = document.createElement('input'); inputButton.id = 'muteWordAdd'; inputButton.type = 'button'; inputButton.value = YTChatWordMute.main.strings.inputButton; addMuteWordContainer.appendChild(inputButton); inputButton.addEventListener('click', () => { this.addMuteWord(); }, false); let muteWordListContainer = document.createElement('div'); muteWordListContainer.id = 'muteWordListContainer'; let muteWordList = document.createElement('ul'); muteWordList.id = 'muteWordList'; muteWordList.className = 'listContainer'; muteWordList.setAttribute('empty', 'true'); if(YTChatWordMute.main.config.openList) { muteWordList.setAttribute('open', 'true'); }else { muteWordList.removeAttribute('open'); } let openButton = document.createElement('div'); openButton.className = 'openButton'; openButton.textContent = YTChatWordMute.main.strings.toggleMuteWordsList; muteWordListContainer.appendChild(openButton); openButton.addEventListener('click', () => { YTChatWordMute.main.config.openList = !YTChatWordMute.main.config.openList; YTChatWordMute.main.save(); if(YTChatWordMute.main.config.openList) { muteWordList.setAttribute('open', 'true'); }else { muteWordList.removeAttribute('open'); } }, false); muteWordListContainer.appendChild(muteWordList); let SettingContainer = document.createElement('div'); SettingContainer.id = 'SettingContainer'; let SettingList = document.createElement('ul'); SettingList.id = 'SettingList'; SettingList.className = 'listContainer'; SettingList.setAttribute('empty', 'false'); SettingContainer.appendChild(SettingList); let listItem, title, checkBox; listItem = document.createElement('li'); listItem.className = 'listItem'; title = document.createElement('span'); title.className = 'title'; title.textContent = YTChatWordMute.main.strings.excludeOwner; checkBox = document.createElement('input'); checkBox.type = 'checkbox'; checkBox.checked = YTChatWordMute.main.config.excludeOwner; listItem.appendChild(title); listItem.appendChild(checkBox); SettingList.appendChild(listItem); checkBox.addEventListener('click', (e) => { YTChatWordMute.main.config.excludeOwner = e.target.checked; YTChatWordMute.main.save(); YTChatWordMute.wordMute.applyMuteAll(); }, false); listItem = document.createElement('li'); listItem.className = 'listItem'; title = document.createElement('span'); title.className = 'title'; title.textContent = YTChatWordMute.main.strings.excludePaidMessage; checkBox = document.createElement('input'); checkBox.type = 'checkbox'; checkBox.checked = YTChatWordMute.main.config.excludePaidMessage; listItem.appendChild(title); listItem.appendChild(checkBox); SettingList.appendChild(listItem); checkBox.addEventListener('click', (e) => { YTChatWordMute.main.config.excludePaidMessage = e.target.checked; YTChatWordMute.main.save(); YTChatWordMute.wordMute.applyMuteAll(); }, false); listItem = document.createElement('li'); listItem.className = 'listItem'; title = document.createElement('span'); title.className = 'title'; title.textContent = YTChatWordMute.main.strings.showMuteWordOnHover; checkBox = document.createElement('input'); checkBox.type = 'checkbox'; checkBox.checked = YTChatWordMute.main.config.showMuteWordOnHover; listItem.appendChild(title); listItem.appendChild(checkBox); SettingList.appendChild(listItem); checkBox.addEventListener('click', (e) => { YTChatWordMute.main.config.showMuteWordOnHover = e.target.checked; YTChatWordMute.main.save(); YTChatWordMute.wordMute.applyMuteAll(); }, false); listItem = document.createElement('li'); listItem.className = 'listItem'; title = document.createElement('span'); title.className = 'title'; title.textContent = YTChatWordMute.main.strings.showMutedContentOnHover; checkBox = document.createElement('input'); checkBox.type = 'checkbox'; checkBox.checked = YTChatWordMute.main.config.showMutedContentOnHover; listItem.appendChild(title); listItem.appendChild(checkBox); SettingList.appendChild(listItem); checkBox.addEventListener('click', (e) => { YTChatWordMute.main.config.showMutedContentOnHover = e.target.checked; YTChatWordMute.main.save(); YTChatWordMute.wordMute.applyMuteAll(); }, false); listItem = document.createElement('li'); listItem.className = 'listItem'; title = document.createElement('span'); title.className = 'title'; title.textContent = YTChatWordMute.main.strings.selectionEnabled; checkBox = document.createElement('input'); checkBox.type = 'checkbox'; checkBox.checked = YTChatWordMute.main.config.selectionEnabled; listItem.appendChild(title); listItem.appendChild(checkBox); SettingList.appendChild(listItem); checkBox.addEventListener('click', (e) => { YTChatWordMute.main.config.selectionEnabled = e.target.checked; YTChatWordMute.main.save(); YTChatWordMute.selection.addEventListener(); }, false); listItem = document.createElement('li'); listItem.className = 'listItem'; title = document.createElement('span'); title.className = 'title'; title.textContent = YTChatWordMute.main.strings.hideChatCompletely; checkBox = document.createElement('input'); checkBox.type = 'checkbox'; checkBox.checked = YTChatWordMute.main.config.hideChatCompletely; listItem.appendChild(title); listItem.appendChild(checkBox); SettingList.appendChild(listItem); checkBox.addEventListener('click', (e) => { YTChatWordMute.main.config.hideChatCompletely = e.target.checked; YTChatWordMute.main.save(); YTChatWordMute.wordMute.refreshStylesheet(); }, false); mutePopupContainer.appendChild(addMuteWordContainer); mutePopupContainer.appendChild(muteWordListContainer); mutePopupContainer.appendChild(SettingContainer); document.body.appendChild(mutePopupContainer); YTChatWordMute.main.muteWords.forEach(muteWord => { this.addListItem(muteWord); }); document.body.addEventListener('click', (e) => { let mutePopupContainer = document.querySelector('#mutePopupContainer'); let muteMenu = document.querySelector('#muteMenu'); let target = e.target; if(!mutePopupContainer.classList.contains('hidden') && target != muteMenu && target.className != 'removeButton') { while (target && (target != document.body)) { if (target == mutePopupContainer) return; target = target.parentNode; } this.toggle(); } }, false); } addMuteWord(muteWord) { let inputText = document.querySelector('#muteWordInput'); if(!muteWord) muteWord = inputText.value; if(muteWord !== '' && YTChatWordMute.main.muteWords.indexOf(muteWord) == -1) { YTChatWordMute.wordMute.addMuteWord(muteWord); this.addListItem(muteWord); inputText.value = ''; YTChatWordMute.wordMute.applyMuteAll(); } } toggle() { let mutePopupContainer = document.querySelector('#mutePopupContainer'); let muteMenu = document.querySelector('#muteMenu'); if(mutePopupContainer.classList.contains('hidden')) { mutePopupContainer.classList.remove('hidden'); let inputText = document.querySelector('#muteWordInput'); inputText.focus(); muteMenu.setAttribute('active', 'true'); }else { mutePopupContainer.classList.add('hidden'); muteMenu.removeAttribute('active'); } } addListItem(muteWord) { let parent = document.querySelector('#muteWordList'); let listItem = document.createElement('li'); listItem.setAttribute('muteword', muteWord); listItem.className = 'listItem'; let title = document.createElement('span'); title.className = 'title'; title.title = muteWord; title.textContent = muteWord; let removeButton = document.createElement('a'); removeButton.setAttribute('muteword', muteWord); removeButton.className = 'removeButton'; removeButton.textContent = '[x]'; listItem.appendChild(title); listItem.appendChild(removeButton); if(YTChatWordMute.main.config.reverseList) { parent.insertBefore(listItem, parent.firstChild); }else { parent.appendChild(listItem); } removeButton.addEventListener('click', (e) => { YTChatWordMute.wordMute.removeMuteWord(muteWord); this.removeListItems(e.target.getAttribute('muteword')); YTChatWordMute.wordMute.applyMuteAll(); }, false); parent.setAttribute('empty', 'false'); } removeListItems(muteWord) { let listItems = Array.from(document.querySelectorAll('.listItem')); listItems.forEach(item => { if(muteWord == item.getAttribute('muteword')) { item.remove(); } }); if(listItems.length == 1) { let parent = document.querySelector('#muteWordList'); parent.setAttribute('empty', 'true'); } } } YTChatWordMute.main = new Main(); YTChatWordMute.wordMute = new WordMute(); YTChatWordMute.selection = new Selection(); YTChatWordMute.popup = new Popup(); })(YTChatWordMute); YTChatWordMute.main.load(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址