YouTube Blacklist

在YouTube用户界面添加按钮,本地屏蔽用户的评论区发言。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         YouTube Blacklist
// @namespace    http://tampermonkey.net/
// @version      1.1
// @license      LGPL
// @description  在YouTube用户界面添加按钮,本地屏蔽用户的评论区发言。
// @author       Ptilopsis
// @match        https://www.youtube.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function() {
    'use strict';

    // 等待页面加载完成
    window.addEventListener('load', function() {
        setTimeout(init, 1000);
    });

    function init() {
        if (isChannelPage()) {
            const uid = extractUidFromUrl();
            if (uid && !document.querySelector('.floating-button')) {
                const floatingButton = createButton(uid);
                document.body.appendChild(floatingButton);
            }
        }

        // 监听评论区的变化
        observeComments();

        GM_registerMenuCommand('管理黑名单', showBlacklist);
    }

    function isChannelPage() {
        const path = window.location.pathname;
        return /^\/@[\w-]+\/?$/.test(path);
    }

    function extractUidFromUrl() {
        const path = window.location.pathname;
        const match = path.match(/^\/@([\w-]+)\/?$/);
        return match ? match[1] : null;
    }

    function createButton(uid) {
        const floatingButton = document.createElement('button');
        floatingButton.innerText = '加入黑名单';
        floatingButton.style.position = 'fixed';
        floatingButton.style.bottom = '20px';
        floatingButton.style.right = '20px';
        floatingButton.style.padding = '10px 20px';
        floatingButton.style.border = '1px solid';
        floatingButton.style.borderRadius = '4px';
        floatingButton.style.cursor = 'pointer';
        floatingButton.style.zIndex = 1000;

        const style = document.createElement('style');
        style.textContent = `
            @media (prefers-color-scheme: dark) {
                .floating-button {
                    background-color: rgba(34, 34, 34, 0.95);
                    color: white;
                    border-color: white;
                }
            }
            @media (prefers-color-scheme: light) {
                .floating-button {
                    background-color: rgba(255, 255, 255, 0.95);
                    color: black;
                    border-color: black;
                }
            }
        `;
        document.head.appendChild(style);
        floatingButton.classList.add('floating-button');

        floatingButton.onclick = () => {
            addToBlacklist(uid);
        };

        return floatingButton;
    }

    function addToBlacklist(uid) {
        let blacklist = GM_getValue('blacklist', []);
        if (!blacklist.includes(uid)) {
            blacklist.push(uid);
            GM_setValue('blacklist', blacklist);
            alert(`用户 ${uid} 已加入黑名单`);
        } else {
            alert(`用户 ${uid} 已在黑名单中`);
        }
    }

    function observeComments() {
        const target = document.querySelector('ytd-comments');

        if (target) {
            const observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    if (mutation.type === 'childList') {
                        mutation.addedNodes.forEach(node => {
                            if (node.tagName === 'YTD-COMMENT-THREAD-RENDERER') {
                                blockComment(node);
                                observeReplies(node);
                            }
                        });
                    }
                });
            });

            observer.observe(target, { childList: true, subtree: true });
        }
    }

    function observeReplies(commentThread) {
        const replies = commentThread.querySelector('ytd-comment-replies-renderer');

        if (replies) {
            const observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    if (mutation.type === 'childList') {
                        mutation.addedNodes.forEach(node => {
                            if (node.classList && node.classList.contains('style-scope') && node.classList.contains('ytd-comment-view-model')) {
                                blockComment(node);
                            }
                        });
                    }
                });
            });

            observer.observe(replies, { childList: true, subtree: true });
        }
    }

    function blockComment(comment) {
        let blacklist = GM_getValue('blacklist', []);
        const authorLink = findAuthorLink(comment);
        if (authorLink) {
            const commentUid = new URL(authorLink.href).pathname.split('/').pop().replace('@', '');
            if (blacklist.includes(commentUid)) {
                comment.style.display = 'none';
            }
        }
    }

    function findAuthorLink(element) {
        if (element.tagName === 'A' && element.href.includes('/@')) {
            return element;
        }
        for (let child of element.children) {
            const found = findAuthorLink(child);
            if (found) {
                return found;
            }
        }
        return null;
    }

    function showBlacklist() {
        let blacklist = GM_getValue('blacklist', []);
        const panel = document.createElement('div');
        panel.style.position = 'fixed';
        panel.style.top = '50%';
        panel.style.left = '50%';
        panel.style.transform = 'translate(-50%, -50%)';
        panel.style.border = '1px solid black';
        panel.style.padding = '20px';
        panel.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
        panel.style.zIndex = 10000;
        panel.style.maxWidth = '400px';
        panel.style.width = '80%';
        panel.style.borderRadius = '8px';

        const style = document.createElement('style');
        style.textContent = `
            @media (prefers-color-scheme: dark) {
                .blacklist-panel {
                    background-color: rgba(34, 34, 34, 0.95);
                    color: white;
                }
                .blacklist-panel button {
                    color: white;
                    background-color: #444;
                }
            }
            @media (prefers-color-scheme: light) {
                .blacklist-panel {
                    background-color: rgba(255, 255, 255, 0.95);
                    color: black;
                }
                .blacklist-panel button {
                    color: black;
                    background-color: #ddd;
                }
            }
        `;
        document.head.appendChild(style);
        panel.classList.add('blacklist-panel');

        const title = document.createElement('h2');
        title.innerText = '黑名单';
        title.style.textAlign = 'center';
        title.style.marginBottom = '15px';
        panel.appendChild(title);

        const closeButton = document.createElement('button');
        closeButton.innerText = '❌';
        closeButton.style.position = 'absolute';
        closeButton.style.top = '10px';
        closeButton.style.right = '10px';
        closeButton.style.background = 'none';
        closeButton.style.border = 'none';
        closeButton.style.fontSize = '16px';
        closeButton.style.cursor = 'pointer';
        closeButton.onclick = () => {
            document.body.removeChild(panel);
        };
        panel.appendChild(closeButton);

        const list = document.createElement('ul');
        list.style.maxHeight = '200px';
        list.style.overflowY = 'auto';
        blacklist.forEach(uid => {
            const item = document.createElement('li');
            const link = document.createElement('a');
            link.innerText = uid;
            link.href = `https://www.youtube.com/@${uid}`;
            link.target = '_blank';
            link.style.color = 'inherit';
            item.appendChild(link);
            item.style.marginBottom = '10px';

            const removeButton = document.createElement('button');
            removeButton.innerText = '移除';
            removeButton.style.marginLeft = '10px';
            removeButton.style.border = '1px solid';
            removeButton.style.borderRadius = '4px';
            removeButton.style.cursor = 'pointer';
            removeButton.onclick = () => {
                removeFromBlacklist(uid);
                panel.remove();
                showBlacklist();
            };
            item.appendChild(removeButton);
            list.appendChild(item);
        });
        panel.appendChild(list);

        document.body.appendChild(panel);
    }

    function removeFromBlacklist(uid) {
        let blacklist = GM_getValue('blacklist', []);
        blacklist = blacklist.filter(item => item !== uid);
        GM_setValue('blacklist', blacklist);
        alert(`用户 ${uid} 已从黑名单中移除`);
    }
})();