YouTube User Block

YouTube User BlockList,blocking specific users (hide all their comments, support manual viewing).

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name            YouTube User Block
// @name:en         YouTube User Block
// @namespace       https://github.com/ChanthMiao/
// @icon            https://visualpharm.com/assets/42/Invisible-595b40b65ba036ed117d2e78.svg
// @version         1.6.2
// @description     YouTube用户黑名单,拉黑指定用户(自动隐藏其评论,可手动查看)。
// @description:en  YouTube User BlockList,blocking specific users (hide all their comments, support manual viewing).
// @author          ChanthMiao
// @match           *://www.youtube.com/*
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_addStyle
// @grant           GM_registerMenuCommand
// @grant           GM_addValueChangeListener
// @grant           GM_notification
// @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)) {
            unblockUser(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", uiText.unblock);
        } else {
            block.setAttribute("title", uiText.block);
        }
        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)) {
            comment.setAttribute("user-blocked", true);
            btnDiv.setAttribute("title", uiText.unblock);
            btn.setAttribute("aria-pressed", true);
        } else {
            comment.setAttribute("user-blocked", false);
            btnDiv.setAttribute("title", uiText.block);
            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 unblockUser(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);
        });
        sendNotification(uiText.resetSuccess);
    }
    function blockExport() {
        var link = document.createElement("a");
        var text = encodeURIComponent(JSON.stringify(blockList, null, 2));
        link.setAttribute("download", "blockList.json");
        link.setAttribute("href", "data:application/json;charset=utf-8," + text);
        link.click();
    }
    const distinct = (value, index, self) => { return self.indexOf(value) === index; };
    function handleFileSelect(e) {
        var files = e.target.files;
        var file = files[0];
        if (file.type != "application/json") {
            sendNotification(uiText.InvaildFile);
            return;
        }
        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);
                    });
                    sendNotification(uiText.ImportSuccess);
                } else {
                    sendNotification(uiText.InvaildFile);
                }
            } catch (e) {
                sendNotification(e);
            }
        }
        reader.readAsText(file);
    }
    function blockImport() {
        var input = document.createElement("input");
        input.setAttribute("type", "file");
        input.setAttribute("accept", "application/json");
        input.addEventListener("change", handleFileSelect);
        input.click();
    }
    function registerCrossTabHandler() {
        if (GM_addValueChangeListener) {
            GM_addValueChangeListener("blockList", (name, oldValue, newValue, remote) => {
                // 监听来自其他Tab的数据变动
                if (remote) {
                    blockList = newValue;
                    document.querySelectorAll("ytd-comment-renderer[user-blocked]").forEach((v, i) => {
                        commentHandler(v);
                    });
                }
            });
        } else if (!GM_getValue("API_CHECKED")) {
            GM_setValue("API_CHECKED", true);
            sendNotification(uiText.apiChecked);
        }
    }
    function sendNotification(msg) {
        if (typeof GM_notification === 'function') {
            // 使用Tampermonkey提供的通知API
            GM_notification({ text: msg, title: scriptName, image: icon64, timeout: msgTimeout })
        } else {
            if (!("Notification" in window)) {
                // 不支持html5的通知机制,回退至alert
                setTimeout(() => { alert(msg); }, 1);
            } else if (Notification.permission === "granted") {
                // 已授权,创建通知
                var ntf = new Notification(scriptName, { body: msg, lang: lang, icon: icon64 });
                if (msgTimeout) {
                    setTimeout(() => { ntf.close() }, msgTimeout);
                }
            } else if (Notification.permission !== "denied") {
                // 未明确拒绝,尝试申请通知权限
                Notification.requestPermission().then((permission) => {
                    if (permission === "granted") {
                        var ntf = new Notification(scriptName, { body: msg, lang: lang, icon: icon64 });
                        if (msgTimeout) {
                            setTimeout(() => { ntf.close() }, msgTimeout);
                        }
                    }
                })
            }
        }
    }
    /**
    * @type {string[]}
    */
    var blockList = GM_getValue("blockList");
    if (!blockList) {
        blockList = [];
    }
    const scriptName = "YouTube User Block"
    const icon64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAABJ0AAASdAHeZh94AAAAEXRFWHRTb2Z0d2FyZQBTbmlwYXN0ZV0Xzt0AAAumSURBVHic7Zt7cFN1Fsc/N7l9JamlLU1bCi20vPogdBGkYRcKuyLu4LQOtLAUUUcdd5ad0VFH1tG1qLM6iLrja1UcQReoCoWZRYZxFPBR1Ja1dqFPLJBCobRJDW1M0leSZv9I0yZtk9wUUBDPP2fOvef3u+ecnPO95/f73QgdHR1OQQCn04kgCDidcD3JQmdnh5PrmGQAgiBwvXKZKy080+P6kmXeF+F6kwWTqfNXDLgaavHn4gEx4KvyyquqZn9SDPiq/Fue3vQKH+zZf9XU7OWWZQPZ4JEWQ/LCBfO4Z90qtu3YzQd79o+4/0uQRbcwlBbevKgwDwHYumM3ArCmMM+v/rXGRe/aGJ2vKcyHgSAArCnM96t/LXHRKbF3XlOYNywIeX71rxVZDKZmilZ5Z0LRqvyga66vq51eazvDKUwZR5gy7urDgBGYsCofBIGt23eBGyP86PdYDHS0VGI8/1+6Os9g77OOcN5NYqgSxbjJxE68ieikuYSr1FccAwSLxewcSgvp/P3SfWzdvot771xNUWH+iPvtZ76g9fsDWDvOABCXnEPk+DRiEjWEq+KJiIwfdLzbrKfHoudiazXmH07T3lwBgDJ6MokzlhM3eXHQ9knmZrPZOdbBowXBoPuC87Wl9FgNxKVomTB9KeoUrc9f3RcZzpZzofEg7WfLCVeqmZhViDp18WUPgmCxmC9pLVCy2xWEtSsWkz3+GL3WdiZMu5nUOXd4/cpjpW6zHl3VTi6cPESYUs3UnPVEqTMveV43CRaL2XkpNWTrtfCvlzeyr+wCeQvjePjR5y6L48Op26ynruwlOlpriJ14E2nz1xMSprocGGBxwlBaDEZGgtypr+P7I5sBqDNr2bHnS/5y3zruXls4qhOGjm6ONugpr2/D0T+UhnIZaDMSmJ8ejzo6wm8gDGfLqfvyJQBmLNwwmA1jsd/pxBWAoYhI76Wba3ZxrmY30YmzyF66ETFUyXslpbz5zo4RQahtMrJlfwPN+h8JEUUUSiUqlRK5XI7D4cBisdJltWKz25mZEsO6pdPImhLrMwj2PivHDj5NR2sNk2atInnW6qDtH9wPsFqtTvCEAQF/sr2vi6aqdzHoPid1zlrS5tzhZZxnEApX3s6re6upqNcTFRVFUlIisbG+HTMajbS0tGIymcjJiOeBlRqU4SE+9U9X7URXVYI6dQlT5tyNGKoMaP9wWbBaLU73RXdkfMkOWxc1h4rptbaTmfuIT3R3B2FixiKE8RrSM2YSFRXl05HhZDKZaKg/QWKsgk33z/cbBHdJhCnjmHXzM8hDFD7tH02WuYSh3tiX7HJ+I73WduYu3+z31Va48nYmZizifH0Zir5zkpz/7bRwVt+k5JbMCJITopmlyaKzy8Fjbx/F2mPzOU6domXu8s30WtupObQRe1+XJH/cssxT8GwTPWW384IAc5dvJjI21a8zr+ypRhivYUZOPt9X7KOh/CO/+stnK9CmhTEpRkQzKZSCuS6MmDptGq3GLl7dW+13fGRsKnOXb8Zhs1J7eCMOW5dffzxlmasndg7rkZ3DnC9GEGDebS8EdL62ycjRBj3pGTPR5BaQtaiQ2rJSn0FQhAqkJ3qneLRSxrT4EJRKJekZM6mo11PbZAwYBO2KNxAEqDlUjMNmHdWf4bLoAgT/Ne+wWdGueGMAZPzTlv31REVFDaZ9uta1iqwtKwUgadqNNH77Ma264/RYOklISmb971/3OZ97rh0HT/L8/b4BFFxriXm3vcCRD++i5tBGNEsDY4JPDHDYuqg+WEyv1TD4mgtEho5umvVmkpISva6na/MGM+GTrY/RVP0lPZZOANpamvn0cJmXfoe1n5P6obpPSkrkxNmLGDq6A9oghioHMMFA9cHigJjgEwN0le/SazVIqnk3VTToCRHFUV91SdNu9DnuyX+8yNbtu/juWA2VJzvYU2n1alpiY2MJEUUqGvSS7HBjQq/VQNN37waPAWerP0Sv+4zM3EckOw9QUdeGQjl6pjR++7HfsW+/W8L6h57gxde3YeruH3FfoVRSUdcm2ZbI2FQycx9Br/uMlhP7fWKAbAgDXDdN+hqaq3eROmdt0Ks4ez+oVKMHoFV3XNIcvvRUKiX2kXHxS+oULalz1qKr3IZJXzvovKe/IzCg8ZvXiIiMH9HhSSGnE+Ry+aj33DUfiHzpyeVyr7KQSmkDq9LGb16ThgHTFzxAt1nP6aqdQT9MEMDhcIx6L1w1TtIcvvQcDsdg2gZDp6t20m3WM33BA9IwYFzCLFI0q9FVlWA4Wx7Uw0QZWCyjb3klps6WNIcvPYvFiigLyhwuNB5EV1VCimY14xKyAAkYAE5SZq8hPm0JdV++hNmok/zAnMwEurpGD8D0eX+UNIcvvS6rlZzMBMm2mI06vq/YQnzaElJmrwGQhgFueeq8+whTxlF5YIPkIOSkx2Oz2TEaR3ZtN4xPIifvrwHnaDn53YhrRqMRm91OTrq0jRazUUflgQ2EKeOYseBBYAx9gDxESfayZweD4G83103q6AhmJsfQ0tI66v1J6Tksu3cTUzS5g7UerhrHFE0uy+7d5LNtbmlpZWZyTMDNEnDtFbidz172rJezo2GA0Nvb6/Rsh4dze18Xxz95HEEmY+7yzQE7wtomI0+8cxSNJiuoJbCbGso/oraslKxFhaRr8zAajdTXn+DZ++b73STxdN7Z38/sZc8hhg61wb64/Mkn//6UPyWZPAT1lEW0nTpEc90+YhI1hCmifRqhjlbQ1PojNY3niI6OJjQ0NKgAxE2agUwuUltWisPRj97kYN6MOFbmpvkd50r7vyGTiZKdBw8M8MfFUKVr0hCFJEx4sEBDQkwEp06exGoNXDrDKV2bN7iUdrYf58ECjV99d82LIYoB5907Q/79giC+EBFDlWTf+hzhSjWVBzb4fUUqw0N4/v4cohQyampqMZlM0r3HtSPUFTaJiRkLOV9/hNK9//GpazhbTuWBDYQr1WTf6nJeij9uLi8uLn7KvTQMxN3l0GPRc7ryPQQEohNH/3VCRTm5syfQ3Gamqq4Jk+lHRFFEofANZEajkVOndDQ3n2PejDiefygfRUQYb76zg5AQkWyN93mArqqEhq9fQz15IRm5GxBDFZL88OJ9fb1OKbUynJ859j5njn9IdKKG7KXFfsGxtsnIjk9PcqL5IiEhIgqFa8dHFEXsdrtrV7jLis1mZ2ZyDOtu8d4VHr7b7NoVfoaO1momz/4Tk7OLgrbfzYW+vr4xnwx1ttVQ+/lzAH43Sd1k6OimokFPRV0bdo9zAVHmaqJy/JwLuIOwriCXzEhX+WUteZxxCbPGaj6AKwCXejJ04uuX+aH5KNGJGjIXPXzFTob++cLjfHSknfzcCTz06KbLczJks9k8MmCoTQxW7myroeGrl+mxGJgwfSmpv1l7+c4G/1fChcaDhKvUVF+cw/bSw/z5nrXcuWbFmO11y+JYa2c4H5eQhbZgK22nDtF07AMuNB5EPXA6HDeG0+H2gdNhw9lywlVq0n/3IAlTb0aLk4jIBLZsKwHwCMIYMcA7Ay4ftZ06zLn6fVguNgEQl6Llhtg0ohM1hKvUo3wfYKCjtZofjadpH3jFqmKmMCkjn4Spfxgx//YP9rJlW8lAJqwcs52CzWa7JAwIxLvNen5orqC9uRzLxaaAX4ioYqYQl6xlfHIOEZHxfucfHoQxYYDdbr8sGCBV7rEY6LYYRtyPUKkJV6mDnu/f7w8F4a6ilUGPF70vClxpOVylJlzlCY7+jAw8n8tpBjHBOwiBx4veD4drUb6rqADwDEKB5PFBfyV2tfK7igoQBIG3tu5EEATJmCB6LxDcPfK1KbvL4a2tOwcyYWXA8T85Blxp2TMIKZOSWLwwx6++4HA4rkgf8HPTF0fKWbwwcAMmOByOK9oHXO1c6O/v/0n7gKtNHhaA64+CPGv55dF1/68xwekcy5nrL4d+LYGf24Cfm/4PeQu9PrCfvs4AAAAASUVORK5CYII="
    const msgTimeout = 8000; //ms
    const lang = (navigator.language || navigator.userLanguage);
    var uiText = {
        menuR: "重置",
        menuI: "导入",
        menuE: "导出",
        block: "拉黑",
        unblock: "解封",
        resetSuccess: "黑名单重置成功!",
        ImportSuccess: "黑名单导入成功",
        InvaildFile: "错误,文件格式非法!",
        apiChecked: "GM_addValueChangeListener未定义,无法同步跨标签操作!"
    };
    switch (lang) {
        case "zh-CN":
            // do nothing
            break;
        case "en-US":
        default:
            // fallback to english.
            uiText = {
                menuR: "Reset",
                menuI: "Import",
                menuE: "Export",
                block: "block",
                unblock: "unblock",
                resetSuccess: "Blocklist resets successfully!",
                ImportSuccess: "Blocklist Import successfully!",
                InvaildFile: "Error, invaild file format!",
                apiChecked: "GM_addValueChangeListener undefined, unable to sync cross-tab operations!"
            };
    }
    GM_addStyle("ytd-comment-renderer[user-blocked='true']{height:1.8rem;opacity:0.3;overflow:hidden;box-shadow:0 0 5px #F44336;}"
        + "ytd-comment-renderer[user-blocked='true']:hover{height:auto;transition:all 1s;-ms-transition:all 1s;-o-transition:all 1s;-moz-transition:all 1s;-webkit-transition:all 1s;opacity:1;}"
        + "#block-button{width:32px;height:32px;border:none;border-radius:50%;outline:none;}"
        + "#block-button-item{cursor:pointer;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 };
    createNodeListener(targetNode, config, callback_top);
    registerCrossTabHandler();
    GM_registerMenuCommand(uiText.menuR, blockReset, "R");
    GM_registerMenuCommand(uiText.menuI, blockImport, "I");
    GM_registerMenuCommand(uiText.menuE, blockExport, "E");
})();