增加是否自动插入字幕列表选项

This commit is contained in:
IndieKKY
2024-10-04 21:45:47 +08:00
parent a02825b3eb
commit 38baf571b7
7 changed files with 97 additions and 38 deletions

View File

@@ -41,7 +41,7 @@ export default defineManifest(async (env) => ({
"128": "favicon-128x128.png" "128": "favicon-128x128.png"
}, },
"action": { "action": {
"default_popup": "popup.html", // "default_popup": "popup.html",
"default_icon": { "default_icon": {
"16": "favicon-16x16.png", "16": "favicon-16x16.png",
"32": "favicon-32x32.png", "32": "favicon-32x32.png",

View File

@@ -1,6 +1,6 @@
import {v4} from 'uuid' import {v4} from 'uuid'
import {handleTask, initTaskService, tasksMap} from './taskService' import {handleTask, initTaskService, tasksMap} from './taskService'
import {MESSAGE_TO_EXTENSION_ADD_TASK, MESSAGE_TO_EXTENSION_GET_TASK} from '@/const' import {MESSAGE_TARGET_INJECT, MESSAGE_TO_EXTENSION_ADD_TASK, MESSAGE_TO_EXTENSION_GET_TASK, MESSAGE_TO_INJECT_TOGGLE_DISPLAY} from '@/const'
import ExtensionMessage from '@/messaging/ExtensionMessage' import ExtensionMessage from '@/messaging/ExtensionMessage'
const methods: { const methods: {
@@ -65,4 +65,9 @@ chrome.runtime.onMessage.addListener((event: MessageData, sender: chrome.runtime
} }
}) })
//点击扩展图标
chrome.action.onClicked.addListener(async (tab) => {
extensionMessage.broadcastMessageExact([tab.id!], MESSAGE_TARGET_INJECT, MESSAGE_TO_INJECT_TOGGLE_DISPLAY).catch(console.error)
})
initTaskService() initTaskService()

View File

@@ -5,6 +5,7 @@ export const MESSAGE_TARGET_APP = 'BilibiliAPP'
export const MESSAGE_TO_EXTENSION_ADD_TASK = 'addTask' export const MESSAGE_TO_EXTENSION_ADD_TASK = 'addTask'
export const MESSAGE_TO_EXTENSION_GET_TASK = 'getTask' export const MESSAGE_TO_EXTENSION_GET_TASK = 'getTask'
export const MESSAGE_TO_INJECT_TOGGLE_DISPLAY = 'toggleDisplay'
export const MESSAGE_TO_INJECT_FOLD = 'fold' export const MESSAGE_TO_INJECT_FOLD = 'fold'
export const MESSAGE_TO_INJECT_MOVE = 'move' export const MESSAGE_TO_INJECT_MOVE = 'move'
export const MESSAGE_TO_INJECT_PLAY = 'play' export const MESSAGE_TO_INJECT_PLAY = 'play'

View File

@@ -1,18 +1,32 @@
import { TOTAL_HEIGHT_DEF, HEADER_HEIGHT, TOTAL_HEIGHT_MIN, TOTAL_HEIGHT_MAX, IFRAME_ID, MESSAGE_TO_INJECT_DOWNLOAD_AUDIO, MESSAGE_TARGET_INJECT, MESSAGE_TO_APP_SET_INFOS } from '@/const' import { TOTAL_HEIGHT_DEF, HEADER_HEIGHT, TOTAL_HEIGHT_MIN, TOTAL_HEIGHT_MAX, IFRAME_ID, MESSAGE_TO_INJECT_DOWNLOAD_AUDIO, MESSAGE_TARGET_INJECT, MESSAGE_TO_APP_SET_INFOS, MESSAGE_TO_INJECT_TOGGLE_DISPLAY, STORAGE_ENV } from '@/const'
import { MESSAGE_TO_INJECT_FOLD, MESSAGE_TO_INJECT_MOVE, MESSAGE_TO_APP_SET_VIDEO_INFO, MESSAGE_TO_INJECT_GET_SUBTITLE, MESSAGE_TO_INJECT_GET_VIDEO_STATUS, MESSAGE_TO_INJECT_GET_VIDEO_ELEMENT_INFO, MESSAGE_TO_INJECT_UPDATETRANSRESULT, MESSAGE_TO_INJECT_PLAY, MESSAGE_TO_INJECT_HIDE_TRANS, MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO} from '@/const' import { MESSAGE_TO_INJECT_FOLD, MESSAGE_TO_INJECT_MOVE, MESSAGE_TO_APP_SET_VIDEO_INFO, MESSAGE_TO_INJECT_GET_SUBTITLE, MESSAGE_TO_INJECT_GET_VIDEO_STATUS, MESSAGE_TO_INJECT_GET_VIDEO_ELEMENT_INFO, MESSAGE_TO_INJECT_UPDATETRANSRESULT, MESSAGE_TO_INJECT_PLAY, MESSAGE_TO_INJECT_HIDE_TRANS, MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO } from '@/const'
import InjectMessage from '@/messaging/InjectMessage' import InjectMessage from '@/messaging/InjectMessage'
const debug = (...args: any[]) => { const debug = (...args: any[]) => {
console.debug('[Inject]', ...args) console.debug('[Inject]', ...args)
} }
(function () { (async function () {
// 如果路径不是/video或/list则不注入 // 如果路径不是/video或/list则不注入
if (!location.pathname.startsWith('/video') && !location.pathname.startsWith('/list')) { if (!location.pathname.startsWith('/video') && !location.pathname.startsWith('/list')) {
debug('Not inject') debug('Not inject')
return return
} }
//读取envData
const envDataStr = (await chrome.storage.sync.get(STORAGE_ENV))[STORAGE_ENV]
let manualInsert: boolean | null = null
if (envDataStr) {
try {
const envData = JSON.parse(envDataStr)
debug('envData: ', envData)
manualInsert = envData.manualInsert
} catch (error) {
console.error('Error parsing envData:', error)
}
}
const runtime: { const runtime: {
injectMessage: InjectMessage injectMessage: InjectMessage
// lastV?: string | null // lastV?: string | null
@@ -42,7 +56,7 @@ const debug = (...args: any[]) => {
*/ */
const refreshVideoElement = () => { const refreshVideoElement = () => {
const newVideoElement = getVideoElement() const newVideoElement = getVideoElement()
const newVideoElementHeight = (newVideoElement != null)?(Math.min(Math.max(newVideoElement.offsetHeight, TOTAL_HEIGHT_MIN), TOTAL_HEIGHT_MAX)):TOTAL_HEIGHT_DEF const newVideoElementHeight = (newVideoElement != null) ? (Math.min(Math.max(newVideoElement.offsetHeight, TOTAL_HEIGHT_MIN), TOTAL_HEIGHT_MAX)) : TOTAL_HEIGHT_DEF
if (newVideoElement === runtime.videoElement && Math.abs(newVideoElementHeight - runtime.videoElementHeight) < 1) { if (newVideoElement === runtime.videoElement && Math.abs(newVideoElementHeight - runtime.videoElementHeight) < 1) {
return false return false
} else { } else {
@@ -54,39 +68,50 @@ const debug = (...args: any[]) => {
} }
} }
const timerIframe = setInterval(function () { const createIframe = () => {
var danmukuBox = document.getElementById('danmukuBox') var danmukuBox = document.getElementById('danmukuBox')
if (danmukuBox) { if (danmukuBox) {
clearInterval(timerIframe) var vKey = ''
for (const key in danmukuBox?.dataset) {
//延迟插入iframe插入太快网络较差时容易出现b站网页刷新原因暂时未知可能b站的某种机制 if (key.startsWith('v-')) {
setTimeout(() => { vKey = key
var vKey = '' break
for (const key in danmukuBox?.dataset) {
if (key.startsWith('v-')) {
vKey = key
break
}
} }
}
const iframe = document.createElement('iframe') const iframe = document.createElement('iframe')
iframe.id = IFRAME_ID iframe.id = IFRAME_ID
iframe.src = chrome.runtime.getURL('index.html') iframe.src = chrome.runtime.getURL('index.html')
iframe.style.border = 'none' iframe.style.border = 'none'
iframe.style.width = '100%' iframe.style.width = '100%'
iframe.style.height = '44px' iframe.style.height = '44px'
iframe.style.marginBottom = '3px' iframe.style.marginBottom = '3px'
iframe.allow = 'clipboard-read; clipboard-write;' iframe.allow = 'clipboard-read; clipboard-write;'
if (vKey) {
iframe.dataset[vKey] = danmukuBox?.dataset[vKey]
}
//insert before first child
danmukuBox?.insertBefore(iframe, danmukuBox?.firstChild)
debug('iframe inserted') if (vKey) {
}, 1500) iframe.dataset[vKey] = danmukuBox?.dataset[vKey]
}
//insert before first child
danmukuBox?.insertBefore(iframe, danmukuBox?.firstChild)
debug('iframe inserted')
return iframe
} }
}, 1000) }
if (!manualInsert) {
const timerIframe = setInterval(function () {
var danmukuBox = document.getElementById('danmukuBox')
if (danmukuBox) {
clearInterval(timerIframe)
//延迟插入iframe插入太快网络较差时容易出现b站网页刷新原因暂时未知可能b站的某种机制
setTimeout(createIframe, 1500)
}
}, 1000)
}
let aid: number | null = null let aid: number | null = null
let title = '' let title = ''
@@ -204,6 +229,14 @@ const debug = (...args: any[]) => {
const methods: { const methods: {
[key: string]: (params: any, context: MethodContext) => Promise<any> [key: string]: (params: any, context: MethodContext) => Promise<any>
} = { } = {
[MESSAGE_TO_INJECT_TOGGLE_DISPLAY]: async (params) => {
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
if (iframe != null) {
iframe.style.display = iframe.style.display === 'none' ? 'block' : 'none'
} else {
createIframe()
}
},
[MESSAGE_TO_INJECT_FOLD]: async (params) => { [MESSAGE_TO_INJECT_FOLD]: async (params) => {
runtime.fold = params.fold runtime.fold = params.fold
updateIframeHeight() updateIframeHeight()
@@ -249,7 +282,7 @@ const debug = (...args: any[]) => {
let text = document.getElementById('trans-result-text') let text = document.getElementById('trans-result-text')
if (text) { if (text) {
text.innerHTML = runtime.curTrans??'' text.innerHTML = runtime.curTrans ?? ''
} else { } else {
const container = document.getElementsByClassName('bpx-player-subtitle-panel-wrap')?.[0] const container = document.getElementsByClassName('bpx-player-subtitle-panel-wrap')?.[0]
if (container) { if (container) {
@@ -259,7 +292,7 @@ const debug = (...args: any[]) => {
div.style.margin = '2px' div.style.margin = '2px'
text = document.createElement('text') text = document.createElement('text')
text.id = 'trans-result-text' text.id = 'trans-result-text'
text.innerHTML = runtime.curTrans??'' text.innerHTML = runtime.curTrans ?? ''
text.style.fontSize = '1rem' text.style.fontSize = '1rem'
text.style.padding = '5px' text.style.padding = '5px'
text.style.color = 'white' text.style.color = 'white'
@@ -309,7 +342,10 @@ const debug = (...args: any[]) => {
runtime.injectMessage.init(methods) runtime.injectMessage.init(methods)
setInterval(() => { setInterval(() => {
refreshVideoInfo().catch(console.error) const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
refreshSubtitles() if (iframe != null && iframe.style.display !== 'none') {
refreshVideoInfo().catch(console.error)
refreshSubtitles()
}
}, 1000) }, 1000)
})() })()

View File

@@ -61,6 +61,16 @@ class ExtensionMessage {
}) })
} }
broadcastMessageExact = async (tabIds: number[], target: string, method: string, params?: any) => {
for (const tabId of tabIds) {
try {
await chrome.tabs.sendMessage(tabId, {target, method, params})
} catch (e) {
console.error('send message to tab error', tabId, e)
}
}
}
broadcastMessage = async (ignoreTabIds: number[] | undefined | null, target: string, method: string, params?: any) => { broadcastMessage = async (ignoreTabIds: number[] | undefined | null, target: string, method: string, params?: any) => {
const tabs = await chrome.tabs.query({ const tabs = await chrome.tabs.query({
discarded: false, discarded: false,

View File

@@ -59,6 +59,7 @@ const FormItem = (props: {
const OptionsPage = () => { const OptionsPage = () => {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const envData = useAppSelector(state => state.env.envData) const envData = useAppSelector(state => state.env.envData)
const {value: autoInsertValue, onChange: setAutoInsertValue} = useEventChecked(!envData.manualInsert)
const {value: autoExpandValue, onChange: setAutoExpandValue} = useEventChecked(envData.autoExpand) const {value: autoExpandValue, onChange: setAutoExpandValue} = useEventChecked(envData.autoExpand)
// const {value: autoScrollValue, onChange: setAutoScrollValue} = useEventChecked(envData.autoScroll) // const {value: autoScrollValue, onChange: setAutoScrollValue} = useEventChecked(envData.autoScroll)
const {value: translateEnableValue, onChange: setTranslateEnableValue} = useEventChecked(envData.translateEnable) const {value: translateEnableValue, onChange: setTranslateEnableValue} = useEventChecked(envData.translateEnable)
@@ -111,6 +112,7 @@ const OptionsPage = () => {
const onSave = useCallback(() => { const onSave = useCallback(() => {
dispatch(setEnvData({ dispatch(setEnvData({
manualInsert: !autoInsertValue,
autoExpand: autoExpandValue, autoExpand: autoExpandValue,
aiType: aiTypeValue, aiType: aiTypeValue,
apiKey: apiKeyValue, apiKey: apiKeyValue,
@@ -140,7 +142,7 @@ const OptionsPage = () => {
setTimeout(() => { setTimeout(() => {
window.close() window.close()
}, 3000) }, 3000)
}, [dispatch, autoExpandValue, aiTypeValue, apiKeyValue, serverUrlValue, modelValue, customModelValue, customModelTokensValue, geminiApiKeyValue, translateEnableValue, languageValue, hideOnDisableAutoTranslateValue, themeValue, transDisplayValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, wordsValue, fetchAmountValue, fontSizeValue, promptsValue, searchEnabledValue, cnSearchEnabledValue, askEnabledValue]) }, [dispatch, autoInsertValue, autoExpandValue, aiTypeValue, apiKeyValue, serverUrlValue, modelValue, customModelValue, customModelTokensValue, geminiApiKeyValue, translateEnableValue, languageValue, hideOnDisableAutoTranslateValue, themeValue, transDisplayValue, summarizeEnableValue, summarizeFloatValue, summarizeLanguageValue, wordsValue, fetchAmountValue, fontSizeValue, promptsValue, searchEnabledValue, cnSearchEnabledValue, askEnabledValue])
const onCancel = useCallback(() => { const onCancel = useCallback(() => {
window.close() window.close()
@@ -197,6 +199,10 @@ const OptionsPage = () => {
return <div className='flex justify-center'> return <div className='flex justify-center'>
<div className="w-2/3 max-w-[600px] flex flex-col gap-3 p-2"> <div className="w-2/3 max-w-[600px] flex flex-col gap-3 p-2">
<Section title='通用配置'> <Section title='通用配置'>
<FormItem title='自动插入' htmlFor='autoInsert' tip='是否自动插入字幕列表(可以手动点击扩展图标插入)'>
<input id='autoInsert' type='checkbox' className='toggle toggle-primary' checked={autoInsertValue}
onChange={setAutoInsertValue}/>
</FormItem>
<FormItem title='自动展开' htmlFor='autoExpand' tip='是否视频有字幕时自动展开字幕列表'> <FormItem title='自动展开' htmlFor='autoExpand' tip='是否视频有字幕时自动展开字幕列表'>
<input id='autoExpand' type='checkbox' className='toggle toggle-primary' checked={autoExpandValue} <input id='autoExpand' type='checkbox' className='toggle toggle-primary' checked={autoExpandValue}
onChange={setAutoExpandValue}/> onChange={setAutoExpandValue}/>

1
src/typings.d.ts vendored
View File

@@ -22,6 +22,7 @@ interface MethodContext {
} }
interface EnvData { interface EnvData {
manualInsert?: boolean //是否手动插入字幕列表
autoExpand?: boolean autoExpand?: boolean
flagDot?: boolean flagDot?: boolean