Bilibili-Evolved/registry/lib/plugins/video/download/aria2-output/aria2-rpc.ts
2022-10-12 23:27:58 +08:00

184 lines
5.4 KiB
TypeScript

import { getJson, monkey, postJson } from '@/core/ajax'
import { Toast } from '@/core/toast'
import { UserAgent } from '@/core/utils/constants'
import { logError } from '@/core/utils/log'
import { DownloadVideoOutput } from '../../../../components/video/download/types'
import { Aria2RpcProfile } from './rpc-profiles'
interface RpcParam {
params: (string | string[] | Record<string, string>)[]
id: string
}
interface RpcResult {
success: boolean
param: RpcParam
message: string
}
const getOption = (profile: Aria2RpcProfile) => {
const host = profile.host.match(/^http[s]?:\/\//) ? profile.host : `http://${profile.host}`
const methodName = 'aria2.addUri'
return {
option: profile,
host,
methodName,
}
}
const handleRpcResponse = async (
param: RpcParam,
getResponse: () => Promise<any>,
): Promise<RpcResult> => {
try {
let response = await getResponse()
if (typeof response === 'string') {
response = JSON.parse(response)
}
if (response.error !== undefined) {
if (response.error.code === 1) {
return {
param,
success: false,
message: '请求遭到拒绝, 请检查您的密钥相关设置.',
}
}
return {
param,
success: false,
message: `请求发生错误, code = ${response.error.code}, message = ${response.error.message}`,
}
}
return {
param,
success: true,
message: response.result,
}
} catch (error) {
// Host or port is invalid
return {
param,
success: false,
message: `无法连接到RPC主机, error = ${error.toString()}`,
}
}
}
const getRpc = async (profile: Aria2RpcProfile, rpcParam: RpcParam) => {
const { option, host, methodName } = getOption(profile)
return handleRpcResponse(rpcParam, async () => {
const base64Params = window.btoa(unescape(encodeURIComponent(JSON.stringify(rpcParam.params))))
const url = `${host}:${option.port}/jsonrpc?method=${methodName}&id=${rpcParam.id}&params=${base64Params}`
console.log(`RPC request: ${url}`)
if (url.startsWith('http:')) {
return monkey({
method: 'GET',
url,
responseType: 'json',
})
}
return getJson(url)
})
}
const postRpc = async (profile: Aria2RpcProfile, rpcParam: RpcParam) => {
const { option, host, methodName } = getOption(profile)
return handleRpcResponse(rpcParam, async () => {
const url = `${host}:${option.port}/jsonrpc`
const data = {
method: methodName,
id: rpcParam.id,
params: rpcParam.params,
}
if (url.startsWith('http:')) {
return monkey({
method: 'POST',
url,
responseType: 'json',
data: JSON.stringify(data),
})
}
return postJson(url, data)
})
}
export const sendRpc = async (profile: Aria2RpcProfile, params: RpcParam[]) => {
const results: RpcResult[] = []
for (const param of params) {
let result: RpcResult
if (profile.method === 'get') {
result = await getRpc(profile, param)
} else {
result = await postRpc(profile, param)
}
results.push(result)
}
return results
}
const parseRpcOptions = (option: string): Record<string, string> => {
if (!option) {
return {}
}
const pairs = option
.split('\n')
.map(line => {
// 实际上就是按第一个 = 号分割出 key / value, 其他后面的 = 还是算进 value 里
const [key, ...values] = line.trim().split('=')
return [key.trim(), values.join('=').trim()]
})
.filter(it => Boolean(it[1])) // 过滤掉没有 = 的行 (value 为空)
return Object.fromEntries(pairs)
}
export const aria2Rpc: DownloadVideoOutput = {
name: 'aria2Rpc',
displayName: 'aria2 RPC',
description: '使用 aria2 RPC 功能发送下载请求.',
runAction: async (
action,
instance: Vue & {
selectedRpcProfile: Aria2RpcProfile
},
) => {
const { infos } = action
const { selectedRpcProfile } = instance
const { secretKey, dir, other } = selectedRpcProfile
const referer = document.URL.replace(window.location.search, '')
const totalParams = infos
.map(info =>
info.titledFragments.map(fragment => {
const singleInfoParams = []
if (secretKey) {
singleInfoParams.push(`token:${secretKey}`)
}
singleInfoParams.push([fragment.url])
singleInfoParams.push({
referer,
'user-agent': UserAgent,
out: fragment.title,
dir: dir || undefined,
...parseRpcOptions(other),
})
const id = encodeURIComponent(fragment.title)
return {
params: singleInfoParams,
id,
}
}),
)
.flat()
const results = await sendRpc(selectedRpcProfile, totalParams)
console.table(results)
if (results.length === 1) {
const result = results[0]
if (result.success) {
Toast.success(`成功发送了请求, GID = ${result.message}`, 'aria2 RPC', 5000)
} else {
logError(result.message)
}
} else {
const successCount = results.filter(r => r.success).length
const failCount = results.length - successCount
Toast.info(
`发送了 ${results.length} 个请求, 成功 ${successCount} 个, 失败 ${failCount} 个.`,
'aria2 RPC',
5000,
)
}
},
component: () => import('./RpcConfig.vue').then(m => m.default),
}