bilibili_toy

toy

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         bilibili_toy
// @namespace    https://github.com/netnr
// @icon         https://zme.ink/favicon.svg
// @version      2025.12.20.1900
// @description  toy
// @author       netnr
// @license      MIT
// @match        *://www.bilibili.com/*
// ==/UserScript==

let nrBilibili = {
    init: async () => {
        if (location.pathname != "/") return;

        // 等待就绪
        for (let i = 0; i < 5; i++) {
            nrBilibili.domCards = document.querySelectorAll(nrBilibili.tsCardSelector);
            if (nrBilibili.domCards.length) {
                nrBilibili.domContainer = document.querySelector(".container");
                await nrBilibili.sleep(500);
                break;
            }
            await nrBilibili.sleep();
        }

        for (let domCard of nrBilibili.domCards) {
            nrBilibili.handleRocket(domCard);
        }

        if (nrBilibili.domContainer && !nrBilibili.tsObServer) {
            nrBilibili.tsObServer = new MutationObserver(function (mutationsList) {
                for (const mutation of mutationsList) {
                    if (mutation.type !== 'childList') continue;

                    // 新增的节点
                    mutation.addedNodes.forEach(addedNode => {
                        if (addedNode.nodeType === Node.ELEMENT_NODE) {
                            nrBilibili.handleRocket(addedNode);
                        }
                    });

                    // 子节点内容变动
                    let targetCard = nrBilibili.findCard(mutation.target);
                    if (targetCard) {
                        nrBilibili.handleRocket(targetCard);
                    }
                }
            });

            nrBilibili.tsObServer.observe(nrBilibili.domContainer, nrBilibili.tsConfig);
            console.warn("\n\n===== nrBilibili mount \n\n\n");
        }
    },

    // 查找卡片容器
    findCard: (node) => {
        if (!node || node.nodeType !== Node.ELEMENT_NODE) return null;

        if (nrBilibili.isCard(node)) return node;

        let parent = node.closest?.(nrBilibili.tsCardSelector);
        if (parent) return parent;

        return node.querySelector?.(nrBilibili.tsCardSelector);
    },

    // 判断是否为卡片
    isCard: (node) => {
        return node.classList?.contains("feed-card") || node.classList?.contains("bili-feed-card");
    },

    handleRocket: (domCard) => {
        if (!domCard || domCard.nodeType !== Node.ELEMENT_NODE || domCard.nodeName !== "DIV") {
            return;
        }

        let card = nrBilibili.findCard(domCard);
        if (!card) return;

        // 已处理过则跳过
        if (card.dataset.nrChecked === "done") return;

        let isAd = false;

        // 1. 检查广告文字(优先检查,无需等待 SVG)
        card.querySelectorAll("span").forEach(domSpan => {
            if (domSpan.textContent.trim() === "广告") {
                isAd = true;
            }
        });

        // 2. 检查 SVG 特征
        if (!isAd) {
            let svgNodes = card.querySelectorAll(".bili-video-card__mask svg");
            let hasSvgContent = Array.from(svgNodes).some(svg => svg.innerHTML.length > 0);

            // 有 SVG 但内容未加载,等待下次 mutation
            if (svgNodes.length > 0 && !hasSvgContent) return;

            card.querySelectorAll("svg").forEach(domSvg => {
                let svgLength = domSvg.innerHTML.length;
                if (Math.abs(svgLength - 3438) < 9) {
                    isAd = true;
                    console.debug("SVG特征匹配, 长度:", svgLength);
                }
            });

            // 有 SVG 容器但未匹配到特征,可能还在加载
            if (!isAd && svgNodes.length > 0) return;
        }

        // 标记已检查
        card.dataset.nrChecked = "done";

        if (isAd) {
            card.style.visibility = "hidden";
            console.debug("隐藏广告卡片");
        }
    },

    domContainer: null,
    domCards: null,
    tsCardSelector: ".feed-card, .bili-feed-card",
    tsObServer: null,
    tsConfig: {
        childList: true,
        attributes: false,
        subtree: true,
        characterData: false
    },

    destroy: () => {
        if (nrBilibili.tsObServer) {
            nrBilibili.tsObServer.disconnect();
        }
        console.warn("\n\n===== nrBilibili unmount \n\n\n");
    },

    sleep: (time) => new Promise(resolve => setTimeout(() => resolve(), time || 1000))
};

nrBilibili.init();