// ==UserScript== // @name B站自动开启中文字幕 // @namespace https://github.com/AICoder // @version 1.0.0 // @description 自动打开B站视频的中文字幕 // @author AICoder // @match *://*.bilibili.com/video/* // @match *://*.bilibili.com/bangumi/play/* // @match *://*.bilibili.com/list/* // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; // 配置 const CONFIG = { // 中文字幕的语言代码(按优先级排序) chineseLangCodes: ['zh-CN', 'zh-TW', 'ai-zh', 'zh-Hans', 'zh-Hant'], // 轮询间隔(毫秒) pollInterval: 100, // 最大等待时间(毫秒) maxWait: 15000, }; // 选择器 const SELECTORS = { subtitleBtn: '.bpx-player-ctrl-subtitle', subtitleBtnInner: '.bpx-player-ctrl-subtitle > div > span', closeSwitch: '.bpx-player-ctrl-subtitle-close-switch', langItem: '.bpx-player-ctrl-subtitle-language-item[data-lan]', activeLangItem: '.bpx-player-ctrl-subtitle-language-item.bpx-state-active', }; // 快速轮询等待元素 function waitForElement(selector, timeout = CONFIG.maxWait) { return new Promise((resolve, reject) => { const start = Date.now(); function check() { const el = document.querySelector(selector); if (el) { resolve(el); return; } if (Date.now() - start > timeout) { reject(new Error(`超时: ${selector}`)); return; } requestAnimationFrame(check); } check(); }); } // 检查字幕是否已开启 function isSubtitleEnabled() { const closeSwitch = document.querySelector(SELECTORS.closeSwitch); if (!closeSwitch) return null; return !closeSwitch.classList.contains('bpx-state-active'); } // 开启字幕 function enableSubtitle() { const closeSwitch = document.querySelector(SELECTORS.closeSwitch); if (!closeSwitch) return false; if (closeSwitch.classList.contains('bpx-state-active')) { closeSwitch.click(); return true; } return false; // 已经开启 } // 选择中文字幕 function selectChineseSubtitle() { const langItems = document.querySelectorAll(SELECTORS.langItem); if (!langItems || langItems.length === 0) return false; // 按优先级匹配中文字幕 for (const langCode of CONFIG.chineseLangCodes) { for (const item of langItems) { if (item.dataset.lan === langCode) { if (!item.classList.contains('bpx-state-active')) { item.click(); } return true; } } } // 模糊匹配 for (const item of langItems) { const lan = (item.dataset.lan || '').toLowerCase(); if (lan.includes('zh')) { if (!item.classList.contains('bpx-state-active')) { item.click(); } return true; } } // 兜底选第一个 const first = langItems[0]; if (first && !first.classList.contains('bpx-state-active')) { first.click(); } return true; } // 核心逻辑:一次性完成所有操作 async function run() { try { // 等待字幕按钮出现 await waitForElement(SELECTORS.subtitleBtn); // 点击字幕按钮打开菜单 const btn = document.querySelector(SELECTORS.subtitleBtnInner) || document.querySelector(SELECTORS.subtitleBtn); if (!btn) return; btn.click(); // 等待菜单元素出现(使用高频轮询) await waitForElement(SELECTORS.closeSwitch, 3000); // 检查字幕状态 const enabled = isSubtitleEnabled(); if (enabled === null) { // 无字幕 btn.click(); // 关闭菜单 return; } if (!enabled) { // 字幕关闭,开启它 enableSubtitle(); // 等待字幕开启后语言选项出现 await waitForElement(SELECTORS.langItem, 2000); } // 选择中文字幕 selectChineseSubtitle(); // 关闭菜单 setTimeout(() => { const player = document.querySelector('.bpx-player-container') || document.querySelector('.bpx-player'); if (player) player.click(); }, 50); } catch (e) { // 静默处理 } } // 延迟执行函数 function debounce(fn, delay) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } // 页面切换时重新执行 const debouncedRun = debounce(run, 300); // 监听URL变化(SPA页面) let lastUrl = location.href; const observeUrl = () => { const url = location.href; if (url !== lastUrl) { lastUrl = url; debouncedRun(); } }; // 使用更高效的监听方式 const pushState = history.pushState; history.pushState = function() { pushState.apply(this, arguments); observeUrl(); }; const replaceState = history.replaceState; history.replaceState = function() { replaceState.apply(this, arguments); observeUrl(); }; window.addEventListener('popstate', observeUrl); // 监听视频加载 function attachVideoListener() { const video = document.querySelector('video'); if (video && !video._subtitleAttached) { video._subtitleAttached = true; video.addEventListener('loadeddata', () => { debouncedRun(); }, { once: false }); } } // 使用 MutationObserver 监听视频元素 const videoObserver = new MutationObserver(() => { attachVideoListener(); }); // 页面加载后启动 function init() { attachVideoListener(); videoObserver.observe(document.body, { childList: true, subtree: true }); run(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();