哔哩哔哩自动打开字幕+倍速(兼容自动连播,可配置UP主白名单)

仅在指定UP主的视频下自动开启字幕,并支持设置默认倍速(可选择是否跟随白名单),新增跳过片头片尾功能

// ==UserScript==
// @name         哔哩哔哩自动打开字幕+倍速(兼容自动连播,可配置UP主白名单)
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  仅在指定UP主的视频下自动开启字幕,并支持设置默认倍速(可选择是否跟随白名单),新增跳过片头片尾功能
// @author       Scabish
// @match        https://www.bilibili.com/video/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let currentVideoId = '';

    // 初始化页面变化观察器
    function initUrlObserver() {
        // 监听URL变化(History API)
        const pushState = history.pushState;
        history.pushState = function() {
            pushState.apply(this, arguments);
            checkForVideoChange();
        };

        const replaceState = history.replaceState;
        history.replaceState = function() {
            replaceState.apply(this, arguments);
            checkForVideoChange();
        };

        window.addEventListener('popstate', checkForVideoChange);

        // 初始检查
        checkForVideoChange();
    }

    // 检查视频是否变化
    function checkForVideoChange() {
        const newVideoId = getBilibiliVideoIdFromUrlRegex();
        if (newVideoId && newVideoId !== currentVideoId) {
            currentVideoId = newVideoId;
            handlePage();
        }
    }

    // 从URL获取视频ID
    function getBilibiliVideoIdFromUrlRegex() {
        const url = window.location.href;
        const regex = /https:\/\/www\.bilibili\.com\/video\/(BV[a-zA-Z0-9]+)/;
        const match = url.match(regex);
        return (match && match[1]) || null;
    }

    // 处理页面逻辑
    async function handlePage() {
        console.log("[字幕脚本] 检测到新视频,开始处理...");

        // 等待视频播放器加载完成
        await waitForElm('.bpx-player-container');

        // 等待UP主信息加载
        const upName = await getUpName();
        if (!upName) {
            console.log("[字幕脚本] 未获取到UP主信息");
            return;
        }

        const whitelist = getWhitelist();
        const onlyWhitelistForSpeed = getOnlyWhitelistForSpeed();
        const skipIntroOutro = getSkipIntroOutro();

        console.log("[字幕脚本] 当前UP主:", upName,
                  " 白名单:", whitelist,
                  " 倍速跟随白名单:", onlyWhitelistForSpeed,
                  " 自动跳过片头片尾:", skipIntroOutro);

        // 处理字幕
        if (whitelist.includes(upName)) {
            openChineseSubtitle(); // 修改为新的字幕打开方式
        }

        // 处理倍速
        const speed = getDefaultSpeed();
        if (speed) {
            if (!onlyWhitelistForSpeed || whitelist.includes(upName)) {
                setPlaybackRate(speed);
            }
        }

        // 处理跳过片头片尾
        if (skipIntroOutro) {
            setupIntroOutroSkipper();
        }
    }

    // 获取UP主名称(兼容新旧界面)
    async function getUpName() {
        // 尝试多种选择器,兼容不同界面
        const selectors = [
            '.up-name',
            '.video-author-name',
            '.user-name'
        ];

        for (const selector of selectors) {
            const element = await waitForElm(selector, 5000); // 5秒超时
            if (element) {
                return element.textContent.trim();
            }
        }

        return null;
    }

    // 打开中文字幕(修改为点击中文选项的方式)
    function openChineseSubtitle() {
        // 多次尝试,确保能找到按钮
        const maxAttempts = 8;
        let attempts = 0;

        const tryOpenSubtitle = () => {
            if (attempts >= maxAttempts) {
                console.log("[字幕脚本] 尝试打开字幕达到最大次数,可能未找到字幕选项");
                return;
            }

            attempts++;

            // 首先检查字幕是否已经打开
            const subtitleTrack = document.querySelector('.bpx-player-subtitle-track');
            if (subtitleTrack && !subtitleTrack.classList.contains('hide')) {
                console.log("[字幕脚本] 字幕已打开");
                return;
            }

            // 步骤1: 找到并点击字幕按钮,打开字幕菜单
            const subtitleButtons = [
                '.bpx-player-ctrl-btn[aria-label="字幕"]',
                '.sub-btn',
                '.bpx-player-subtitle-switch'
            ].map(selector => document.querySelector(selector));

            const subtitleButton = subtitleButtons.find(btn => btn !== null);

            if (subtitleButton) {
                // 点击字幕按钮打开菜单
                subtitleButton.click();
                console.log("[字幕脚本] 已点击字幕按钮,打开菜单");

                // 步骤2: 等待并点击"中文"字幕选项
                setTimeout(() => {
                    const chineseSubtitle = document.querySelector('.bpx-player-ctrl-subtitle-language-item-text');
                    if (chineseSubtitle && chineseSubtitle.textContent.trim() === "中文") {
                        chineseSubtitle.click();
                        console.log("[字幕脚本] 已点击中文选项,打开字幕");
                    } else {
                        console.log("[字幕脚本] 未找到中文选项,将重试");
                        // 关闭字幕菜单
                        subtitleButton.click();
                        setTimeout(tryOpenSubtitle, 1000);
                    }
                }, 500); // 等待菜单展开
            } else {
                console.log("[字幕脚本] 未找到字幕按钮,将重试");
                setTimeout(tryOpenSubtitle, 1000);
            }
        };

        tryOpenSubtitle();
    }

    // 设置播放速度
    async function setPlaybackRate(speed) {
        // 等待倍速菜单加载
        const speedMenu = await waitForElm('.bpx-player-ctrl-playbackrate', 5000);
        if (!speedMenu) {
            console.warn("[字幕脚本] 未找到倍速控制元素");
            return;
        }

        // 点击打开倍速菜单
        speedMenu.click();

        // 等待菜单项加载
        await waitForElm('.bpx-player-ctrl-playbackrate-menu-item', 2000);

        // 尝试找到对应的倍速选项
        let item = document.querySelector(`.bpx-player-ctrl-playbackrate-menu-item[data-value="${speed}"]`);

        // 如果直接找不到,尝试通过文本查找
        if (!item) {
            const items = document.querySelectorAll('.bpx-player-ctrl-playbackrate-menu-item');
            for (const i of items) {
                if (i.textContent.trim() === `${speed}x`) {
                    item = i;
                    break;
                }
            }
        }

        if (item) {
            item.click();
            console.log("[字幕脚本] 设置倍速:", speed);
        } else {
            console.warn(`[字幕脚本] 未找到倍速 ${speed}x`);
            // 关闭菜单
            speedMenu.click();
        }
    }

    // 设置自动跳过片头片尾
    function setupIntroOutroSkipper() {
        // 监听跳过按钮出现
        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                const skipButtons = document.querySelectorAll('.bpx-player-ctrl-jump-item, .jump-btn');
                if (skipButtons.length > 0) {
                    skipButtons[0].click();
                    console.log("[字幕脚本] 已自动跳过片头/片尾");
                }
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: false,
            characterData: false
        });

        // 5分钟后停止观察,避免内存泄漏
        setTimeout(() => observer.disconnect(), 300000);
    }

    // =============== 白名单管理 ===============
    function getWhitelist() {
        return GM_getValue('subtitleWhitelist', []);
    }

    function setWhitelist(arr) {
        GM_setValue('subtitleWhitelist', arr);
    }

    // =============== 倍速管理 ===============
    function getDefaultSpeed() {
        return GM_getValue('defaultPlaybackRate', null);
    }

    function setDefaultSpeed(value) {
        GM_setValue('defaultPlaybackRate', value);
    }

    function getOnlyWhitelistForSpeed() {
        return GM_getValue('onlyWhitelistForSpeed', false);
    }

    function setOnlyWhitelistForSpeed(value) {
        GM_setValue('onlyWhitelistForSpeed', value);
    }

    // =============== 跳过片头片尾管理 ===============
    function getSkipIntroOutro() {
        return GM_getValue('skipIntroOutro', false);
    }

    function setSkipIntroOutro(value) {
        GM_setValue('skipIntroOutro', value);
    }

    // 注册(不可用) Tampermonkey 菜单命令
    GM_registerMenuCommand("设置字幕白名单", function() {
        const whitelist = getWhitelist();
        const input = prompt("请输入UP主白名单(用英文逗号分隔)", whitelist.join(","));
        if (input !== null) {
            const arr = input.split(",").map(x => x.trim()).filter(x => x);
            setWhitelist(arr);
            alert("已保存白名单: " + arr.join(", ") + ",刷新页面后生效");
        }
    });

    GM_registerMenuCommand("设置默认倍速", function() {
        const current = getDefaultSpeed() || "1";
        const input = prompt("请输入默认倍速(可选: 2, 1.5, 1.25, 1, 0.75, 0.5)", current);
        if (input !== null) {
            const valid = ["2", "1.5", "1.25", "1", "0.75", "0.5"];
            if (valid.includes(input)) {
                setDefaultSpeed(input);
                alert("已设置默认倍速: " + input + "x" + ",刷新页面后生效");
            } else {
                alert("输入无效,请输入以下之一: " + valid.join(", "));
            }
        }
    });

    GM_registerMenuCommand("切换:倍速仅对白名单UP主生效", function() {
        const current = getOnlyWhitelistForSpeed();
        setOnlyWhitelistForSpeed(!current);
        alert("倍速仅对白名单UP主生效: " + (!current ? "开启" : "关闭") + ",刷新页面后生效");
    });

    GM_registerMenuCommand("切换:自动跳过片头片尾", function() {
        const current = getSkipIntroOutro();
        setSkipIntroOutro(!current);
        alert("自动跳过片头片尾: " + (!current ? "开启" : "关闭") + ",新视频生效");
    });

    // =============== 工具函数 ===============
    function waitForElm(selector, timeout = 10000) {
        return new Promise((resolve) => {
            // 立即检查
            const element = document.querySelector(selector);
            if (element) {
                return resolve(element);
            }

            // 设置超时
            const timer = setTimeout(() => {
                observer.disconnect();
                resolve(null); // 超时返回null
            }, timeout);

            // 观察DOM变化
            const observer = new MutationObserver(() => {
                const element = document.querySelector(selector);
                if (element) {
                    clearTimeout(timer);
                    observer.disconnect();
                    resolve(element);
                }
            });

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

    // 初始化
    initUrlObserver();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址