YouTubeのチャット欄にワードNG機能を追加します
当前为
// ==UserScript==
// @name YouTube - チャット欄にワードNG機能を追加
// @namespace https://twitter.com/4chouyou
// @version 0.0.2
// @description YouTubeのチャット欄にワードNG機能を追加します
// @author mufuuuu
// @match https://www.youtube.com/live_chat*
// @grant none
// ==/UserScript==
/* jshint esversion: 6 */
(function() {
var YouTubeChatWordNG = {};
(function (YouTubeChatWordNG) {
class Main {
load() {
let style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '.NG #message, .NG #author-name, .NG #chat-badges { display: none; } .NG yt-live-chat-author-chip.yt-live-chat-text-message-renderer { margin-right: 0px !important; } .NG #author-photo { visibility: collapse; } .NG #content::after { content: "[NGコメント]"; opacity: .25; } #ngMenu { display: inline-block; position: relative; width: 40px; height: 40px; text-align: center; line-height: 40px; color: var(--yt-spec-icon-inactive); cursor: pointer; } #ngMenu:hover, #ngMenu[active="true"] { color: var(--yt-spec-icon-active-other); } #ngMenu::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; } #ngMenu[active="true"]::after { width: 40px; height: 40px; left: 0px; top: 0px; } #ngPopupContainer { 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; } #addNGWordContainer { display: flex; } #ngWordInput { flex: 1; } #listContainer { padding: 0; margin: 0; max-height: 300px; overflow: auto; } #listContainer[empty="false"] { margin-top: 12px; } .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; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .removeButton { width: 16px; cursor: pointer } .removeButton:hover { text-decoration: underline; }';
document.head.append(style);
YouTubeChatWordNG.wordNG.load();
YouTubeChatWordNG.popup.load();
}
}
class WordNG {
constructor() {
this.ngWords = [];
}
load() {
let item = JSON.parse(localStorage.getItem('ngWord'));
if(item) {
this.ngWords = item;
}else {
this.ngWords = [];
}
let chatItems = Array.from(document.querySelectorAll('#chat #items > .yt-live-chat-item-list-renderer'));
this.applyNG(chatItems);
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.applyNG(chatItems);
});
let chat = document.querySelector('#chat #items');
observer.observe(chat, {childList: true});
}
save() {
localStorage.setItem('ngWord', JSON.stringify(this.ngWords));
}
addNGWord(ngWord) {
this.ngWords.push(ngWord);
this.save();
}
removeNGWord(ngWord) {
if (this.ngWords.indexOf(ngWord) >= 0){
this.ngWords.splice(this.ngWords.indexOf(ngWord), 1);
}
this.save();
}
applyNG(chatItems) {
chatItems.forEach(node => {
if(node.tagName == 'YT-LIVE-CHAT-TEXT-MESSAGE-RENDERER' || node.tagName == 'YT-LIVE-CHAT-PAID-MESSAGE-RENDERER') {
let content = node.querySelector('#content');
let message = node.querySelector('#message');
content.removeAttribute('ngword');
node.classList.remove('NG');
let text = message.innerHTML.replace(/<[^<]*alt=\"([^"]+)\"[^>]*>/g, '$1');
for(let i = 0; i < this.ngWords.length; i++) {
let ngWord = this.ngWords[i];
if(ngWord.substring(0, 1) == '/' && ngWord.substring(ngWord.length - 1) == '/') {
ngWord = ngWord.substring(1, ngWord.length - 1);
}else {
ngWord = ngWord.replace(/[-.*+^|[\]()?${}\\]/g, '\\$&');
}
let regex = new RegExp(ngWord);
if(regex.test(text)) {
/*console.log('match: ' + this.ngWords[i] + ', text: ' + text);*/
content.setAttribute('ngword', this.ngWords[i]);
node.classList.add('NG');
break;
}
}
}
});
}
}
class Popup {
constructor() {
this.isReverse = true;
}
load() {
let menu = document.querySelector('#overflow');
let ngMenu = document.createElement('a');
ngMenu.id = 'ngMenu';
ngMenu.innerText = 'NG';
menu.parentNode.insertBefore(ngMenu, menu);
ngMenu.addEventListener('click', () => {
this.toggle();
});
let ngPopupContainer = document.createElement('div');
ngPopupContainer.id = 'ngPopupContainer';
ngPopupContainer.className = 'hidden';
let addNGWordContainer = document.createElement('div');
addNGWordContainer.id = 'addNGWordContainer';
let inputText = document.createElement('input');
inputText.id = 'ngWordInput';
inputText.type = 'text';
inputText.setAttribute('autocomplete', 'off');
addNGWordContainer.appendChild(inputText);
let inputButton = document.createElement('input');
inputButton.type = 'button';
inputButton.value = 'add';
addNGWordContainer.appendChild(inputButton);
let removeNGWordContainer = document.createElement('div');
removeNGWordContainer.id = 'removeNGWordContainer';
let listContainer = document.createElement('ul');
listContainer.id = 'listContainer';
listContainer.setAttribute('empty', 'true');
removeNGWordContainer.appendChild(listContainer);
ngPopupContainer.appendChild(addNGWordContainer);
ngPopupContainer.appendChild(removeNGWordContainer);
document.body.appendChild(ngPopupContainer);
YouTubeChatWordNG.wordNG.ngWords.forEach(ngWord => {
this.addListItem(ngWord);
});
inputButton.addEventListener('click', () => {
this.addNGWord();
});
inputText.addEventListener('keypress', (e) => {
if(e.keyCode == '13') {
this.addNGWord();
}
});
document.body.addEventListener('click', (e) => {
let ngPopupContainer = document.querySelector('#ngPopupContainer');
let ngMenu = document.querySelector('#ngMenu');
let target = e.target;
if(!ngPopupContainer.classList.contains('hidden') && target != ngMenu && target.className != 'removeButton') {
while (target && (target != document.body)) {
if (target == ngPopupContainer) return;
target = target.parentNode;
}
this.toggle();
}
});
}
addNGWord() {
let inputText = document.querySelector('#ngWordInput');
let ngWord = inputText.value;
if(ngWord !== '' && YouTubeChatWordNG.wordNG.ngWords.indexOf(ngWord) == -1) {
YouTubeChatWordNG.wordNG.addNGWord(ngWord);
this.addListItem(ngWord);
inputText.value = '';
let chatItems = Array.from(document.querySelectorAll('#chat #items > .yt-live-chat-item-list-renderer'));
YouTubeChatWordNG.wordNG.applyNG(chatItems);
}
}
toggle() {
let ngPopupContainer = document.querySelector('#ngPopupContainer');
let ngMenu = document.querySelector('#ngMenu');
if(ngPopupContainer.classList.contains('hidden')) {
ngPopupContainer.classList.remove('hidden');
let inputText = document.querySelector('#ngWordInput');
inputText.focus();
ngMenu.setAttribute('active', 'true');
}else {
ngPopupContainer.classList.add('hidden');
ngMenu.removeAttribute('active');
}
}
addListItem(ngWord) {
let parent = document.querySelector('#listContainer');
let listItem = document.createElement('li');
listItem.setAttribute('ngword', ngWord);
listItem.className = 'listItem';
let title = document.createElement('span');
title.className = 'title';
title.textContent = ngWord;
let removeButton = document.createElement('a');
removeButton.setAttribute('ngword', ngWord);
removeButton.className = 'removeButton';
removeButton.textContent = '[x]';
listItem.appendChild(title);
listItem.appendChild(removeButton);
if(this.isReverse) {
parent.insertBefore(listItem, parent.firstChild);
}else {
parent.appendChild(listItem);
}
removeButton.addEventListener('click', (e) => {
YouTubeChatWordNG.wordNG.removeNGWord(ngWord);
this.removeListItems(e.target.getAttribute('ngword'));
let chatItems = Array.from(document.querySelectorAll('#chat #items > .yt-live-chat-item-list-renderer'));
YouTubeChatWordNG.wordNG.applyNG(chatItems);
});
parent.setAttribute('empty', 'false');
}
removeListItems(ngWord) {
let listItems = Array.from(document.querySelectorAll('.listItem'));
listItems.forEach(item => {
if(ngWord == item.getAttribute('ngword')) {
item.remove();
}
});
if(listItems.length == 1) {
let parent = document.querySelector('#listContainer');
parent.setAttribute('empty', 'true');
}
}
}
YouTubeChatWordNG.main = new Main();
YouTubeChatWordNG.wordNG = new WordNG();
YouTubeChatWordNG.popup = new Popup();
})(YouTubeChatWordNG);
window.addEventListener('load', () => {
YouTubeChatWordNG.main.load();
});
})();