YouTube - チャット欄にワードNG機能を追加

YouTubeのチャット欄にワードNG機能を追加します

目前為 2019-09-03 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
    });
})();