From 2a2a18a70ac99aea83e9acb3bb0d52311266d227 Mon Sep 17 00:00:00 2001 From: renxia Date: Wed, 28 Jun 2023 18:55:42 +0800 Subject: [PATCH] =?UTF-8?q?wip:=20=E6=96=B0=E5=A2=9E=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E9=87=87=E9=9B=86=E7=AB=99=E8=BF=9C=E7=A8=8B=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=EF=BC=8C=E6=94=AF=E6=8C=81=E4=BB=8E=E8=BF=9C=E7=A8=8B=E4=BB=93?= =?UTF-8?q?=E5=BA=93=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cli.ts | 8 ++--- src/lib/storage.ts | 11 ++++++ src/lib/video-search.ts | 76 ++++++++++++++++++++++++++++++----------- test/remote-config.json | 19 +++++++++++ 4 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 test/remote-config.json diff --git a/src/cli.ts b/src/cli.ts index 1220e28..590e5b5 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -75,17 +75,17 @@ function getOptions() { async function VideoSerachAndDL(keyword: string, options: { url?: string[] }, baseOpts: POptions): Promise { const vs = new VideoSearch(); await vs.updateOptions({ api: options.url || [] }); - let apiUrl = vs.api[0]; + const apis = vs.api; + let apiUrl = apis[0]; if (!options.url && vs.api.length > 0) { await prompt<{ k: string }>({ type: 'select', name: 'k', message: '请选择 API 站点', - choices: vs.api.map(d => ({ name: d, message: d })) as never, - + choices: apis.map(d => ({ name: d.url, message: d.desc })) as never, validate: value => value.length >= 1, - }).then(v => (apiUrl = v.k)); + }).then(v => (apiUrl = vs.apiMap.get(v.k))); } await prompt<{ k: string }>({ diff --git a/src/lib/storage.ts b/src/lib/storage.ts index 09a0ae4..d9f5f81 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -6,6 +6,17 @@ import { homedir } from 'node:os'; export interface M3u8StorConfig extends VSOptions { /** 播放地址缓存 */ api?: string[]; + remoteConfig?: { + updateTime?: number; + data?: { + apiSites: { + url: string; + desc?: string; + enable?: 0 | 1 | boolean; + remote?: boolean; + }[]; + }; + }; } export const stor = LiteStorage.getInstance({ uuid: 'm3u8dl', filepath: resolve(homedir(), '.liteStorage/m3u8dl.json') }); diff --git a/src/lib/video-search.ts b/src/lib/video-search.ts index 3560cd8..ea627b4 100644 --- a/src/lib/video-search.ts +++ b/src/lib/video-search.ts @@ -1,6 +1,6 @@ import { Request } from '@lzwme/fe-utils'; import { VideoListResult, VideoSearchResult } from '../types'; -import { stor } from './storage'; +import { stor, type M3u8StorConfig } from './storage'; const req = new Request(null, { 'content-type': 'application/json; charset=UTF-8', @@ -9,31 +9,41 @@ const req = new Request(null, { export interface VSOptions { /** 播放地址缓存 */ api?: string[]; + force?: boolean; } export class VideoSearch { + public apiMap = new Map(); public get api() { - return this.options.api; + return [...this.apiMap.values()]; } constructor(protected options: VSOptions = {}) { if (!options.api?.length) options.api = []; if (process.env.VAPI) options.api.push(...process.env.VAPI.split('$$$')); this.updateOptions(options).then(() => { - if (!this.api.length) throw Error('没有可用站点,请添加或指定'); + if (!this.apiMap.size) throw Error('没有可用站点,请添加或指定'); }); } async updateOptions(options: VSOptions) { const cache = stor.get(); - if (Array.isArray(cache.api)) this.options.api.push(...cache.api); + const apis = [...(cache.api || []), ...options.api]; + + await this.formatUrl(apis); + if (options.api?.length) { - this.options.api.unshift(...options.api); - this.options.api = await this.formatUrl(this.options.api); - stor.set({ api: this.options.api }); + stor.set({ api: [...(cache.api || []), ...options.api] }); } + + (cache.api || []).forEach(url => { + this.apiMap.set(url, { url, desc: url }); + }); + + await this.updateApiFromRemote(options.force); + return this; } async search(wd: string, api = this.api[0]) { - let { data } = await req.get(api, { wd }, null, { rejectUnauthorized: false }); + let { data } = await req.get(api.url, { wd }, null, { rejectUnauthorized: false }); if (typeof data == 'string') data = JSON.parse(data) as VideoSearchResult; @@ -41,7 +51,7 @@ export class VideoSearch { } async getVideoList(ids: number | string | (number | string)[], api = this.api[0]) { let { data } = await req.get( - api, + api.url, { ac: 'videolist', ids: Array.isArray(ids) ? ids.join(',') : ids, @@ -54,22 +64,15 @@ export class VideoSearch { return data; } - async formatUrl(url: string | string[]) { + private formatUrl(url: string | string[]) { const urls: string[] = []; if (!url) return urls; if (typeof url === 'string') url = [url]; for (let u of url) { u = String(u || '').trim(); - if (!u) continue; - if (u.endsWith('.json')) { - const { data } = await req.get>(u, null, null, { rejectUnauthorized: false }); - if (Array.isArray(data)) { - urls.push(...(await this.formatUrl(data as string[]))); - } else { - urls.push(...Object.values(data)); - } - } else if (u.startsWith('http')) { + + if (u.startsWith('http')) { if (u.endsWith('provide/')) u += 'vod/'; if (u.endsWith('provide/vod')) u += '/'; urls.push(u.replace('/at/xml/', '/')); @@ -78,6 +81,41 @@ export class VideoSearch { return [...new Set(urls)]; } + async updateApiFromRemote(force = false) { + const cache = stor.get(); + let needReadFromRemote = true; + + if (!force && cache.remoteConfig?.updateTime) { + needReadFromRemote = Date.now() - cache.remoteConfig.updateTime > 1 * 60 * 60 * 1000; + } + + if (needReadFromRemote) { + const url = 'https://ghproxy.com/raw.githubusercontent.com/lzwme/m3u8-dl/main/test/remote-config.json'; + const { data } = await req.get( + url, + null, + { 'content-type': 'application/json' }, + { rejectUnauthorized: false } + ); + + if (Array.isArray(data.apiSites)) { + cache.remoteConfig = { + updateTime: Date.now(), + data, + }; + stor.save(); + } + } + + if (Array.isArray(cache.remoteConfig?.data?.apiSites)) { + cache.remoteConfig.data.apiSites.forEach(item => { + if (item.enable === 0 || item.enable === false) return; + item.url = this.formatUrl(item.url)[0]; + item.remote = true; + this.apiMap.set(item.url, item); + }); + } + } } // const v = new VideoSearch({ api: ['https://api.xinlangapi.com/xinlangapi.php/provide/vod/'] }); diff --git a/test/remote-config.json b/test/remote-config.json new file mode 100644 index 0000000..0bd97e1 --- /dev/null +++ b/test/remote-config.json @@ -0,0 +1,19 @@ +{ + "apiSites": [ + { + "url": "https://jyzyapi.com/provide/vod/", + "desc": "金鹰", + "enable": 1 + }, + { + "url": "https://jyzy1.com/provide/vod/", + "desc": "金鹰备用1", + "enable": 0 + }, + { + "url": "https://api.xinlangapi.com/xinlangapi.php/provide/vod/", + "desc": "新浪API", + "enable": 1 + } + ] +}