xref: /MusicFree/src/core/pluginManager.ts (revision a7b42a4cb388793e58dcefce06a78fa094acf8de)
14060c00aS猫头猫import {
2927dbe93S猫头猫    copyFile,
3927dbe93S猫头猫    exists,
4927dbe93S猫头猫    readDir,
5927dbe93S猫头猫    readFile,
6927dbe93S猫头猫    unlink,
7927dbe93S猫头猫    writeFile,
8927dbe93S猫头猫} from 'react-native-fs';
9927dbe93S猫头猫import CryptoJs from 'crypto-js';
10927dbe93S猫头猫import dayjs from 'dayjs';
11927dbe93S猫头猫import axios from 'axios';
12ef60be1cS猫头猫import bigInt from 'big-integer';
13ef60be1cS猫头猫import qs from 'qs';
1428ccceb7S猫头猫import * as webdav from 'webdav';
15d4cd40d8S猫头猫import {InteractionManager, ToastAndroid} from 'react-native';
16927dbe93S猫头猫import pathConst from '@/constants/pathConst';
1725c1bd29S猫头猫import {compare, satisfies} from 'compare-versions';
18927dbe93S猫头猫import DeviceInfo from 'react-native-device-info';
19927dbe93S猫头猫import StateMapper from '@/utils/stateMapper';
20927dbe93S猫头猫import MediaMeta from './mediaMeta';
21927dbe93S猫头猫import {nanoid} from 'nanoid';
22ea6d708fS猫头猫import {devLog, errorLog, trace} from '../utils/log';
23927dbe93S猫头猫import Cache from './cache';
24cfa0fc07S猫头猫import {
250e4173cdS猫头猫    getInternalData,
260e4173cdS猫头猫    InternalDataType,
270e4173cdS猫头猫    isSameMediaItem,
280e4173cdS猫头猫    resetMediaItem,
290e4173cdS猫头猫} from '@/utils/mediaItem';
307993f90eS猫头猫import {
317993f90eS猫头猫    CacheControl,
32e08d37a3S猫头猫    emptyFunction,
337993f90eS猫头猫    internalSerializeKey,
347993f90eS猫头猫    localPluginHash,
357993f90eS猫头猫    localPluginPlatform,
367993f90eS猫头猫} from '@/constants/commonConst';
37927dbe93S猫头猫import delay from '@/utils/delay';
384d9d3c4cS猫头猫import * as cheerio from 'cheerio';
397d7e864fS猫头猫import CookieManager from '@react-native-cookies/cookies';
407d7e864fS猫头猫import he from 'he';
41ef714860S猫头猫import Network from './network';
420e4173cdS猫头猫import LocalMusicSheet from './localMusicSheet';
430e4173cdS猫头猫import {FileSystem} from 'react-native-file-access';
4474d0cf81S猫头猫import Mp3Util from '@/native/mp3Util';
45e08d37a3S猫头猫import {PluginMeta} from './pluginMeta';
4634588741S猫头猫import {useEffect, useState} from 'react';
47a84a85c5S猫头猫import {getFileName} from '@/utils/fileUtils';
48*a7b42a4cS猫头猫import {URL} from 'react-native-url-polyfill';
49*a7b42a4cS猫头猫import Base64 from '@/utils/base64';
50927dbe93S猫头猫
5161aca335S猫头猫axios.defaults.timeout = 2000;
52927dbe93S猫头猫
53927dbe93S猫头猫const sha256 = CryptoJs.SHA256;
54927dbe93S猫头猫
55cfa0fc07S猫头猫export enum PluginStateCode {
56927dbe93S猫头猫    /** 版本不匹配 */
57927dbe93S猫头猫    VersionNotMatch = 'VERSION NOT MATCH',
58927dbe93S猫头猫    /** 无法解析 */
59927dbe93S猫头猫    CannotParse = 'CANNOT PARSE',
60927dbe93S猫头猫}
61927dbe93S猫头猫
629c34d637S猫头猫const packages: Record<string, any> = {
639c34d637S猫头猫    cheerio,
649c34d637S猫头猫    'crypto-js': CryptoJs,
659c34d637S猫头猫    axios,
669c34d637S猫头猫    dayjs,
679c34d637S猫头猫    'big-integer': bigInt,
689c34d637S猫头猫    qs,
699c34d637S猫头猫    he,
703b3d6357S猫头猫    '@react-native-cookies/cookies': CookieManager,
7128ccceb7S猫头猫    webdav,
729c34d637S猫头猫};
739c34d637S猫头猫
74b43683eaS猫头猫const _require = (packageName: string) => {
75b43683eaS猫头猫    let pkg = packages[packageName];
76b43683eaS猫头猫    pkg.default = pkg;
77b43683eaS猫头猫    return pkg;
78b43683eaS猫头猫};
799c34d637S猫头猫
8053f8cd8eS猫头猫const _consoleBind = function (
8153f8cd8eS猫头猫    method: 'log' | 'error' | 'info' | 'warn',
8253f8cd8eS猫头猫    ...args: any
8353f8cd8eS猫头猫) {
8453f8cd8eS猫头猫    const fn = console[method];
8553f8cd8eS猫头猫    if (fn) {
8653f8cd8eS猫头猫        fn(...args);
8753f8cd8eS猫头猫        devLog(method, ...args);
8853f8cd8eS猫头猫    }
8953f8cd8eS猫头猫};
9053f8cd8eS猫头猫
9153f8cd8eS猫头猫const _console = {
9253f8cd8eS猫头猫    log: _consoleBind.bind(null, 'log'),
9353f8cd8eS猫头猫    warn: _consoleBind.bind(null, 'warn'),
9453f8cd8eS猫头猫    info: _consoleBind.bind(null, 'info'),
9553f8cd8eS猫头猫    error: _consoleBind.bind(null, 'error'),
9653f8cd8eS猫头猫};
9753f8cd8eS猫头猫
98*a7b42a4cS猫头猫function formatAuthUrl(url: string) {
99*a7b42a4cS猫头猫    const urlObj = new URL(url);
100*a7b42a4cS猫头猫
101*a7b42a4cS猫头猫    try {
102*a7b42a4cS猫头猫        if (urlObj.username && urlObj.password) {
103*a7b42a4cS猫头猫            const auth = `Basic ${Base64.btoa(
104*a7b42a4cS猫头猫                `${decodeURIComponent(urlObj.username)}:${decodeURIComponent(
105*a7b42a4cS猫头猫                    urlObj.password,
106*a7b42a4cS猫头猫                )}`,
107*a7b42a4cS猫头猫            )}`;
108*a7b42a4cS猫头猫            urlObj.username = '';
109*a7b42a4cS猫头猫            urlObj.password = '';
110*a7b42a4cS猫头猫
111*a7b42a4cS猫头猫            return {
112*a7b42a4cS猫头猫                url: urlObj.toString(),
113*a7b42a4cS猫头猫                auth,
114*a7b42a4cS猫头猫            };
115*a7b42a4cS猫头猫        }
116*a7b42a4cS猫头猫    } catch (e) {
117*a7b42a4cS猫头猫        return {
118*a7b42a4cS猫头猫            url,
119*a7b42a4cS猫头猫        };
120*a7b42a4cS猫头猫    }
121*a7b42a4cS猫头猫    return {
122*a7b42a4cS猫头猫        url,
123*a7b42a4cS猫头猫    };
124*a7b42a4cS猫头猫}
125*a7b42a4cS猫头猫
126d5bfeb7eS猫头猫//#region 插件类
127927dbe93S猫头猫export class Plugin {
128927dbe93S猫头猫    /** 插件名 */
129927dbe93S猫头猫    public name: string;
130927dbe93S猫头猫    /** 插件的hash,作为唯一id */
131927dbe93S猫头猫    public hash: string;
132927dbe93S猫头猫    /** 插件状态:激活、关闭、错误 */
133927dbe93S猫头猫    public state: 'enabled' | 'disabled' | 'error';
134927dbe93S猫头猫    /** 插件状态信息 */
135927dbe93S猫头猫    public stateCode?: PluginStateCode;
136927dbe93S猫头猫    /** 插件的实例 */
137927dbe93S猫头猫    public instance: IPlugin.IPluginInstance;
138927dbe93S猫头猫    /** 插件路径 */
139927dbe93S猫头猫    public path: string;
140927dbe93S猫头猫    /** 插件方法 */
141927dbe93S猫头猫    public methods: PluginMethods;
142927dbe93S猫头猫
14374d0cf81S猫头猫    constructor(
14474d0cf81S猫头猫        funcCode: string | (() => IPlugin.IPluginInstance),
14574d0cf81S猫头猫        pluginPath: string,
14674d0cf81S猫头猫    ) {
147927dbe93S猫头猫        this.state = 'enabled';
148927dbe93S猫头猫        let _instance: IPlugin.IPluginInstance;
1493b3d6357S猫头猫        const _module: any = {exports: {}};
150927dbe93S猫头猫        try {
15174d0cf81S猫头猫            if (typeof funcCode === 'string') {
152e0caf342S猫头猫                // 插件的环境变量
153e0caf342S猫头猫                const env = {
154e0caf342S猫头猫                    getUserVariables: () => {
155e0caf342S猫头猫                        return (
156e0caf342S猫头猫                            PluginMeta.getPluginMeta(this)?.userVariables ?? {}
157e0caf342S猫头猫                        );
158e0caf342S猫头猫                    },
159e3fa9b3cS猫头猫                    os: 'android',
160e0caf342S猫头猫                };
161e0caf342S猫头猫
1624060c00aS猫头猫                // eslint-disable-next-line no-new-func
163927dbe93S猫头猫                _instance = Function(`
164927dbe93S猫头猫                    'use strict';
165e0caf342S猫头猫                    return function(require, __musicfree_require, module, exports, console, env) {
1669c34d637S猫头猫                        ${funcCode}
167927dbe93S猫头猫                    }
168e0caf342S猫头猫                `)()(
169e0caf342S猫头猫                    _require,
170e0caf342S猫头猫                    _require,
171e0caf342S猫头猫                    _module,
172e0caf342S猫头猫                    _module.exports,
173e0caf342S猫头猫                    _console,
174e0caf342S猫头猫                    env,
175e0caf342S猫头猫                );
1763b3d6357S猫头猫                if (_module.exports.default) {
1773b3d6357S猫头猫                    _instance = _module.exports
1783b3d6357S猫头猫                        .default as IPlugin.IPluginInstance;
1793b3d6357S猫头猫                } else {
1809c34d637S猫头猫                    _instance = _module.exports as IPlugin.IPluginInstance;
1813b3d6357S猫头猫                }
18274d0cf81S猫头猫            } else {
18374d0cf81S猫头猫                _instance = funcCode();
18474d0cf81S猫头猫            }
185c2b3a262S猫头猫            // 插件初始化后的一些操作
18695297592S猫头猫            if (Array.isArray(_instance.userVariables)) {
18795297592S猫头猫                _instance.userVariables = _instance.userVariables.filter(
18895297592S猫头猫                    it => it?.key,
18995297592S猫头猫                );
19095297592S猫头猫            }
191927dbe93S猫头猫            this.checkValid(_instance);
192927dbe93S猫头猫        } catch (e: any) {
193b43683eaS猫头猫            console.log(e);
194927dbe93S猫头猫            this.state = 'error';
195927dbe93S猫头猫            this.stateCode = PluginStateCode.CannotParse;
196927dbe93S猫头猫            if (e?.stateCode) {
197927dbe93S猫头猫                this.stateCode = e.stateCode;
198927dbe93S猫头猫            }
199927dbe93S猫头猫            errorLog(`${pluginPath}插件无法解析 `, {
200927dbe93S猫头猫                stateCode: this.stateCode,
201927dbe93S猫头猫                message: e?.message,
202927dbe93S猫头猫                stack: e?.stack,
203927dbe93S猫头猫            });
204927dbe93S猫头猫            _instance = e?.instance ?? {
205927dbe93S猫头猫                _path: '',
206927dbe93S猫头猫                platform: '',
207927dbe93S猫头猫                appVersion: '',
20820e6a092S猫头猫                async getMediaSource() {
209927dbe93S猫头猫                    return null;
210927dbe93S猫头猫                },
211927dbe93S猫头猫                async search() {
212927dbe93S猫头猫                    return {};
213927dbe93S猫头猫                },
214927dbe93S猫头猫                async getAlbumInfo() {
215927dbe93S猫头猫                    return null;
216927dbe93S猫头猫                },
217927dbe93S猫头猫            };
218927dbe93S猫头猫        }
219927dbe93S猫头猫        this.instance = _instance;
220927dbe93S猫头猫        this.path = pluginPath;
221927dbe93S猫头猫        this.name = _instance.platform;
222ab8941d9S猫头猫        if (
223ab8941d9S猫头猫            this.instance.platform === '' ||
224ab8941d9S猫头猫            this.instance.platform === undefined
225ab8941d9S猫头猫        ) {
226927dbe93S猫头猫            this.hash = '';
227927dbe93S猫头猫        } else {
22874d0cf81S猫头猫            if (typeof funcCode === 'string') {
229927dbe93S猫头猫                this.hash = sha256(funcCode).toString();
23074d0cf81S猫头猫            } else {
23174d0cf81S猫头猫                this.hash = sha256(funcCode.toString()).toString();
23274d0cf81S猫头猫            }
233927dbe93S猫头猫        }
234927dbe93S猫头猫
235927dbe93S猫头猫        // 放在最后
236927dbe93S猫头猫        this.methods = new PluginMethods(this);
237927dbe93S猫头猫    }
238927dbe93S猫头猫
239927dbe93S猫头猫    private checkValid(_instance: IPlugin.IPluginInstance) {
240927dbe93S猫头猫        /** 版本号校验 */
241927dbe93S猫头猫        if (
242927dbe93S猫头猫            _instance.appVersion &&
243927dbe93S猫头猫            !satisfies(DeviceInfo.getVersion(), _instance.appVersion)
244927dbe93S猫头猫        ) {
245927dbe93S猫头猫            throw {
246927dbe93S猫头猫                instance: _instance,
247927dbe93S猫头猫                stateCode: PluginStateCode.VersionNotMatch,
248927dbe93S猫头猫            };
249927dbe93S猫头猫        }
250927dbe93S猫头猫        return true;
251927dbe93S猫头猫    }
252927dbe93S猫头猫}
253d5bfeb7eS猫头猫//#endregion
254927dbe93S猫头猫
255d5bfeb7eS猫头猫//#region 基于插件类封装的方法,供给APP侧直接调用
256927dbe93S猫头猫/** 有缓存等信息 */
257927dbe93S猫头猫class PluginMethods implements IPlugin.IPluginInstanceMethods {
258927dbe93S猫头猫    private plugin;
259927dbe93S猫头猫    constructor(plugin: Plugin) {
260927dbe93S猫头猫        this.plugin = plugin;
261927dbe93S猫头猫    }
262927dbe93S猫头猫    /** 搜索 */
263927dbe93S猫头猫    async search<T extends ICommon.SupportMediaType>(
264927dbe93S猫头猫        query: string,
265927dbe93S猫头猫        page: number,
266927dbe93S猫头猫        type: T,
267927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
268927dbe93S猫头猫        if (!this.plugin.instance.search) {
269927dbe93S猫头猫            return {
270927dbe93S猫头猫                isEnd: true,
271927dbe93S猫头猫                data: [],
272927dbe93S猫头猫            };
273927dbe93S猫头猫        }
274927dbe93S猫头猫
2754060c00aS猫头猫        const result =
2764060c00aS猫头猫            (await this.plugin.instance.search(query, page, type)) ?? {};
277927dbe93S猫头猫        if (Array.isArray(result.data)) {
278927dbe93S猫头猫            result.data.forEach(_ => {
279927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
280927dbe93S猫头猫            });
281927dbe93S猫头猫            return {
282927dbe93S猫头猫                isEnd: result.isEnd ?? true,
283927dbe93S猫头猫                data: result.data,
284927dbe93S猫头猫            };
285927dbe93S猫头猫        }
286927dbe93S猫头猫        return {
287927dbe93S猫头猫            isEnd: true,
288927dbe93S猫头猫            data: [],
289927dbe93S猫头猫        };
290927dbe93S猫头猫    }
291927dbe93S猫头猫
292927dbe93S猫头猫    /** 获取真实源 */
29320e6a092S猫头猫    async getMediaSource(
294927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
295abaede57S猫头猫        quality: IMusic.IQualityKey = 'standard',
296927dbe93S猫头猫        retryCount = 1,
297dc160d50S猫头猫        notUpdateCache = false,
298192ae2b0S猫头猫    ): Promise<IPlugin.IMediaSourceResult | null> {
299927dbe93S猫头猫        // 1. 本地搜索 其实直接读mediameta就好了
300927dbe93S猫头猫        const localPath =
3010e4173cdS猫头猫            getInternalData<string>(musicItem, InternalDataType.LOCALPATH) ??
3020e4173cdS猫头猫            getInternalData<string>(
3030e4173cdS猫头猫                LocalMusicSheet.isLocalMusic(musicItem),
3040e4173cdS猫头猫                InternalDataType.LOCALPATH,
3050e4173cdS猫头猫            );
306a84a85c5S猫头猫        if (
307a84a85c5S猫头猫            localPath &&
308a84a85c5S猫头猫            (localPath.startsWith('content://') ||
309a84a85c5S猫头猫                (await FileSystem.exists(localPath)))
310a84a85c5S猫头猫        ) {
3110e4173cdS猫头猫            trace('本地播放', localPath);
312927dbe93S猫头猫            return {
313927dbe93S猫头猫                url: localPath,
314927dbe93S猫头猫            };
315927dbe93S猫头猫        }
316a84a85c5S猫头猫
3177993f90eS猫头猫        if (musicItem.platform === localPluginPlatform) {
318f5935920S猫头猫            throw new Error('本地音乐不存在');
319f5935920S猫头猫        }
320927dbe93S猫头猫        // 2. 缓存播放
321927dbe93S猫头猫        const mediaCache = Cache.get(musicItem);
322985f8e75S猫头猫        const pluginCacheControl =
323985f8e75S猫头猫            this.plugin.instance.cacheControl ?? 'no-cache';
324cfa0fc07S猫头猫        if (
325cfa0fc07S猫头猫            mediaCache &&
326abaede57S猫头猫            mediaCache?.qualities?.[quality]?.url &&
32748f4b873S猫头猫            (pluginCacheControl === CacheControl.Cache ||
32848f4b873S猫头猫                (pluginCacheControl === CacheControl.NoCache &&
329ef714860S猫头猫                    Network.isOffline()))
330cfa0fc07S猫头猫        ) {
3315276aef9S猫头猫            trace('播放', '缓存播放');
332abaede57S猫头猫            const qualityInfo = mediaCache.qualities[quality];
333927dbe93S猫头猫            return {
334abaede57S猫头猫                url: qualityInfo.url,
335927dbe93S猫头猫                headers: mediaCache.headers,
3364060c00aS猫头猫                userAgent:
3374060c00aS猫头猫                    mediaCache.userAgent ?? mediaCache.headers?.['user-agent'],
338927dbe93S猫头猫            };
339927dbe93S猫头猫        }
340927dbe93S猫头猫        // 3. 插件解析
34120e6a092S猫头猫        if (!this.plugin.instance.getMediaSource) {
342*a7b42a4cS猫头猫            const {url, auth} = formatAuthUrl(
343*a7b42a4cS猫头猫                musicItem?.qualities?.[quality]?.url ?? musicItem.url,
344*a7b42a4cS猫头猫            );
345*a7b42a4cS猫头猫            return {
346*a7b42a4cS猫头猫                url: url,
347*a7b42a4cS猫头猫                headers: auth
348*a7b42a4cS猫头猫                    ? {
349*a7b42a4cS猫头猫                          Authorization: auth,
350*a7b42a4cS猫头猫                      }
351*a7b42a4cS猫头猫                    : undefined,
352*a7b42a4cS猫头猫            };
353927dbe93S猫头猫        }
354927dbe93S猫头猫        try {
355abaede57S猫头猫            const {url, headers} = (await this.plugin.instance.getMediaSource(
356abaede57S猫头猫                musicItem,
357abaede57S猫头猫                quality,
358abaede57S猫头猫            )) ?? {url: musicItem?.qualities?.[quality]?.url};
359927dbe93S猫头猫            if (!url) {
360a28eac61S猫头猫                throw new Error('NOT RETRY');
361927dbe93S猫头猫            }
3625276aef9S猫头猫            trace('播放', '插件播放');
363927dbe93S猫头猫            const result = {
364927dbe93S猫头猫                url,
365927dbe93S猫头猫                headers,
366927dbe93S猫头猫                userAgent: headers?.['user-agent'],
367cfa0fc07S猫头猫            } as IPlugin.IMediaSourceResult;
368*a7b42a4cS猫头猫            const authFormattedResult = formatAuthUrl(result.url!);
369*a7b42a4cS猫头猫            if (authFormattedResult.auth) {
370*a7b42a4cS猫头猫                result.url = authFormattedResult.url;
371*a7b42a4cS猫头猫                result.headers = {
372*a7b42a4cS猫头猫                    ...(result.headers ?? {}),
373*a7b42a4cS猫头猫                    Authorization: authFormattedResult.auth,
374*a7b42a4cS猫头猫                };
375*a7b42a4cS猫头猫            }
376927dbe93S猫头猫
377dc160d50S猫头猫            if (
378dc160d50S猫头猫                pluginCacheControl !== CacheControl.NoStore &&
379dc160d50S猫头猫                !notUpdateCache
380dc160d50S猫头猫            ) {
381abaede57S猫头猫                Cache.update(musicItem, [
382abaede57S猫头猫                    ['headers', result.headers],
383abaede57S猫头猫                    ['userAgent', result.userAgent],
384abaede57S猫头猫                    [`qualities.${quality}.url`, url],
385abaede57S猫头猫                ]);
386752ffc5aS猫头猫            }
387927dbe93S猫头猫            return result;
388927dbe93S猫头猫        } catch (e: any) {
389a28eac61S猫头猫            if (retryCount > 0 && e?.message !== 'NOT RETRY') {
390927dbe93S猫头猫                await delay(150);
391abaede57S猫头猫                return this.getMediaSource(musicItem, quality, --retryCount);
392927dbe93S猫头猫            }
393927dbe93S猫头猫            errorLog('获取真实源失败', e?.message);
394ea6d708fS猫头猫            devLog('error', '获取真实源失败', e, e?.message);
395192ae2b0S猫头猫            return null;
396927dbe93S猫头猫        }
397927dbe93S猫头猫    }
398927dbe93S猫头猫
399927dbe93S猫头猫    /** 获取音乐详情 */
400927dbe93S猫头猫    async getMusicInfo(
401927dbe93S猫头猫        musicItem: ICommon.IMediaBase,
40274d0cf81S猫头猫    ): Promise<Partial<IMusic.IMusicItem> | null> {
403927dbe93S猫头猫        if (!this.plugin.instance.getMusicInfo) {
404d704daedS猫头猫            return null;
405927dbe93S猫头猫        }
40674d0cf81S猫头猫        try {
407927dbe93S猫头猫            return (
408927dbe93S猫头猫                this.plugin.instance.getMusicInfo(
4097993f90eS猫头猫                    resetMediaItem(musicItem, undefined, true),
410d704daedS猫头猫                ) ?? null
411927dbe93S猫头猫            );
412ea6d708fS猫头猫        } catch (e: any) {
413ea6d708fS猫头猫            devLog('error', '获取音乐详情失败', e, e?.message);
414d704daedS猫头猫            return null;
41574d0cf81S猫头猫        }
416927dbe93S猫头猫    }
417927dbe93S猫头猫
418927dbe93S猫头猫    /** 获取歌词 */
419927dbe93S猫头猫    async getLyric(
420927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
421927dbe93S猫头猫        from?: IMusic.IMusicItemBase,
422927dbe93S猫头猫    ): Promise<ILyric.ILyricSource | null> {
423927dbe93S猫头猫        // 1.额外存储的meta信息
424927dbe93S猫头猫        const meta = MediaMeta.get(musicItem);
425927dbe93S猫头猫        if (meta && meta.associatedLrc) {
426927dbe93S猫头猫            // 有关联歌词
427927dbe93S猫头猫            if (
428927dbe93S猫头猫                isSameMediaItem(musicItem, from) ||
429927dbe93S猫头猫                isSameMediaItem(meta.associatedLrc, musicItem)
430927dbe93S猫头猫            ) {
431927dbe93S猫头猫                // 形成环路,断开当前的环
432927dbe93S猫头猫                await MediaMeta.update(musicItem, {
433927dbe93S猫头猫                    associatedLrc: undefined,
434927dbe93S猫头猫                });
435927dbe93S猫头猫                // 无歌词
436927dbe93S猫头猫                return null;
437927dbe93S猫头猫            }
438927dbe93S猫头猫            // 获取关联歌词
4397a91f04fS猫头猫            const associatedMeta = MediaMeta.get(meta.associatedLrc) ?? {};
4404060c00aS猫头猫            const result = await this.getLyric(
4417a91f04fS猫头猫                {...meta.associatedLrc, ...associatedMeta},
4424060c00aS猫头猫                from ?? musicItem,
4434060c00aS猫头猫            );
444927dbe93S猫头猫            if (result) {
445927dbe93S猫头猫                // 如果有关联歌词,就返回关联歌词,深度优先
446927dbe93S猫头猫                return result;
447927dbe93S猫头猫            }
448927dbe93S猫头猫        }
449927dbe93S猫头猫        const cache = Cache.get(musicItem);
450927dbe93S猫头猫        let rawLrc = meta?.rawLrc || musicItem.rawLrc || cache?.rawLrc;
451927dbe93S猫头猫        let lrcUrl = meta?.lrc || musicItem.lrc || cache?.lrc;
452927dbe93S猫头猫        // 如果存在文本
453927dbe93S猫头猫        if (rawLrc) {
454927dbe93S猫头猫            return {
455927dbe93S猫头猫                rawLrc,
456927dbe93S猫头猫                lrc: lrcUrl,
457927dbe93S猫头猫            };
458927dbe93S猫头猫        }
459927dbe93S猫头猫        // 2.本地缓存
460927dbe93S猫头猫        const localLrc =
4610e4173cdS猫头猫            meta?.[internalSerializeKey]?.local?.localLrc ||
4620e4173cdS猫头猫            cache?.[internalSerializeKey]?.local?.localLrc;
463927dbe93S猫头猫        if (localLrc && (await exists(localLrc))) {
464927dbe93S猫头猫            rawLrc = await readFile(localLrc, 'utf8');
465927dbe93S猫头猫            return {
466927dbe93S猫头猫                rawLrc,
467927dbe93S猫头猫                lrc: lrcUrl,
468927dbe93S猫头猫            };
469927dbe93S猫头猫        }
470927dbe93S猫头猫        // 3.优先使用url
471927dbe93S猫头猫        if (lrcUrl) {
472927dbe93S猫头猫            try {
473927dbe93S猫头猫                // 需要超时时间 axios timeout 但是没生效
47461aca335S猫头猫                rawLrc = (await axios.get(lrcUrl, {timeout: 2000})).data;
475927dbe93S猫头猫                return {
476927dbe93S猫头猫                    rawLrc,
477927dbe93S猫头猫                    lrc: lrcUrl,
478927dbe93S猫头猫                };
479927dbe93S猫头猫            } catch {
480927dbe93S猫头猫                lrcUrl = undefined;
481927dbe93S猫头猫            }
482927dbe93S猫头猫        }
483927dbe93S猫头猫        // 4. 如果地址失效
484927dbe93S猫头猫        if (!lrcUrl) {
485927dbe93S猫头猫            // 插件获得url
486927dbe93S猫头猫            try {
4877a91f04fS猫头猫                let lrcSource;
4887a91f04fS猫头猫                if (from) {
4897a91f04fS猫头猫                    lrcSource = await PluginManager.getByMedia(
4907a91f04fS猫头猫                        musicItem,
4917a91f04fS猫头猫                    )?.instance?.getLyric?.(
492927dbe93S猫头猫                        resetMediaItem(musicItem, undefined, true),
493927dbe93S猫头猫                    );
4947a91f04fS猫头猫                } else {
4957a91f04fS猫头猫                    lrcSource = await this.plugin.instance?.getLyric?.(
4967a91f04fS猫头猫                        resetMediaItem(musicItem, undefined, true),
4977a91f04fS猫头猫                    );
4987a91f04fS猫头猫                }
4997a91f04fS猫头猫
500927dbe93S猫头猫                rawLrc = lrcSource?.rawLrc;
501927dbe93S猫头猫                lrcUrl = lrcSource?.lrc;
502927dbe93S猫头猫            } catch (e: any) {
503927dbe93S猫头猫                trace('插件获取歌词失败', e?.message, 'error');
504ea6d708fS猫头猫                devLog('error', '插件获取歌词失败', e, e?.message);
505927dbe93S猫头猫            }
506927dbe93S猫头猫        }
507927dbe93S猫头猫        // 5. 最后一次请求
508927dbe93S猫头猫        if (rawLrc || lrcUrl) {
509927dbe93S猫头猫            const filename = `${pathConst.lrcCachePath}${nanoid()}.lrc`;
510927dbe93S猫头猫            if (lrcUrl) {
511927dbe93S猫头猫                try {
51261aca335S猫头猫                    rawLrc = (await axios.get(lrcUrl, {timeout: 2000})).data;
513927dbe93S猫头猫                } catch {}
514927dbe93S猫头猫            }
515927dbe93S猫头猫            if (rawLrc) {
516927dbe93S猫头猫                await writeFile(filename, rawLrc, 'utf8');
517927dbe93S猫头猫                // 写入缓存
518927dbe93S猫头猫                Cache.update(musicItem, [
5190e4173cdS猫头猫                    [`${internalSerializeKey}.local.localLrc`, filename],
520927dbe93S猫头猫                ]);
521927dbe93S猫头猫                // 如果有meta
522927dbe93S猫头猫                if (meta) {
523927dbe93S猫头猫                    MediaMeta.update(musicItem, [
5240e4173cdS猫头猫                        [`${internalSerializeKey}.local.localLrc`, filename],
525927dbe93S猫头猫                    ]);
526927dbe93S猫头猫                }
527927dbe93S猫头猫                return {
528927dbe93S猫头猫                    rawLrc,
529927dbe93S猫头猫                    lrc: lrcUrl,
530927dbe93S猫头猫                };
531927dbe93S猫头猫            }
532927dbe93S猫头猫        }
5333a6f67b1S猫头猫        // 6. 如果是本地文件
5343a6f67b1S猫头猫        const isDownloaded = LocalMusicSheet.isLocalMusic(musicItem);
5353a6f67b1S猫头猫        if (musicItem.platform !== localPluginPlatform && isDownloaded) {
5363a6f67b1S猫头猫            const res = await localFilePlugin.instance!.getLyric!(isDownloaded);
5373a6f67b1S猫头猫            if (res) {
5383a6f67b1S猫头猫                return res;
5393a6f67b1S猫头猫            }
5403a6f67b1S猫头猫        }
541ea6d708fS猫头猫        devLog('warn', '无歌词');
542927dbe93S猫头猫
543927dbe93S猫头猫        return null;
544927dbe93S猫头猫    }
545927dbe93S猫头猫
546927dbe93S猫头猫    /** 获取歌词文本 */
547927dbe93S猫头猫    async getLyricText(
548927dbe93S猫头猫        musicItem: IMusic.IMusicItem,
549927dbe93S猫头猫    ): Promise<string | undefined> {
550927dbe93S猫头猫        return (await this.getLyric(musicItem))?.rawLrc;
551927dbe93S猫头猫    }
552927dbe93S猫头猫
553927dbe93S猫头猫    /** 获取专辑信息 */
554927dbe93S猫头猫    async getAlbumInfo(
555927dbe93S猫头猫        albumItem: IAlbum.IAlbumItemBase,
556f9afcc0dS猫头猫        page: number = 1,
557f9afcc0dS猫头猫    ): Promise<IPlugin.IAlbumInfoResult | null> {
558927dbe93S猫头猫        if (!this.plugin.instance.getAlbumInfo) {
559f9afcc0dS猫头猫            return {
560f9afcc0dS猫头猫                albumItem,
561f2a4767cS猫头猫                musicList: (albumItem?.musicList ?? []).map(
562f2a4767cS猫头猫                    resetMediaItem,
563f2a4767cS猫头猫                    this.plugin.name,
564f2a4767cS猫头猫                    true,
565f2a4767cS猫头猫                ),
566f9afcc0dS猫头猫                isEnd: true,
567f9afcc0dS猫头猫            };
568927dbe93S猫头猫        }
569927dbe93S猫头猫        try {
570927dbe93S猫头猫            const result = await this.plugin.instance.getAlbumInfo(
571927dbe93S猫头猫                resetMediaItem(albumItem, undefined, true),
572f9afcc0dS猫头猫                page,
573927dbe93S猫头猫            );
5745276aef9S猫头猫            if (!result) {
5755276aef9S猫头猫                throw new Error();
5765276aef9S猫头猫            }
577927dbe93S猫头猫            result?.musicList?.forEach(_ => {
578927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
57996744680S猫头猫                _.album = albumItem.title;
580927dbe93S猫头猫            });
5815276aef9S猫头猫
582f9afcc0dS猫头猫            if (page <= 1) {
583f9afcc0dS猫头猫                // 合并信息
584f9afcc0dS猫头猫                return {
585f9afcc0dS猫头猫                    albumItem: {...albumItem, ...(result?.albumItem ?? {})},
586f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
587f9afcc0dS猫头猫                    musicList: result.musicList,
588f9afcc0dS猫头猫                };
589f9afcc0dS猫头猫            } else {
590f9afcc0dS猫头猫                return {
591f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
592f9afcc0dS猫头猫                    musicList: result.musicList,
593f9afcc0dS猫头猫                };
594f9afcc0dS猫头猫            }
5954394410dS猫头猫        } catch (e: any) {
5964394410dS猫头猫            trace('获取专辑信息失败', e?.message);
597ea6d708fS猫头猫            devLog('error', '获取专辑信息失败', e, e?.message);
598ea6d708fS猫头猫
599f9afcc0dS猫头猫            return null;
600927dbe93S猫头猫        }
601927dbe93S猫头猫    }
602927dbe93S猫头猫
6035830c002S猫头猫    /** 获取歌单信息 */
6045830c002S猫头猫    async getMusicSheetInfo(
6055830c002S猫头猫        sheetItem: IMusic.IMusicSheetItem,
6065830c002S猫头猫        page: number = 1,
6075830c002S猫头猫    ): Promise<IPlugin.ISheetInfoResult | null> {
6085281926bS猫头猫        if (!this.plugin.instance.getMusicSheetInfo) {
6095830c002S猫头猫            return {
6105830c002S猫头猫                sheetItem,
6115830c002S猫头猫                musicList: sheetItem?.musicList ?? [],
6125830c002S猫头猫                isEnd: true,
6135830c002S猫头猫            };
6145830c002S猫头猫        }
6155830c002S猫头猫        try {
6165830c002S猫头猫            const result = await this.plugin.instance?.getMusicSheetInfo?.(
6175830c002S猫头猫                resetMediaItem(sheetItem, undefined, true),
6185830c002S猫头猫                page,
6195830c002S猫头猫            );
6205830c002S猫头猫            if (!result) {
6215830c002S猫头猫                throw new Error();
6225830c002S猫头猫            }
6235830c002S猫头猫            result?.musicList?.forEach(_ => {
6245830c002S猫头猫                resetMediaItem(_, this.plugin.name);
6255830c002S猫头猫            });
6265830c002S猫头猫
6275830c002S猫头猫            if (page <= 1) {
6285830c002S猫头猫                // 合并信息
6295830c002S猫头猫                return {
6305830c002S猫头猫                    sheetItem: {...sheetItem, ...(result?.sheetItem ?? {})},
6315830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
6325830c002S猫头猫                    musicList: result.musicList,
6335830c002S猫头猫                };
6345830c002S猫头猫            } else {
6355830c002S猫头猫                return {
6365830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
6375830c002S猫头猫                    musicList: result.musicList,
6385830c002S猫头猫                };
6395830c002S猫头猫            }
6405830c002S猫头猫        } catch (e: any) {
6415830c002S猫头猫            trace('获取歌单信息失败', e, e?.message);
6425830c002S猫头猫            devLog('error', '获取歌单信息失败', e, e?.message);
6435830c002S猫头猫
6445830c002S猫头猫            return null;
6455830c002S猫头猫        }
6465830c002S猫头猫    }
6475830c002S猫头猫
648927dbe93S猫头猫    /** 查询作者信息 */
649efb9da24S猫头猫    async getArtistWorks<T extends IArtist.ArtistMediaType>(
650927dbe93S猫头猫        artistItem: IArtist.IArtistItem,
651927dbe93S猫头猫        page: number,
652927dbe93S猫头猫        type: T,
653927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
654efb9da24S猫头猫        if (!this.plugin.instance.getArtistWorks) {
655927dbe93S猫头猫            return {
656927dbe93S猫头猫                isEnd: true,
657927dbe93S猫头猫                data: [],
658927dbe93S猫头猫            };
659927dbe93S猫头猫        }
660927dbe93S猫头猫        try {
661efb9da24S猫头猫            const result = await this.plugin.instance.getArtistWorks(
662927dbe93S猫头猫                artistItem,
663927dbe93S猫头猫                page,
664927dbe93S猫头猫                type,
665927dbe93S猫头猫            );
666927dbe93S猫头猫            if (!result.data) {
667927dbe93S猫头猫                return {
668927dbe93S猫头猫                    isEnd: true,
669927dbe93S猫头猫                    data: [],
670927dbe93S猫头猫                };
671927dbe93S猫头猫            }
672927dbe93S猫头猫            result.data?.forEach(_ => resetMediaItem(_, this.plugin.name));
673927dbe93S猫头猫            return {
674927dbe93S猫头猫                isEnd: result.isEnd ?? true,
675927dbe93S猫头猫                data: result.data,
676927dbe93S猫头猫            };
6774394410dS猫头猫        } catch (e: any) {
6784394410dS猫头猫            trace('查询作者信息失败', e?.message);
679ea6d708fS猫头猫            devLog('error', '查询作者信息失败', e, e?.message);
680ea6d708fS猫头猫
681927dbe93S猫头猫            throw e;
682927dbe93S猫头猫        }
683927dbe93S猫头猫    }
68408380090S猫头猫
68508380090S猫头猫    /** 导入歌单 */
68608380090S猫头猫    async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> {
68708380090S猫头猫        try {
68808380090S猫头猫            const result =
68908380090S猫头猫                (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? [];
69008380090S猫头猫            result.forEach(_ => resetMediaItem(_, this.plugin.name));
69108380090S猫头猫            return result;
692ea6d708fS猫头猫        } catch (e: any) {
6930e4173cdS猫头猫            console.log(e);
694ea6d708fS猫头猫            devLog('error', '导入歌单失败', e, e?.message);
695ea6d708fS猫头猫
69608380090S猫头猫            return [];
69708380090S猫头猫        }
69808380090S猫头猫    }
6994d9d3c4cS猫头猫    /** 导入单曲 */
7004d9d3c4cS猫头猫    async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> {
7014d9d3c4cS猫头猫        try {
7024d9d3c4cS猫头猫            const result = await this.plugin.instance?.importMusicItem?.(
7034d9d3c4cS猫头猫                urlLike,
7044d9d3c4cS猫头猫            );
7054d9d3c4cS猫头猫            if (!result) {
7064d9d3c4cS猫头猫                throw new Error();
7074d9d3c4cS猫头猫            }
7084d9d3c4cS猫头猫            resetMediaItem(result, this.plugin.name);
7094d9d3c4cS猫头猫            return result;
710ea6d708fS猫头猫        } catch (e: any) {
711ea6d708fS猫头猫            devLog('error', '导入单曲失败', e, e?.message);
712ea6d708fS猫头猫
7134d9d3c4cS猫头猫            return null;
7144d9d3c4cS猫头猫        }
7154d9d3c4cS猫头猫    }
716d52aa40eS猫头猫    /** 获取榜单 */
71792b6c95aS猫头猫    async getTopLists(): Promise<IMusic.IMusicSheetGroupItem[]> {
718d52aa40eS猫头猫        try {
719d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopLists?.();
720d52aa40eS猫头猫            if (!result) {
721d52aa40eS猫头猫                throw new Error();
722d52aa40eS猫头猫            }
723d52aa40eS猫头猫            return result;
724d52aa40eS猫头猫        } catch (e: any) {
725d52aa40eS猫头猫            devLog('error', '获取榜单失败', e, e?.message);
726d52aa40eS猫头猫            return [];
727d52aa40eS猫头猫        }
728d52aa40eS猫头猫    }
729d52aa40eS猫头猫    /** 获取榜单详情 */
730d52aa40eS猫头猫    async getTopListDetail(
73192b6c95aS猫头猫        topListItem: IMusic.IMusicSheetItemBase,
732956ee1b7S猫头猫        page: number,
733956ee1b7S猫头猫    ): Promise<IPlugin.ITopListInfoResult> {
734d52aa40eS猫头猫        try {
735d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopListDetail?.(
736d52aa40eS猫头猫                topListItem,
737956ee1b7S猫头猫                page,
738d52aa40eS猫头猫            );
739d52aa40eS猫头猫            if (!result) {
740d52aa40eS猫头猫                throw new Error();
741d52aa40eS猫头猫            }
742d384662fS猫头猫            if (result.musicList) {
743d384662fS猫头猫                result.musicList.forEach(_ =>
744d384662fS猫头猫                    resetMediaItem(_, this.plugin.name),
745d384662fS猫头猫                );
746d384662fS猫头猫            }
747956ee1b7S猫头猫            if (result.isEnd !== false) {
748956ee1b7S猫头猫                result.isEnd = true;
749956ee1b7S猫头猫            }
750d52aa40eS猫头猫            return result;
751d52aa40eS猫头猫        } catch (e: any) {
752d52aa40eS猫头猫            devLog('error', '获取榜单详情失败', e, e?.message);
753d52aa40eS猫头猫            return {
754956ee1b7S猫头猫                isEnd: true,
755956ee1b7S猫头猫                topListItem: topListItem as IMusic.IMusicSheetItem,
756d52aa40eS猫头猫                musicList: [],
757d52aa40eS猫头猫            };
758d52aa40eS猫头猫        }
759d52aa40eS猫头猫    }
760ceb900cdS猫头猫
7615830c002S猫头猫    /** 获取推荐歌单的tag */
762ceb900cdS猫头猫    async getRecommendSheetTags(): Promise<IPlugin.IGetRecommendSheetTagsResult> {
763ceb900cdS猫头猫        try {
764ceb900cdS猫头猫            const result =
765ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetTags?.();
766ceb900cdS猫头猫            if (!result) {
767ceb900cdS猫头猫                throw new Error();
768ceb900cdS猫头猫            }
769ceb900cdS猫头猫            return result;
770ceb900cdS猫头猫        } catch (e: any) {
771ceb900cdS猫头猫            devLog('error', '获取推荐歌单失败', e, e?.message);
772ceb900cdS猫头猫            return {
773ceb900cdS猫头猫                data: [],
774ceb900cdS猫头猫            };
775ceb900cdS猫头猫        }
776ceb900cdS猫头猫    }
7775830c002S猫头猫    /** 获取某个tag的推荐歌单 */
778ceb900cdS猫头猫    async getRecommendSheetsByTag(
779ceb900cdS猫头猫        tagItem: ICommon.IUnique,
780ceb900cdS猫头猫        page?: number,
781ceb900cdS猫头猫    ): Promise<ICommon.PaginationResponse<IMusic.IMusicSheetItemBase>> {
782ceb900cdS猫头猫        try {
783ceb900cdS猫头猫            const result =
784ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetsByTag?.(
785ceb900cdS猫头猫                    tagItem,
786ceb900cdS猫头猫                    page ?? 1,
787ceb900cdS猫头猫                );
788ceb900cdS猫头猫            if (!result) {
789ceb900cdS猫头猫                throw new Error();
790ceb900cdS猫头猫            }
791ceb900cdS猫头猫            if (result.isEnd !== false) {
792ceb900cdS猫头猫                result.isEnd = true;
793ceb900cdS猫头猫            }
794ceb900cdS猫头猫            if (!result.data) {
795ceb900cdS猫头猫                result.data = [];
796ceb900cdS猫头猫            }
797ceb900cdS猫头猫            result.data.forEach(item => resetMediaItem(item, this.plugin.name));
798ceb900cdS猫头猫
799ceb900cdS猫头猫            return result;
800ceb900cdS猫头猫        } catch (e: any) {
801ceb900cdS猫头猫            devLog('error', '获取推荐歌单详情失败', e, e?.message);
802ceb900cdS猫头猫            return {
803ceb900cdS猫头猫                isEnd: true,
804ceb900cdS猫头猫                data: [],
805ceb900cdS猫头猫            };
806ceb900cdS猫头猫        }
807ceb900cdS猫头猫    }
808927dbe93S猫头猫}
809d5bfeb7eS猫头猫//#endregion
8101a5528a0S猫头猫
811927dbe93S猫头猫let plugins: Array<Plugin> = [];
812927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins);
81374d0cf81S猫头猫
814d5bfeb7eS猫头猫//#region 本地音乐插件
81574d0cf81S猫头猫/** 本地插件 */
81674d0cf81S猫头猫const localFilePlugin = new Plugin(function () {
8170e4173cdS猫头猫    return {
818d5bfeb7eS猫头猫        platform: localPluginPlatform,
81974d0cf81S猫头猫        _path: '',
82074d0cf81S猫头猫        async getMusicInfo(musicBase) {
82174d0cf81S猫头猫            const localPath = getInternalData<string>(
82274d0cf81S猫头猫                musicBase,
82374d0cf81S猫头猫                InternalDataType.LOCALPATH,
8240e4173cdS猫头猫            );
82574d0cf81S猫头猫            if (localPath) {
82674d0cf81S猫头猫                const coverImg = await Mp3Util.getMediaCoverImg(localPath);
82774d0cf81S猫头猫                return {
82874d0cf81S猫头猫                    artwork: coverImg,
82974d0cf81S猫头猫                };
83074d0cf81S猫头猫            }
83174d0cf81S猫头猫            return null;
83274d0cf81S猫头猫        },
8337993f90eS猫头猫        async getLyric(musicBase) {
8347993f90eS猫头猫            const localPath = getInternalData<string>(
8357993f90eS猫头猫                musicBase,
8367993f90eS猫头猫                InternalDataType.LOCALPATH,
8377993f90eS猫头猫            );
8383a6f67b1S猫头猫            let rawLrc: string | null = null;
8397993f90eS猫头猫            if (localPath) {
8403a6f67b1S猫头猫                // 读取内嵌歌词
8413a6f67b1S猫头猫                try {
8423a6f67b1S猫头猫                    rawLrc = await Mp3Util.getLyric(localPath);
8433a6f67b1S猫头猫                } catch (e) {
844a84a85c5S猫头猫                    console.log('读取内嵌歌词失败', e);
8457993f90eS猫头猫                }
8463a6f67b1S猫头猫                if (!rawLrc) {
8473a6f67b1S猫头猫                    // 读取配置歌词
8483a6f67b1S猫头猫                    const lastDot = localPath.lastIndexOf('.');
8493a6f67b1S猫头猫                    const lrcPath = localPath.slice(0, lastDot) + '.lrc';
8503a6f67b1S猫头猫
8513a6f67b1S猫头猫                    try {
8523a6f67b1S猫头猫                        if (await exists(lrcPath)) {
8533a6f67b1S猫头猫                            rawLrc = await readFile(lrcPath, 'utf8');
8543a6f67b1S猫头猫                        }
8553a6f67b1S猫头猫                    } catch {}
8563a6f67b1S猫头猫                }
8573a6f67b1S猫头猫            }
8583a6f67b1S猫头猫
8593a6f67b1S猫头猫            return rawLrc
8603a6f67b1S猫头猫                ? {
8613a6f67b1S猫头猫                      rawLrc,
8623a6f67b1S猫头猫                  }
8633a6f67b1S猫头猫                : null;
8647993f90eS猫头猫        },
865a84a85c5S猫头猫        async importMusicItem(urlLike) {
866a84a85c5S猫头猫            let meta: any = {};
867a84a85c5S猫头猫            try {
868a84a85c5S猫头猫                meta = await Mp3Util.getBasicMeta(urlLike);
869a84a85c5S猫头猫            } catch {}
870a84a85c5S猫头猫            const id = await FileSystem.hash(urlLike, 'MD5');
871a84a85c5S猫头猫            return {
872a84a85c5S猫头猫                id: id,
873a84a85c5S猫头猫                platform: '本地',
874a84a85c5S猫头猫                title: meta?.title ?? getFileName(urlLike),
875a84a85c5S猫头猫                artist: meta?.artist ?? '未知歌手',
876a84a85c5S猫头猫                duration: parseInt(meta?.duration ?? '0') / 1000,
877a84a85c5S猫头猫                album: meta?.album ?? '未知专辑',
878a84a85c5S猫头猫                artwork: '',
879a84a85c5S猫头猫                [internalSerializeKey]: {
880a84a85c5S猫头猫                    localPath: urlLike,
881a84a85c5S猫头猫                },
882a84a85c5S猫头猫            };
883a84a85c5S猫头猫        },
88474d0cf81S猫头猫    };
88574d0cf81S猫头猫}, '');
8867993f90eS猫头猫localFilePlugin.hash = localPluginHash;
887927dbe93S猫头猫
888d5bfeb7eS猫头猫//#endregion
889d5bfeb7eS猫头猫
890927dbe93S猫头猫async function setup() {
891927dbe93S猫头猫    const _plugins: Array<Plugin> = [];
892927dbe93S猫头猫    try {
893927dbe93S猫头猫        // 加载插件
894927dbe93S猫头猫        const pluginsPaths = await readDir(pathConst.pluginPath);
895927dbe93S猫头猫        for (let i = 0; i < pluginsPaths.length; ++i) {
896927dbe93S猫头猫            const _pluginUrl = pluginsPaths[i];
8971e263108S猫头猫            trace('初始化插件', _pluginUrl);
8981e263108S猫头猫            if (
8991e263108S猫头猫                _pluginUrl.isFile() &&
9001e263108S猫头猫                (_pluginUrl.name?.endsWith?.('.js') ||
9011e263108S猫头猫                    _pluginUrl.path?.endsWith?.('.js'))
9021e263108S猫头猫            ) {
903927dbe93S猫头猫                const funcCode = await readFile(_pluginUrl.path, 'utf8');
904927dbe93S猫头猫                const plugin = new Plugin(funcCode, _pluginUrl.path);
9054060c00aS猫头猫                const _pluginIndex = _plugins.findIndex(
9064060c00aS猫头猫                    p => p.hash === plugin.hash,
9074060c00aS猫头猫                );
908927dbe93S猫头猫                if (_pluginIndex !== -1) {
909927dbe93S猫头猫                    // 重复插件,直接忽略
9100c266394S猫头猫                    continue;
911927dbe93S猫头猫                }
912927dbe93S猫头猫                plugin.hash !== '' && _plugins.push(plugin);
913927dbe93S猫头猫            }
914927dbe93S猫头猫        }
915927dbe93S猫头猫
916927dbe93S猫头猫        plugins = _plugins;
917e08d37a3S猫头猫        /** 初始化meta信息 */
918c2b3a262S猫头猫        await PluginMeta.setupMeta(plugins.map(_ => _.name));
919c2b3a262S猫头猫        /** 查看一下是否有禁用的标记 */
920c2b3a262S猫头猫        const allMeta = PluginMeta.getPluginMetaAll() ?? {};
921c2b3a262S猫头猫        for (let plugin of plugins) {
922c2b3a262S猫头猫            if (allMeta[plugin.name]?.enabled === false) {
923c2b3a262S猫头猫                plugin.state = 'disabled';
924c2b3a262S猫头猫            }
925c2b3a262S猫头猫        }
926c2b3a262S猫头猫        pluginStateMapper.notify();
927927dbe93S猫头猫    } catch (e: any) {
9284060c00aS猫头猫        ToastAndroid.show(
9294060c00aS猫头猫            `插件初始化失败:${e?.message ?? e}`,
9304060c00aS猫头猫            ToastAndroid.LONG,
9314060c00aS猫头猫        );
9321a5528a0S猫头猫        errorLog('插件初始化失败', e?.message);
933927dbe93S猫头猫        throw e;
934927dbe93S猫头猫    }
935927dbe93S猫头猫}
936927dbe93S猫头猫
937e36e2599S猫头猫interface IInstallPluginConfig {
938e36e2599S猫头猫    notCheckVersion?: boolean;
939e36e2599S猫头猫}
940e36e2599S猫头猫
941927dbe93S猫头猫// 安装插件
942e36e2599S猫头猫async function installPlugin(
943e36e2599S猫头猫    pluginPath: string,
944e36e2599S猫头猫    config?: IInstallPluginConfig,
945e36e2599S猫头猫) {
94622c09412S猫头猫    // if (pluginPath.endsWith('.js')) {
947927dbe93S猫头猫    const funcCode = await readFile(pluginPath, 'utf8');
948e36e2599S猫头猫
949e36e2599S猫头猫    if (funcCode) {
950927dbe93S猫头猫        const plugin = new Plugin(funcCode, pluginPath);
951927dbe93S猫头猫        const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
952927dbe93S猫头猫        if (_pluginIndex !== -1) {
953e36e2599S猫头猫            // 静默忽略
954e36e2599S猫头猫            return plugin;
955927dbe93S猫头猫        }
956e36e2599S猫头猫        const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
957e36e2599S猫头猫        if (oldVersionPlugin && !config?.notCheckVersion) {
958e36e2599S猫头猫            if (
959e36e2599S猫头猫                compare(
960e36e2599S猫头猫                    oldVersionPlugin.instance.version ?? '',
961e36e2599S猫头猫                    plugin.instance.version ?? '',
962e36e2599S猫头猫                    '>',
963e36e2599S猫头猫                )
964e36e2599S猫头猫            ) {
965e36e2599S猫头猫                throw new Error('已安装更新版本的插件');
966e36e2599S猫头猫            }
967e36e2599S猫头猫        }
968e36e2599S猫头猫
969927dbe93S猫头猫        if (plugin.hash !== '') {
970927dbe93S猫头猫            const fn = nanoid();
971e36e2599S猫头猫            if (oldVersionPlugin) {
972e36e2599S猫头猫                plugins = plugins.filter(_ => _.hash !== oldVersionPlugin.hash);
973e36e2599S猫头猫                try {
974e36e2599S猫头猫                    await unlink(oldVersionPlugin.path);
975e36e2599S猫头猫                } catch {}
976e36e2599S猫头猫            }
977927dbe93S猫头猫            const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
978927dbe93S猫头猫            await copyFile(pluginPath, _pluginPath);
979927dbe93S猫头猫            plugin.path = _pluginPath;
980927dbe93S猫头猫            plugins = plugins.concat(plugin);
981927dbe93S猫头猫            pluginStateMapper.notify();
982a84a85c5S猫头猫            return plugin;
983927dbe93S猫头猫        }
984e36e2599S猫头猫        throw new Error('插件无法解析!');
985927dbe93S猫头猫    }
986e36e2599S猫头猫    throw new Error('插件无法识别!');
987c2b3a262S猫头猫}
988c2b3a262S猫头猫
989c2b3a262S猫头猫async function installPluginFromUrl(
990c2b3a262S猫头猫    url: string,
991c2b3a262S猫头猫    config?: IInstallPluginConfig,
992c2b3a262S猫头猫) {
99358992c6bS猫头猫    try {
99458992c6bS猫头猫        const funcCode = (await axios.get(url)).data;
99558992c6bS猫头猫        if (funcCode) {
99658992c6bS猫头猫            const plugin = new Plugin(funcCode, '');
99758992c6bS猫头猫            const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
99858992c6bS猫头猫            if (_pluginIndex !== -1) {
9998b7ddca8S猫头猫                // 静默忽略
10008b7ddca8S猫头猫                return;
100158992c6bS猫头猫            }
100225c1bd29S猫头猫            const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
1003c2b3a262S猫头猫            if (oldVersionPlugin && !config?.notCheckVersion) {
100425c1bd29S猫头猫                if (
100525c1bd29S猫头猫                    compare(
100625c1bd29S猫头猫                        oldVersionPlugin.instance.version ?? '',
100725c1bd29S猫头猫                        plugin.instance.version ?? '',
100825c1bd29S猫头猫                        '>',
100925c1bd29S猫头猫                    )
101025c1bd29S猫头猫                ) {
101125c1bd29S猫头猫                    throw new Error('已安装更新版本的插件');
101225c1bd29S猫头猫                }
101325c1bd29S猫头猫            }
101425c1bd29S猫头猫
101558992c6bS猫头猫            if (plugin.hash !== '') {
101658992c6bS猫头猫                const fn = nanoid();
101758992c6bS猫头猫                const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
101858992c6bS猫头猫                await writeFile(_pluginPath, funcCode, 'utf8');
101958992c6bS猫头猫                plugin.path = _pluginPath;
102058992c6bS猫头猫                plugins = plugins.concat(plugin);
102125c1bd29S猫头猫                if (oldVersionPlugin) {
102225c1bd29S猫头猫                    plugins = plugins.filter(
102325c1bd29S猫头猫                        _ => _.hash !== oldVersionPlugin.hash,
102425c1bd29S猫头猫                    );
102525c1bd29S猫头猫                    try {
102625c1bd29S猫头猫                        await unlink(oldVersionPlugin.path);
102725c1bd29S猫头猫                    } catch {}
102825c1bd29S猫头猫                }
102958992c6bS猫头猫                pluginStateMapper.notify();
103058992c6bS猫头猫                return;
103158992c6bS猫头猫            }
103274acbfc0S猫头猫            throw new Error('插件无法解析!');
103358992c6bS猫头猫        }
103425c1bd29S猫头猫    } catch (e: any) {
1035ea6d708fS猫头猫        devLog('error', 'URL安装插件失败', e, e?.message);
103658992c6bS猫头猫        errorLog('URL安装插件失败', e);
103725c1bd29S猫头猫        throw new Error(e?.message ?? '');
103858992c6bS猫头猫    }
103958992c6bS猫头猫}
104058992c6bS猫头猫
1041927dbe93S猫头猫/** 卸载插件 */
1042927dbe93S猫头猫async function uninstallPlugin(hash: string) {
1043927dbe93S猫头猫    const targetIndex = plugins.findIndex(_ => _.hash === hash);
1044927dbe93S猫头猫    if (targetIndex !== -1) {
1045927dbe93S猫头猫        try {
104624e5e74aS猫头猫            const pluginName = plugins[targetIndex].name;
1047927dbe93S猫头猫            await unlink(plugins[targetIndex].path);
1048927dbe93S猫头猫            plugins = plugins.filter(_ => _.hash !== hash);
1049927dbe93S猫头猫            pluginStateMapper.notify();
105024e5e74aS猫头猫            if (plugins.every(_ => _.name !== pluginName)) {
105124e5e74aS猫头猫                await MediaMeta.removePlugin(pluginName);
105224e5e74aS猫头猫            }
1053927dbe93S猫头猫        } catch {}
1054927dbe93S猫头猫    }
1055927dbe93S猫头猫}
1056927dbe93S猫头猫
105708882a77S猫头猫async function uninstallAllPlugins() {
105808882a77S猫头猫    await Promise.all(
105908882a77S猫头猫        plugins.map(async plugin => {
106008882a77S猫头猫            try {
106108882a77S猫头猫                const pluginName = plugin.name;
106208882a77S猫头猫                await unlink(plugin.path);
106308882a77S猫头猫                await MediaMeta.removePlugin(pluginName);
106408882a77S猫头猫            } catch (e) {}
106508882a77S猫头猫        }),
106608882a77S猫头猫    );
106708882a77S猫头猫    plugins = [];
106808882a77S猫头猫    pluginStateMapper.notify();
1069e08d37a3S猫头猫
1070e08d37a3S猫头猫    /** 清除空余文件,异步做就可以了 */
1071e08d37a3S猫头猫    readDir(pathConst.pluginPath)
1072e08d37a3S猫头猫        .then(fns => {
1073e08d37a3S猫头猫            fns.forEach(fn => {
1074e08d37a3S猫头猫                unlink(fn.path).catch(emptyFunction);
1075e08d37a3S猫头猫            });
1076e08d37a3S猫头猫        })
1077e08d37a3S猫头猫        .catch(emptyFunction);
107808882a77S猫头猫}
107908882a77S猫头猫
108025c1bd29S猫头猫async function updatePlugin(plugin: Plugin) {
108125c1bd29S猫头猫    const updateUrl = plugin.instance.srcUrl;
108225c1bd29S猫头猫    if (!updateUrl) {
108325c1bd29S猫头猫        throw new Error('没有更新源');
108425c1bd29S猫头猫    }
108525c1bd29S猫头猫    try {
108625c1bd29S猫头猫        await installPluginFromUrl(updateUrl);
108725c1bd29S猫头猫    } catch (e: any) {
108825c1bd29S猫头猫        if (e.message === '插件已安装') {
108925c1bd29S猫头猫            throw new Error('当前已是最新版本');
109025c1bd29S猫头猫        } else {
109125c1bd29S猫头猫            throw e;
109225c1bd29S猫头猫        }
109325c1bd29S猫头猫    }
109425c1bd29S猫头猫}
109525c1bd29S猫头猫
1096927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) {
10972c595535S猫头猫    return getByName(mediaItem?.platform);
1098927dbe93S猫头猫}
1099927dbe93S猫头猫
1100927dbe93S猫头猫function getByHash(hash: string) {
11017993f90eS猫头猫    return hash === localPluginHash
11027993f90eS猫头猫        ? localFilePlugin
11037993f90eS猫头猫        : plugins.find(_ => _.hash === hash);
1104927dbe93S猫头猫}
1105927dbe93S猫头猫
1106927dbe93S猫头猫function getByName(name: string) {
11077993f90eS猫头猫    return name === localPluginPlatform
11080e4173cdS猫头猫        ? localFilePlugin
11090e4173cdS猫头猫        : plugins.find(_ => _.name === name);
1110927dbe93S猫头猫}
1111927dbe93S猫头猫
1112927dbe93S猫头猫function getValidPlugins() {
1113927dbe93S猫头猫    return plugins.filter(_ => _.state === 'enabled');
1114927dbe93S猫头猫}
1115927dbe93S猫头猫
11162b80a429S猫头猫function getSearchablePlugins(supportedSearchType?: ICommon.SupportMediaType) {
11172b80a429S猫头猫    return plugins.filter(
11182b80a429S猫头猫        _ =>
11192b80a429S猫头猫            _.state === 'enabled' &&
11202b80a429S猫头猫            _.instance.search &&
112139ac60f7S猫头猫            (supportedSearchType && _.instance.supportedSearchType
112239ac60f7S猫头猫                ? _.instance.supportedSearchType.includes(supportedSearchType)
11232b80a429S猫头猫                : true),
11242b80a429S猫头猫    );
1125efb9da24S猫头猫}
1126efb9da24S猫头猫
11272b80a429S猫头猫function getSortedSearchablePlugins(
11282b80a429S猫头猫    supportedSearchType?: ICommon.SupportMediaType,
11292b80a429S猫头猫) {
11302b80a429S猫头猫    return getSearchablePlugins(supportedSearchType).sort((a, b) =>
1131e08d37a3S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1132e08d37a3S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1133e08d37a3S猫头猫        0
1134e08d37a3S猫头猫            ? -1
1135e08d37a3S猫头猫            : 1,
1136e08d37a3S猫头猫    );
1137e08d37a3S猫头猫}
1138e08d37a3S猫头猫
113915feccc1S猫头猫function getTopListsablePlugins() {
114015feccc1S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists);
114115feccc1S猫头猫}
114215feccc1S猫头猫
114315feccc1S猫头猫function getSortedTopListsablePlugins() {
114415feccc1S猫头猫    return getTopListsablePlugins().sort((a, b) =>
114515feccc1S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
114615feccc1S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
114715feccc1S猫头猫        0
114815feccc1S猫头猫            ? -1
114915feccc1S猫头猫            : 1,
115015feccc1S猫头猫    );
115115feccc1S猫头猫}
115215feccc1S猫头猫
1153ceb900cdS猫头猫function getRecommendSheetablePlugins() {
1154ceb900cdS猫头猫    return plugins.filter(
1155ceb900cdS猫头猫        _ => _.state === 'enabled' && _.instance.getRecommendSheetsByTag,
1156ceb900cdS猫头猫    );
1157ceb900cdS猫头猫}
1158ceb900cdS猫头猫
1159ceb900cdS猫头猫function getSortedRecommendSheetablePlugins() {
1160ceb900cdS猫头猫    return getRecommendSheetablePlugins().sort((a, b) =>
1161ceb900cdS猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1162ceb900cdS猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1163ceb900cdS猫头猫        0
1164ceb900cdS猫头猫            ? -1
1165ceb900cdS猫头猫            : 1,
1166ceb900cdS猫头猫    );
1167ceb900cdS猫头猫}
1168ceb900cdS猫头猫
1169e08d37a3S猫头猫function useSortedPlugins() {
1170e08d37a3S猫头猫    const _plugins = pluginStateMapper.useMappedState();
1171e08d37a3S猫头猫    const _pluginMetaAll = PluginMeta.usePluginMetaAll();
1172e08d37a3S猫头猫
117334588741S猫头猫    const [sortedPlugins, setSortedPlugins] = useState(
117434588741S猫头猫        [..._plugins].sort((a, b) =>
1175e08d37a3S猫头猫            (_pluginMetaAll[a.name]?.order ?? Infinity) -
1176e08d37a3S猫头猫                (_pluginMetaAll[b.name]?.order ?? Infinity) <
1177e08d37a3S猫头猫            0
1178e08d37a3S猫头猫                ? -1
1179e08d37a3S猫头猫                : 1,
118034588741S猫头猫        ),
1181e08d37a3S猫头猫    );
118234588741S猫头猫
118334588741S猫头猫    useEffect(() => {
1184d4cd40d8S猫头猫        InteractionManager.runAfterInteractions(() => {
118534588741S猫头猫            setSortedPlugins(
118634588741S猫头猫                [..._plugins].sort((a, b) =>
118734588741S猫头猫                    (_pluginMetaAll[a.name]?.order ?? Infinity) -
118834588741S猫头猫                        (_pluginMetaAll[b.name]?.order ?? Infinity) <
118934588741S猫头猫                    0
119034588741S猫头猫                        ? -1
119134588741S猫头猫                        : 1,
119234588741S猫头猫                ),
119334588741S猫头猫            );
1194d4cd40d8S猫头猫        });
119534588741S猫头猫    }, [_plugins, _pluginMetaAll]);
119634588741S猫头猫
119734588741S猫头猫    return sortedPlugins;
1198e08d37a3S猫头猫}
1199e08d37a3S猫头猫
1200c2b3a262S猫头猫async function setPluginEnabled(plugin: Plugin, enabled?: boolean) {
1201c2b3a262S猫头猫    const target = plugins.find(it => it.hash === plugin.hash);
1202c2b3a262S猫头猫    if (target) {
1203c2b3a262S猫头猫        target.state = enabled ? 'enabled' : 'disabled';
1204c2b3a262S猫头猫        plugins = [...plugins];
1205c2b3a262S猫头猫        pluginStateMapper.notify();
1206c2b3a262S猫头猫        PluginMeta.setPluginMetaProp(plugin, 'enabled', enabled);
1207c2b3a262S猫头猫    }
1208c2b3a262S猫头猫}
1209c2b3a262S猫头猫
1210927dbe93S猫头猫const PluginManager = {
1211927dbe93S猫头猫    setup,
1212927dbe93S猫头猫    installPlugin,
121358992c6bS猫头猫    installPluginFromUrl,
121425c1bd29S猫头猫    updatePlugin,
1215927dbe93S猫头猫    uninstallPlugin,
1216927dbe93S猫头猫    getByMedia,
1217927dbe93S猫头猫    getByHash,
1218927dbe93S猫头猫    getByName,
1219927dbe93S猫头猫    getValidPlugins,
1220efb9da24S猫头猫    getSearchablePlugins,
1221e08d37a3S猫头猫    getSortedSearchablePlugins,
122215feccc1S猫头猫    getTopListsablePlugins,
1223ceb900cdS猫头猫    getSortedRecommendSheetablePlugins,
122415feccc1S猫头猫    getSortedTopListsablePlugins,
12255276aef9S猫头猫    usePlugins: pluginStateMapper.useMappedState,
1226e08d37a3S猫头猫    useSortedPlugins,
122708882a77S猫头猫    uninstallAllPlugins,
1228c2b3a262S猫头猫    setPluginEnabled,
12295276aef9S猫头猫};
1230927dbe93S猫头猫
1231927dbe93S猫头猫export default PluginManager;
1232