Bilibili-BlackList

屏蔽指定UP主的视频推荐,支持精确匹配和正则表达式匹配

// ==UserScript==
// @name         Bilibili-BlackList
// @namespace    https://github.com/HeavenTTT/bilibili-blacklist
// @version      1.0.7
// @author       HeavenTTT
// @description  屏蔽指定UP主的视频推荐,支持精确匹配和正则表达式匹配
// @match        *://*.bilibili.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @icon         https://www.bilibili.com/favicon.ico
// @require      https://update.greasyfork.org/scripts/533940/Bilibili-BlackList.user.js
// @license      MIT
// ==/UserScript==

(function () {
    "use strict";
    // 从存储中获取黑名单
    // 默认精确匹配黑名单(区分大小写)
    let exactBlacklist = GM_getValue("exactBlacklist", [
        "绝区零",
        "崩坏星穹铁道",
        "崩坏3",
        "原神",
        "米哈游miHoYo",
    ]);
    // 默认正则匹配黑名单
    let regexBlacklist = GM_getValue("regexBlacklist", [
        "王者荣耀",
        "和平精英",
        "PUBG",
        "绝地求生",
        "吃鸡",
    ]);
    // 保存黑名单到存储
    function saveBlacklists() {
        GM_setValue("exactBlacklist", exactBlacklist);
        GM_setValue("regexBlacklist", regexBlacklist);
    }
    //#region 核心功能 - 屏蔽视频卡片
    let isShowAll = false; // 是否显示全部视频卡片
    let isBlocking = false; // 是否正在执行屏蔽操作
    let lastBlockTime = 0; // 上次执行屏蔽的时间戳
    let blockedCards = new Set(); // 存储已屏蔽的视频卡片元素
    let processedCards = new WeakSet(); // 记录已处理过的卡片(避免重复处理)

    // 视频卡片选择器
    const selectorVideoCards = [
        ".feed-card", // 旧版卡片样式
        ".bili-video-card", // 新版卡片样式
        ".video-page-card-small", // 播放页小卡片
    ];
    /// 查找所有视频卡片
    function querySelectorAllVideoCard() {
        return selectorVideoCards.flatMap((selector) =>
            Array.from(document.querySelectorAll(selector))
        ); // 使用flatMap将所有选择器匹配到的元素合并为一个数组
    }

    function BlockCard(force = false) {
        const now = Date.now();
        // 节流控制:1秒内只执行一次 force参数用于强制执行
        if (!force) {
            if (isBlocking || now - lastBlockTime < 1000) {
                return;
            }
        }
        isBlocking = true;
        lastBlockTime = now;
        try {
            const cards = querySelectorAllVideoCard();
            console.log(`检测到 ${cards.length} 个视频卡片`);
            cards.forEach((card) => {
                if (processedCards.has(card)) {
                    return; // 如果卡片已经处理过,则跳过
                }
                // 获取视频信息
                GetVideoInfo(card, (upName, title) => {
                    console.log(`回调 : UP主: ${upName}, 标题: ${title}`);
                    if (upName && title) {
                        if(processedCards.has(card)) {
                            return; // 如果卡片已经处理过,则跳过
                        }
                        processedCards.add(card); // 将卡片标记为已处理
                        // 如果UP主名称和视频标题都存在
                        if (!card.querySelector(".bilibili-blacklist-block-btn")) {
                            // 创建屏蔽按钮
                            if (!isVideoPage()) {
                                const blockButton = createBlockButton(upName);
                                card.appendChild(blockButton); // 将按钮添加到卡片中
                            } else {
                                if (isInit) {
                                    const blockButton = createBlockButton(upName);
                                    card.querySelector(".card-box").style.position = "relative"; // 确保信息容器有相对定位
                                    card.querySelector(".card-box").appendChild(blockButton); // 将按钮添加到卡片信息中
                                    //card.appendChild(blockButton); // 将按钮添加到卡片中
                                }
                            }
                        }
                        // 检查是否在黑名单中
                        if (isBlacklisted(upName, title)) {
                            // 如果在黑名单中,则隐藏卡片
                            if (!blockedCards.has(card)) {
                                blockedCards.add(card); // 将卡片添加到已屏蔽列表
                            }
                            if (!isShowAll) {
                                card.style.display = "none"; // 隐藏卡片
                            }
                        }
                    }
                });
            });
            
            updateBlockCountDisplay();
        } finally {
            isBlocking = false; // 重置屏蔽状态
        }
    }
    // 更新屏蔽计数显示
    function updateBlockCountDisplay() {
        if (blockCountDiv) {
            blockCountDiv.textContent = `${blockedCards.size}`; // 更新右侧导航栏的屏蔽计数
        }
        // 更新面板标题(如果面板已打开)
        const panel = document.getElementById('bilibili-blacklist-panel');
        if (panel) {
            const titleElement = panel.querySelector('h3');
            if (titleElement) {
                titleElement.textContent = `已屏蔽视频 (${blockedCards.size})`;
            }
        }
    }
    // 暂时取消屏蔽/恢复屏蔽功能
    function toggleShowAll() {
        isShowAll = !isShowAll;
        blockedCards.forEach(card => {
            card.style.display = isShowAll ? "block" : "none";
        });
        btnTempUnblock.textContent = isShowAll ? '恢复屏蔽' : '取消屏蔽';
        btnTempUnblock.style.background = isShowAll ? '#dddddd' : '#fb7299';
        // 不需要更新blockCount,因为总数没有变化
    }
    const selectorUpName = [
        ".bili-video-card__info--author", // 主页
        ".bili-video-card__author", // 分类页面--> span title
        ".name", // 播放页面
    ];
    const selectorTitle = [
        ".bili-video-card__info--tit", // 主页
        ".bili-video-card__title", // 分类页面--> span title
        ".title", // 播放页面
    ];
    //获取视频信息 -UP主名称 -视频标题
    function GetVideoInfo(card, callback) {
        let flag = false; // 标志位,表示是否找到视频信息
        let upName = "";
        let title = "";
        if (card.style.display === "none") return false; // 如果卡片已经被隐藏,则跳过
        const upNameElement = card.querySelectorAll(selectorUpName.join(", ")); // 使用逗号分隔的选择器
        if (upNameElement.length > 0) {
            upName = upNameElement[0].textContent.trim(); // 获取第一个匹配到的元素的内容,并去除首尾空格
            //处理分类页面的UP主名称
            if (isCategoryPage()) {
                upName = upName.split(" · ")[0].trim();
                //console.log(`分类页面UP主名称: ${upName}`);
            }
        }
        const titleElement = card.querySelectorAll(selectorTitle.join(", ")); // 使用逗号分隔的选择器
        if (titleElement.length > 0) {
            title = titleElement[0].textContent.trim(); // 获取第一个匹配到的元素的内容,并去除首尾空格
        }
        if (upName && title) {
            flag = true;
            callback(upName, title);
        }
        return flag;
    }
    function isBlacklisted(upName, title) {
        if (exactBlacklist.includes(upName)) {
            return true; // 精确匹配黑名单
        }
        if (regexBlacklist.some((regex) => new RegExp(regex).test(upName))) {
            return true; // 正则匹配黑名单
        }
        if (regexBlacklist.some((regex) => new RegExp(regex).test(title))) {
            return true; // 新增标题正则黑名单
        }
        return false; // 不在黑名单中
    }
    /// 添加UP主到精确黑名单并刷新页面
    function addToExactBlacklistAndRefresh(upName) {
        try {
            if (!upName) return;
            if (!exactBlacklist.includes(upName)) {
                exactBlacklist.push(upName);
                saveBlacklists();
                updateExactList();
                BlockCard(true);
            }
        } catch (e) {
            console.error("添加黑名单出错:", e);
        }
    }
    //#endregion

    //#region 页面修改
    //创建屏蔽按钮(悬停在视频卡片上时显示)
    function createBlockButton(upName) {
        const btn = document.createElement("div");
        btn.className = "bilibili-blacklist-block-btn";
        btn.innerHTML = "×";
        btn.title = `屏蔽: ${upName}`;

        // 屏蔽按钮样式
        btn.style.position = "absolute";
        btn.style.top = "5px";
        btn.style.left = "5px";
        btn.style.width = "35px";
        btn.style.height = "20px";
        btn.style.backgroundColor = "#fb7299dd";
        btn.style.color = "white";
        btn.style.borderRadius = "5%";
        btn.style.display = "none";
        btn.style.justifyContent = "center";
        btn.style.alignItems = "center";
        btn.style.cursor = "pointer";
        btn.style.zIndex = "100";
        btn.style.fontSize = "16px";
        btn.style.fontWeight = "bold";
        btn.style.transition = "opacity 0.2s";

        // 点击时添加到黑名单
        btn.addEventListener("click", (e) => {
            e.stopPropagation(); // 防止事件冒泡
            addToExactBlacklistAndRefresh(upName);// 使用公共函数
        });

        return btn;
    }
    // 在右侧导航栏添加黑名单管理按钮
    let blockCountDiv = null;
    function addBlacklistManagerButton() {
        if (isVideoPage()) {
            return;
        }
        const rightEntry = document.querySelector('.right-entry');
        if (!rightEntry || rightEntry.querySelector('#bilibili-blacklist-manager')) {
            return;
        }

        const li = document.createElement('li');
        li.id = 'bilibili-blacklist-manager';
        li.style.cursor = 'pointer';
        li.className = 'v-popover-wrap';

        const btn = document.createElement('div');
        btn.className = 'right-entry-item';
        btn.style.display = 'flex';
        btn.style.flexDirection = 'column';
        btn.style.alignItems = 'center';
        btn.style.justifyContent = 'center';

        // 盾牌图标SVG
        const icon = document.createElement('div');
        icon.className = 'right-entry__outside';
        icon.innerHTML = getKirbySVG();
        //icon.style.color = '#fb7299'; // B站粉色
        icon.style.marginBottom = '-5px';
        blockCountDiv = document.createElement('span');
        //const text = document.createElement('div');
        blockCountDiv.textContent = `0`;
        btn.appendChild(icon);
        btn.appendChild(blockCountDiv);
        li.appendChild(btn);

        // 在导航中插入按钮
        if (rightEntry.children.length > 1) {
            rightEntry.insertBefore(li, rightEntry.children[1]);
        } else {
            rightEntry.appendChild(li);
        }

        // 如果面板不存在则创建
        let panel = document.getElementById('bilibili-blacklist-panel');
        if (!panel) {
            panel = createBlacklistPanel();
        }

        // 点击按钮时显示面板
        li.addEventListener('click', () => {
            if (panel.style.display === 'none') {
                panel.style.display = 'flex';
                //updateBlockCountDisplay(); // 更新屏蔽计数显示
            } else {
                panel.style.display = 'none';
            }

        });
    }
    // 创建黑名单管理面板
    let btnTempUnblock = null; // 暂时取消屏蔽按钮
    let exactList; // 精确匹配列表
    function updateExactList() {
        if (!exactList) return; // 安全检查

        exactList.innerHTML = '';
        exactBlacklist.forEach((upName, index) => {
            const item = document.createElement('li');
            item.style.display = 'flex';
            item.style.justifyContent = 'space-between';
            item.style.alignItems = 'center';
            item.style.padding = '8px 0';
            item.style.borderBottom = '1px solid #f1f2f3';

            const name = document.createElement('span');
            name.textContent = upName;
            name.style.flex = '1';

            const removeBtn = document.createElement('button');
            removeBtn.textContent = '移除';
            removeBtn.style.padding = '4px 8px';
            removeBtn.style.background = '#f56c6c'; // 红色
            removeBtn.style.color = '#fff';
            removeBtn.style.border = 'none';
            removeBtn.style.borderRadius = '4px';
            removeBtn.style.cursor = 'pointer';
            removeBtn.addEventListener('click', () => {
                exactBlacklist.splice(index, 1);
                saveBlacklists();
                updateExactList();
                BlockCard(true); // 更新后重新执行屏蔽
            });

            item.appendChild(name);
            item.appendChild(removeBtn);
            exactList.appendChild(item);
        });

        if (exactBlacklist.length === 0) {
            const empty = document.createElement('div');
            empty.textContent = '暂无精确匹配屏蔽UP主';
            empty.style.textAlign = 'center';
            empty.style.padding = '16px';
            empty.style.color = '#999';
            exactList.appendChild(empty);
        }
    }
    function createBlacklistPanel() {
        // 创建主面板容器
        const panel = document.createElement('div');
        panel.id = 'bilibili-blacklist-panel';
        // 面板样式(居中模态框)
        panel.style.position = 'fixed';
        panel.style.top = '50%';
        panel.style.left = '50%';
        panel.style.transform = 'translate(-50%, -50%)';
        panel.style.width = '500px';
        panel.style.maxHeight = '80vh';
        panel.style.backgroundColor = '#fff';
        panel.style.borderRadius = '8px';
        panel.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
        panel.style.zIndex = '99999';
        panel.style.overflow = 'hidden';
        panel.style.display = 'none';
        panel.style.flexDirection = 'column';

        // 选项卡
        const tabContainer = document.createElement('div');
        tabContainer.style.display = 'flex';
        tabContainer.style.borderBottom = '1px solid #f1f2f3';

        // 精确匹配选项卡
        const exactTab = document.createElement('div');
        exactTab.textContent = '精确匹配';
        exactTab.style.padding = '12px 16px';
        exactTab.style.cursor = 'pointer';
        exactTab.style.fontWeight = '500';
        exactTab.style.borderBottom = '2px solid #fb7299'; // 活动选项卡的粉色下划线

        // 正则匹配选项卡
        const regexTab = document.createElement('div');
        regexTab.textContent = '正则匹配(Up/标题)';
        regexTab.style.padding = '12px 16px';
        regexTab.style.cursor = 'pointer';

        tabContainer.appendChild(exactTab);
        tabContainer.appendChild(regexTab);

        // 面板头部
        const header = document.createElement('div');
        header.style.padding = '16px';
        header.style.borderBottom = '1px solid #f1f2f3';
        header.style.display = 'flex';
        header.style.justifyContent = 'space-between';
        header.style.alignItems = 'center';

        const title = document.createElement('h3');
        title.textContent = '已屏蔽UP主';
        title.style.margin = '0';
        title.style.fontSize = '16px';
        title.style.fontWeight = '500';

        // 暂时取消屏蔽
        btnTempUnblock = document.createElement('button');
        btnTempUnblock.textContent = isShowAll ? '恢复屏蔽' : '取消屏蔽';
        btnTempUnblock.style.padding = '8px 16px';
        btnTempUnblock.style.border = 'none';
        btnTempUnblock.style.borderRadius = '4px';
        btnTempUnblock.style.backgroundColor = '#fb7299';
        btnTempUnblock.style.color = '#fff';
        btnTempUnblock.style.cursor = 'pointer';
        btnTempUnblock.style.marginRight = '8px';
        btnTempUnblock.addEventListener('click', toggleShowAll);
        // 关闭按钮
        const closeBtn = document.createElement('button');
        closeBtn.textContent = '×';
        closeBtn.style.background = 'none';
        closeBtn.style.border = 'none';
        closeBtn.style.fontSize = '20px';
        closeBtn.style.cursor = 'pointer';
        closeBtn.style.padding = '0 8px';
        closeBtn.addEventListener('click', () => {
            panel.style.display = 'none';
        });


        header.appendChild(title);
        header.appendChild(btnTempUnblock);
        header.appendChild(closeBtn);
        // 内容区域
        const contentContainer = document.createElement('div');
        contentContainer.style.display = 'flex';
        contentContainer.style.flexDirection = 'column';
        contentContainer.style.flex = '1';
        contentContainer.style.overflow = 'hidden';

        // 精确匹配内容
        const exactContent = document.createElement('div');
        exactContent.style.padding = '16px';
        exactContent.style.overflowY = 'auto';
        exactContent.style.flex = '1';
        exactContent.style.display = 'block';

        // 正则匹配内容
        const regexContent = document.createElement('div');
        regexContent.style.padding = '16px';
        regexContent.style.overflowY = 'auto';
        regexContent.style.flex = '1';
        regexContent.style.display = 'none';

        // 添加新UP主的输入框
        const addExactContainer = document.createElement('div');
        addExactContainer.style.display = 'flex';
        addExactContainer.style.marginBottom = '16px';
        addExactContainer.style.gap = '8px';

        const exactInput = document.createElement('input');
        exactInput.type = 'text';
        exactInput.placeholder = '输入要屏蔽的UP主名称';
        exactInput.style.flex = '1';
        exactInput.style.padding = '8px';
        exactInput.style.border = '1px solid #ddd';
        exactInput.style.borderRadius = '4px';

        const addExactBtn = document.createElement('button');
        addExactBtn.textContent = '添加';
        addExactBtn.style.padding = '8px 16px';
        addExactBtn.style.background = '#fb7299'; // B站粉色
        addExactBtn.style.color = '#fff';
        addExactBtn.style.border = 'none';
        addExactBtn.style.borderRadius = '4px';
        addExactBtn.style.cursor = 'pointer';
        addExactBtn.addEventListener('click', () => {
            const upName = exactInput.value.trim();
            if (upName) {
                addToExactBlacklistAndRefresh(upName); // 使用公共函数
                exactInput.value = ''; // 清空输入框
            }
        });

        addExactContainer.appendChild(exactInput);
        addExactContainer.appendChild(addExactBtn);
        exactContent.appendChild(addExactContainer);

        // 添加正则表达式的输入框
        const addRegexContainer = document.createElement('div');
        addRegexContainer.style.display = 'flex';
        addRegexContainer.style.marginBottom = '16px';
        addRegexContainer.style.gap = '8px';

        const regexInput = document.createElement('input');
        regexInput.type = 'text';
        regexInput.placeholder = '输入正则表达式 (如: 小小.*Official)';
        regexInput.style.flex = '1';
        regexInput.style.padding = '8px';
        regexInput.style.border = '1px solid #ddd';
        regexInput.style.borderRadius = '4px';

        const addRegexBtn = document.createElement('button');
        addRegexBtn.textContent = '添加';
        addRegexBtn.style.padding = '8px 16px';
        addRegexBtn.style.background = '#fb7299';
        addRegexBtn.style.color = '#fff';
        addRegexBtn.style.border = 'none';
        addRegexBtn.style.borderRadius = '4px';
        addRegexBtn.style.cursor = 'pointer';
        addRegexBtn.addEventListener('click', () => {
            const regex = regexInput.value.trim();
            if (regex && !regexBlacklist.includes(regex)) {
                try {
                    new RegExp(regex); // 测试正则表达式是否有效
                    regexBlacklist.push(regex);
                    saveBlacklists();
                    regexInput.value = '';
                    updateRegexList();
                    BlockCard(true); // 更新后重新执行屏蔽
                } catch (e) {
                    alert('无效的正则表达式: ' + e.message);
                }
            }
        });

        addRegexContainer.appendChild(regexInput);
        addRegexContainer.appendChild(addRegexBtn);
        regexContent.appendChild(addRegexContainer);

        // 精确匹配列表
        exactList = document.createElement('ul');
        exactList.style.listStyle = 'none';
        exactList.style.padding = '0';
        exactList.style.margin = '0';

        // 正则匹配列表
        const regexList = document.createElement('ul');
        regexList.style.listStyle = 'none';
        regexList.style.padding = '0';
        regexList.style.margin = '0';

        // 更新正则匹配列表显示
        function updateRegexList() {
            regexList.innerHTML = '';
            regexBlacklist.forEach((regex, index) => {
                const item = document.createElement('li');
                item.style.display = 'flex';
                item.style.justifyContent = 'space-between';
                item.style.alignItems = 'center';
                item.style.padding = '8px 0';
                item.style.borderBottom = '1px solid #f1f2f3';

                const regexText = document.createElement('span');
                regexText.textContent = regex;
                regexText.style.flex = '1';
                regexText.style.fontFamily = 'monospace'; // 正则表达式使用等宽字体

                const removeBtn = document.createElement('button');
                removeBtn.textContent = '移除';
                removeBtn.style.padding = '4px 8px';
                removeBtn.style.background = '#f56c6c';
                removeBtn.style.color = '#fff';
                removeBtn.style.border = 'none';
                removeBtn.style.borderRadius = '4px';
                removeBtn.style.cursor = 'pointer';
                removeBtn.addEventListener('click', () => {
                    regexBlacklist.splice(index, 1);
                    saveBlacklists();
                    updateRegexList();
                    BlockCard(true); // 更新后重新执行屏蔽
                });

                item.appendChild(regexText);
                item.appendChild(removeBtn);
                regexList.appendChild(item);
            });

            // 如果没有项目则显示空状态
            if (regexBlacklist.length === 0) {
                const empty = document.createElement('div');
                empty.textContent = '暂无正则匹配屏蔽规则';
                empty.style.textAlign = 'center';
                empty.style.padding = '16px';
                empty.style.color = '#999';
                regexList.appendChild(empty);
            }
        }

        // 初始化列表
        updateExactList();
        updateRegexList();

        exactContent.appendChild(exactList);
        regexContent.appendChild(regexList);

        contentContainer.appendChild(exactContent);
        contentContainer.appendChild(regexContent);

        panel.appendChild(tabContainer);
        panel.appendChild(header);
        panel.appendChild(contentContainer);

        // 选项卡切换
        exactTab.addEventListener('click', () => {
            exactTab.style.borderBottom = '2px solid #fb7299';
            regexTab.style.borderBottom = 'none';
            exactContent.style.display = 'block';
            regexContent.style.display = 'none';
        });

        regexTab.addEventListener('click', () => {
            regexTab.style.borderBottom = '2px solid #fb7299';
            exactTab.style.borderBottom = 'none';
            exactContent.style.display = 'none';
            regexContent.style.display = 'block';
        });

        document.body.appendChild(panel);
        return panel;
    }
    // 添加全局样式
    GM_addStyle(`
        /* 屏蔽按钮悬停效果 */
        .bili-video-card:hover .bilibili-blacklist-block-btn {
            display: flex !important;
            opacity: 1 !important;
        }
        /* 屏蔽按钮悬停效果 - 支持card-box内的按钮 */
        .card-box:hover .bilibili-blacklist-block-btn {
            display: flex !important;
            opacity: 1 !important;
        }
        /* 修复视频卡片布局 */
        .bili-video-card__cover {
            contain: layout !important;
        }
        /* 确保屏蔽按钮可点击 */
        .bilibili-blacklist-block-btn {
            pointer-events: auto !important;
        }
        /* 面板样式 */
        #bilibili-blacklist-panel {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        }
        /* 按钮悬停效果 */
        #bilibili-blacklist-panel button {
            transition: background-color 0.2s;
        }
        #bilibili-blacklist-panel button:hover {
            opacity: 0.9;
        }
        /* 管理按钮悬停效果 */
        #bilibili-blacklist-manager:hover svg {
            transform: scale(1.1);
        }
        #bilibili-blacklist-manager svg {
            transition: transform 0.2s;
        }
        /* 输入框聚焦效果 */
        #bilibili-blacklist-panel input:focus {
            outline: none;
            border-color: #fb7299 !important;
        }


    `);
    //可爱的卡比图标
    function getKirbySVG() {
        return `
        <svg width="35" height="35" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"  >
            <ellipse cx="70" cy="160" rx="30" ry="15" fill="#cc3333" />
            <ellipse cx="130" cy="160" rx="30" ry="15" fill="#cc3333" />
            <ellipse cx="50" cy="120" rx="20" ry="20" fill="#ffb6c1" />
            <ellipse cx="150" cy="120" rx="20" ry="20" fill="#ffb6c1" />
            <circle cx="100" cy="110" r="60" fill="#ffb6c1" />
            <ellipse cx="80" cy="90" rx="10" ry="22" fill="blue" />
            <ellipse cx="80" cy="88" rx="10" ry="15" fill="black" />
            <ellipse cx="80" cy="82" rx="8" ry="12" fill="#ffffff" />
            <ellipse cx="80" cy="90" rx="10" ry="22" fill="#00000000" stroke="#000000" strokeWidth="4" />
            <ellipse cx="120" cy="90" rx="10" ry="22" fill="blue" />
            <ellipse cx="120" cy="88" rx="10" ry="15" fill="black" />
            <ellipse cx="120" cy="82" rx="8" ry="12" fill="#ffffff" />
            <ellipse cx="120" cy="90" rx="10" ry="22" fill="#00000000" stroke="#000000" strokeWidth="4" />
            <ellipse cx="60" cy="110" rx="8" ry="5" fill="#ff4466" />
            <ellipse cx="140" cy="110" rx="8" ry="5" fill="#ff4466" />
            <path d="M 90 118 Q 100 125, 110 118" stroke="black" strokeWidth="3" fill="transparent" />
        </svg>
    `;
    }
    //#endregion
    //##########################

    //#region 观察者
    // MutationObserver 检测动态加载的新内容(仅当节点可见时才触发)
    const observer = new MutationObserver((mutations) => {
        let shouldCheck = false;
        if (isVideoPage()) {
            mutations.forEach((mutation) => {
                if (mutation.addedNodes.length > 0) {
                    // 检查新增节点是否可见(有宽度或高度)
                    shouldCheck = Array.from(mutation.addedNodes).some((node) => {
                        // 仅检查元素节点(跳过文本节点、注释等)
                        if (node.nodeType !== Node.ELEMENT_NODE) return false;

                        // 检查元素或其子元素是否可见
                        const hasVisibleContent =
                            node.offsetWidth > 0 ||
                            node.offsetHeight > 0 ||
                            node.querySelector("[offsetWidth], [offsetHeight]");

                        return hasVisibleContent;
                    });
                }
            });
        } else {
            mutations.forEach((mutation) => {
                if (mutation.addedNodes.length > 0) {
                    shouldCheck = true;
                }
            });
        }

        // 如果有可见的新内容,延迟 1 秒后执行屏蔽(确保 DOM 完全渲染)
        if (shouldCheck) {
            processedCards = new WeakSet(); // 重置已处理卡片集合
            setTimeout(() => {
                BlockCard();
                addBlacklistManagerButton(); // 确保每次都添加黑名单管理按钮
                if (isMainPage()) {
                    BlockAD(); // 屏蔽页面广告
                }
                if (isVideoPage()) {
                    BlockVideoPageAd(); // 屏蔽视频页面广告
                }
            }, 1000);
        }
    });

    // 初始化观察者(监视 DOM 变化)
    let observerError = 0;
    function initObserver(container) {
        const rootNode =
            document.getElementById(container) || // B站的主容器 ID
            document.documentElement; // 回退到整个文档

        if (rootNode) {
            observer.observe(rootNode, {
                childList: true, // 监视添加/移除的节点
                subtree: true, // 监视所有后代
            });
            return true;
        } else {
            // 如果没找到根节点则重试
            setTimeout(() => initObserver(container), 500);
            console.warn("未找到根节点,正在重试...");
            observerError++;

            if (observerError > 10) {
                console.error("重试次数过多,停止重试。");
                return false;
            }
        }
    }
    //#endregion
    //#region 初始化函数

    let isInit = false; // 是否已经初始化
    function init() {
        // 重置状态
        isBlocking = false;
        lastBlockTime = 0;
        blockedCards = new Set(); // 使用 Set 存储已屏蔽的卡片
        processedCards = new WeakSet();
        if (isMainPage()) {
            initMainPage(); // 初始化主页
            BlockAD(); // 屏蔽主页广告
        } else if (isSearchPage()) {
            initSearchPage(); // 初始化搜索页
        } else if (isVideoPage()) {
            initVideoPage(); // 初始化播放页
            //BlockVideoPageAd(); // 屏蔽视频页面广告
        } else if (isCategoryPage()) {
            initCategoryPage(); // 初始化分类页
        } else {
            //console.log("🥚");
        }
        if(!isVideoPage()) {

            BlockCard(); // 初始化时立即执行屏蔽
            addBlacklistManagerButton(); // 添加黑名单管理按钮
        }
        isInit = true; // 标记为已初始化
        console.log("BiliBili黑名单脚本已加载🥔");
    }
    // 监听页面加载完成事件
    document.addEventListener("DOMContentLoaded", init);
    if (
        document.readyState === "interactive" ||
        document.readyState === "complete"
    ) {
        init();
    }
    // 检查当前页面是否为B站主页
    function isMainPage() {
        return location.pathname === "/";
    }

    function initMainPage() {
        initObserver("i_cecream"); // 传入B站主页的主容器ID
        console.log("主页已加载🍓");
    }
    /// -----搜索页----
    function isSearchPage() {
        //页面链接 https://search.bilibili.com/all?keyword=xxx
        // 通过检查路径名是否以 "/search" 开头来判断是否为搜索页
        return location.hostname === "search.bilibili.com";
    }
    function initSearchPage() {
        initObserver("i_cecream"); // 传入B站搜索页的主容器ID
        console.log("搜索页已加载🍉");
    }
    /// --- 播放页 ---
    function isVideoPage() {
        // 页面链接 https://www.bilibili.com/video/BV1xxxxxx
        // 通过检查路径名是否以 "/video/" 开头来判断是否为视频页
        return location.pathname.startsWith("/video/");
    }
    function initVideoPage() {
        initObserver("app"); // 传入B站播放页的主容器ID
        console.log("播放页已加载🍇");
    }
    // ---- 分类页 ----
    function isCategoryPage() {
        // 页面链接 https://www.bilibili.com/c/xxxxxx
        // 通过检查路径名是否以 "/c/" 开头来判断是否为分类页
        return location.pathname.startsWith("/c/");
    }
    function initCategoryPage() {
        initObserver("app"); // 传入B站分类页的主容器ID
        console.log("分类页已加载🍊");
    }
    //#endregion

    //#region 额外功能-屏蔽广告
    // 屏蔽广告
    function BlockAD() {
        // 屏蔽某些推广
        document.querySelectorAll('.floor-single-card').forEach(adCard => {
            adCard.remove();
        });
        // 屏蔽直播推广
        document.querySelectorAll('.bili-live-card').forEach(adCard => {
            adCard.remove();
        });
    }

    // 屏蔽视频页面广告(使用数组优化)
    function BlockVideoPageAd() {
        const adSelectors = [
            '.video-card-ad-small', // 右上角推广
            '.slide-ad-exp', // 大推广
            '.video-page-game-card-small', // 游戏推广
            '.activity-m-v1', // 活动推广
            '.video-page-special-card-small', // 特殊卡片推广
            '.ad-floor-exp' // 广告地板
        ];

        adSelectors.forEach(selector => {
            document.querySelectorAll(selector).forEach(adCard => {
                adCard.remove();
            });
        });
    }
    //#endregion
})();