您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
B站观影 弹幕 字幕替换
当前为
// ==UserScript== // @name B站大会员影视+弹幕+字幕 // @namespace http://tampermonkey.net/ // @version 1.6 // @description B站观影 弹幕 字幕替换 // @author Polygon // @match https://www.cuan.la/m3u8.php* // @match https://vip.parwix.com/* // @require https://gf.qytechs.cn/scripts/407985-ajax-hook/code/Ajax-hook.js?version=940269 // @include https://www.bilibili.com/bangumi/play/* // @include https://www.bilibili.com/video/BV* // @require http://code.jquery.com/jquery-1.11.0.min.js // @grant GM_xmlhttpRequest // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @run-at document-ildm // @connect * // ==/UserScript== async function dataQuery() { 'use strict' var cid, aid if (document.URL.startsWith('https://www.bilibili.com/')) { // 网页加载完后在bili网站获取cid,aid,并储存 (function() { if (typeof cid == "undefined") { cid = unsafeWindow.__INITIAL_STATE__.epInfo.cid console.log('更新cid=' + unsafeWindow.__INITIAL_STATE__.epInfo.cid) GM_setValue('cid', cid) } if (typeof aid == "undefined") { aid = unsafeWindow.__INITIAL_STATE__.epInfo.aid console.log('更新aid=' + aid) GM_setValue('aid', aid) } })() var danmu = { code: 23, msg: "success", dannum: 0, danmuku: [], name: cid } // 字幕查询 let subtitleApi = 'https://api.bilibili.com/x/player/v2?' let season_id = document.querySelector("meta[property='og:url']").content.match(/ss(\d+)/g)[0].replace('ss', '') let params = ['aid=' + aid, 'cid=' + cid, 'season_id=' + season_id] console.log(subtitleApi + params.join('&')) let subtitleUrl = await fetch(subtitleApi + params.join('&'), { credentials: 'include' }) .then(res => res.json()) .then(res => { console.log(res) if (!res['data']['subtitle']['subtitles'].length) { console.log('未发现字幕'); return '' } else { // 0中文简体 1中文繁体 let index = (res['data']['subtitle']['subtitles'][0]['lan_doc'].search('简体') !== -1) ? 0: 1 let url = 'https:' + res['data']['subtitle']['subtitles'][index]['subtitle_url'] console.log(`发现字幕 [${res['data']['subtitle']['subtitles'][index]['lan_doc']}]- ` + url) return url } }) var subtitleArray = [] if (subtitleUrl) { subtitleArray = await fetch(subtitleUrl) .then(res => res.json()) .then(res => res.body) console.log(subtitleArray) } GM_setValue('subtitleArray', subtitleArray) // 弹幕 let danmuApi = `https://api.bilibili.com/x/v1/dm/list.so?oid=${cid}` danmu = await fetch(danmuApi, { credentials: 'include' }) .then(res => res.text()) .then(restxt => { let matchObj = restxt.match(/<d p=".+?">.+?<\/d>/g) matchObj.forEach(ele => { let r = /<d p="(.+?)">(.+?)<\/d>/g.exec(ele) let params = r[1].split(',') let content = r[2] let time = params[0] let direction = parseInt(params[1]) let fontsize = params[2] let color = '#' + parseInt(params[3]).toString(16) let direction_info if (direction <= 3) { direction_info = 'right' } else if (direction == 4) { direction_info = 'bottom' } else if (direction == 5) { direction_info = 'top' } else { direction_info = 'right' } danmu.danmuku.push([time, direction_info, color, "", content, "", "", `${fontsize}px`]) }) danmu.danmuku.push(["0", 'top', '#FF616D', "", `替换弹幕源成功,前方共有${danmu.danmuku.length}条弹幕,请做好准备哟`, "", "", "25px"]) danmu.dannum = danmu.danmuku.length return danmu }) GM_setValue('danmu', danmu) console.log(danmu) } return danmu } function pageChange() { // 添加按钮和frame let parentId = '#toolbar_module' let addSubtitle = () => { if (document.URL.startsWith('https://www.bilibili.com/')) { return } // 字幕 参考 <bilibili外挂字幕 哔哩哔哩外挂字幕> // https://raw.githubusercontent.com/jonwinters/bilibili-subtitles-plugin/master/build.dist.js let subtitles = GM_getValue('subtitleArray') window.endTime = 0 window.offsetSubtitle = 0 window.fontsize = 20 if (!subtitles.length) { return } subtitles.unshift({ content: '字幕加载成功[简体中文]', from: 1, location: 2, to: 4 }) // 00:00 let percentNode = document.querySelector("[class*='player-played']") let totalTime = document.querySelector("[class*='player-dtime']") if (!(percentNode && totalTime)) { return } console.log(subtitles) // 把小时 分钟 秒解析为秒 let toSecond = timeStr => { let timeArr = timeStr.split(':') let timeSec = 0 for (let i = 0; i < timeArr.length; i++) { timeSec += 60 ** (timeArr.length - i - 1) * parseInt(timeArr[i]) } return timeSec } let totalSec = toSecond(totalTime.textContent) console.log(totalSec) let subtitleNode = document.querySelector("div[class*='player-subtitle']") let insertSubtitle = function (mutationsList, observer) { // 00:00:00 => 秒 let timeSec = totalSec * parseFloat(percentNode.style.width.replace('%', '')) / 100 // 保护时间,防止重复 if (timeSec > window.endTime || timeSec < window.endTime - 4){ // 此时用户可能在拖动进度条,反之拖动后重叠,清空subtitleNode subtitleNode.innerHTML = "" } else if (timeSec < window.endTime - 1 && timeSec >= window.endTime - 4) { // 此时可能自身进入判断 return } // console.log(timeSec) // 快速查找 let binarySearch = function (target, arr) { var from = 0; var to = arr.length - 1; while (from <= to) { var mid = parseInt(from + (to - from) / 2); if (target >= arr[mid].from && target <= arr[mid].to) { return mid } else if (target > arr[mid].to) { from = mid + 1; } else { to = mid - 1; } } return -1; } var index = binarySearch(timeSec, subtitles) if (index == -1) { return } // 两个解析播放器都支持这个div,应该是基于一套模板 //<div class="leleplayer-subtitle yzmplayer-subtitle" >替换弹幕源成功,前方共有223条弹幕,请做好准备哟</div> let oneSubtitle = subtitles[index] if (oneSubtitle.content == window.currentSubtitle) { return } let subtitleDiv = document.createElement('div') subtitleDiv.setAttribute('class', 'leleplayer-subtitle yzmplayer-subtitle player-subtitle .leleplayer-danmaku') subtitleDiv.innerHTML = oneSubtitle.content let duration = oneSubtitle.to - oneSubtitle.from - (timeSec - oneSubtitle.from) subtitleNode.appendChild(subtitleDiv) let offsetStyle = `bottom: ${30 * (parseInt(oneSubtitle.from) < window.endTime - 1)}px; font-size: ${window.fontsize}px;` // 此处可以自定义一些东西 let customStyle = '' subtitleDiv.style = `animation: danmaku-center ${duration}s linear;visibility: hidden; ${offsetStyle} ${customStyle}` // 记录结束时间 window.endTime = parseFloat(oneSubtitle.to) window.currentSubtitle = oneSubtitle.content } var config = { attributes: true, childList: true, subtree: true } var observer = new MutationObserver(insertSubtitle) observer.observe(percentNode, config) // 暂停播放事件 document.querySelector('#player').addEventListener('click', () => { // console.log(totalSec * parseFloat(percentNode.style.width.replace('%', '')) / 100, window.endTime) // console.log(document.querySelector('#player').className) // console.log(document.querySelector('#player').className.search('playing')) let timeSec = totalSec * parseFloat(percentNode.style.width.replace('%', '')) / 100 if (document.querySelector('#player').className.search('playing') !== -1) { console.log('开始播放') // 播放状态 if (timeSec <= window.endTime) { if (subtitleNode.lastChild) { subtitleNode.lastChild.style.visibility = 'hidden' } } insertSubtitle(null, null) return } else { // 暂停状态 console.log('暂停播放') if (timeSec <= window.endTime) { if (subtitleNode.lastChild) { (subtitleNode.lastChild.style.visibility = 'visible') } } } }) document.querySelector('.leleplayer-full').addEventListener('click', () => { window.fontsize = (window.fontsize == 20) ? 40 : 20 console.log('全屏播放') }) } let addButton = () => { // 切换 if (!document.URL.startsWith('https://www.bilibili.com/')) { return } let ele = document.querySelector(parentId) let switchButton = document.createElement("div") switchButton.setAttribute('class', 'share-info') switchButton.setAttribute('id', 'switch') switchButton.innerHTML = '<i class="iconfont"></i><span style="background-color: #FB7299; border: 1px solid #FB7299; color: #fff; border-radius: 16px; text-align: center;">切换</span> <!---->' ele.appendChild(switchButton) let modules = document.querySelectorAll('.player-module') switchButton.addEventListener('click', function () { let activateIndex for (let i = 0; i < modules.length; i++) { if (modules[i].style.display == 'block') { activateIndex = i modules[i].style.display = 'none' break } } let nextIndex = (activateIndex < modules.length - 1) ? activateIndex + 1 : 0 modules[nextIndex].style.display = 'block' // 按钮颜色 let color if (nextIndex == 0) { color = '#fb7299' } else { color = origin[nextIndex - 1].color } switchButton.querySelector('span').style['background-color'] = color switchButton.querySelector('span').style['border-color'] = color }) $(switchButton).hover(function () { let gray = '#757575' switchButton.querySelector('span').style['background-color'] = gray switchButton.querySelector('span').style['border-color'] = gray }, function () { let activateIndex, color for (let i = 0; i < modules.length; i++) { if (modules[i].style.display == 'block') { activateIndex = i break } } if (activateIndex == 0) { color = '#fb7299' } else { color = origin[activateIndex - 1].color } switchButton.querySelector('span').style['background-color'] = color switchButton.querySelector('span').style['border-color'] = color }) } let addFrame = (index) => { if (!document.URL.startsWith('https://www.bilibili.com/')) { return } // 防止匹配到解析网址,控制台会输出错误 if (!document.URL.startsWith('https://www.bilibili.com/')) return let biliDiv = document.querySelector('#player_module') biliDiv.style.display = 'block' // 创建新player_module let diyDiv = biliDiv.cloneNode(true) diyDiv.style['padding-left'] = '0px' diyDiv.style['margin-left'] = '0px' diyDiv.style.display = 'none' diyDiv.innerHTML = "" diyDiv.setAttribute('id', `diy_module_${index}`) let iframe = document.createElement("iframe") iframe.id = 'video-iframe' iframe.style.height = biliDiv.style.height diyDiv.append(iframe) let read_url = location.href iframe.src = origin[index].api + read_url console.log(iframe.src) if (document.body.className.includes('player-mode-widescreen')) { iframe.style.position = 'absolute' iframe.style.top = '0px' } iframe.height = '0%' iframe.width = '100%' iframe.setAttribute('frameborder', 'no') iframe.setAttribute('border', '0') iframe.setAttribute('allowfullscreen', 'allowfullscreen') iframe.setAttribute('webkitallowfullscreen', 'webkitallowfullscreen') document.querySelector('.plp-l').insertBefore(diyDiv, document.querySelector('.media-wrapper')) } let init = () => { // 将所源作为frmae添加到页面 for (let i = 0; i < origin.length; i++) { addFrame(i) } // 设置button addSubtitle() addButton() } setTimeout(init, 800) // 这里延迟可以避免频繁刷新 setTimeout(() => { let obs = document.querySelector('head title') if (obs) { new MutationObserver(function (mutations, observer) { setTimeout(() => { location.reload() }, 500) }).observe(obs, { childList: true }) } }, 800) } const origin = [ { regex: 'barrage', api: 'https://www.cuan.la/m3u8.php?url=', color: '#a62aee' }, { regex: 'dmku', api: 'https://vip.parwix.com:4433/player/?url=', color: '#ff6429' } ] // 开启代理 ah.proxy({ onRequest: (config, handler) => { let match = false for (let i = 0; i < origin.length; i++) { if (config.url.search(origin[i].regex) !== -1) { match = true break } } if (match) { let response = GM_getValue('danmu') console.log('拦截弹幕接口 - ' + config.url) console.log(response) handler.resolve({ config: config, status: 200, headers: { 'content-type': 'text/text' }, response: JSON.stringify(response) }) } else handler.next(config) } }) window.addEventListener("load", () => { dataQuery().then( () => { setTimeout(pageChange, 1000) } ) })
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址