B站净化器

Better Bilibili 是一款Tampermonkey脚本,旨在为您提供更清爽、无广告的B站浏览体验。它能自动过滤广告、推广内容,并允许您根据播放量、视频时长、UP主、标题关键词和分区等多种条件自定义隐藏视频,让您的B站首页和视频页面只显示您真正感兴趣的内容。轻松配置,即刻享受纯净B站!

目前為 2025-07-14 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         B站净化器
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Better Bilibili 是一款Tampermonkey脚本,旨在为您提供更清爽、无广告的B站浏览体验。它能自动过滤广告、推广内容,并允许您根据播放量、视频时长、UP主、标题关键词和分区等多种条件自定义隐藏视频,让您的B站首页和视频页面只显示您真正感兴趣的内容。轻松配置,即刻享受纯净B站!
// @author       Kiyuiro
// @match        https://*.bilibili.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        none
// @license      Apache-2.0
// ==/UserScript==

(function () {
    'use strict';

    function findElement(selector, timeout = 5000, interval = 50) {
        return new Promise((resolve) => {
            const start = Date.now();

            function check() {
                const element = document.querySelector(selector);
                if (element) {
                    resolve(element);
                } else if (Date.now() - start >= timeout) {
                    resolve(undefined);
                } else {
                    setTimeout(check, interval);
                }
            }

            check();
        });
    }

    function findElements(selector, timeout = 5000, interval = 50) {
        return new Promise((resolve) => {
            const start = Date.now();

            function check() {
                const elements = document.querySelectorAll(selector) || [];
                if (elements.length > 0) {
                    resolve(elements);
                } else if (Date.now() - start >= timeout) {
                    resolve([]);
                } else {
                    setTimeout(check, interval);
                }
            }

            check();
        });
    }

    function parseDuration(duration) {
        const parts = duration.split(':').map(Number);
        let hours = 0, minutes = 0, seconds = 0;
        if (parts.length === 2) {
            // mm:ss 格式
            [minutes, seconds] = parts;
        } else if (parts.length === 3) {
            // hh:mm:ss 格式
            [hours, minutes, seconds] = parts;
        } else {
            throw new Error('Invalid duration format');
        }
        return hours * 3600 + minutes * 60 + seconds;
    }

    const path = window.location.href;
    const paths = path.split('/').filter(it => it.length > 0);

    // 广告tips
    async function adblockClear() {
        const adblock = await findElement('.adblock-tips');
        if (adblock) {
            adblock.remove();
        }
    }

    // 视频过滤
    async function videoFilter() {
        const config = JSON.parse(localStorage.getItem('BiliFilterConfig')) || [];
        console.log(config);
        // 过滤
        setInterval(async () => {
            // 广告/播放量/时长 过滤
            if (config.ad || config.video.duration || config.video.playCount) {
                const items = await findElements(".bili-video-card__stats");
                items.forEach(it => {
                    // 广告
                    if (config.ad && it.innerHTML.length < 150) {
                        it.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
                            .remove();
                        // it.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
                        //     .style.backgroundColor = 'red'
                        return;
                    }
                    // 播放量过滤
                    if (config.video.playCount) {
                        const t = it.querySelectorAll('.bili-video-card__stats--text')
                        if (t[0]) {
                            if (t[0].innerHTML === '广告') return;
                            let count;
                            const countText = t[0].innerHTML;
                            if (countText.includes('万')) {
                                count = parseFloat(countText.replace('万', '')) * 10000;
                            } else if (countText.includes('亿')) {
                                count = parseFloat(countText.replace('亿', '')) * 100000000;
                            } else {
                                count = parseInt(countText);
                            }
                            if (count < config.video.playCount) {
                                // it.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
                                //     .style.backgroundColor = 'red'
                                it.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
                                    .remove();
                                return;
                            }
                        }
                        // if(t[1]) {
                        //     t[1].style.color = 'blue'
                        // }
                    }
                    // 时长过滤
                    if (config.video.duration) {
                        const t = it.querySelector('.bili-video-card__stats__duration')
                        const configDuration = parseDuration(config.video.duration);
                        if (t) {
                            const duration = parseDuration(t.innerHTML);
                            if (duration < configDuration) {
                                // it.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
                                //     .style.backgroundColor = 'green'
                                it.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
                                    .remove();
                            }
                        }
                    }
                })
            }
            // 推广过滤
            if (config.prom) {
                const items = await findElements('.vui_icon')
                items.forEach(it => {
                    it.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
                        .remove();
                })
            }
            // UP 主过滤
            if (config.upList && config.upList.length > 0) {
                const items = await findElements('.bili-video-card__info--author')
                items.forEach(it => {
                    if (config.upList.find(up => it.title.includes(up))) {
                        it.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
                            .remove();
                    }
                })
            }
            // 标题过滤
            if (config.titleList && config.titleList.length > 0) {
                const items = await findElements('.bili-video-card__info--tit')
                items.forEach(it => {
                    if (config.titleList.find(up => it.title.includes(up))) {
                        it.parentNode.parentNode.parentNode.parentNode.parentNode
                            .remove();
                    }
                })
            }
            // 分区过滤
            if (config.partList && config.partList.length > 0) {
                const items = await findElements('.floor-title')
                const isAll = config.partList[0] === 'ALL';
                items.forEach(it => {
                    if (isAll || config.partList.find(up => it.title.includes(up))) {
                        it.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
                            .remove();
                    }
                })
            }
            // 空元素清除
            const feeds = await findElements('.feed-card');
            feeds.forEach(it => {
                if (it.innerHTML.length < 10) {
                    it.remove();
                }
            })
        }, 100);
    }

    // 视频页
    async function videoPage() {
        if (paths.indexOf('video') === -1) {
            return
        }

        // 删除投币展示框
        setInterval(async () => {
            const toubi = await findElement('.bili-danmaku-x-guide-all');
            if (toubi) {
                toubi.remove();
            }
        }, 100)

        // 删除视频下广告
        setInterval(async () => {
            const ads = await findElements('.ad-report, #slide_ad, .activity-m-v1, .video-page-game-card-small');
            if (ads) {
                ads.forEach(ad => {
                    ad.innerHTML = "";
                });
            }
        }, 100);

        // 多 p 视频列表扩展
        const list = await findElement('.video-pod__body');
        const video = await findElement('.bpx-player-video-area')
        if (list) {
            if (video) {
                setInterval(() => {
                    list.style.maxHeight = video.offsetHeight - 165 + 'px'
                }, 10)
            } else {
                list.style.maxHeight = '400px';
            }
        }
    }

    // 添加配置窗口
    function addConfigOverlay() {
        // 过滤配置
        // 定义配置窗口的 HTML、CSS 和 JavaScript
        const configHtml = `
        <div id="BiliFilterConfigOverlay" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
            <div id="configWindow" class="config-window-container">

                <!-- 视频过滤设置 -->
                <div class="config-section">
                    <h3 class="text-xl font-semibold mb-4 text-center">视频过滤设置</h3>
                    
                    <!-- 广告 -->
                    <div class="flex items-center mb-4">
                        <label for="adToggle" class="switch-label">广告</label>
                        <label class="switch">
                            <input type="checkbox" id="adToggle" checked>
                            <span class="slider round"></span>
                        </label>
                    </div>
                    
                    <!-- 推广 -->
                    <div class="flex items-center mb-4">
                        <label for="promToggle" class="switch-label">推广</label>
                        <label class="switch">
                            <input type="checkbox" id="promToggle" checked>
                            <span class="slider round"></span>
                        </label>
                    </div>
                    
                    <label for="videoPlayCount">播放量:</label>
                    <input type="text" id="videoPlayCount" placeholder="输入播放量(例如:10000)">

                    <label for="videoDuration">视频时长:</label>
                    <input type="text" id="videoDuration" placeholder="输入视频时长(例如:05:30)">
                    
                    <label for="upList">UP过滤:</label>
                    <textarea id="upList" placeholder="每行输入一个 UP 名称"></textarea>
                    
                    <label for="titleList">标题过滤:</label>
                    <textarea id="titleList" placeholder="每行输入一个标题"></textarea>
                    
                    <label for="partList">分区过滤(填写ALL表示所有分区):</label>
                    <textarea id="partList" placeholder="每行输入一个分区,例如:直播、游戏、综艺"></textarea>
                </div>

                <!-- 按钮 -->
                <button id="saveConfig" class="bg-green-500 text-white px-4 py-2 rounded-md hover:bg-green-600">保存配置</button>
                <button id="closeConfig" class="bg-red-500 text-white px-4 py-2 rounded-md hover:bg-red-600">关闭</button>
            </div>
        </div>
    `;

        const configCss = `
        .config-window-container {
            background-color: #ffffff;
            padding: 30px;
            border-radius: 12px;
            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
            width: 100%;
            max-width: 500px;
            box-sizing: border-box;
            max-height: 80vh; /* 设置最大高度为视口高度的 80% */
            overflow-y: auto; /* 启用垂直滚动 */
        }

        .config-window-container h2, .config-window-container h3 {
            text-align: center;
            color: #1f2937;
            margin-bottom: 25px;
            font-weight: 700;
        }

        .config-section {
            margin-bottom: 20px;
            padding: 20px;
            border: 1px solid #e5e7eb;
            border-radius: 8px;
            background-color: #f9fafb;
        }

        .config-section label {
            display: block;
            margin-bottom: 10px;
            font-weight: 600;
            color: #374151;
            font-size: 0.95rem;
        }

        .config-section textarea,
        .config-section input[type="text"] {
            width: 100%;
            padding: 10px;
            margin-bottom: 15px;
            border: 1px solid #d1d5db;
            border-radius: 6px;
            box-sizing: border-box;
            min-height: 40px;
            resize: vertical;
            font-size: 0.9rem;
            color: #4b5563;
        }

        .config-section textarea {
            min-height: 80px;
        }

        .subsection {
            margin-top: 20px;
            padding-top: 15px;
            border-top: 1px dashed #d1d5db;
        }

        /* 开关样式 */
        .config-window-container .switch {
            position: relative;
            display: inline-block;
            width: 50px;
            height: 20px;
            vertical-align: middle;
        }

        .config-window-container .switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }

        .config-window-container .slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #d1d5db;
            transition: .4s;
            border-radius: 28px;
        }

        .config-window-container .slider:before {
            position: absolute;
            content: "";
            height: 16px;
            width: 24px;
            left: 2px;
            bottom: 2px;
            background-color: white;
            transition: .4s;
            border-radius: 28px;
        }

        .config-window-container input:checked + .slider {
            background-color: #2563eb;
        }

        .config-window-container input:focus + .slider {
            box-shadow: 0 0 1px #2563eb;
        }

        .config-window-container input:checked + .slider:before {
            transform: translateX(22px);
        }

        .switch-label {
            display: inline-block;
            vertical-align: middle;
            color: #374151;
            font-weight: 600;
            margin-right: 15px;
        }

        /* 隐藏的元素 */
        .config-window-container .hidden {
            display: none !important; /* 使用 !important 确保覆盖 Tailwind 的 display */
        }
    `;

        // 注入 Tailwind CSS CDN
        const tailwindScript = document.createElement('script');
        tailwindScript.src = 'https://cdn.tailwindcss.com';
        document.head.appendChild(tailwindScript);

        // 注入自定义 CSS
        const styleElement = document.createElement('style');
        styleElement.textContent = configCss;
        document.head.appendChild(styleElement);

        // 注入配置窗口 HTML
        document.body.insertAdjacentHTML('beforeend', configHtml);

        // 配置窗口的 DOM 元素
        const biliFilterConfigOverlay = document.getElementById('BiliFilterConfigOverlay');

        // 注入配置按钮,在头像下面
        const interval = setInterval(async () => {
            const links = await findElement('.links-item', 1000);
            if (links) {
                const configButton = document.createElement('div');
                configButton.className = 'single-link-item';
                configButton.insertAdjacentHTML('beforeend', `
                  <div class="link-title"><svg t="1752405413351" class="link-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7688" width="18" height="18"><path d="M512 298.705455a212.014545 212.014545 0 0 0-150.900364 62.487272 212.014545 212.014545 0 0 0-62.510545 150.900364 212.014545 212.014545 0 0 0 62.510545 150.900364A212.014545 212.014545 0 0 0 512 725.504a212.014545 212.014545 0 0 0 150.900364-62.510545 212.014545 212.014545 0 0 0 62.510545-150.900364 212.014545 212.014545 0 0 0-62.510545-150.900364A212.014545 212.014545 0 0 0 512 298.705455z m0 362.58909A149.504 149.504 0 0 1 362.705455 512 149.504 149.504 0 0 1 512 362.705455 149.504 149.504 0 0 1 661.294545 512 149.504 149.504 0 0 1 512 661.294545z m448-57.297454v-183.994182l-105.099636-30.603636c-4.002909-11.194182-8.610909-22.295273-13.800728-33.093818l52.712728-96-130.094546-130.094546-95.906909 52.596364a371.432727 371.432727 0 0 0-33.303273-13.917091l-30.487272-104.890182h-184.017455l-30.487273 104.890182c-11.310545 4.119273-22.504727 8.704-33.326545 13.917091l-95.883636-52.596364-130.094546 130.094546 52.689455 96.116363a371.432727 371.432727 0 0 0-13.893819 33.28L64 419.909818v183.994182l105.192727 30.906182c4.096 11.170909 8.704 22.295273 13.800728 33.093818L130.094545 763.694545l130.094546 130.094546 96.209454-52.48c10.891636 5.189818 21.992727 9.774545 33.28 13.800727l30.324364 104.890182h183.994182l30.813091-105.006545c11.287273-4.096 22.481455-8.704 33.28-13.893819l95.604363 52.805819 130.094546-130.094546-52.596364-96.302545c5.12-10.705455 9.611636-21.713455 13.707637-32.907637l105.099636-30.603636z m-124.695273-22.993455h-20.712727l-6.493091 21.294546a311.156364 311.156364 0 0 1-22.900364 55.109818l-10.496 19.688727 15.290182 15.313455 35.304728 64.581818-68.305455 68.119273-65.792-36.305455-14.103273-14.010182-19.688727 10.496a308.596364 308.596364 0 0 1-55.109818 22.900364l-21.410909 6.516364v21.410909l-20.689455 70.586182H463.825455l-20.805819-72.215273v-19.781818l-21.410909-6.516364a308.596364 308.596364 0 0 1-55.086545-22.900364l-19.781818-10.589091-15.127273 15.290182-64.581818 35.211637-68.119273-68.096 36.305455-65.815273 14.103272-14.103273-10.496-19.688727a305.570909 305.570909 0 0 1-22.900363-55.109818l-6.516364-21.294546h-21.480727l-70.609455-20.712727v-96.372364l72.215273-20.805818h19.898182l6.493091-21.294545c5.701818-18.804364 13.498182-37.306182 22.900363-55.109818l10.496-19.712-14.708363-14.685091-35.700364-65.396364 68.119273-68.119273 65.186909 35.816728 14.615273 14.592 19.688727-10.496a301.428364 301.428364 0 0 1 55.109818-22.900364l21.410909-6.516364v-20.48l20.782546-71.400727h96.395636l20.805818 71.400727v20.48l21.410909 6.516364a305.570909 305.570909 0 0 1 55.086546 22.900364l19.898181 10.58909 14.49891-14.801454 65.093818-35.700364 68.119272 68.119273-35.816727 65.186909-14.685091 14.708364 10.472728 19.688727c9.425455 17.687273 17.128727 36.212364 22.923636 55.109818l6.516364 21.294546h20.689454l71.400727 20.805818v96.395636l-71.307636 20.805818z" fill="#61666D" p-id="7689"></path></svg><span>过滤配置</span><!----></div>
                `);
                links.appendChild(configButton);
                configButton.addEventListener('click', () => {
                    biliFilterConfigOverlay.classList.remove('hidden'); // 显示配置窗口
                });
                clearInterval(interval)
            }
        }, 1200);

        // 配置窗口的 JavaScript 逻辑
        // 确保在 DOM 元素可用后再执行
        tailwindScript.onload = () => { // 等待 Tailwind 加载完成
            const saveConfigButton = document.getElementById('saveConfig');
            const closeConfigButton = document.getElementById('closeConfig');

            // 收集所有配置数据并返回一个对象
            const collectConfigData = () => {
                return {
                    ad: document.getElementById('adToggle').checked,
                    prom: document.getElementById('promToggle').checked,
                    upList: document.getElementById('upList').value.split('\n').filter(item => item.trim() !== ''),
                    titleList: document.getElementById('titleList').value.split('\n').filter(item => item.trim() !== ''),
                    partList: document.getElementById('partList').value.split('\n').filter(item => item.trim() !== ''),
                    video: {
                        playCount: parseInt(document.getElementById('videoPlayCount').value.trim()),
                        duration: document.getElementById('videoDuration').value.trim()
                    },
                };
            };

            // 保存配置按钮点击事件
            saveConfigButton.addEventListener('click', () => {
                const config = collectConfigData();
                console.log('保存的配置数据:', config);
                // 将配置数据保存到 localStorage
                localStorage.setItem('BiliFilterConfig', JSON.stringify(config));
                const messageBox = document.createElement('div');
                messageBox.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
                messageBox.innerHTML = `
                    <div class="bg-white p-6 rounded-lg shadow-xl text-center">
                        <p class="text-lg font-semibold mb-4">配置已保存,刷新页面生效。</p>
                        <button id="messageBoxClose" class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600">确定</button>
                    </div>
                `;
                document.body.appendChild(messageBox);
                document.getElementById('messageBoxClose').addEventListener('click', () => {
                    document.body.removeChild(messageBox);
                    biliFilterConfigOverlay.classList.add('hidden'); // 隐藏配置窗口
                });
            });

            // 关闭配置窗口按钮点击事件
            closeConfigButton.addEventListener('click', () => {
                biliFilterConfigOverlay.classList.add('hidden'); // 隐藏配置窗口
            });

            // 加载配置数据到表单
            const loadConfigData = (data) => {
                if (!data) { // 如果没有数据,则初始化为空对象
                    data = {
                        ad: false,
                        upList: [],
                        titleList: [],
                        video: {playCount: '', duration: ''},
                    };
                }
                document.getElementById('adToggle').checked = data.ad || true;
                document.getElementById('promToggle').checked = data.prom || true;
                document.getElementById('upList').value = data.upList ? data.upList.join('\n') : '';
                document.getElementById('titleList').value = data.titleList ? data.titleList.join('\n') : '';
                document.getElementById('partList').value = data.partList ? data.partList.join('\n') : '';
                if (data.video) {
                    document.getElementById('videoPlayCount').value = data.video.playCount || '';
                    document.getElementById('videoDuration').value = data.video.duration || '';
                }
            };

            // 初始加载配置数据
            loadConfigData(JSON.parse(localStorage.getItem('BiliFilterConfig')));
        };
    }

    function _main() {
        adblockClear();
        videoFilter();
        videoPage();
        addConfigOverlay();
    }

    _main();

})();