// ==UserScript== // @name B站字幕自动开启 // @namespace http://tampermonkey.net/ // @version 0.6 // @description 自动点击开启字幕,支持Shift+C快捷键切换开启和关闭(可配置是否启用快捷键),全局有效 // @author wilsend // @match *://*.bilibili.com/video/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // ==/UserScript== (function () { 'use strict'; //// *************** 配置项,你可以更改的配置 *************** //// const config = { // ① 是否显示页面下方的消息提示,true显示 false关闭 showStatusMessages: true, // ② 开启字幕的延迟时间 单位:ms 1s = 1000ms delayTime: 2000, // ③ 是否启用Shift+C快捷键切换字幕状态,true启用 false禁用 (先切换到你想要的字幕开启/关闭状态后,再决定关闭快捷键功能) enableHotkey: true, // 按顺序查找可用字幕,当成功开启字幕时将停止向下查找。 == 你可以调整顺序,或着删除多余的 == subtitleSelectorList: [ { name: "shuangyu", //双语字幕 type: "switch", closeClass: '.bpx-player-ctrl-subtitle-close-switch', openClass: '.bui-switch-input[aria-label="双语字幕"]' }, { name: "aichinese", //AI中文 type: "button", closeClass: '.bpx-player-ctrl-subtitle-close-switch', openClass: '.bpx-player-ctrl-subtitle-language-item[data-lan="ai-zh"]' }, { name: "chinese", //中文字幕 type: "button", closeClass: '.bpx-player-ctrl-subtitle-close-switch', openClass: '.bpx-player-ctrl-subtitle-language-item[data-lan="zh-CN"]' }, { name: "english", //英语字幕 type: "button", closeClass: '.bpx-player-ctrl-subtitle-close-switch', openClass: '.bpx-player-ctrl-subtitle-language-item[data-lan="en-US"]' }, ] }; //// *************** 配置项结束 *************** //// const STORAGE_KEY = 'subtitleAutoEnabled'; let isEnabled; let currentUrl = location.href; let clickTimer = null; let indicatorTimer = null; // 保存所有监听器引用,便于清理 const eventListeners = []; // 初始化状态 try { const storedValue = GM_getValue(STORAGE_KEY); isEnabled = typeof storedValue === 'boolean' ? storedValue : true; GM_setValue(STORAGE_KEY, isEnabled); } catch (e) { console.error('读取存储状态失败,使用默认值:', e); isEnabled = true; GM_setValue(STORAGE_KEY, isEnabled); } // 初始化日志:增加快捷键状态提示 console.log(`脚本初始化状态: 字幕自动功能${isEnabled ? '开启' : '关闭'} | 快捷键功能${config.enableHotkey ? '启用(Shift+C切换)' : '禁用'}`); // 创建页面提示元素 function createStatusIndicator() { if (!config.showStatusMessages) return null; let indicator = document.getElementById('subtitle-auto-indicator'); if (indicator) return indicator; indicator = document.createElement('div'); indicator.id = 'subtitle-auto-indicator'; indicator.style.position = 'fixed'; indicator.style.bottom = '30px'; indicator.style.left = '50%'; indicator.style.transform = 'translateX(-50%)'; indicator.style.padding = '8px 16px'; indicator.style.borderRadius = '20px'; indicator.style.backgroundColor = 'rgba(0,0,0,0.8)'; indicator.style.color = 'white'; indicator.style.fontSize = '14px'; indicator.style.zIndex = '999999'; indicator.style.transition = 'all 0.3s ease'; indicator.style.opacity = '0'; indicator.style.pointerEvents = 'none'; document.body.appendChild(indicator); return indicator; } // 显示状态提示 function showStatusMessage(message, duration = 2000) { if (!config.showStatusMessages) return; const indicator = createStatusIndicator(); if (!indicator) return; if (indicatorTimer) { clearTimeout(indicatorTimer); } indicator.style.transition = 'none'; indicator.offsetHeight; // 触发重绘 indicator.style.transition = 'all 0.3s ease'; indicator.style.opacity = '0'; indicator.style.transform = 'translate(-50%, 20px)'; indicator.textContent = message; indicator.offsetHeight; // 触发重绘 indicator.style.opacity = '1'; indicator.style.transform = 'translate(-50%, 0)'; indicatorTimer = setTimeout(() => { indicator.style.opacity = '0'; indicator.style.transform = 'translate(-50%, 20px)'; }, duration); } // 点击开启字幕元素 function clickSubtitleElement() { let targetElement = null; for (const selector of config.subtitleSelectorList) { console.log(selector.name) targetElement = document.querySelector(selector.openClass); console.log(targetElement) if (!targetElement) continue; console.log(selector.name) try { if(selector.type == "switch"){ const switchObjects = config.subtitleSelectorList.filter(item => item.type === "button") let targetElement2 = document.querySelector(switchObjects[0].openClass); targetElement2.click() } targetElement.click(); console.log(`点击${selector.name}成功`) return true; } catch (e) { console.error('点击字幕按钮失败:', e); return false; } } } // 点击关闭字幕元素 function clickCloseSubtitleElement() { let closeElement = null; for (const selector of config.subtitleSelectorList) { closeElement = document.querySelector(selector.closeClass); if (!closeElement) continue; try { if (selector.type == 'button') { closeElement.click(); console.log(`点击${selector.name}成功`) return true; }else if(selector.type == 'switch'){ const switchElement = document.querySelector(selector.openClass); if (switchElement && switchElement.type === "checkbox" && switchElement.checked) { closeElement.click(); switchElement.click(); } } } catch (e) { console.error('点击字幕按钮失败:', e); return false; } } } // 处理URL变化 function handleUrlChange() { if (clickTimer) { clearTimeout(clickTimer); } // 仅在B站视频页面执行操作 if (currentUrl.includes('bilibili.com/video/')) { console.log('检测到视频URL变化,将在指定时间后执行操作'); clickTimer = setTimeout(() => { if (isEnabled) { clickSubtitleElement(); } else { clickCloseSubtitleElement(); } }, config.delayTime); } } // 切换功能状态 function toggleEnabledState() { isEnabled = !isEnabled; GM_setValue(STORAGE_KEY, isEnabled); if (isEnabled) { clickSubtitleElement(); } else { clickCloseSubtitleElement(); } const statusText = isEnabled ? '开启' : '关闭'; console.log(`字幕自动开启功能已${statusText}`); showStatusMessage(`中文字幕: ${statusText}`); } // 注册快捷键事件 function registerHotkey() { if (!config.enableHotkey) { console.log('快捷键功能已禁用(配置项enableHotkey=false)'); return; } const handleKeyDown = function (e) { if (e.shiftKey && e.key.toLowerCase() === 'c' && !e.ctrlKey && !e.altKey) { e.preventDefault(); e.stopPropagation(); toggleEnabledState(); } }; document.addEventListener('keydown', handleKeyDown, true); eventListeners.push({ target: document, type: 'keydown', listener: handleKeyDown, options: true }); } // 监听URL变化 function observeUrlChanges() { // 重写pushState const originalPushState = history.pushState; history.pushState = function (...args) { const result = originalPushState.apply(this, args); if (location.href !== currentUrl) { currentUrl = location.href; handleUrlChange(); } return result; }; // 重写replaceState const originalReplaceState = history.replaceState; history.replaceState = function (...args) { const result = originalReplaceState.apply(this, args); if (location.href !== currentUrl) { currentUrl = location.href; handleUrlChange(); } return result; }; // 监听popstate事件(前进/后退按钮) const handlePopstate = () => { if (location.href !== currentUrl) { currentUrl = location.href; handleUrlChange(); } }; window.addEventListener('popstate', handlePopstate); eventListeners.push({ target: window, type: 'popstate', listener: handlePopstate, options: false }); console.log('URL变化监听器已启动(基于history API)'); } // 资源清理函数 function cleanupResources() { console.log('清理脚本资源...'); // 清除计时器 if (clickTimer) { clearTimeout(clickTimer); clickTimer = null; } if (indicatorTimer) { clearTimeout(indicatorTimer); indicatorTimer = null; } // 移除所有事件监听器 eventListeners.forEach(({ target, type, listener, options }) => { target.removeEventListener(type, listener, options); }); eventListeners.length = 0; // 移除提示元素 const indicator = document.getElementById('subtitle-auto-indicator'); if (indicator && indicator.parentNode) { indicator.parentNode.removeChild(indicator); } } // 初始化 function init() { console.log('B站字幕自动点击脚本初始化'); registerHotkey(); // 注册快捷键 observeUrlChanges(); // 启动URL监听 // 注册页面卸载时的清理函数 const handleBeforeUnload = cleanupResources; window.addEventListener('beforeunload', handleBeforeUnload); eventListeners.push({ target: window, type: 'beforeunload', listener: handleBeforeUnload, options: false }); showStatusMessage(`中文字幕: ${isEnabled ? '开启' : '关闭'}`); // 初始页面加载时执行一次字幕操作 setTimeout(() => { if (isEnabled) { clickSubtitleElement(); } else { clickCloseSubtitleElement(); } }, config.delayTime); } // 页面加载完成后初始化 if (document.readyState === 'complete') { init(); } else { const handleLoad = () => { init(); window.removeEventListener('load', handleLoad); }; window.addEventListener('load', handleLoad); eventListeners.push({ target: window, type: 'load', listener: handleLoad, options: false }); } })();