You've already forked bilibili-subtitle
重构消息通信
This commit is contained in:
@@ -16,7 +16,7 @@
|
|||||||
"@crxjs/vite-plugin": "^1.0.14",
|
"@crxjs/vite-plugin": "^1.0.14",
|
||||||
"@kky002/kky-hooks": "^1.2.1",
|
"@kky002/kky-hooks": "^1.2.1",
|
||||||
"@kky002/kky-ui": "^1.0.9",
|
"@kky002/kky-ui": "^1.0.9",
|
||||||
"@kky002/kky-util": "^1.4.2",
|
"@kky002/kky-util": "^1.13.13",
|
||||||
"@logto/react": "1.0.0-beta.13",
|
"@logto/react": "1.0.0-beta.13",
|
||||||
"@popperjs/core": "^2.11.6",
|
"@popperjs/core": "^2.11.6",
|
||||||
"@reduxjs/toolkit": "^1.8.5",
|
"@reduxjs/toolkit": "^1.8.5",
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"pako": "^2.1.0",
|
"pako": "^2.1.0",
|
||||||
|
"postmessage-promise": "^3.2.1",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@@ -46,10 +47,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/line-clamp": "^0.4.2",
|
"@tailwindcss/line-clamp": "^0.4.2",
|
||||||
"@tailwindcss/typography": "^0.5.8",
|
"@tailwindcss/typography": "^0.5.8",
|
||||||
"@types/node": "^20.8.10",
|
|
||||||
"@types/chrome": "^0.0.203",
|
"@types/chrome": "^0.0.203",
|
||||||
"@types/js-search": "^1.4.0",
|
"@types/js-search": "^1.4.0",
|
||||||
"@types/lodash-es": "^4.17.6",
|
"@types/lodash-es": "^4.17.6",
|
||||||
|
"@types/node": "^20.8.10",
|
||||||
"@types/pako": "^2.0.0",
|
"@types/pako": "^2.0.0",
|
||||||
"@types/qs": "^6.9.7",
|
"@types/qs": "^6.9.7",
|
||||||
"@types/react": "^18.0.20",
|
"@types/react": "^18.0.20",
|
||||||
|
29
pnpm-lock.yaml
generated
29
pnpm-lock.yaml
generated
@@ -18,8 +18,8 @@ importers:
|
|||||||
specifier: ^1.0.9
|
specifier: ^1.0.9
|
||||||
version: 1.0.9
|
version: 1.0.9
|
||||||
'@kky002/kky-util':
|
'@kky002/kky-util':
|
||||||
specifier: ^1.4.2
|
specifier: ^1.13.13
|
||||||
version: 1.4.2
|
version: 1.13.13
|
||||||
'@logto/react':
|
'@logto/react':
|
||||||
specifier: 1.0.0-beta.13
|
specifier: 1.0.0-beta.13
|
||||||
version: 1.0.0-beta.13(react@18.2.0)
|
version: 1.0.0-beta.13(react@18.2.0)
|
||||||
@@ -53,6 +53,9 @@ importers:
|
|||||||
pako:
|
pako:
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
|
postmessage-promise:
|
||||||
|
specifier: ^3.2.1
|
||||||
|
version: 3.2.1
|
||||||
qs:
|
qs:
|
||||||
specifier: ^6.11.0
|
specifier: ^6.11.0
|
||||||
version: 6.11.0
|
version: 6.11.0
|
||||||
@@ -390,8 +393,8 @@ packages:
|
|||||||
'@kky002/kky-ui@1.0.9':
|
'@kky002/kky-ui@1.0.9':
|
||||||
resolution: {integrity: sha512-pepfRcLfC1eIQ1lsSJLWNr4PgdLqFLuvQMlitJy7W668yZ7qu8yAHSjg8A20R7HB4mFkJ+B96WETalOar1e/kA==}
|
resolution: {integrity: sha512-pepfRcLfC1eIQ1lsSJLWNr4PgdLqFLuvQMlitJy7W668yZ7qu8yAHSjg8A20R7HB4mFkJ+B96WETalOar1e/kA==}
|
||||||
|
|
||||||
'@kky002/kky-util@1.4.2':
|
'@kky002/kky-util@1.13.13':
|
||||||
resolution: {integrity: sha512-gpZHWuCBBgYV1rnZ07FhriCR7x2228LOnf6PI6nyfWXxYYy1RQ8MZcdegbBsi/HRmh7EsW1yPqq85pwNLX0B9w==}
|
resolution: {integrity: sha512-DvePr8J7dyOaVteU/bskuoL3noHiOKpX3IGhN1h0v/Nt/fGI/tA1JKwUVrg4PE89xKAhH7c2Z+RHLa8zj7w7ng==}
|
||||||
|
|
||||||
'@logto/browser@1.0.0-beta.13':
|
'@logto/browser@1.0.0-beta.13':
|
||||||
resolution: {integrity: sha512-ddAVggFcbS9yfG8Gvn2xknE2NZd6+lGxOQ6UbjIJKsYBAsJG95u1ITYaP7tNSDdxqZPmSBGXp4rfsQB+u0JPJQ==}
|
resolution: {integrity: sha512-ddAVggFcbS9yfG8Gvn2xknE2NZd6+lGxOQ6UbjIJKsYBAsJG95u1ITYaP7tNSDdxqZPmSBGXp4rfsQB+u0JPJQ==}
|
||||||
@@ -2005,6 +2008,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==}
|
resolution: {integrity: sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
postmessage-promise@3.2.1:
|
||||||
|
resolution: {integrity: sha512-cSs5eg+DvBQIdIQK9Cimd1wB2eb85xlzJXkJwm6jYNcTlsiwTFXvdyF/69JFozX6vIkdYz2Jv31W+BvSKQXNVg==}
|
||||||
|
|
||||||
prelude-ls@1.2.1:
|
prelude-ls@1.2.1:
|
||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@@ -2384,6 +2390,10 @@ packages:
|
|||||||
engines: {node: '>=4.2.0'}
|
engines: {node: '>=4.2.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
ua-parser-js@1.0.39:
|
||||||
|
resolution: {integrity: sha512-k24RCVWlEcjkdOxYmVJgeD/0a1TiSpqLg+ZalVGV9lsnr4yqu0w7tX/x2xX6G4zpkgQnRf89lxuZ1wsbjXM8lw==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
uberproto@1.2.0:
|
uberproto@1.2.0:
|
||||||
resolution: {integrity: sha512-pGtPAQmLwh+R9w81WVHzui1FfedpQWQpiaIIfPCwhtsBez4q6DYbJFfyXPVHPUTNFnedAvNEnkoFiLuhXIR94w==}
|
resolution: {integrity: sha512-pGtPAQmLwh+R9w81WVHzui1FfedpQWQpiaIIfPCwhtsBez4q6DYbJFfyXPVHPUTNFnedAvNEnkoFiLuhXIR94w==}
|
||||||
|
|
||||||
@@ -2804,7 +2814,7 @@ snapshots:
|
|||||||
|
|
||||||
'@kky002/kky-hooks@1.2.1':
|
'@kky002/kky-hooks@1.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@kky002/kky-util': 1.4.2
|
'@kky002/kky-util': 1.13.13
|
||||||
ahooks: 3.7.5(react@18.2.0)
|
ahooks: 3.7.5(react@18.2.0)
|
||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
@@ -2813,10 +2823,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
|
|
||||||
'@kky002/kky-util@1.4.2':
|
'@kky002/kky-util@1.13.13':
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
qs: 6.11.0
|
qs: 6.11.0
|
||||||
|
ua-parser-js: 1.0.39
|
||||||
|
|
||||||
'@logto/browser@1.0.0-beta.13':
|
'@logto/browser@1.0.0-beta.13':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4657,6 +4668,10 @@ snapshots:
|
|||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
|
|
||||||
|
postmessage-promise@3.2.1:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.19.0
|
||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
prop-types@15.8.1:
|
prop-types@15.8.1:
|
||||||
@@ -5045,6 +5060,8 @@ snapshots:
|
|||||||
|
|
||||||
typescript@4.8.3: {}
|
typescript@4.8.3: {}
|
||||||
|
|
||||||
|
ua-parser-js@1.0.39: {}
|
||||||
|
|
||||||
uberproto@1.2.0: {}
|
uberproto@1.2.0: {}
|
||||||
|
|
||||||
unbox-primitive@1.0.2:
|
unbox-primitive@1.0.2:
|
||||||
|
@@ -6,16 +6,17 @@ import Header from './biz/Header'
|
|||||||
import Body from './biz/Body'
|
import Body from './biz/Body'
|
||||||
import useSubtitleService from './hooks/useSubtitleService'
|
import useSubtitleService from './hooks/useSubtitleService'
|
||||||
import {cloneDeep} from 'lodash-es'
|
import {cloneDeep} from 'lodash-es'
|
||||||
import {EVENT_EXPAND, PAGE_MAIN, PAGE_SETTINGS, STORAGE_ENV, STORAGE_TEMP} from './const'
|
import {EVENT_EXPAND, MESSAGE_TO_INJECT_FOLD, PAGE_MAIN, PAGE_SETTINGS, STORAGE_ENV, STORAGE_TEMP} from './const'
|
||||||
import {EventBusContext} from './Router'
|
import {EventBusContext} from './Router'
|
||||||
import useTranslateService from './hooks/useTranslateService'
|
import useTranslateService from './hooks/useTranslateService'
|
||||||
import Settings from './biz/Settings'
|
import Settings from './biz/Settings'
|
||||||
import classNames from 'classnames'
|
|
||||||
import {handleJson} from '@kky002/kky-util'
|
import {handleJson} from '@kky002/kky-util'
|
||||||
import {useLocalStorage} from '@kky002/kky-hooks'
|
import {useLocalStorage} from '@kky002/kky-hooks'
|
||||||
import {Toaster} from 'react-hot-toast'
|
import {Toaster} from 'react-hot-toast'
|
||||||
import {setTheme} from './util/biz_util'
|
import {setTheme} from './util/biz_util'
|
||||||
|
import {sendInject} from './util/biz_util'
|
||||||
import useSearchService from './hooks/useSearchService'
|
import useSearchService from './hooks/useSearchService'
|
||||||
|
import useMessageService from './hooks/useMessageService'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
@@ -29,7 +30,7 @@ function App() {
|
|||||||
const foldCallback = useCallback(() => {
|
const foldCallback = useCallback(() => {
|
||||||
dispatch(setFold(!fold))
|
dispatch(setFold(!fold))
|
||||||
dispatch(setPage(PAGE_MAIN))
|
dispatch(setPage(PAGE_MAIN))
|
||||||
window.parent.postMessage({type: 'fold', fold: !fold}, '*')
|
sendInject(MESSAGE_TO_INJECT_FOLD, {fold: !fold})
|
||||||
}, [dispatch, fold])
|
}, [dispatch, fold])
|
||||||
|
|
||||||
// handle event
|
// handle event
|
||||||
@@ -74,6 +75,7 @@ function App() {
|
|||||||
useSubtitleService()
|
useSubtitleService()
|
||||||
useTranslateService()
|
useTranslateService()
|
||||||
useSearchService()
|
useSearchService()
|
||||||
|
useMessageService()
|
||||||
|
|
||||||
return <div className='select-none w-full' style={{
|
return <div className='select-none w-full' style={{
|
||||||
height: fold?undefined:`${totalHeight}px`,
|
height: fold?undefined:`${totalHeight}px`,
|
||||||
|
@@ -13,11 +13,11 @@ import {Placement} from '@popperjs/core/lib/enums'
|
|||||||
import {useAppDispatch, useAppSelector} from '../hooks/redux'
|
import {useAppDispatch, useAppSelector} from '../hooks/redux'
|
||||||
import {setEnvData, setPage, setTempData} from '../redux/envReducer'
|
import {setEnvData, setPage, setTempData} from '../redux/envReducer'
|
||||||
import {EventBusContext} from '../Router'
|
import {EventBusContext} from '../Router'
|
||||||
import {EVENT_EXPAND, PAGE_SETTINGS} from '../const'
|
import {EVENT_EXPAND, MESSAGE_TO_INJECT_DOWNLOAD_AUDIO, PAGE_SETTINGS} from '../const'
|
||||||
import {formatSrtTime, formatTime, formatVttTime} from '../util/util'
|
import {formatSrtTime, formatTime, formatVttTime} from '../util/util'
|
||||||
import {downloadText, openUrl} from '@kky002/kky-util'
|
import {downloadText, openUrl} from '@kky002/kky-util'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import {getSummarize} from '../util/biz_util'
|
import {getSummarize, sendInject} from '../util/biz_util'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
placement: Placement
|
placement: Placement
|
||||||
@@ -160,9 +160,7 @@ const MoreBtn = (props: Props) => {
|
|||||||
}, [curSummaryType, data, downloadType, segments, title, url])
|
}, [curSummaryType, data, downloadType, segments, title, url])
|
||||||
|
|
||||||
const downloadAudioCallback = useCallback(() => {
|
const downloadAudioCallback = useCallback(() => {
|
||||||
window.parent.postMessage({
|
sendInject(MESSAGE_TO_INJECT_DOWNLOAD_AUDIO, {})
|
||||||
type: 'downloadAudio',
|
|
||||||
}, '*')
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const selectCallback = useCallback((e: any) => {
|
const selectCallback = useCallback((e: any) => {
|
||||||
|
@@ -1,59 +1,37 @@
|
|||||||
import {v4} from 'uuid'
|
import {v4} from 'uuid'
|
||||||
import {handleTask, initTaskService, tasksMap} from './taskService'
|
import {handleTask, initTaskService, tasksMap} from './taskService'
|
||||||
|
import {MESSAGE_TARGET_EXTENSION, MESSAGE_TO_EXTENSION_ADD_TASK, MESSAGE_TO_EXTENSION_GET_TASK} from '@/const'
|
||||||
|
|
||||||
/**
|
const debug = (...args: any[]) => {
|
||||||
* 消息处理入口
|
console.debug('[Extension]', ...args)
|
||||||
* 注意:需要异步sendResponse时返回true
|
}
|
||||||
*/
|
|
||||||
chrome.runtime.onMessage.addListener((event, sender, sendResponse) => {
|
const methods: {
|
||||||
console.debug('收到请求: ', event)
|
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
||||||
if (event.type === 'p') { // 发出http请求
|
} = {
|
||||||
const {url, options} = event
|
[MESSAGE_TO_EXTENSION_ADD_TASK]: async (params, context) => {
|
||||||
// 发出请求
|
|
||||||
fetch('http://localhost:27081/oproxy/p', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
url,
|
|
||||||
options,
|
|
||||||
}),
|
|
||||||
}).then(async res => await res.json()).then(sendResponse).catch(console.error)
|
|
||||||
return true
|
|
||||||
} else if (event.type === 'syncGet') { // sync.get
|
|
||||||
chrome.storage.sync.get(event.keys, data => {
|
|
||||||
sendResponse(data)
|
|
||||||
})
|
|
||||||
return true
|
|
||||||
} else if (event.type === 'syncSet') { // sync.set
|
|
||||||
chrome.storage.sync.set(event.items).catch(console.error)
|
|
||||||
} else if (event.type === 'syncRemove') { // sync.remove
|
|
||||||
chrome.storage.sync.remove(event.keys).catch(console.error)
|
|
||||||
} else if (event.type === 'addTask') {
|
|
||||||
// 新建任务
|
// 新建任务
|
||||||
const task: Task = {
|
const task: Task = {
|
||||||
id: v4(),
|
id: v4(),
|
||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
def: event.taskDef,
|
def: params.taskDef,
|
||||||
}
|
}
|
||||||
tasksMap.set(task.id, task)
|
tasksMap.set(task.id, task)
|
||||||
|
|
||||||
// 立即触发任务
|
// 立即触发任务
|
||||||
handleTask(task).catch(console.error)
|
handleTask(task).catch(console.error)
|
||||||
|
|
||||||
|
return task
|
||||||
|
},
|
||||||
|
[MESSAGE_TO_EXTENSION_GET_TASK]: async (params, context) => {
|
||||||
// 返回任务信息
|
// 返回任务信息
|
||||||
sendResponse(task)
|
const taskId = params.taskId
|
||||||
} else if (event.type === 'getTask') {
|
|
||||||
// 返回任务信息
|
|
||||||
const taskId = event.taskId
|
|
||||||
const task = tasksMap.get(taskId)
|
const task = tasksMap.get(taskId)
|
||||||
if (task == null) {
|
if (task == null) {
|
||||||
sendResponse({
|
return {
|
||||||
code: 'not_found',
|
code: 'not_found',
|
||||||
})
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检测删除缓存
|
// 检测删除缓存
|
||||||
@@ -62,9 +40,68 @@ chrome.runtime.onMessage.addListener((event, sender, sendResponse) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 返回任务
|
// 返回任务
|
||||||
sendResponse({
|
return {
|
||||||
code: 'ok',
|
code: 'ok',
|
||||||
task,
|
task,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Return true when sending a response asynchronously.
|
||||||
|
*/
|
||||||
|
chrome.runtime.onMessage.addListener((event: MessageData, sender: chrome.runtime.MessageSender, sendResponse: (result: any) => void) => {
|
||||||
|
debug((sender.tab != null) ? `tab ${sender.tab.url ?? ''} => ` : 'extension => ', event)
|
||||||
|
|
||||||
|
// legacy
|
||||||
|
if (event.type === 'syncGet') { // sync.get
|
||||||
|
chrome.storage.sync.get(event.keys, data => {
|
||||||
|
sendResponse(data)
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
} else if (event.type === 'syncSet') { // sync.set
|
||||||
|
chrome.storage.sync.set(event.items).catch(console.error)
|
||||||
|
return
|
||||||
|
} else if (event.type === 'syncRemove') { // sync.remove
|
||||||
|
chrome.storage.sync.remove(event.keys).catch(console.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check event target
|
||||||
|
if (event.target !== MESSAGE_TARGET_EXTENSION) return
|
||||||
|
|
||||||
|
const method = methods[event.method]
|
||||||
|
if (method != null) {
|
||||||
|
method(event.params, {
|
||||||
|
event,
|
||||||
|
sender,
|
||||||
|
}).then(data => sendResponse({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
data,
|
||||||
|
})).catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
let message
|
||||||
|
if (err instanceof Error) {
|
||||||
|
message = err.message
|
||||||
|
} else if (typeof err === 'string') {
|
||||||
|
message = err
|
||||||
|
} else {
|
||||||
|
message = 'error: ' + JSON.stringify(err)
|
||||||
|
}
|
||||||
|
sendResponse({
|
||||||
|
success: false,
|
||||||
|
code: 500,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
console.error('Unknown method:', event.method)
|
||||||
|
sendResponse({
|
||||||
|
success: false,
|
||||||
|
code: 501,
|
||||||
|
message: 'Unknown method: ' + event.method,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -1,3 +1,26 @@
|
|||||||
|
export const MESSAGE_TARGET_EXTENSION = 'BilibiliExtension'
|
||||||
|
export const MESSAGE_TARGET_INJECT = 'BilibiliInject'
|
||||||
|
export const MESSAGE_TARGET_APP = 'BilibiliAPP'
|
||||||
|
|
||||||
|
export const MESSAGE_TO_EXTENSION_ADD_TASK = 'addTask'
|
||||||
|
export const MESSAGE_TO_EXTENSION_GET_TASK = 'getTask'
|
||||||
|
|
||||||
|
export const MESSAGE_TO_INJECT_FOLD = 'fold'
|
||||||
|
export const MESSAGE_TO_INJECT_MOVE = 'move'
|
||||||
|
export const MESSAGE_TO_INJECT_PLAY = 'play'
|
||||||
|
export const MESSAGE_TO_INJECT_DOWNLOAD_AUDIO = 'downloadAudio'
|
||||||
|
export const MESSAGE_TO_INJECT_GET_VIDEO_STATUS = 'getVideoStatus'
|
||||||
|
export const MESSAGE_TO_INJECT_GET_VIDEO_ELEMENT_INFO = 'getVideoElementInfo'
|
||||||
|
export const MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO = 'refreshVideoInfo'
|
||||||
|
export const MESSAGE_TO_INJECT_UPDATETRANSRESULT = 'updateTransResult'
|
||||||
|
export const MESSAGE_TO_INJECT_HIDE_TRANS = 'hideTrans'
|
||||||
|
export const MESSAGE_TO_INJECT_GET_SUBTITLE = 'getSubtitle'
|
||||||
|
|
||||||
|
export const MESSAGE_TO_APP_SET_INFOS = 'setInfos'
|
||||||
|
export const MESSAGE_TO_APP_SET_VIDEO_INFO = 'setVideoInfo'
|
||||||
|
|
||||||
|
export const EVENT_EXPAND = 'expand'
|
||||||
|
|
||||||
export const APP_DOM_ID = 'bilibili-subtitle'
|
export const APP_DOM_ID = 'bilibili-subtitle'
|
||||||
|
|
||||||
export const IFRAME_ID = 'bilibili-subtitle-iframe'
|
export const IFRAME_ID = 'bilibili-subtitle-iframe'
|
||||||
@@ -189,8 +212,6 @@ Answer:
|
|||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EVENT_EXPAND = 'expand'
|
|
||||||
|
|
||||||
export const TASK_EXPIRE_TIME = 15*60*1000
|
export const TASK_EXPIRE_TIME = 15*60*1000
|
||||||
|
|
||||||
export const PAGE_MAIN = 'main'
|
export const PAGE_MAIN = 'main'
|
||||||
|
101
src/hooks/useMessageService.ts
Normal file
101
src/hooks/useMessageService.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import {useCallback, useContext, useEffect} from 'react'
|
||||||
|
import {
|
||||||
|
MESSAGE_TARGET_APP,
|
||||||
|
MESSAGE_TARGET_EXTENSION,
|
||||||
|
MESSAGE_TARGET_INJECT,
|
||||||
|
MESSAGE_TO_APP_SET_INFOS,
|
||||||
|
MESSAGE_TO_APP_SET_VIDEO_INFO,
|
||||||
|
} from '@/const'
|
||||||
|
import {debug} from '@/util/biz_util'
|
||||||
|
import {callServer, PostMessagePayload, PostMessageResponse} from 'postmessage-promise'
|
||||||
|
import {useAppDispatch} from '../hooks/redux'
|
||||||
|
import {Waiter} from '@kky002/kky-util'
|
||||||
|
import {setInfos, setTitle, setUrl, setCurInfo, setCurFetched, setData} from '@/redux/envReducer'
|
||||||
|
|
||||||
|
let postInjectMessage: (method: string, params: PostMessagePayload) => Promise<PostMessageResponse> | undefined
|
||||||
|
|
||||||
|
export const injectWaiter = new Waiter<typeof postInjectMessage>(() => ({
|
||||||
|
finished: postInjectMessage != null,
|
||||||
|
data: postInjectMessage
|
||||||
|
}), 100, 15000)
|
||||||
|
|
||||||
|
const useMessageService = () => {
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const path = 'app' //useAppSelector(state => state.env.path)
|
||||||
|
|
||||||
|
const messageHandler = useCallback((method: string, params: any, from: string, context: any): boolean => {
|
||||||
|
switch (method) {
|
||||||
|
case MESSAGE_TO_APP_SET_INFOS:
|
||||||
|
dispatch(setInfos(params.infos))
|
||||||
|
dispatch(setCurInfo(undefined))
|
||||||
|
dispatch(setCurFetched(false))
|
||||||
|
dispatch(setData(undefined))
|
||||||
|
break
|
||||||
|
case MESSAGE_TO_APP_SET_VIDEO_INFO:
|
||||||
|
dispatch(setInfos(params.infos))
|
||||||
|
dispatch(setUrl(params.url))
|
||||||
|
dispatch(setTitle(params.title))
|
||||||
|
console.debug('video title: ', params.title)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
debug('unknown message method: ', method)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}, [dispatch])
|
||||||
|
|
||||||
|
// connect to inject
|
||||||
|
useEffect(() => {
|
||||||
|
if (path !== 'app') return
|
||||||
|
|
||||||
|
let destroyFunc: (() => void) | undefined
|
||||||
|
|
||||||
|
const serverObject = {
|
||||||
|
server: window.parent, // openedWindow / window.parent / window.opener;
|
||||||
|
origin: '*', // target-window's origin or *
|
||||||
|
}
|
||||||
|
const options = {}
|
||||||
|
callServer(serverObject, options).then(e => {
|
||||||
|
const { postMessage, listenMessage, destroy } = e
|
||||||
|
postInjectMessage = postMessage
|
||||||
|
destroyFunc = destroy
|
||||||
|
|
||||||
|
listenMessage((method, params, sendResponse) => {
|
||||||
|
debug('inject => ', method, params)
|
||||||
|
|
||||||
|
const success = messageHandler(method, params, MESSAGE_TARGET_INJECT, {})
|
||||||
|
sendResponse({
|
||||||
|
success,
|
||||||
|
code: success ? 200 : 500
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
debug('message ready')
|
||||||
|
}).catch(console.error)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
destroyFunc?.()
|
||||||
|
}
|
||||||
|
}, [messageHandler, path])
|
||||||
|
|
||||||
|
const extensionMessageCallback = useCallback((event: MessageData, sender: chrome.runtime.MessageSender, sendResponse: (response?: any) => void) => {
|
||||||
|
debug((sender.tab != null) ? `tab ${sender.tab.url??''} => ` : 'extension => ', JSON.stringify(event))
|
||||||
|
|
||||||
|
// check event target
|
||||||
|
if (!event || event.target !== MESSAGE_TARGET_APP) return
|
||||||
|
|
||||||
|
messageHandler(event.method, event.params, MESSAGE_TARGET_EXTENSION, {
|
||||||
|
sender
|
||||||
|
})
|
||||||
|
}, [messageHandler])
|
||||||
|
|
||||||
|
// listen for message
|
||||||
|
useEffect(() => {
|
||||||
|
chrome.runtime.onMessage.addListener(extensionMessageCallback)
|
||||||
|
return () => {
|
||||||
|
chrome.runtime.onMessage.removeListener(extensionMessageCallback)
|
||||||
|
}
|
||||||
|
}, [extensionMessageCallback])
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useMessageService
|
@@ -1,6 +1,8 @@
|
|||||||
import {useAppDispatch, useAppSelector} from './redux'
|
import {useAppDispatch, useAppSelector} from './redux'
|
||||||
import React, {useCallback} from 'react'
|
import React, {useCallback} from 'react'
|
||||||
import {setNeedScroll, setReviewAction, setTempData} from '../redux/envReducer'
|
import {setNeedScroll, setReviewAction, setTempData} from '../redux/envReducer'
|
||||||
|
import {sendInject} from '../util/biz_util'
|
||||||
|
import {MESSAGE_TO_INJECT_MOVE} from '../const'
|
||||||
|
|
||||||
const useSubtitle = () => {
|
const useSubtitle = () => {
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
@@ -9,7 +11,7 @@ const useSubtitle = () => {
|
|||||||
const reviewActions = useAppSelector(state => state.env.tempData.reviewActions)
|
const reviewActions = useAppSelector(state => state.env.tempData.reviewActions)
|
||||||
|
|
||||||
const move = useCallback((time: number, togglePause: boolean) => {
|
const move = useCallback((time: number, togglePause: boolean) => {
|
||||||
window.parent.postMessage({type: 'move', time, togglePause}, '*')
|
sendInject(MESSAGE_TO_INJECT_MOVE, {time, togglePause})
|
||||||
|
|
||||||
//review action
|
//review action
|
||||||
if (reviewed === undefined && !reviewAction) {
|
if (reviewed === undefined && !reviewAction) {
|
||||||
|
@@ -16,9 +16,11 @@ import {
|
|||||||
setTempData,
|
setTempData,
|
||||||
} from '../redux/envReducer'
|
} from '../redux/envReducer'
|
||||||
import {EventBusContext} from '../Router'
|
import {EventBusContext} from '../Router'
|
||||||
import {EVENT_EXPAND, GEMINI_TOKENS, TOTAL_HEIGHT_MAX, TOTAL_HEIGHT_MIN, WORDS_MIN, WORDS_RATE} from '../const'
|
import {EVENT_EXPAND, GEMINI_TOKENS, TOTAL_HEIGHT_MAX, TOTAL_HEIGHT_MIN, WORDS_MIN, WORDS_RATE, MESSAGE_TO_INJECT_GET_VIDEO_STATUS, MESSAGE_TO_INJECT_GET_VIDEO_ELEMENT_INFO, MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO, MESSAGE_TO_INJECT_HIDE_TRANS, MESSAGE_TO_INJECT_UPDATETRANSRESULT} from '../const'
|
||||||
import {useInterval} from 'ahooks'
|
import {useInterval} from 'ahooks'
|
||||||
import {getModelMaxTokens, getWholeText} from '../util/biz_util'
|
import {getModelMaxTokens, getWholeText} from '../util/biz_util'
|
||||||
|
import {sendInject} from '../util/biz_util'
|
||||||
|
import {MESSAGE_TO_INJECT_GET_SUBTITLE} from '../const'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service是单例,类似后端的服务概念
|
* Service是单例,类似后端的服务概念
|
||||||
@@ -52,55 +54,6 @@ const useSubtitleService = () => {
|
|||||||
}
|
}
|
||||||
}, [reviewActions, dispatch, reviewed])
|
}, [reviewActions, dispatch, reviewed])
|
||||||
|
|
||||||
// 监听消息
|
|
||||||
useEffect(() => {
|
|
||||||
const listener = (event: MessageEvent) => {
|
|
||||||
const data = event.data
|
|
||||||
|
|
||||||
if (data.type === 'setVideoInfo') {
|
|
||||||
dispatch(setInfos(data.infos))
|
|
||||||
dispatch(setUrl(data.url))
|
|
||||||
dispatch(setTitle(data.title))
|
|
||||||
console.debug('video title: ', data.title)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === 'setInfos') {
|
|
||||||
dispatch(setInfos(data.infos))
|
|
||||||
dispatch(setCurInfo(undefined))
|
|
||||||
dispatch(setCurFetched(false))
|
|
||||||
dispatch(setData(undefined))
|
|
||||||
// console.log('setInfos', data.infos)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === 'setSubtitle') {
|
|
||||||
const data_ = data.data.data
|
|
||||||
data_?.body?.forEach((item: TranscriptItem, idx: number) => {
|
|
||||||
item.idx = idx
|
|
||||||
})
|
|
||||||
// dispatch(setCurInfo(data.data.info))
|
|
||||||
dispatch(setCurFetched(true))
|
|
||||||
dispatch(setData(data_))
|
|
||||||
// console.log('setSubtitle', data.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === 'setCurrentTime') {
|
|
||||||
dispatch(setCurrentTime(data.data.currentTime))
|
|
||||||
}
|
|
||||||
if (data.type === 'setSettings') {
|
|
||||||
dispatch(setNoVideo(data.data.noVideo))
|
|
||||||
if (data.data.totalHeight) {
|
|
||||||
dispatch(setTotalHeight(Math.min(Math.max(data.data.totalHeight, TOTAL_HEIGHT_MIN), TOTAL_HEIGHT_MAX)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('message', listener)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('message', listener)
|
|
||||||
}
|
|
||||||
}, [dispatch, eventBus])
|
|
||||||
|
|
||||||
// 有数据时自动展开
|
// 有数据时自动展开
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ((data != null) && data.body.length > 0) {
|
if ((data != null) && data.body.length > 0) {
|
||||||
@@ -120,15 +73,30 @@ const useSubtitleService = () => {
|
|||||||
// 获取
|
// 获取
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (curInfo && !curFetched) {
|
if (curInfo && !curFetched) {
|
||||||
window.parent.postMessage({type: 'getSubtitle', info: curInfo}, '*')
|
sendInject(MESSAGE_TO_INJECT_GET_SUBTITLE, {info: curInfo}).then(data => {
|
||||||
|
const data_ = data.data
|
||||||
|
data_?.body?.forEach((item: TranscriptItem, idx: number) => {
|
||||||
|
item.idx = idx
|
||||||
|
})
|
||||||
|
// dispatch(setCurInfo(data.data.info))
|
||||||
|
dispatch(setCurFetched(true))
|
||||||
|
dispatch(setData(data_))
|
||||||
|
|
||||||
|
console.log('subtitle', data)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}, [curFetched, curInfo])
|
}, [curFetched, curInfo])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 初始获取列表
|
// 初始获取列表
|
||||||
window.parent.postMessage({type: 'refreshVideoInfo'}, '*')
|
sendInject(MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO, {})
|
||||||
// 初始获取设置信息
|
// 初始获取设置信息
|
||||||
window.parent.postMessage({type: 'getSettings'}, '*')
|
sendInject(MESSAGE_TO_INJECT_GET_VIDEO_ELEMENT_INFO, {}).then(info => {
|
||||||
|
dispatch(setNoVideo(info.noVideo))
|
||||||
|
if (info.totalHeight) {
|
||||||
|
dispatch(setTotalHeight(Math.min(Math.max(info.totalHeight, TOTAL_HEIGHT_MIN), TOTAL_HEIGHT_MAX)))
|
||||||
|
}
|
||||||
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 更新当前位置
|
// 更新当前位置
|
||||||
@@ -216,21 +184,23 @@ const useSubtitleService = () => {
|
|||||||
|
|
||||||
// 每秒更新当前视频时间
|
// 每秒更新当前视频时间
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
window.parent.postMessage({type: 'getCurrentTime'}, '*')
|
sendInject(MESSAGE_TO_INJECT_GET_VIDEO_STATUS, {}).then(status => {
|
||||||
|
dispatch(setCurrentTime(status.currentTime))
|
||||||
|
})
|
||||||
}, 500)
|
}, 500)
|
||||||
|
|
||||||
// show translated text in the video
|
// show translated text in the video
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hideOnDisableAutoTranslate && !autoTranslate) {
|
if (hideOnDisableAutoTranslate && !autoTranslate) {
|
||||||
window.parent.postMessage({type: 'updateTransResult'}, '*')
|
sendInject(MESSAGE_TO_INJECT_HIDE_TRANS, {})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const transResult = curIdx?transResults[curIdx]:undefined
|
const transResult = curIdx?transResults[curIdx]:undefined
|
||||||
if (transResult?.code === '200' && transResult.data) {
|
if (transResult?.code === '200' && transResult.data) {
|
||||||
window.parent.postMessage({type: 'updateTransResult', result: transResult.data}, '*')
|
sendInject(MESSAGE_TO_INJECT_UPDATETRANSRESULT, {result: transResult.data})
|
||||||
} else {
|
} else {
|
||||||
window.parent.postMessage({type: 'updateTransResult'}, '*')
|
sendInject(MESSAGE_TO_INJECT_HIDE_TRANS, {})
|
||||||
}
|
}
|
||||||
}, [autoTranslate, curIdx, hideOnDisableAutoTranslate, transResults])
|
}, [autoTranslate, curIdx, hideOnDisableAutoTranslate, transResults])
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
LANGUAGE_DEFAULT,
|
LANGUAGE_DEFAULT,
|
||||||
LANGUAGES_MAP,
|
LANGUAGES_MAP,
|
||||||
|
MESSAGE_TO_EXTENSION_ADD_TASK,
|
||||||
|
MESSAGE_TO_EXTENSION_GET_TASK,
|
||||||
PROMPT_DEFAULTS,
|
PROMPT_DEFAULTS,
|
||||||
PROMPT_TYPE_ASK,
|
PROMPT_TYPE_ASK,
|
||||||
PROMPT_TYPE_TRANSLATE,
|
PROMPT_TYPE_TRANSLATE,
|
||||||
@@ -27,7 +29,7 @@ import {
|
|||||||
} from '../const'
|
} from '../const'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import {useMemoizedFn} from 'ahooks/es'
|
import {useMemoizedFn} from 'ahooks/es'
|
||||||
import {extractJsonArray, extractJsonObject, getModel} from '../util/biz_util'
|
import {extractJsonArray, extractJsonObject, getModel, sendExtension} from '../util/biz_util'
|
||||||
import {formatTime} from '../util/util'
|
import {formatTime} from '../util/util'
|
||||||
|
|
||||||
const useTranslate = () => {
|
const useTranslate = () => {
|
||||||
@@ -135,7 +137,7 @@ const useTranslate = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
dispatch(addTransResults(result))
|
dispatch(addTransResults(result))
|
||||||
const task = await chrome.runtime.sendMessage({type: 'addTask', taskDef})
|
const task = await sendExtension(MESSAGE_TO_EXTENSION_ADD_TASK, {taskDef})
|
||||||
dispatch(addTaskId(task.id))
|
dispatch(addTaskId(task.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,7 +207,7 @@ const useTranslate = () => {
|
|||||||
console.debug('addSummarizeTask', taskDef)
|
console.debug('addSummarizeTask', taskDef)
|
||||||
dispatch(setSummaryStatus({segmentStartIdx: segment.startIdx, type, status: 'pending'}))
|
dispatch(setSummaryStatus({segmentStartIdx: segment.startIdx, type, status: 'pending'}))
|
||||||
dispatch(setLastSummarizeTime(Date.now()))
|
dispatch(setLastSummarizeTime(Date.now()))
|
||||||
const task = await chrome.runtime.sendMessage({type: 'addTask', taskDef})
|
const task = await sendExtension(MESSAGE_TO_EXTENSION_ADD_TASK, {taskDef})
|
||||||
dispatch(addTaskId(task.id))
|
dispatch(addTaskId(task.id))
|
||||||
}
|
}
|
||||||
}, [dispatch, envData, summarizeLanguage.name, title])
|
}, [dispatch, envData, summarizeLanguage.name, title])
|
||||||
@@ -262,7 +264,7 @@ const useTranslate = () => {
|
|||||||
id,
|
id,
|
||||||
status: 'pending'
|
status: 'pending'
|
||||||
}))
|
}))
|
||||||
const task = await chrome.runtime.sendMessage({type: 'addTask', taskDef})
|
const task = await sendExtension(MESSAGE_TO_EXTENSION_ADD_TASK, {taskDef})
|
||||||
dispatch(addTaskId(task.id))
|
dispatch(addTaskId(task.id))
|
||||||
}
|
}
|
||||||
}, [dispatch, envData, summarizeLanguage.name, title])
|
}, [dispatch, envData, summarizeLanguage.name, title])
|
||||||
@@ -330,7 +332,7 @@ const useTranslate = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const getTask = useCallback(async (taskId: string) => {
|
const getTask = useCallback(async (taskId: string) => {
|
||||||
const taskResp = await chrome.runtime.sendMessage({type: 'getTask', taskId})
|
const taskResp = await sendExtension(MESSAGE_TO_EXTENSION_GET_TASK, {taskId})
|
||||||
if (taskResp.code === 'ok') {
|
if (taskResp.code === 'ok') {
|
||||||
console.debug('getTask', taskResp.task)
|
console.debug('getTask', taskResp.task)
|
||||||
const task: Task = taskResp.task
|
const task: Task = taskResp.task
|
||||||
|
@@ -1,12 +1,82 @@
|
|||||||
import {TOTAL_HEIGHT_DEF, HEADER_HEIGHT, TOTAL_HEIGHT_MIN, TOTAL_HEIGHT_MAX, IFRAME_ID} 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 } from '@/const'
|
||||||
let totalHeight = TOTAL_HEIGHT_DEF
|
import { PostMessagePayload, PostMessageResponse, startListening } from 'postmessage-promise'
|
||||||
|
import {MESSAGE_TARGET_EXTENSION, 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'
|
||||||
|
|
||||||
const getVideoElement = () => {
|
const debug = (...args: any[]) => {
|
||||||
const videoWrapper = document.getElementById('bilibili-player')
|
console.debug('[Inject]', ...args)
|
||||||
return videoWrapper?.querySelector('video') as HTMLVideoElement | undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const timerIframe = setInterval(function () {
|
(function () {
|
||||||
|
const runtime: {
|
||||||
|
postMessageToApp?: (method: string, payload: PostMessagePayload) => Promise<PostMessageResponse>
|
||||||
|
// lastV?: string | null
|
||||||
|
// lastVideoInfo?: VideoInfo
|
||||||
|
|
||||||
|
fold: boolean
|
||||||
|
|
||||||
|
videoElement?: HTMLVideoElement
|
||||||
|
videoElementHeight: number
|
||||||
|
|
||||||
|
showTrans: boolean
|
||||||
|
curTrans?: string
|
||||||
|
} = {
|
||||||
|
fold: true,
|
||||||
|
videoElementHeight: TOTAL_HEIGHT_DEF,
|
||||||
|
showTrans: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendExtension = async <T = any>(method: string, params?: any) => {
|
||||||
|
return await chrome.runtime.sendMessage<MessageData, MessageResult>({
|
||||||
|
target: MESSAGE_TARGET_EXTENSION,
|
||||||
|
method,
|
||||||
|
params: params??{},
|
||||||
|
}).then((messageResult) => {
|
||||||
|
if (messageResult.success) {
|
||||||
|
return messageResult.data as T
|
||||||
|
} else {
|
||||||
|
throw new Error(messageResult.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendApp = async <T>(method: string, params: any) => {
|
||||||
|
if (runtime.postMessageToApp != null) {
|
||||||
|
const messageResult = await runtime.postMessageToApp(method, params) as MessageResult | undefined
|
||||||
|
if (messageResult != null) {
|
||||||
|
if (messageResult.success) {
|
||||||
|
return messageResult.data as T
|
||||||
|
} else {
|
||||||
|
throw new Error(messageResult.message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('no response')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getVideoElement = () => {
|
||||||
|
const videoWrapper = document.getElementById('bilibili-player')
|
||||||
|
return videoWrapper?.querySelector('video') as HTMLVideoElement | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if changed
|
||||||
|
*/
|
||||||
|
const refreshVideoElement = () => {
|
||||||
|
const newVideoElement = getVideoElement()
|
||||||
|
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) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
runtime.videoElement = newVideoElement
|
||||||
|
runtime.videoElementHeight = newVideoElementHeight
|
||||||
|
// update iframe height
|
||||||
|
updateIframeHeight()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const timerIframe = setInterval(function () {
|
||||||
var danmukuBox = document.getElementById('danmukuBox')
|
var danmukuBox = document.getElementById('danmukuBox')
|
||||||
if (danmukuBox) {
|
if (danmukuBox) {
|
||||||
clearInterval(timerIframe)
|
clearInterval(timerIframe)
|
||||||
@@ -35,18 +105,18 @@ const timerIframe = setInterval(function () {
|
|||||||
//insert before first child
|
//insert before first child
|
||||||
danmukuBox?.insertBefore(iframe, danmukuBox?.firstChild)
|
danmukuBox?.insertBefore(iframe, danmukuBox?.firstChild)
|
||||||
|
|
||||||
console.debug('iframe inserted')
|
debug('iframe inserted')
|
||||||
}, 1500)
|
}, 1500)
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
let aid: number | null = null
|
let aid: number | null = null
|
||||||
let title = ''
|
let title = ''
|
||||||
let pages: any[] = []
|
let pages: any[] = []
|
||||||
let pagesMap: Record<string, any> = {}
|
let pagesMap: Record<string, any> = {}
|
||||||
|
|
||||||
let lastAidOrBvid: string | null = null
|
let lastAidOrBvid: string | null = null
|
||||||
const refreshVideoInfo = async () => {
|
const refreshVideoInfo = async () => {
|
||||||
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
||||||
if (!iframe) return
|
if (!iframe) return
|
||||||
|
|
||||||
@@ -76,20 +146,20 @@ const refreshVideoInfo = async () => {
|
|||||||
let subtitles
|
let subtitles
|
||||||
if (aidOrBvid.toLowerCase().startsWith('av')) {//avxxx
|
if (aidOrBvid.toLowerCase().startsWith('av')) {//avxxx
|
||||||
aid = parseInt(aidOrBvid.slice(2))
|
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)
|
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
|
cid = pages[0].cid
|
||||||
title = pages[0].part
|
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 => {
|
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
|
subtitles = res.data.subtitle.subtitles
|
||||||
})
|
})
|
||||||
} else {//bvxxx
|
} else {//bvxxx
|
||||||
await fetch(`https://api.bilibili.com/x/web-interface/view?bvid=${aidOrBvid}`, {credentials: 'include'}).then(res => res.json()).then(async res => {
|
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
|
title = res.data.title
|
||||||
aid = res.data.aid
|
aid = res.data.aid
|
||||||
cid = res.data.cid
|
cid = res.data.cid
|
||||||
pages = res.data.pages
|
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 => {
|
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
|
subtitles = res.data.subtitle.subtitles
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -100,24 +170,23 @@ const refreshVideoInfo = async () => {
|
|||||||
pagesMap[page.page + ''] = page
|
pagesMap[page.page + ''] = page
|
||||||
})
|
})
|
||||||
|
|
||||||
console.debug('refreshVideoInfo: ', aid, cid, pages, subtitles)
|
debug('refreshVideoInfo: ', aid, cid, pages, subtitles)
|
||||||
|
|
||||||
//send setVideoInfo
|
//send setVideoInfo
|
||||||
iframe.contentWindow?.postMessage({
|
sendApp(MESSAGE_TO_APP_SET_VIDEO_INFO, {
|
||||||
type: 'setVideoInfo',
|
|
||||||
url: location.origin + location.pathname,
|
url: location.origin + location.pathname,
|
||||||
title,
|
title,
|
||||||
aid,
|
aid,
|
||||||
pages,
|
pages,
|
||||||
infos: subtitles,
|
infos: subtitles,
|
||||||
}, '*')
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let lastAid: number | null = null
|
let lastAid: number | null = null
|
||||||
let lastCid: number | null = null
|
let lastCid: number | null = null
|
||||||
const refreshSubtitles = () => {
|
const refreshSubtitles = () => {
|
||||||
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
||||||
if (!iframe) return
|
if (!iframe) return
|
||||||
|
|
||||||
@@ -128,7 +197,7 @@ const refreshSubtitles = () => {
|
|||||||
const cid = page.cid
|
const cid = page.cid
|
||||||
|
|
||||||
if (aid !== lastAid || cid !== lastCid) {
|
if (aid !== lastAid || cid !== lastCid) {
|
||||||
console.debug('refreshSubtitles', aid, cid)
|
debug('refreshSubtitles', aid, cid)
|
||||||
|
|
||||||
lastAid = aid
|
lastAid = aid
|
||||||
lastCid = cid
|
lastCid = cid
|
||||||
@@ -139,85 +208,112 @@ const refreshSubtitles = () => {
|
|||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
// console.log('refreshSubtitles: ', aid, cid, res)
|
// console.log('refreshSubtitles: ', aid, cid, res)
|
||||||
iframe.contentWindow?.postMessage({
|
sendApp(MESSAGE_TO_APP_SET_INFOS, {
|
||||||
type: 'setInfos',
|
|
||||||
infos: res.data.subtitle.subtitles
|
infos: res.data.subtitle.subtitles
|
||||||
}, '*')
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听消息
|
const updateIframeHeight = () => {
|
||||||
window.addEventListener("message", (event) => {
|
|
||||||
const {data} = event
|
|
||||||
|
|
||||||
if (data.type === 'fold') {
|
|
||||||
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
const iframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement | undefined
|
||||||
if (iframe) {
|
if (iframe != null) {
|
||||||
iframe.style.height = (data.fold ? HEADER_HEIGHT : totalHeight) + 'px'
|
iframe.style.height = (runtime.fold ? HEADER_HEIGHT : runtime.videoElementHeight) + 'px'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type === 'move') {
|
const methods: {
|
||||||
|
[key: string]: (params: any, context: MethodContext) => Promise<any>
|
||||||
|
} = {
|
||||||
|
[MESSAGE_TO_INJECT_FOLD]: async (params) => {
|
||||||
|
runtime.fold = params.fold
|
||||||
|
updateIframeHeight()
|
||||||
|
},
|
||||||
|
[MESSAGE_TO_INJECT_MOVE]: async (params) => {
|
||||||
const video = getVideoElement()
|
const video = getVideoElement()
|
||||||
if (video) {
|
if (video != null) {
|
||||||
video.currentTime = data.time
|
video.currentTime = params.time
|
||||||
if (data.togglePause) {
|
if (params.togglePause) {
|
||||||
video.paused ? video.play() : video.pause()
|
video.paused ? video.play() : video.pause()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
[MESSAGE_TO_INJECT_GET_SUBTITLE]: async (params) => {
|
||||||
//刷新视频信息
|
let url = params.info.subtitle_url
|
||||||
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://')) {
|
if (url.startsWith('http://')) {
|
||||||
url = url.replace('http://', 'https://')
|
url = url.replace('http://', 'https://')
|
||||||
}
|
}
|
||||||
fetch(url).then(res => res.json()).then(res => {
|
return await fetch(url).then(res => res.json())
|
||||||
event.source?.postMessage({
|
},
|
||||||
data: {
|
[MESSAGE_TO_INJECT_GET_VIDEO_STATUS]: async (params) => {
|
||||||
info: data.info,
|
|
||||||
data: res,
|
|
||||||
}, type: 'setSubtitle'
|
|
||||||
// @ts-ignore
|
|
||||||
}, '*')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === 'getCurrentTime') {
|
|
||||||
const video = getVideoElement()
|
const video = getVideoElement()
|
||||||
if (video) {
|
if (video != null) {
|
||||||
event.source?.postMessage({
|
return {
|
||||||
data: {
|
paused: video.paused,
|
||||||
currentTime: video.currentTime
|
currentTime: video.currentTime
|
||||||
}, type: 'setCurrentTime'
|
|
||||||
// @ts-ignore
|
|
||||||
}, '*')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[MESSAGE_TO_INJECT_GET_VIDEO_ELEMENT_INFO]: async (params) => {
|
||||||
|
refreshVideoElement()
|
||||||
|
return {
|
||||||
|
noVideo: runtime.videoElement == null,
|
||||||
|
totalHeight: runtime.videoElementHeight,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[MESSAGE_TO_INJECT_REFRESH_VIDEO_INFO]: async (params) => {
|
||||||
|
refreshVideoInfo()
|
||||||
|
},
|
||||||
|
[MESSAGE_TO_INJECT_UPDATETRANSRESULT]: async (params) => {
|
||||||
|
runtime.showTrans = true
|
||||||
|
runtime.curTrans = params?.result
|
||||||
|
|
||||||
if (data.type === 'getSettings') {
|
let text = document.getElementById('trans-result-text')
|
||||||
const videoElement = getVideoElement()
|
if (text) {
|
||||||
totalHeight = videoElement ? Math.min(Math.max(videoElement.offsetHeight, TOTAL_HEIGHT_MIN), TOTAL_HEIGHT_MAX) : TOTAL_HEIGHT_DEF
|
text.innerHTML = runtime.curTrans??''
|
||||||
event.source?.postMessage({
|
} else {
|
||||||
data: {
|
const container = document.getElementsByClassName('bpx-player-subtitle-panel-wrap')?.[0]
|
||||||
noVideo: !videoElement,
|
if (container) {
|
||||||
totalHeight,
|
const div = document.createElement('div')
|
||||||
}, type: 'setSettings'
|
div.style.display = 'flex'
|
||||||
// @ts-ignore
|
div.style.justifyContent = 'center'
|
||||||
}, '*')
|
div.style.margin = '2px'
|
||||||
}
|
text = document.createElement('text')
|
||||||
|
text.id = 'trans-result-text'
|
||||||
|
text.innerHTML = runtime.curTrans??''
|
||||||
|
text.style.fontSize = '1rem'
|
||||||
|
text.style.padding = '5px'
|
||||||
|
text.style.color = 'white'
|
||||||
|
text.style.background = 'rgba(0, 0, 0, 0.4)'
|
||||||
|
div.append(text)
|
||||||
|
|
||||||
if (data.type === 'downloadAudio') {
|
container.append(div)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text && (text.style.display = runtime.curTrans ? 'block' : 'none')
|
||||||
|
},
|
||||||
|
[MESSAGE_TO_INJECT_HIDE_TRANS]: async (params) => {
|
||||||
|
runtime.showTrans = false
|
||||||
|
runtime.curTrans = undefined
|
||||||
|
|
||||||
|
let text = document.getElementById('trans-result-text')
|
||||||
|
if (text) {
|
||||||
|
text.style.display = 'none'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[MESSAGE_TO_INJECT_PLAY]: async (params) => {
|
||||||
|
const { play } = params
|
||||||
|
const video = getVideoElement()
|
||||||
|
if (video != null) {
|
||||||
|
if (play) {
|
||||||
|
await video.play()
|
||||||
|
} else {
|
||||||
|
video.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[MESSAGE_TO_INJECT_DOWNLOAD_AUDIO]: async (params) => {
|
||||||
const html = document.getElementsByTagName('html')[0].innerHTML
|
const html = document.getElementsByTagName('html')[0].innerHTML
|
||||||
const playInfo = JSON.parse(html.match(/window.__playinfo__=(.+?)<\/script/)?.[1] ?? '{}')
|
const playInfo = JSON.parse(html.match(/window.__playinfo__=(.+?)<\/script/)?.[1] ?? '{}')
|
||||||
const audioUrl = playInfo.data.dash.audio[0].baseUrl
|
const audioUrl = playInfo.data.dash.audio[0].baseUrl
|
||||||
@@ -228,37 +324,79 @@ window.addEventListener("message", (event) => {
|
|||||||
a.download = `${title}.m4s`
|
a.download = `${title}.m4s`
|
||||||
a.click()
|
a.click()
|
||||||
})
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type === 'updateTransResult') {
|
/**
|
||||||
const trans = data.result??''
|
* @param sendResponse No matter what is returned, this method will definitely be called.
|
||||||
let text = document.getElementById('trans-result-text')
|
*/
|
||||||
if (text) {
|
const messageHandler = (event: MessageData, sender: chrome.runtime.MessageSender | null, sendResponse: (response?: MessageResult) => void) => {
|
||||||
text.innerHTML = trans
|
const source = sender != null?((sender.tab != null) ? `tab ${sender.tab.url ?? ''}` : 'extension'):'app'
|
||||||
|
debug(`${source} => `, JSON.stringify(event))
|
||||||
|
|
||||||
|
// check event target
|
||||||
|
if (event.target !== MESSAGE_TARGET_INJECT) return
|
||||||
|
|
||||||
|
const method = methods[event.method]
|
||||||
|
if (method != null) {
|
||||||
|
method(event.params, {
|
||||||
|
event,
|
||||||
|
sender,
|
||||||
|
}).then(data => {
|
||||||
|
// debug(`${source} <= `, event.method, JSON.stringify(data))
|
||||||
|
return data
|
||||||
|
}).then(data => sendResponse({
|
||||||
|
success: true,
|
||||||
|
code: 200,
|
||||||
|
data,
|
||||||
|
})).catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
let message
|
||||||
|
if (err instanceof Error) {
|
||||||
|
message = err.message
|
||||||
|
} else if (typeof err === 'string') {
|
||||||
|
message = err
|
||||||
} else {
|
} else {
|
||||||
const container = document.getElementsByClassName('bpx-player-subtitle-panel-wrap')?.[0]
|
message = 'error: ' + JSON.stringify(err)
|
||||||
if (container) {
|
}
|
||||||
const div = document.createElement('div')
|
sendResponse({
|
||||||
div.style.display = 'flex'
|
success: false,
|
||||||
div.style.justifyContent = 'center'
|
code: 500,
|
||||||
div.style.margin = '2px'
|
message,
|
||||||
text = document.createElement('text')
|
})
|
||||||
text.id = 'trans-result-text'
|
})
|
||||||
text.innerHTML = trans
|
return true
|
||||||
text.style.fontSize = '1rem'
|
} else {
|
||||||
text.style.padding = '5px'
|
console.error('Unknown method:', event.method)
|
||||||
text.style.color = 'white'
|
sendResponse({
|
||||||
text.style.background = 'rgba(0, 0, 0, 0.4)'
|
success: false,
|
||||||
div.append(text)
|
code: 501,
|
||||||
|
message: 'Unknown method: ' + event.method,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
container.append(div)
|
// listen message from app
|
||||||
}
|
startListening({}).then(e => {
|
||||||
}
|
const { postMessage, listenMessage, destroy } = e
|
||||||
text && (text.style.display = trans ? 'block' : 'none')
|
runtime.postMessageToApp = postMessage
|
||||||
}
|
listenMessage((method, params, sendResponse) => {
|
||||||
}, false);
|
messageHandler({
|
||||||
|
target: MESSAGE_TARGET_INJECT,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
}, null, sendResponse)
|
||||||
|
})
|
||||||
|
}).catch(console.error)
|
||||||
|
|
||||||
setInterval(() => {
|
/**
|
||||||
|
* listen message from extension
|
||||||
|
* Attention: return true if you need to sendResponse asynchronously
|
||||||
|
*/
|
||||||
|
chrome.runtime.onMessage.addListener(messageHandler)
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
refreshVideoInfo().catch(console.error)
|
refreshVideoInfo().catch(console.error)
|
||||||
refreshSubtitles()
|
refreshSubtitles()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
})()
|
||||||
|
20
src/typings.d.ts
vendored
20
src/typings.d.ts
vendored
@@ -1,3 +1,23 @@
|
|||||||
|
|
||||||
|
interface MessageData {
|
||||||
|
target: string
|
||||||
|
method: string
|
||||||
|
params?: any
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MessageResult {
|
||||||
|
success: boolean
|
||||||
|
code: number
|
||||||
|
message?: string
|
||||||
|
data?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MethodContext {
|
||||||
|
event: any
|
||||||
|
sender?: chrome.runtime.MessageSender | null
|
||||||
|
}
|
||||||
|
|
||||||
interface EnvData {
|
interface EnvData {
|
||||||
autoExpand?: boolean
|
autoExpand?: boolean
|
||||||
flagDot?: boolean
|
flagDot?: boolean
|
||||||
|
@@ -2,6 +2,42 @@ import {APP_DOM_ID, CUSTOM_MODEL_TOKENS, MODEL_DEFAULT, MODEL_MAP, SUMMARIZE_TYP
|
|||||||
import {isDarkMode} from '@kky002/kky-util'
|
import {isDarkMode} from '@kky002/kky-util'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import {findIndex} from 'lodash-es'
|
import {findIndex} from 'lodash-es'
|
||||||
|
import {MESSAGE_TARGET_EXTENSION} from '../const'
|
||||||
|
import {injectWaiter} from '../hooks/useMessageService'
|
||||||
|
|
||||||
|
export const debug = (...args: any[]) => {
|
||||||
|
console.debug('[APP]', ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendExtension = async <T = any>(method: string, params?: any) => {
|
||||||
|
return await chrome.runtime.sendMessage<MessageData, MessageResult>({
|
||||||
|
target: MESSAGE_TARGET_EXTENSION,
|
||||||
|
method,
|
||||||
|
params: params??{},
|
||||||
|
}).then((messageResult) => {
|
||||||
|
if (messageResult.success) {
|
||||||
|
return messageResult.data as T
|
||||||
|
} else {
|
||||||
|
throw new Error(messageResult.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendInject = async <T = any>(method: string, params?: any) => {
|
||||||
|
// wait
|
||||||
|
const postInjectMessage = await injectWaiter.wait()
|
||||||
|
// send message
|
||||||
|
const messageResult = await postInjectMessage(method, params) as MessageResult | undefined
|
||||||
|
if (messageResult != null) {
|
||||||
|
if (messageResult.success) {
|
||||||
|
return messageResult.data as T
|
||||||
|
} else {
|
||||||
|
throw new Error(messageResult.message)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('no response')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取译文
|
* 获取译文
|
||||||
|
Reference in New Issue
Block a user