Files
bilibili-subtitle/src/inject/inject.ts
IndieKKY 66cd431c3b fix
2024-10-03 19:48:55 +08:00

265 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {TOTAL_HEIGHT_DEF, HEADER_HEIGHT, TOTAL_HEIGHT_MIN, TOTAL_HEIGHT_MAX, IFRAME_ID} from '@/const'
let totalHeight = TOTAL_HEIGHT_DEF
const getVideoElement = () => {
const videoWrapper = document.getElementById('bilibili-player')
return videoWrapper?.querySelector('video') as HTMLVideoElement | undefined
}
const timerIframe = setInterval(function () {
var danmukuBox = document.getElementById('danmukuBox')
if (danmukuBox) {
clearInterval(timerIframe)
//延迟插入iframe插入太快网络较差时容易出现b站网页刷新原因暂时未知可能b站的某种机制
setTimeout(() => {
var vKey = ''
for (const key in danmukuBox?.dataset) {
if (key.startsWith('v-')) {
vKey = key
break
}
}
const iframe = document.createElement('iframe')
iframe.id = IFRAME_ID
iframe.src = chrome.runtime.getURL('index.html')
iframe.style.border = 'none'
iframe.style.width = '100%'
iframe.style.height = '44px'
iframe.style.marginBottom = '3px'
iframe.allow = 'clipboard-read; clipboard-write;'
if (vKey) {
iframe.dataset[vKey] = danmukuBox?.dataset[vKey]
}
//insert before first child
danmukuBox?.insertBefore(iframe, danmukuBox?.firstChild)
console.debug('iframe inserted')
}, 1500)
}
}, 1000)
let aid: number | null = null
let title = ''
let pages: any[] = []
let pagesMap: Record<string, any> = {}
let lastAidOrBvid: string | null = null
const refreshVideoInfo = async () => {
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
if (!iframe) return
// fix: https://github.com/IndieKKY/bilibili-subtitle/issues/5
// 处理稍后再看的url( https://www.bilibili.com/list/watchlater?bvid=xxx&oid=xxx )
const pathSearchs: Record<string, string> = {}
location.search.slice(1).replace(/([^=&]*)=([^=&]*)/g, (matchs, a, b, c) => pathSearchs[a] = b)
// bvid
let aidOrBvid = pathSearchs.bvid // 默认为稍后再看
if (!aidOrBvid) {
let path = location.pathname
if (path.endsWith('/')) {
path = path.slice(0, -1)
}
const paths = path.split('/')
aidOrBvid = paths[paths.length - 1]
}
if (aidOrBvid !== lastAidOrBvid) {
// console.debug('refreshVideoInfo')
lastAidOrBvid = aidOrBvid
if (aidOrBvid) {
//aid,pages
let cid
let subtitles
if (aidOrBvid.toLowerCase().startsWith('av')) {//avxxx
aid = parseInt(aidOrBvid.slice(2))
pages = await fetch(`https://api.bilibili.com/x/player/pagelist?aid=${aid}`, {credentials: 'include'}).then(res => res.json()).then(res => res.data)
cid = pages[0].cid
title = pages[0].part
await fetch(`https://api.bilibili.com/x/player/v2?aid=${aid}&cid=${cid}`, {credentials: 'include'}).then(res => res.json()).then(res => {
subtitles = res.data.subtitle.subtitles
})
} else {//bvxxx
await fetch(`https://api.bilibili.com/x/web-interface/view?bvid=${aidOrBvid}`, {credentials: 'include'}).then(res => res.json()).then(async res => {
title = res.data.title
aid = res.data.aid
cid = res.data.cid
pages = res.data.pages
})
await fetch(`https://api.bilibili.com/x/player/v2?aid=${aid}&cid=${cid}`, {credentials: 'include'}).then(res => res.json()).then(res => {
subtitles = res.data.subtitle.subtitles
})
}
//pagesMap
pagesMap = {}
pages.forEach(page => {
pagesMap[page.page + ''] = page
})
console.debug('refreshVideoInfo: ', aid, cid, pages, subtitles)
//send setVideoInfo
iframe.contentWindow?.postMessage({
type: 'setVideoInfo',
url: location.origin + location.pathname,
title,
aid,
pages,
infos: subtitles,
}, '*')
}
}
}
let lastAid: number | null = null
let lastCid: number | null = null
const refreshSubtitles = () => {
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
if (!iframe) return
const urlSearchParams = new URLSearchParams(window.location.search)
const p = urlSearchParams.get('p') || 1
const page = pagesMap[p]
if (!page) return
const cid = page.cid
if (aid !== lastAid || cid !== lastCid) {
console.debug('refreshSubtitles', aid, cid)
lastAid = aid
lastCid = cid
if (aid && cid) {
fetch(`https://api.bilibili.com/x/player/v2?aid=${aid}&cid=${cid}`, {
credentials: 'include',
})
.then(res => res.json())
.then(res => {
// console.log('refreshSubtitles: ', aid, cid, res)
iframe.contentWindow?.postMessage({
type: 'setInfos',
infos: res.data.subtitle.subtitles
}, '*')
})
}
}
}
// 监听消息
window.addEventListener("message", (event) => {
const {data} = event
if (data.type === 'fold') {
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
if (iframe) {
iframe.style.height = (data.fold ? HEADER_HEIGHT : totalHeight) + 'px'
}
}
if (data.type === 'move') {
const video = getVideoElement()
if (video) {
video.currentTime = data.time
if (data.togglePause) {
video.paused ? video.play() : video.pause()
}
}
}
//刷新视频信息
if (data.type === 'refreshVideoInfo') {
refreshVideoInfo().catch(console.error)
}
//刷新字幕
if (data.type === 'refreshSubtitles') {
refreshSubtitles()
}
if (data.type === 'getSubtitle') {
let url = data.info.subtitle_url
if (url.startsWith('http://')) {
url = url.replace('http://', 'https://')
}
fetch(url).then(res => res.json()).then(res => {
event.source?.postMessage({
data: {
info: data.info,
data: res,
}, type: 'setSubtitle'
// @ts-ignore
}, '*')
})
}
if (data.type === 'getCurrentTime') {
const video = getVideoElement()
if (video) {
event.source?.postMessage({
data: {
currentTime: video.currentTime
}, type: 'setCurrentTime'
// @ts-ignore
}, '*')
}
}
if (data.type === 'getSettings') {
const videoElement = getVideoElement()
totalHeight = videoElement ? Math.min(Math.max(videoElement.offsetHeight, TOTAL_HEIGHT_MIN), TOTAL_HEIGHT_MAX) : TOTAL_HEIGHT_DEF
event.source?.postMessage({
data: {
noVideo: !videoElement,
totalHeight,
}, type: 'setSettings'
// @ts-ignore
}, '*')
}
if (data.type === 'downloadAudio') {
const html = document.getElementsByTagName('html')[0].innerHTML
const playInfo = JSON.parse(html.match(/window.__playinfo__=(.+?)<\/script/)?.[1] ?? '{}')
const audioUrl = playInfo.data.dash.audio[0].baseUrl
fetch(audioUrl).then(res => res.blob()).then(blob => {
const a = document.createElement('a')
a.href = URL.createObjectURL(blob)
a.download = `${title}.m4s`
a.click()
})
}
if (data.type === 'updateTransResult') {
const trans = data.result??''
let text = document.getElementById('trans-result-text')
if (text) {
text.innerHTML = trans
} else {
const container = document.getElementsByClassName('bpx-player-subtitle-panel-wrap')?.[0]
if (container) {
const div = document.createElement('div')
div.style.display = 'flex'
div.style.justifyContent = 'center'
div.style.margin = '2px'
text = document.createElement('text')
text.id = 'trans-result-text'
text.innerHTML = trans
text.style.fontSize = '1rem'
text.style.padding = '5px'
text.style.color = 'white'
text.style.background = 'rgba(0, 0, 0, 0.4)'
div.append(text)
container.append(div)
}
}
text && (text.style.display = trans ? 'block' : 'none')
}
}, false);
setInterval(() => {
refreshVideoInfo().catch(console.error)
refreshSubtitles()
}, 1000)