Skip to content

Commit

Permalink
wip: 新增视频采集站远程配置,支持从远程仓库加载
Browse files Browse the repository at this point in the history
  • Loading branch information
renxia committed Jun 28, 2023
1 parent e0aefc7 commit 588809d
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 26 deletions.
13 changes: 7 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ program
.option('--debug', `开启调试模式。`)
.option('-f, --filename <name>', `指定下载文件的保存名称。默认取 url md5 值。若指定了多个 url 地址,则会在末尾增加序号`)
.option('-n, --thread-num <number>', `并发下载线程数。默认为 cpu * 2。可设置不同数值观察下载效果`)
.option('-F, --force', `文件已存在时,是否仍继续下载和生成`)
.option('-F, --force', `启用强制执行模式。文件已存在时,是否仍继续下载和生成`)
.option('--no-progress', `是否不打印进度信息`)
.option('-p, --play', `是否边下边看`)
.option('-C, --cache-dir <dirpath>', `临时文件保存目录。默认为 cache`)
Expand All @@ -54,6 +54,7 @@ program
.command('search [keyword]')
.alias('s')
.option('-u,--url <api...>', '影视搜索的接口地址(m3u8采集站标准接口)')
.option('-F, --force', `是否强制`)
.description('m3u8视频在线搜索与下载')
.action(async (keyword, options: { url?: string[] }) => {
VideoSerachAndDL(keyword, options, getOptions());
Expand All @@ -74,18 +75,18 @@ function getOptions() {

async function VideoSerachAndDL(keyword: string, options: { url?: string[] }, baseOpts: POptions): Promise<void> {
const vs = new VideoSearch();
await vs.updateOptions({ api: options.url || [] });
let apiUrl = vs.api[0];
await vs.updateOptions({ api: options.url || [], force: baseOpts.force });
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 }>({
Expand Down
11 changes: 11 additions & 0 deletions src/lib/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<M3u8StorConfig>({ uuid: 'm3u8dl', filepath: resolve(homedir(), '.liteStorage/m3u8dl.json') });
85 changes: 65 additions & 20 deletions src/lib/video-search.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Request } from '@lzwme/fe-utils';
import { VideoListResult, VideoSearchResult } from '../types';
import { stor } from './storage';
import type { VideoListResult, VideoSearchResult } from '../types';
import { stor, type M3u8StorConfig } from './storage.js';
import { logger } from './utils.js';

const req = new Request(null, {
'content-type': 'application/json; charset=UTF-8',
Expand All @@ -9,39 +10,49 @@ const req = new Request(null, {
export interface VSOptions {
/** 播放地址缓存 */
api?: string[];
force?: boolean;
}

export class VideoSearch {
public apiMap = new Map<string, M3u8StorConfig['remoteConfig']['data']['apiSites'][0]>();
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<VideoSearchResult>(api, { wd }, null, { rejectUnauthorized: false });
let { data } = await req.get<VideoSearchResult>(api.url, { wd }, null, { rejectUnauthorized: false });

if (typeof data == 'string') data = JSON.parse(data) as VideoSearchResult;

return data;
}
async getVideoList(ids: number | string | (number | string)[], api = this.api[0]) {
let { data } = await req.get<VideoListResult>(
api,
api.url,
{
ac: 'videolist',
ids: Array.isArray(ids) ? ids.join(',') : ids,
Expand All @@ -54,22 +65,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<Record<string, string>>(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/', '/'));
Expand All @@ -78,6 +82,47 @@ export class VideoSearch {

return [...new Set(urls)];
}
private async loadRemoteConfig(force = false) {
const cache = stor.get();
let needUpdate = true;

if (!force && cache.remoteConfig?.updateTime) {
needUpdate = Date.now() - cache.remoteConfig.updateTime > 1 * 60 * 60 * 1000;
}

if (needUpdate) {
const url = 'https://ghproxy.com/raw.githubusercontent.com/lzwme/m3u8-dl/main/test/remote-config.json';
const { data } = await req.get<M3u8StorConfig['remoteConfig']['data']>(
url,
null,
{ 'content-type': 'application/json' },
{ rejectUnauthorized: false }
);
logger.debug('加载远程配置', data);

if (Array.isArray(data.apiSites)) {
cache.remoteConfig = {
updateTime: Date.now(),
data,
};
stor.save(cache);
}
}

return cache.remoteConfig;
}
async updateApiFromRemote(force = false) {
const remoteConfig = await this.loadRemoteConfig(force);

if (Array.isArray(remoteConfig?.data?.apiSites)) {
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/'] });
Expand Down
19 changes: 19 additions & 0 deletions test/remote-config.json
Original file line number Diff line number Diff line change
@@ -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
}
]
}

0 comments on commit 588809d

Please sign in to comment.