YouTube User Block

YouTube用户黑名单,拉黑指定用户(自动隐藏其评论,可手动查看)。

Versión del día 4/2/2021. Echa un vistazo a la versión más reciente.

// ==UserScript==
// @name         YouTube User Block
// @namespace    http://tampermonkey.net/
// @version      1.2.0
// @description  YouTube用户黑名单,拉黑指定用户(自动隐藏其评论,可手动查看)。
// @author       ChanthMiao
// @match        https://www.youtube.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @compatible   chrome
// @compatible   firefox
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';
    function toggleButton(element) {
        // Check to see if the button is pressed
        var pressed = (element.getAttribute("aria-pressed") === "true");
        const user = element.parentElement.parentElement.parentElement.parentElement.querySelector("#author-text > span").textContent.trim();
        if (!pressed && !blockList.includes(user)) {
            blockUser(user);
        } else if (pressed && blockList.includes(user)) {
            disblockUser(user);
        }
    }
    function handleBtnClick(event) {
        toggleButton(event.target);
    }
    function handleBtnKeyDown(event) {
        // Check to see if space or enter were pressed
        if (event.key === " " || event.key === "Enter" || event.key === "Spacebar") { // "Spacebar" for IE11 support
            // Prevent the default action to stop scrolling when space is pressed
            event.preventDefault();
            toggleButton(event.target);
        }
    }
    function createNodeListener(node, config, mutationCallback) {
        const observer = new MutationObserver(mutationCallback);
        observer.observe(node, config);
        return observer;
    }
    function injectBlockButton(comment) {
        const user = comment.querySelector("#author-text > span").textContent.trim();
        const arc = comment.querySelector("#creator-heart");
        var block = document.createElement("div");
        block.setAttribute("id", "block-button");
        if (blockList.includes(user)) {
            block.setAttribute("title", "解封");
        } else {
            block.setAttribute("title", "拉黑");
        }
        var btn = document.createElement("button");
        btn.setAttribute("id", "block-button-item");
        btn.setAttribute("role", "button");
        btn.setAttribute("aria-pressed", blockList.includes(user));
        btn.addEventListener("tap", handleBtnClick, false);
        btn.addEventListener("keydown", handleBtnKeyDown, false);
        block.append(btn);
        var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        svg.setAttribute("id", "block-button-icon");
        svg.setAttribute("viewbox", "0 0 24 24");
        svg.setAttribute("width", "16px");
        svg.setAttribute("height", "16px");
        svg.setAttribute("focusable", false);
        svg.setAttribute("class", "style-scope yt-icon");
        svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
        svg.setAttribute("style", "pointer-events: none;display: block;width: 100%;height: 100%;");
        btn.append(svg);
        var g = document.createElementNS("http://www.w3.org/2000/svg", "g");
        g.setAttribute("class", "style-scope yt-icon");
        svg.append(g);
        var path = document.createElementNS("http://www.w3.org/2000/svg", "path");
        path.setAttribute("d", "M 8 4.667969 C 9.839844 4.667969 11.332031 6.160156 11.332031 8 C 11.332031 8.433594 11.246094 8.839844 11.09375 9.21875 L 13.039062 11.167969 C 14.046875 10.328125 14.839844 9.238281 15.328125 8 C 14.171875 5.074219 11.328125 3 7.992188 3 C 7.058594 3 6.167969 3.167969 5.339844 3.464844 L 6.78125 4.90625 C 7.160156 4.753906 7.566406 4.667969 8 4.667969 Z M 1.332031 2.847656 L 2.851562 4.367188 L 3.160156 4.671875 C 2.054688 5.535156 1.1875 6.679688 0.667969 8 C 1.820312 10.925781 4.667969 13 8 13 C 9.035156 13 10.019531 12.800781 10.921875 12.441406 L 11.199219 12.71875 L 13.152344 14.667969 L 14 13.820312 L 2.179688 2 Z M 5.019531 6.535156 L 6.054688 7.566406 C 6.019531 7.707031 6 7.851562 6 8 C 6 9.105469 6.894531 10 8 10 C 8.148438 10 8.292969 9.980469 8.433594 9.945312 L 9.464844 10.980469 C 9.019531 11.199219 8.527344 11.332031 8 11.332031 C 6.160156 11.332031 4.667969 9.839844 4.667969 8 C 4.667969 7.472656 4.800781 6.980469 5.019531 6.535156 Z M 7.894531 6.011719 L 9.992188 8.113281 L 10.007812 8.007812 C 10.007812 6.898438 9.113281 6.007812 8.007812 6.007812 Z M 7.894531 6.011719 ");
        path.setAttribute("class", "style-scope yt-icon");
        g.append(path);
        arc.before(block);
    }
    function commentHandler(comment) {
        const user = comment.querySelector("#author-text > span").textContent.trim();
        var btnDiv = comment.querySelector("#block-button");
        var btn = btnDiv.children[0];
        if (blockList.includes(user)) {
            if (!(comment.getAttribute("user-blocked") === "true")) {
                var p = comment.parentElement;
                var fakeBlock = document.createElement("div");
                fakeBlock.id = "ytc-block";
                fakeBlock.innerText = "该用户评论已屏蔽";
                fakeBlock.className = comment.className;
                fakeBlock.commentStyle = "unknown"
                p.insertBefore(fakeBlock, comment);
            }
            comment.setAttribute("user-blocked", true);
            btnDiv.setAttribute("title", "解封");
            btn.setAttribute("aria-pressed", true);
        } else {
            if ((comment.getAttribute("user-blocked") === "true")) {
                comment.previousSibling.remove();
            }
            comment.setAttribute("user-blocked", false);
            btnDiv.setAttribute("title", "拉黑");
            btn.setAttribute("aria-pressed", false);
        }
    }
    function blockUser(user) {
        blockList.push(user); // 添加黑名单,使后续dom由callback处理
        GM_setValue("blockList", blockList);
        // 更正已由callback处理过的dom
        document.querySelectorAll("ytd-comment-renderer[user-blocked='false']").forEach((v, i) => {
            commentHandler(v);
        });
    }
    function disblockUser(user) {
        blockList = blockList.filter(v => v != user); // 从黑名单移除用户,使后续dom由callback处理
        GM_setValue("blockList", blockList);
        // 更正已由callback处理过的dom
        document.querySelectorAll("ytd-comment-renderer[user-blocked='true']").forEach((v, i) => {
            commentHandler(v);
        });
    }
    const callback = function (mutationsList, observer) {
        for (let mutation of mutationsList) {
            if (mutation.type === 'childList') {
                var nodes = mutation.addedNodes;
                nodes.forEach((v, i) => {
                    if (v.nodeName === 'YTD-COMMENT-RENDERER') {
                        if (!v.querySelector("#block-button")) {
                            injectBlockButton(v); // 注入拉黑按钮
                        }
                        commentHandler(v);
                    }
                })
            }
        }
    }
    const callback_top = function (mutationsList, observer) {
        for (let mutation of mutationsList) {
            if (mutation.type === 'childList') {
                var nodes = mutation.addedNodes;
                nodes.forEach((v, i) => {
                    var comments_entry = document.querySelector('ytd-item-section-renderer > #contents:not([hooked])');
                    if (comments_entry) {
                        comments_entry.setAttribute("hooked", true);
                        observer.disconnect();
                        createNodeListener(comments_entry, config, callback);
                    }
                })
            }
        }
    }
    function blockReset() {
        blockList = [];
        GM_setValue("blockList", blockList);
        document.querySelectorAll("ytd-comment-renderer[user-blocked='true']").forEach((v, i) => {
            commentHandler(v);
        });
        setTimeout(function () { alert('YouTube 用户黑名单已重置成功!'); }, 1);
    }
    function blockExport() {
        var link = document.createElement("a");
        var text = JSON.stringify(blockList);
        link.setAttribute("download", "blockList.json");
        link.setAttribute("href", "data:text/json," + text);
        link.click();
    }
    const distinct = (value, index, self) => { return self.indexOf(value) === index; };
    function handleFileSelect(e) {
        var files = e.target.files;
        if (files.length < 1) {
            setTimeout(function () { alert('请选择一份黑名单文件!'); }, 1);
            return;
        }
        var file = files[0];
        var reader = new FileReader();
        reader.onload = (e) => {
            try {
                var content = JSON.parse(e.target.result);
                if (Array.isArray(content)) {
                    blockList = content.concat(blockList).filter(distinct);//拼接,去重。
                    GM_setValue("blockList", blockList);
                    // 更正已由callback处理过的dom
                    document.querySelectorAll("ytd-comment-renderer[user-blocked='false']").forEach((v, i) => {
                        commentHandler(v);
                    });
                    setTimeout(function () { alert('导入成功!'); }, 1);
                } else {
                    setTimeout(function () { alert('错误,格式非法!'); }, 1);
                }
            } catch (e) {
                setTimeout(function () { alert(e); }, 1);
            }
        }
        reader.readAsText(file);
    }
    function blockImport() {
        var input = document.createElement("input");
        input.setAttribute("type", "file");
        input.setAttribute("accept", ".json,text/json");
        input.addEventListener("change", handleFileSelect);
        input.click();
    }
    /**
    * @type {string[]}
    */
    var blockList = GM_getValue("blockList");
    if (!blockList) {
        blockList = [];
    }
    GM_addStyle("#ytc-block{margin-bottom:8px;color:#F44336;text-align:center;}"
        + "#ytc-block + ytd-comment-renderer{box-shadow:0 0 5px #F44336;visibility:hidden;max-height:24px;margin-top:calc((var(--ytd-link-line-height, 1.6rem) + 8px) * -1);opacity:0;}"
        + "#ytc-block:hover + ytd-comment-renderer{visibility:visible;max-height:fit-content;transition: all 0.5s ease-in-out;-ms-transition: all 0.5s ease-in-out;-o-transition: all 0.5s ease-in-out;-moz-transition: all 0.5s ease-in-out;-webkit-transition: all 0.5s ease-in-out;margin-top:calc((var(--ytd-link-line-height, 1.6rem) + 8px) * -1);opacity:1;}"
        + "#ytc-block + ytd-comment-renderer:hover{visibility:visible;max-height:fit-content;transition: all 0.5s ease-in-out;-ms-transition: all 0.5s ease-in-out;-o-transition: all 0.5s ease-in-out;-moz-transition: all 0.5s ease-in-out;-webkit-transition: all 0.5s ease-in-out;margin-top:calc((var(--ytd-link-line-height, 1.6rem) + 8px) * -1);opacity:1;}"
        + "#block-button{width: 32px;height: 32px;border: none;border-radius: 50%;outline: none;}"
        + "#block-button-item{width: 32px;height: 32px;border: none;outline: none;padding: 8px;border-radius: 50%;background: none;}"
        + "#block-button-item:active{width: 32px;height: 32px;background: radial-gradient(#ffffff, #E2E2E2);}"
        + "#block-button-icon{fill: grey;}"
        + "#block-button-item[aria-pressed=\"true\"]>#block-button-icon{fill: #065FD4;}");
    const targetNode = document.querySelector('ytd-app');
    const config = { attributes: false, childList: true, subtree: true };
    const localobserver = createNodeListener(targetNode, config, callback_top);
    GM_registerMenuCommand("重置", blockReset, "R");
    GM_registerMenuCommand("导入", blockImport, "I");
    GM_registerMenuCommand("导出", blockExport, "E");
})();