// ==UserScript== // @name Bilibili CC字幕实时显示插件 // @name:en Bilibili CC Subtitle Extractor // @namespace http://tampermonkey.net/ // @version 1.0.2 // @description 在B站播放器中集成CC字幕列表 // @description:en Integrate CC subtitle list in Bilibili video player // @author Zane // @match *://*.bilibili.com/video/* // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 字幕获取模块 const SubtitleFetcher = { // 获取视频信息 async getVideoInfo() { console.log('Getting video info...'); const info = { aid: window.aid || window.__INITIAL_STATE__?.aid, bvid: window.bvid || window.__INITIAL_STATE__?.bvid, cid: window.cid }; if (!info.cid) { const state = window.__INITIAL_STATE__; info.cid = state?.videoData?.cid || state?.epInfo?.cid; } if (!info.cid && window.player) { try { const playerInfo = window.player.getVideoInfo(); info.cid = playerInfo.cid; info.aid = playerInfo.aid; info.bvid = playerInfo.bvid; } catch (e) { console.log('Failed to get info from player:', e); } } console.log('Video info:', info); return info; }, // 获取字幕配置 async getSubtitleConfig(info) { console.log('Getting subtitle config...'); const apis = [ `//api.bilibili.com/x/player/v2?cid=${info.cid}&bvid=${info.bvid}`, `//api.bilibili.com/x/v2/dm/view?aid=${info.aid}&oid=${info.cid}&type=1`, `//api.bilibili.com/x/player/wbi/v2?cid=${info.cid}` ]; for (const api of apis) { try { console.log('Trying API:', api); const res = await fetch(api); const data = await res.json(); console.log('API response:', data); if (data.code === 0 && data.data?.subtitle?.subtitles?.length > 0) { return data.data.subtitle; } } catch (e) { console.log('API failed:', e); } } return null; }, // 获取字幕内容 async getSubtitleContent(subtitleUrl) { console.log('Getting subtitle content from:', subtitleUrl); try { const url = subtitleUrl.replace(/^http:/, 'https:'); console.log('Using HTTPS URL:', url); const res = await fetch(url); const data = await res.json(); console.log('Subtitle content:', data); return data; } catch (e) { console.error('Failed to get subtitle content:', e); return null; } } }; // 时间格式化模块 const TimeFormatter = { formatTime(seconds) { const mm = String(Math.floor(seconds/60)).padStart(2,'0'); const ss = String(Math.floor(seconds%60)).padStart(2,'0'); return `${mm}:${ss}`; }, // 如果需要其他格式的时间显示,可以添加更多方法 formatTimeWithMs(seconds) { const date = new Date(seconds * 1000); const mm = String(Math.floor(seconds/60)).padStart(2,'0'); const ss = String(Math.floor(seconds%60)).padStart(2,'0'); const ms = String(date.getMilliseconds()).slice(0,3).padStart(3,'0'); return `${mm}:${ss},${ms}`; } }; // UI渲染模块更新 const SubtitleUI = { injectStyles() { const style = document.createElement('style'); style.textContent = ` .subtitle-container { font-family: "PingFang SC", HarmonyOS_Regular, "Helvetica Neue", "Microsoft YaHei", sans-serif; font-size: 12px; -webkit-font-smoothing: antialiased; color: rgb(24, 25, 28); margin-top: 12px; } .subtitle-container * { scrollbar-width: thin; scrollbar-color: #99a2aa #fff; } .subtitle-container *::-webkit-scrollbar { width: 4px; } .subtitle-container *::-webkit-scrollbar-track { background: transparent; } .subtitle-container *::-webkit-scrollbar-thumb { background-color: #99a2aa; border-radius: 2px; } .subtitle-header { display: flex; align-items: center; background-color: rgb(241, 242, 243); height: 44px; padding: 0 12px; border-radius: 6px; cursor: pointer; user-select: none; position: relative; } .subtitle-content { background: var(--bg1, #fff); height: 0; overflow: hidden; transition: all 0.3s; } .subtitle-function { display: flex; align-items: center; height: 36px; padding: 0 12px; border-bottom: 1px solid var(--border, #e3e5e7); } .subtitle-function-btn { display: flex; align-items: center; cursor: pointer; color: #999; } .subtitle-function-btn:first-child { width: 60px; } .subtitle-function-btn:last-child { margin-left: 12px; } .subtitle-wrap { height: 393px; overflow-y: auto; overscroll-behavior: contain; } .subtitle-item { display: flex; align-items: center; padding: 0 12px; height: 24px; transition: background-color 0.3s; cursor: pointer; } .subtitle-item:hover { background: var(--bg2, #f1f2f3); } .subtitle-item.active { background: var(--bg2, #f1f2f3); color: var(--brand_blue, #00a1d6); } .subtitle-time { width: 60px; color: #999; flex-shrink: 0; } .subtitle-text { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin: 0 12px; } .arrow-icon { margin-right: 8px; transition: transform 0.3s; } .arrow-icon.expanded { transform: rotate(90deg); } .bui-collapse-wrap { width: 350px; } `; document.head.appendChild(style); }, createSubtitleUI() { const container = document.createElement('div'); container.className = 'subtitle-container'; // 头部 const header = document.createElement('div'); header.className = 'subtitle-header'; header.innerHTML = `
字幕列表 `; // 内容区 const content = document.createElement('div'); content.className = 'subtitle-content'; const function_bar = document.createElement('div'); function_bar.className = 'subtitle-function'; function_bar.innerHTML = `