YouTube Video & Channel Blacklist

在 YouTube 中添加影片和頻道黑名單功能,分別封鎖不需要的內容

// ==UserScript==
// @license MIT
// @name         YouTube Video & Channel Blacklist
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  在 YouTube 中添加影片和頻道黑名單功能,分別封鎖不需要的內容
// @author       Your Name
// @match        https://www.youtube.com/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 定義黑名單儲存鍵
    const VIDEO_BLACKLIST_KEY = 'youtube_video_blacklist';
    const CHANNEL_BLACKLIST_KEY = 'youtube_channel_blacklist';
    const BUTTON_POSITION_KEY = 'youtube_button_positions';
    const BUTTON_STATE_KEY = 'youtube_button_states';

    // 從 GM_getValue 獲取黑名單
    function getVideoBlacklist() {
        return GM_getValue(VIDEO_BLACKLIST_KEY, []);
    }

    function getChannelBlacklist() {
        return GM_getValue(CHANNEL_BLACKLIST_KEY, []);
    }

    // 儲存黑名單到 GM_setValue
    function saveVideoBlacklist(blacklist) {
        GM_setValue(VIDEO_BLACKLIST_KEY, blacklist);
    }

    function saveChannelBlacklist(blacklist) {
        GM_setValue(CHANNEL_BLACKLIST_KEY, blacklist);
    }

    // 獲取和儲存按鈕位置
    function getButtonPositions() {
        return GM_getValue(BUTTON_POSITION_KEY, {
            videoBtn: { x: 200, y: 70 },
            channelBtn: { x: 10, y: 70 }
        });
    }

    function saveButtonPositions(positions) {
        GM_setValue(BUTTON_POSITION_KEY, positions);
    }

    // 獲取和儲存按鈕狀態
    function getButtonStates() {
        return GM_getValue(BUTTON_STATE_KEY, {
            videoBtn: { minimized: false, hidden: false },
            channelBtn: { minimized: false, hidden: false }
        });
    }

    function saveButtonStates(states) {
        GM_setValue(BUTTON_STATE_KEY, states);
    }

    // 安全地設置 innerHTML - 修復 Trusted Types 錯誤
    function setTrustedHTML(element, htmlString) {
        if (window.trustedTypes && window.trustedTypes.createPolicy) {
            const policy = window.trustedTypes.createPolicy('ytScriptPolicy', {
                createHTML: (s) => s
            });
            element.innerHTML = policy.createHTML(htmlString);
        } else {
            element.innerHTML = htmlString;
        }
    }

    // 提取影片標題
    function extractVideoTitle(element) {
        let titleElement = element.querySelector('h3 a, #video-title, yt-lockup-metadata-view-model__title, a[aria-label*="秒"]');
        if (!titleElement) return null;

        let title = titleElement.getAttribute('title') ||
                   titleElement.getAttribute('aria-label') ||
                   titleElement.textContent.trim();

        if (title && title.includes('秒')) {
            title = title.split(' ')[0];
        }

        return title;
    }

    // 提取頻道名稱
    function extractChannelName(element) {
        const channelSelectors = [
            'ytd-channel-name a',
            '#channel-name a',
            'ytd-video-meta-block a',
            'a.yt-formatted-string[href*="/channel/"]',
            'a.yt-formatted-string[href*="/user/"]',
            'a.yt-core-attributed-string__link'
        ];

        let channelElement = null;
        for (const selector of channelSelectors) {
            channelElement = element.querySelector(selector);
            if (channelElement) break;
        }

        if (!channelElement) return null;

        let channelName = channelElement.getAttribute('title') ||
                         channelElement.textContent.trim();

        if (channelName) {
            channelName = channelName.replace(/\s*•\s*$/, '').trim();
        }

        return channelName;
    }

    // 隱藏黑名單中的影片
    function hideBlacklistedVideos() {
        const blacklist = getVideoBlacklist();
        if (blacklist.length === 0) return;

        document.querySelectorAll('ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer').forEach(card => {
            const title = extractVideoTitle(card);
            if (title && blacklist.includes(title)) {
                card.style.display = 'none';
            }
        });
    }

    // 隱藏黑名單頻道的影片
    function hideBlacklistedChannels() {
        const blacklist = getChannelBlacklist();
        if (blacklist.length === 0) return;

        document.querySelectorAll('ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer').forEach(card => {
            const channelName = extractChannelName(card);
            if (channelName && blacklist.includes(channelName)) {
                card.style.display = 'none';
            }
        });
    }

    // 為每個影片添加移除按鈕
    function addRemoveButtons() {
        const videoBlacklist = getVideoBlacklist();
        const channelBlacklist = getChannelBlacklist();

        document.querySelectorAll('ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer').forEach(card => {
            // 檢查是否已經添加了按鈕
            if (card.querySelector('.video-remove-btn') && card.querySelector('.channel-remove-btn')) return;

            const title = extractVideoTitle(card);
            const channelName = extractChannelName(card);

            if (!title || !channelName) return;

            // 確保卡片有相對定位,以便按鈕可以絕對定位
            if (getComputedStyle(card).position === 'static') {
                card.style.position = 'relative';
            }

            // 添加影片移除按鈕
            if (!card.querySelector('.video-remove-btn') && !videoBlacklist.includes(title)) {
                const removeBtn = createVideoRemoveButton(title);
                card.appendChild(removeBtn);
            }

            // 添加頻道移除按鈕
            if (!card.querySelector('.channel-remove-btn') && !channelBlacklist.includes(channelName)) {
                const channelBtn = createChannelRemoveButton(channelName);
                card.appendChild(channelBtn);
            }
        });
    }

    // 創建影片移除按鈕
    function createVideoRemoveButton(title) {
        const removeBtn = document.createElement('button');
        removeBtn.className = 'video-remove-btn';
        removeBtn.title = '移除此影片';
        removeBtn.dataset.title = title;

        removeBtn.style.cssText = `
            position: absolute;
            top: 8px;
            right: 8px;
            z-index: 10;
            width: 30px;
            height: 30px;
            border-radius: 50%;
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            border: none;
            cursor: pointer;
            font-size: 16px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s ease;
        `;

        setTrustedHTML(removeBtn, '✕');

        removeBtn.addEventListener('mouseover', function() {
            this.style.backgroundColor = 'rgba(0, 0, 0, 0.9)';
            this.style.transform = 'scale(1.1)';
        });

        removeBtn.addEventListener('mouseout', function() {
            this.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
            this.style.transform = 'scale(1)';
        });

        removeBtn.addEventListener('click', function(e) {
            e.stopPropagation();
            e.preventDefault();

            const videoTitle = this.dataset.title;
            let blacklist = getVideoBlacklist();

            if (!blacklist.includes(videoTitle)) {
                blacklist.push(videoTitle);
                saveVideoBlacklist(blacklist);
                this.parentElement.style.display = 'none';
            }
        });

        return removeBtn;
    }

    // 創建頻道移除按鈕
    function createChannelRemoveButton(channelName) {
        const removeBtn = document.createElement('button');
        removeBtn.className = 'channel-remove-btn';
        removeBtn.title = '封鎖此頻道';
        removeBtn.dataset.channel = channelName;

        removeBtn.style.cssText = `
            position: absolute;
            top: 8px;
            right: 43px;
            z-index: 10;
            width: 30px;
            height: 30px;
            border-radius: 50%;
            background-color: rgba(255, 0, 0, 0.7);
            color: white;
            border: none;
            cursor: pointer;
            font-size: 16px;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s ease;
        `;

        setTrustedHTML(removeBtn, '✕');

        removeBtn.addEventListener('mouseover', function() {
            this.style.backgroundColor = 'rgba(255, 0, 0, 0.9)';
            this.style.transform = 'scale(1.1)';
        });

        removeBtn.addEventListener('mouseout', function() {
            this.style.backgroundColor = 'rgba(255, 0, 0, 0.7)';
            this.style.transform = 'scale(1)';
        });

        removeBtn.addEventListener('click', function(e) {
            e.stopPropagation();
            e.preventDefault();

            const channel = this.dataset.channel;
            let blacklist = getChannelBlacklist();

            if (!blacklist.includes(channel)) {
                blacklist.push(channel);
                saveChannelBlacklist(blacklist);

                // 隱藏這個頻道的所有影片
                hideBlacklistedChannels();
            }
        });

        return removeBtn;
    }

    // 顯示影片黑名單管理面板
    function showVideoBlacklistPanel() {
        const existingPanel = document.querySelector('.video-blacklist-panel');
        if (existingPanel) {
            existingPanel.remove();
            return;
        }

        const blacklist = getVideoBlacklist();

        const panel = document.createElement('div');
        panel.className = 'video-blacklist-panel';
        panel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: #1c1c1c;
            border: 1px solid #333;
            padding: 20px;
            z-index: 10000;
            max-height: 80vh;
            overflow-y: auto;
            width: 500px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.6);
            border-radius: 8px;
            color: #fff;
            font-family: inherit;
        `;

        if (blacklist.length === 0) {
            setTrustedHTML(panel, `
                <h2 style="margin-top: 0; color: #ffcc00; border-bottom: 1px solid #444; padding-bottom: 10px;">影片黑名單管理</h2>
                <p style="text-align: center; padding: 20px;">黑名單為空</p>
                <div style="margin-top: 20px; text-align: right;">
                    <button id="close-btn" style="padding: 8px 16px; border: none; border-radius: 5px; background-color: #6c757d; color: white; cursor: pointer;">關閉</button>
                </div>
            `);
        } else {
            setTrustedHTML(panel, `
                <h2 style="margin-top: 0; color: #ffcc00; border-bottom: 1px solid #444; padding-bottom: 10px;">影片黑名單管理</h2>
                <p style="color: #aaa; font-size: 14px;">已屏蔽 ${blacklist.length} 個影片</p>
                <ul id="video-blacklist-items" style="list-style: none; padding: 0; max-height: 300px; overflow-y: auto;"></ul>
                <div style="margin-top: 20px; text-align: right;">
                    <button id="clear-all-btn" style="padding: 8px 16px; border: none; border-radius: 5px; background-color: #dc3545; color: white; cursor: pointer; margin-right: 10px;">清空黑名單</button>
                    <button id="close-btn" style="padding: 8px 16px; border: none; border-radius: 5px; background-color: #6c757d; color: white; cursor: pointer;">關閉</button>
                </div>
            `);

            const blacklistItems = panel.querySelector('#video-blacklist-items');

            blacklist.forEach(title => {
                const li = document.createElement('li');
                li.style.marginBottom = '10px';
                li.style.padding = '8px';
                li.style.backgroundColor = '#2a2a2a';
                li.style.borderRadius = '4px';
                li.style.display = 'flex';
                li.style.justifyContent = 'space-between';
                li.style.alignItems = 'center';

                setTrustedHTML(li, `
                    <span style="flex-grow: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${title}</span>
                    <button class="restore-btn" data-title="${title}" style="background-color: #28a745; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer; margin-left: 10px;">恢復</button>
                `);

                blacklistItems.appendChild(li);
            });

            panel.querySelector('#clear-all-btn').addEventListener('click', function() {
                if (confirm('確定要清空影片黑名單嗎?所有隱藏的影片將重新顯示。')) {
                    saveVideoBlacklist([]);
                    panel.remove();
                    location.reload();
                }
            });

            panel.querySelectorAll('.restore-btn').forEach(btn => {
                btn.addEventListener('click', function() {
                    const title = this.dataset.title;
                    let blacklist = getVideoBlacklist();

                    if (blacklist.includes(title)) {
                        blacklist = blacklist.filter(t => t !== title);
                        saveVideoBlacklist(blacklist);
                        this.parentElement.remove();

                        if (blacklist.length === 0) {
                            panel.querySelector('#video-blacklist-items').innerHTML = '<p style="text-align: center; padding: 20px;">黑名單為空</p>';
                            panel.querySelector('#clear-all-btn').style.display = 'none';
                        }
                    }
                });
            });
        }

        panel.querySelector('#close-btn').addEventListener('click', function() {
            panel.remove();
        });

        document.body.appendChild(panel);
    }

    // 顯示頻道黑名單管理面板
    function showChannelBlacklistPanel() {
        const existingPanel = document.querySelector('.channel-blacklist-panel');
        if (existingPanel) {
            existingPanel.remove();
            return;
        }

        const blacklist = getChannelBlacklist();

        const panel = document.createElement('div');
        panel.className = 'channel-blacklist-panel';
        panel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: #1c1c1c;
            border: 1px solid #333;
            padding: 20px;
            z-index: 10000;
            max-height: 80vh;
            overflow-y: auto;
            width: 500px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.6);
            border-radius: 8px;
            color: #fff;
            font-family: inherit;
        `;

        if (blacklist.length === 0) {
            setTrustedHTML(panel, `
                <h2 style="margin-top: 0; color: #ffcc00; border-bottom: 1px solid #444; padding-bottom: 10px;">頻道黑名單管理</h2>
                <p style="text-align: center; padding: 20px;">黑名單為空</p>
                <div style="margin-top: 20px; text-align: right;">
                    <button id="close-btn" style="padding: 8px 16px; border: none; border-radius: 5px; background-color: #6c757d; color: white; cursor: pointer;">關閉</button>
                </div>
            `);
        } else {
            setTrustedHTML(panel, `
                <h2 style="margin-top: 0; color: #ffcc00; border-bottom: 1px solid #444; padding-bottom: 10px;">頻道黑名單管理</h2>
                <p style="color: #aaa; font-size: 14px;">已屏蔽 ${blacklist.length} 個頻道</p>
                <ul id="channel-blacklist-items" style="list-style: none; padding: 0; max-height: 300px; overflow-y: auto;"></ul>
                <div style="margin-top: 20px; text-align: right;">
                    <button id="clear-all-btn" style="padding: 8px 16px; border: none; border-radius: 5px; background-color: #dc3545; color: white; cursor: pointer; margin-right: 10px;">清空黑名單</button>
                    <button id="close-btn" style="padding: 8px 16px; border: none; border-radius: 5px; background-color: #6c757d; color: white; cursor: pointer;">關閉</button>
                </div>
            `);

            const blacklistItems = panel.querySelector('#channel-blacklist-items');

            blacklist.forEach(channel => {
                const li = document.createElement('li');
                li.style.marginBottom = '10px';
                li.style.padding = '8px';
                li.style.backgroundColor = '#2a2a2a';
                li.style.borderRadius = '4px';
                li.style.display = 'flex';
                li.style.justifyContent = 'space-between';
                li.style.alignItems = 'center';

                setTrustedHTML(li, `
                    <span style="flex-grow: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${channel}</span>
                    <button class="restore-btn" data-channel="${channel}" style="background-color: #28a745; color: white; border: none; border-radius: 3px; padding: 5px 10px; cursor: pointer; margin-left: 10px;">恢復</button>
                `);

                blacklistItems.appendChild(li);
            });

            panel.querySelector('#clear-all-btn').addEventListener('click', function() {
                if (confirm('確定要清空頻道黑名單嗎?所有隱藏的頻道將重新顯示。')) {
                    saveChannelBlacklist([]);
                    panel.remove();
                    location.reload();
                }
            });

            panel.querySelectorAll('.restore-btn').forEach(btn => {
                btn.addEventListener('click', function() {
                    const channel = this.dataset.channel;
                    let blacklist = getChannelBlacklist();

                    if (blacklist.includes(channel)) {
                        blacklist = blacklist.filter(c => c !== channel);
                        saveChannelBlacklist(blacklist);
                        this.parentElement.remove();

                        if (blacklist.length === 0) {
                            panel.querySelector('#channel-blacklist-items').innerHTML = '<p style="text-align: center; padding: 20px;">黑名單為空</p>';
                            panel.querySelector('#clear-all-btn').style.display = 'none';
                        }
                    }
                });
            });
        }

        panel.querySelector('#close-btn').addEventListener('click', function() {
            panel.remove();
        });

        document.body.appendChild(panel);
    }

    // 創建可拖動的管理按鈕
    function createManageButton(type, text, onClick, defaultX, defaultY) {
        const positions = getButtonPositions();
        const states = getButtonStates();

        const btn = document.createElement('button');
        btn.className = `${type}-manage-btn`;
        btn.textContent = text;

        // 設置初始位置
        const pos = positions[type] || { x: defaultX, y: defaultY };
        btn.style.left = `${pos.x}px`;
        btn.style.top = `${pos.y}px`;

        // 設置初始狀態
        const state = states[type] || { minimized: false, hidden: false };
        if (state.minimized) {
            minimizeButton(btn, type);
        }
        if (state.hidden) btn.style.display = 'none';

        btn.style.cssText += `
            position: fixed;
            z-index: 9999;
            padding: 10px 15px;
            border: none;
            border-radius: 5px;
            background-color: #ffcc00;
            color: #000;
            cursor: pointer;
            font-weight: bold;
            box-shadow: 0 2px 5px rgba(0,0,0,0.3);
        `;

        // 添加點擊事件
        btn.addEventListener('click', onClick);

        // 添加拖動功能
        makeDraggable(btn, type);

        // 添加右鍵菜單
        btn.addEventListener('contextmenu', (e) => {
            e.preventDefault();
            showButtonContextMenu(e, btn, type);
        });

        document.body.appendChild(btn);
        return btn;
    }

    // 使元素可拖動
    function makeDraggable(element, type) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;

        element.onmousedown = dragMouseDown;

        function dragMouseDown(e) {
            if (e.button !== 0) return; // 只允許左鍵拖動
            e.preventDefault();
            // 獲取鼠標位置
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e.preventDefault();
            // 計算新位置
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            // 設置元素的新位置
            element.style.top = (element.offsetTop - pos2) + "px";
            element.style.left = (element.offsetLeft - pos1) + "px";
        }

        function closeDragElement() {
            // 停止移動
            document.onmouseup = null;
            document.onmousemove = null;

            // 保存新位置
            const positions = getButtonPositions();
            positions[type] = {
                x: element.offsetLeft,
                y: element.offsetTop
            };
            saveButtonPositions(positions);
        }
    }

    // 顯示按鈕的右鍵菜單
    function showButtonContextMenu(e, button, type) {
        // 移除現有的上下文菜單
        const existingMenu = document.querySelector('.button-context-menu');
        if (existingMenu) existingMenu.remove();

        const menu = document.createElement('div');
        menu.className = 'button-context-menu';
        menu.style.cssText = `
            position: fixed;
            left: ${e.pageX}px;
            top: ${e.pageY}px;
            background-color: #2c2c2c;
            border: 1px solid #444;
            border-radius: 4px;
            padding: 5px 0;
            z-index: 10000;
            box-shadow: 0 2px 10px rgba(0,0,0,0.5);
        `;

        const minimizeOption = document.createElement('div');
        minimizeOption.textContent = '縮小/還原';
        minimizeOption.style.cssText = `
            padding: 8px 15px;
            cursor: pointer;
            color: #fff;
        `;
        minimizeOption.addEventListener('click', () => {
            toggleMinimizeButton(button, type);
            menu.remove();
        });

        const hideOption = document.createElement('div');
        hideOption.textContent = button.style.display === 'none' ? '顯示' : '隱藏';
        hideOption.style.cssText = `
            padding: 8px 15px;
            cursor: pointer;
            color: #fff;
        `;
        hideOption.addEventListener('click', () => {
            toggleHideButton(button, type);
            menu.remove();
        });

        menu.appendChild(minimizeOption);
        menu.appendChild(hideOption);

        document.body.appendChild(menu);

        // 點擊其他地方關閉菜單
        const closeMenu = (e) => {
            if (!menu.contains(e.target)) {
                menu.remove();
                document.removeEventListener('click', closeMenu);
            }
        };
        setTimeout(() => {
            document.addEventListener('click', closeMenu);
        }, 0);
    }

    // 縮小按鈕
    function minimizeButton(button, type) {
        button.textContent = type === 'videoBtn' ? '影' : '頻';
        button.style.width = '40px';
        button.style.height = '40px';
        button.style.padding = '0';
        button.style.borderRadius = '50%';
    }

    // 還原按鈕
    function restoreButton(button, type) {
        button.textContent = type === 'videoBtn' ? '管理影片黑名單' : '管理頻道黑名單';
        button.style.width = '';
        button.style.height = '';
        button.style.padding = '10px 15px';
        button.style.borderRadius = '5px';
    }

    // 切換按鈕的縮小狀態
    function toggleMinimizeButton(button, type) {
        const states = getButtonStates();
        const state = states[type] || { minimized: false, hidden: false };

        if (state.minimized) {
            // 還原按鈕
            restoreButton(button, type);
            state.minimized = false;
        } else {
            // 縮小按鈕
            minimizeButton(button, type);
            state.minimized = true;
        }

        states[type] = state;
        saveButtonStates(states);
    }

    // 切換按鈕的隱藏狀態
    function toggleHideButton(button, type) {
        const states = getButtonStates();
        const state = states[type] || { minimized: false, hidden: false };

        if (button.style.display === 'none') {
            button.style.display = 'block';
            state.hidden = false;
        } else {
            button.style.display = 'none';
            state.hidden = true;
        }

        states[type] = state;
        saveButtonStates(states);

        // 檢查是否需要顯示全局觸發按鈕
        checkGlobalTrigger();
    }

    // 檢查是否需要顯示全局觸發按鈕
    function checkGlobalTrigger() {
        const states = getButtonStates();
        const videoHidden = states.videoBtn?.hidden || false;
        const channelHidden = states.channelBtn?.hidden || false;

        if (videoHidden && channelHidden) {
            addGlobalTrigger();
        } else {
            removeGlobalTrigger();
        }
    }

    // 添加全局觸發按鈕
    function addGlobalTrigger() {
        if (document.querySelector('.global-trigger-btn')) return;

        const trigger = document.createElement('button');
        trigger.className = 'global-trigger-btn';
        trigger.textContent = '黑';
        trigger.title = '顯示黑名單管理按鈕';

        trigger.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 9999;
            width: 40px;
            height: 40px;
            border-radius: 50%;
            background-color: #ffcc00;
            color: #000;
            border: none;
            cursor: pointer;
            font-weight: bold;
            box-shadow: 0 2px 5px rgba(0,0,0,0.3);
        `;

        trigger.addEventListener('click', showAllButtons);

        document.body.appendChild(trigger);
    }

    // 移除全局觸發按鈕
    function removeGlobalTrigger() {
        const trigger = document.querySelector('.global-trigger-btn');
        if (trigger) trigger.remove();
    }

    // 顯示所有按鈕
    function showAllButtons() {
        const states = getButtonStates();
        states.videoBtn.hidden = false;
        states.channelBtn.hidden = false;
        saveButtonStates(states);

        document.querySelectorAll('.video-manage-btn, .channel-manage-btn').forEach(btn => {
            btn.style.display = 'block';
        });

        removeGlobalTrigger();
    }

    // 添加管理按鈕
    function addManageButtons() {
        if (document.querySelector('.video-manage-btn') && document.querySelector('.channel-manage-btn')) return;

        // 添加影片黑名單管理按鈕
        if (!document.querySelector('.video-manage-btn')) {
            createManageButton('videoBtn', '管理影片黑名單', showVideoBlacklistPanel, 200, 70);
        }

        // 添加頻道黑名單管理按鈕
        if (!document.querySelector('.channel-manage-btn')) {
            createManageButton('channelBtn', '管理頻道黑名單', showChannelBlacklistPanel, 10, 70);
        }

        // 檢查是否需要顯示全局觸發按鈕
        checkGlobalTrigger();
    }

    // 監聽動態內容加載
    const observer = new MutationObserver(function(mutations) {
        let shouldProcess = false;

        mutations.forEach(function(mutation) {
            if (mutation.addedNodes.length > 0) {
                shouldProcess = true;
            }
        });

        if (shouldProcess) {
            hideBlacklistedVideos();
            hideBlacklistedChannels();
            addRemoveButtons();
        }
    });

    // 開始監聽頁面變化
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // 頁面加載時執行
    window.addEventListener('load', function() {
        console.log('YouTube 黑名單腳本開始執行...');
        hideBlacklistedVideos();
        hideBlacklistedChannels();
        addRemoveButtons();
        addManageButtons();
    });

    // 添加一些樣式
    GM_addStyle(`
        .video-remove-btn:hover {
            background-color: rgba(0, 0, 0, 0.9) !important;
        }

        .channel-remove-btn:hover {
            background-color: rgba(255, 0, 0, 0.9) !important;
        }

        .video-blacklist-panel, .channel-blacklist-panel {
            font-family: 'YouTube Noto', Roboto, Arial, sans-serif;
        }

        .video-blacklist-panel h2, .channel-blacklist-panel h2 {
            font-size: 1.5rem;
            margin-bottom: 15px;
        }

        .restore-btn:hover {
            background-color: #218838 !important;
        }

        #clear-all-btn:hover {
            background-color: #c82333 !important;
        }

        #close-btn:hover {
            background-color: #5a6268 !important;
        }

        .button-context-menu div:hover {
            background-color: #3c3c3c;
        }
    `);
})();