xref: /MusicFree/src/core/pluginManager.ts (revision 53f8cd8ed1685781cd5f791d8f5d4319710d9a71)
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';
14d4cd40d8S猫头猫import {InteractionManager, ToastAndroid} from 'react-native';
15927dbe93S猫头猫import pathConst from '@/constants/pathConst';
1625c1bd29S猫头猫import {compare, satisfies} from 'compare-versions';
17927dbe93S猫头猫import DeviceInfo from 'react-native-device-info';
18927dbe93S猫头猫import StateMapper from '@/utils/stateMapper';
19927dbe93S猫头猫import MediaMeta from './mediaMeta';
20927dbe93S猫头猫import {nanoid} from 'nanoid';
21ea6d708fS猫头猫import {devLog, errorLog, trace} from '../utils/log';
22927dbe93S猫头猫import Cache from './cache';
23cfa0fc07S猫头猫import {
240e4173cdS猫头猫    getInternalData,
250e4173cdS猫头猫    InternalDataType,
260e4173cdS猫头猫    isSameMediaItem,
270e4173cdS猫头猫    resetMediaItem,
280e4173cdS猫头猫} from '@/utils/mediaItem';
297993f90eS猫头猫import {
307993f90eS猫头猫    CacheControl,
31e08d37a3S猫头猫    emptyFunction,
327993f90eS猫头猫    internalSerializeKey,
337993f90eS猫头猫    localPluginHash,
347993f90eS猫头猫    localPluginPlatform,
357993f90eS猫头猫} from '@/constants/commonConst';
36927dbe93S猫头猫import delay from '@/utils/delay';
374d9d3c4cS猫头猫import * as cheerio from 'cheerio';
387d7e864fS猫头猫import CookieManager from '@react-native-cookies/cookies';
397d7e864fS猫头猫import he from 'he';
40ef714860S猫头猫import Network from './network';
410e4173cdS猫头猫import LocalMusicSheet from './localMusicSheet';
420e4173cdS猫头猫import {FileSystem} from 'react-native-file-access';
4374d0cf81S猫头猫import Mp3Util from '@/native/mp3Util';
44e08d37a3S猫头猫import {PluginMeta} from './pluginMeta';
4534588741S猫头猫import {useEffect, useState} from 'react';
46927dbe93S猫头猫
47927dbe93S猫头猫axios.defaults.timeout = 1500;
48927dbe93S猫头猫
49927dbe93S猫头猫const sha256 = CryptoJs.SHA256;
50927dbe93S猫头猫
51cfa0fc07S猫头猫export enum PluginStateCode {
52927dbe93S猫头猫    /** 版本不匹配 */
53927dbe93S猫头猫    VersionNotMatch = 'VERSION NOT MATCH',
54927dbe93S猫头猫    /** 无法解析 */
55927dbe93S猫头猫    CannotParse = 'CANNOT PARSE',
56927dbe93S猫头猫}
57927dbe93S猫头猫
589c34d637S猫头猫const packages: Record<string, any> = {
599c34d637S猫头猫    cheerio,
609c34d637S猫头猫    'crypto-js': CryptoJs,
619c34d637S猫头猫    axios,
629c34d637S猫头猫    dayjs,
639c34d637S猫头猫    'big-integer': bigInt,
649c34d637S猫头猫    qs,
659c34d637S猫头猫    he,
663b3d6357S猫头猫    '@react-native-cookies/cookies': CookieManager,
679c34d637S猫头猫};
689c34d637S猫头猫
69b43683eaS猫头猫const _require = (packageName: string) => {
70b43683eaS猫头猫    let pkg = packages[packageName];
71b43683eaS猫头猫    pkg.default = pkg;
72b43683eaS猫头猫    return pkg;
73b43683eaS猫头猫};
749c34d637S猫头猫
75*53f8cd8eS猫头猫const _consoleBind = function (
76*53f8cd8eS猫头猫    method: 'log' | 'error' | 'info' | 'warn',
77*53f8cd8eS猫头猫    ...args: any
78*53f8cd8eS猫头猫) {
79*53f8cd8eS猫头猫    const fn = console[method];
80*53f8cd8eS猫头猫    if (fn) {
81*53f8cd8eS猫头猫        fn(...args);
82*53f8cd8eS猫头猫        devLog(method, ...args);
83*53f8cd8eS猫头猫    }
84*53f8cd8eS猫头猫};
85*53f8cd8eS猫头猫
86*53f8cd8eS猫头猫const _console = {
87*53f8cd8eS猫头猫    log: _consoleBind.bind(null, 'log'),
88*53f8cd8eS猫头猫    warn: _consoleBind.bind(null, 'warn'),
89*53f8cd8eS猫头猫    info: _consoleBind.bind(null, 'info'),
90*53f8cd8eS猫头猫    error: _consoleBind.bind(null, 'error'),
91*53f8cd8eS猫头猫};
92*53f8cd8eS猫头猫
93d5bfeb7eS猫头猫//#region 插件类
94927dbe93S猫头猫export class Plugin {
95927dbe93S猫头猫    /** 插件名 */
96927dbe93S猫头猫    public name: string;
97927dbe93S猫头猫    /** 插件的hash,作为唯一id */
98927dbe93S猫头猫    public hash: string;
99927dbe93S猫头猫    /** 插件状态:激活、关闭、错误 */
100927dbe93S猫头猫    public state: 'enabled' | 'disabled' | 'error';
101927dbe93S猫头猫    /** 插件支持的搜索类型 */
102927dbe93S猫头猫    public supportedSearchType?: string;
103927dbe93S猫头猫    /** 插件状态信息 */
104927dbe93S猫头猫    public stateCode?: PluginStateCode;
105927dbe93S猫头猫    /** 插件的实例 */
106927dbe93S猫头猫    public instance: IPlugin.IPluginInstance;
107927dbe93S猫头猫    /** 插件路径 */
108927dbe93S猫头猫    public path: string;
109927dbe93S猫头猫    /** 插件方法 */
110927dbe93S猫头猫    public methods: PluginMethods;
111d5bfeb7eS猫头猫    /** 用户输入 */
112d5bfeb7eS猫头猫    public userEnv?: Record<string, string>;
113927dbe93S猫头猫
11474d0cf81S猫头猫    constructor(
11574d0cf81S猫头猫        funcCode: string | (() => IPlugin.IPluginInstance),
11674d0cf81S猫头猫        pluginPath: string,
11774d0cf81S猫头猫    ) {
118927dbe93S猫头猫        this.state = 'enabled';
119927dbe93S猫头猫        let _instance: IPlugin.IPluginInstance;
1203b3d6357S猫头猫        const _module: any = {exports: {}};
121927dbe93S猫头猫        try {
12274d0cf81S猫头猫            if (typeof funcCode === 'string') {
1234060c00aS猫头猫                // eslint-disable-next-line no-new-func
124927dbe93S猫头猫                _instance = Function(`
125927dbe93S猫头猫                    'use strict';
126*53f8cd8eS猫头猫                    return function(require, __musicfree_require, module, exports, console) {
1279c34d637S猫头猫                        ${funcCode}
128927dbe93S猫头猫                    }
129*53f8cd8eS猫头猫                `)()(_require, _require, _module, _module.exports, _console);
1303b3d6357S猫头猫                if (_module.exports.default) {
1313b3d6357S猫头猫                    _instance = _module.exports
1323b3d6357S猫头猫                        .default as IPlugin.IPluginInstance;
1333b3d6357S猫头猫                } else {
1349c34d637S猫头猫                    _instance = _module.exports as IPlugin.IPluginInstance;
1353b3d6357S猫头猫                }
13674d0cf81S猫头猫            } else {
13774d0cf81S猫头猫                _instance = funcCode();
13874d0cf81S猫头猫            }
139927dbe93S猫头猫            this.checkValid(_instance);
140927dbe93S猫头猫        } catch (e: any) {
141b43683eaS猫头猫            console.log(e);
142927dbe93S猫头猫            this.state = 'error';
143927dbe93S猫头猫            this.stateCode = PluginStateCode.CannotParse;
144927dbe93S猫头猫            if (e?.stateCode) {
145927dbe93S猫头猫                this.stateCode = e.stateCode;
146927dbe93S猫头猫            }
147927dbe93S猫头猫            errorLog(`${pluginPath}插件无法解析 `, {
148927dbe93S猫头猫                stateCode: this.stateCode,
149927dbe93S猫头猫                message: e?.message,
150927dbe93S猫头猫                stack: e?.stack,
151927dbe93S猫头猫            });
152927dbe93S猫头猫            _instance = e?.instance ?? {
153927dbe93S猫头猫                _path: '',
154927dbe93S猫头猫                platform: '',
155927dbe93S猫头猫                appVersion: '',
15620e6a092S猫头猫                async getMediaSource() {
157927dbe93S猫头猫                    return null;
158927dbe93S猫头猫                },
159927dbe93S猫头猫                async search() {
160927dbe93S猫头猫                    return {};
161927dbe93S猫头猫                },
162927dbe93S猫头猫                async getAlbumInfo() {
163927dbe93S猫头猫                    return null;
164927dbe93S猫头猫                },
165927dbe93S猫头猫            };
166927dbe93S猫头猫        }
167927dbe93S猫头猫        this.instance = _instance;
168927dbe93S猫头猫        this.path = pluginPath;
169927dbe93S猫头猫        this.name = _instance.platform;
170ab8941d9S猫头猫        if (
171ab8941d9S猫头猫            this.instance.platform === '' ||
172ab8941d9S猫头猫            this.instance.platform === undefined
173ab8941d9S猫头猫        ) {
174927dbe93S猫头猫            this.hash = '';
175927dbe93S猫头猫        } else {
17674d0cf81S猫头猫            if (typeof funcCode === 'string') {
177927dbe93S猫头猫                this.hash = sha256(funcCode).toString();
17874d0cf81S猫头猫            } else {
17974d0cf81S猫头猫                this.hash = sha256(funcCode.toString()).toString();
18074d0cf81S猫头猫            }
181927dbe93S猫头猫        }
182927dbe93S猫头猫
183927dbe93S猫头猫        // 放在最后
184927dbe93S猫头猫        this.methods = new PluginMethods(this);
185927dbe93S猫头猫    }
186927dbe93S猫头猫
187927dbe93S猫头猫    private checkValid(_instance: IPlugin.IPluginInstance) {
188927dbe93S猫头猫        /** 版本号校验 */
189927dbe93S猫头猫        if (
190927dbe93S猫头猫            _instance.appVersion &&
191927dbe93S猫头猫            !satisfies(DeviceInfo.getVersion(), _instance.appVersion)
192927dbe93S猫头猫        ) {
193927dbe93S猫头猫            throw {
194927dbe93S猫头猫                instance: _instance,
195927dbe93S猫头猫                stateCode: PluginStateCode.VersionNotMatch,
196927dbe93S猫头猫            };
197927dbe93S猫头猫        }
198927dbe93S猫头猫        return true;
199927dbe93S猫头猫    }
200927dbe93S猫头猫}
201d5bfeb7eS猫头猫//#endregion
202927dbe93S猫头猫
203d5bfeb7eS猫头猫//#region 基于插件类封装的方法,供给APP侧直接调用
204927dbe93S猫头猫/** 有缓存等信息 */
205927dbe93S猫头猫class PluginMethods implements IPlugin.IPluginInstanceMethods {
206927dbe93S猫头猫    private plugin;
207927dbe93S猫头猫    constructor(plugin: Plugin) {
208927dbe93S猫头猫        this.plugin = plugin;
209927dbe93S猫头猫    }
210927dbe93S猫头猫    /** 搜索 */
211927dbe93S猫头猫    async search<T extends ICommon.SupportMediaType>(
212927dbe93S猫头猫        query: string,
213927dbe93S猫头猫        page: number,
214927dbe93S猫头猫        type: T,
215927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
216927dbe93S猫头猫        if (!this.plugin.instance.search) {
217927dbe93S猫头猫            return {
218927dbe93S猫头猫                isEnd: true,
219927dbe93S猫头猫                data: [],
220927dbe93S猫头猫            };
221927dbe93S猫头猫        }
222927dbe93S猫头猫
2234060c00aS猫头猫        const result =
2244060c00aS猫头猫            (await this.plugin.instance.search(query, page, type)) ?? {};
225927dbe93S猫头猫        if (Array.isArray(result.data)) {
226927dbe93S猫头猫            result.data.forEach(_ => {
227927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
228927dbe93S猫头猫            });
229927dbe93S猫头猫            return {
230927dbe93S猫头猫                isEnd: result.isEnd ?? true,
231927dbe93S猫头猫                data: result.data,
232927dbe93S猫头猫            };
233927dbe93S猫头猫        }
234927dbe93S猫头猫        return {
235927dbe93S猫头猫            isEnd: true,
236927dbe93S猫头猫            data: [],
237927dbe93S猫头猫        };
238927dbe93S猫头猫    }
239927dbe93S猫头猫
240927dbe93S猫头猫    /** 获取真实源 */
24120e6a092S猫头猫    async getMediaSource(
242927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
243abaede57S猫头猫        quality: IMusic.IQualityKey = 'standard',
244927dbe93S猫头猫        retryCount = 1,
245dc160d50S猫头猫        notUpdateCache = false,
246192ae2b0S猫头猫    ): Promise<IPlugin.IMediaSourceResult | null> {
247927dbe93S猫头猫        // 1. 本地搜索 其实直接读mediameta就好了
248927dbe93S猫头猫        const localPath =
2490e4173cdS猫头猫            getInternalData<string>(musicItem, InternalDataType.LOCALPATH) ??
2500e4173cdS猫头猫            getInternalData<string>(
2510e4173cdS猫头猫                LocalMusicSheet.isLocalMusic(musicItem),
2520e4173cdS猫头猫                InternalDataType.LOCALPATH,
2530e4173cdS猫头猫            );
2540e4173cdS猫头猫        if (localPath && (await FileSystem.exists(localPath))) {
2550e4173cdS猫头猫            trace('本地播放', localPath);
256927dbe93S猫头猫            return {
257927dbe93S猫头猫                url: localPath,
258927dbe93S猫头猫            };
259927dbe93S猫头猫        }
2607993f90eS猫头猫        if (musicItem.platform === localPluginPlatform) {
261f5935920S猫头猫            throw new Error('本地音乐不存在');
262f5935920S猫头猫        }
263927dbe93S猫头猫        // 2. 缓存播放
264927dbe93S猫头猫        const mediaCache = Cache.get(musicItem);
265985f8e75S猫头猫        const pluginCacheControl =
266985f8e75S猫头猫            this.plugin.instance.cacheControl ?? 'no-cache';
267cfa0fc07S猫头猫        if (
268cfa0fc07S猫头猫            mediaCache &&
269abaede57S猫头猫            mediaCache?.qualities?.[quality]?.url &&
27048f4b873S猫头猫            (pluginCacheControl === CacheControl.Cache ||
27148f4b873S猫头猫                (pluginCacheControl === CacheControl.NoCache &&
272ef714860S猫头猫                    Network.isOffline()))
273cfa0fc07S猫头猫        ) {
2745276aef9S猫头猫            trace('播放', '缓存播放');
275abaede57S猫头猫            const qualityInfo = mediaCache.qualities[quality];
276927dbe93S猫头猫            return {
277abaede57S猫头猫                url: qualityInfo.url,
278927dbe93S猫头猫                headers: mediaCache.headers,
2794060c00aS猫头猫                userAgent:
2804060c00aS猫头猫                    mediaCache.userAgent ?? mediaCache.headers?.['user-agent'],
281927dbe93S猫头猫            };
282927dbe93S猫头猫        }
283927dbe93S猫头猫        // 3. 插件解析
28420e6a092S猫头猫        if (!this.plugin.instance.getMediaSource) {
285abaede57S猫头猫            return {url: musicItem?.qualities?.[quality]?.url ?? musicItem.url};
286927dbe93S猫头猫        }
287927dbe93S猫头猫        try {
288abaede57S猫头猫            const {url, headers} = (await this.plugin.instance.getMediaSource(
289abaede57S猫头猫                musicItem,
290abaede57S猫头猫                quality,
291abaede57S猫头猫            )) ?? {url: musicItem?.qualities?.[quality]?.url};
292927dbe93S猫头猫            if (!url) {
293a28eac61S猫头猫                throw new Error('NOT RETRY');
294927dbe93S猫头猫            }
2955276aef9S猫头猫            trace('播放', '插件播放');
296927dbe93S猫头猫            const result = {
297927dbe93S猫头猫                url,
298927dbe93S猫头猫                headers,
299927dbe93S猫头猫                userAgent: headers?.['user-agent'],
300cfa0fc07S猫头猫            } as IPlugin.IMediaSourceResult;
301927dbe93S猫头猫
302dc160d50S猫头猫            if (
303dc160d50S猫头猫                pluginCacheControl !== CacheControl.NoStore &&
304dc160d50S猫头猫                !notUpdateCache
305dc160d50S猫头猫            ) {
306abaede57S猫头猫                Cache.update(musicItem, [
307abaede57S猫头猫                    ['headers', result.headers],
308abaede57S猫头猫                    ['userAgent', result.userAgent],
309abaede57S猫头猫                    [`qualities.${quality}.url`, url],
310abaede57S猫头猫                ]);
311752ffc5aS猫头猫            }
312cfa0fc07S猫头猫
313927dbe93S猫头猫            return result;
314927dbe93S猫头猫        } catch (e: any) {
315a28eac61S猫头猫            if (retryCount > 0 && e?.message !== 'NOT RETRY') {
316927dbe93S猫头猫                await delay(150);
317abaede57S猫头猫                return this.getMediaSource(musicItem, quality, --retryCount);
318927dbe93S猫头猫            }
319927dbe93S猫头猫            errorLog('获取真实源失败', e?.message);
320ea6d708fS猫头猫            devLog('error', '获取真实源失败', e, e?.message);
321192ae2b0S猫头猫            return null;
322927dbe93S猫头猫        }
323927dbe93S猫头猫    }
324927dbe93S猫头猫
325927dbe93S猫头猫    /** 获取音乐详情 */
326927dbe93S猫头猫    async getMusicInfo(
327927dbe93S猫头猫        musicItem: ICommon.IMediaBase,
32874d0cf81S猫头猫    ): Promise<Partial<IMusic.IMusicItem> | null> {
329927dbe93S猫头猫        if (!this.plugin.instance.getMusicInfo) {
330d704daedS猫头猫            return null;
331927dbe93S猫头猫        }
33274d0cf81S猫头猫        try {
333927dbe93S猫头猫            return (
334927dbe93S猫头猫                this.plugin.instance.getMusicInfo(
3357993f90eS猫头猫                    resetMediaItem(musicItem, undefined, true),
336d704daedS猫头猫                ) ?? null
337927dbe93S猫头猫            );
338ea6d708fS猫头猫        } catch (e: any) {
339ea6d708fS猫头猫            devLog('error', '获取音乐详情失败', e, e?.message);
340d704daedS猫头猫            return null;
34174d0cf81S猫头猫        }
342927dbe93S猫头猫    }
343927dbe93S猫头猫
344927dbe93S猫头猫    /** 获取歌词 */
345927dbe93S猫头猫    async getLyric(
346927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
347927dbe93S猫头猫        from?: IMusic.IMusicItemBase,
348927dbe93S猫头猫    ): Promise<ILyric.ILyricSource | null> {
349927dbe93S猫头猫        // 1.额外存储的meta信息
350927dbe93S猫头猫        const meta = MediaMeta.get(musicItem);
351927dbe93S猫头猫        if (meta && meta.associatedLrc) {
352927dbe93S猫头猫            // 有关联歌词
353927dbe93S猫头猫            if (
354927dbe93S猫头猫                isSameMediaItem(musicItem, from) ||
355927dbe93S猫头猫                isSameMediaItem(meta.associatedLrc, musicItem)
356927dbe93S猫头猫            ) {
357927dbe93S猫头猫                // 形成环路,断开当前的环
358927dbe93S猫头猫                await MediaMeta.update(musicItem, {
359927dbe93S猫头猫                    associatedLrc: undefined,
360927dbe93S猫头猫                });
361927dbe93S猫头猫                // 无歌词
362927dbe93S猫头猫                return null;
363927dbe93S猫头猫            }
364927dbe93S猫头猫            // 获取关联歌词
3657a91f04fS猫头猫            const associatedMeta = MediaMeta.get(meta.associatedLrc) ?? {};
3664060c00aS猫头猫            const result = await this.getLyric(
3677a91f04fS猫头猫                {...meta.associatedLrc, ...associatedMeta},
3684060c00aS猫头猫                from ?? musicItem,
3694060c00aS猫头猫            );
370927dbe93S猫头猫            if (result) {
371927dbe93S猫头猫                // 如果有关联歌词,就返回关联歌词,深度优先
372927dbe93S猫头猫                return result;
373927dbe93S猫头猫            }
374927dbe93S猫头猫        }
375927dbe93S猫头猫        const cache = Cache.get(musicItem);
376927dbe93S猫头猫        let rawLrc = meta?.rawLrc || musicItem.rawLrc || cache?.rawLrc;
377927dbe93S猫头猫        let lrcUrl = meta?.lrc || musicItem.lrc || cache?.lrc;
378927dbe93S猫头猫        // 如果存在文本
379927dbe93S猫头猫        if (rawLrc) {
380927dbe93S猫头猫            return {
381927dbe93S猫头猫                rawLrc,
382927dbe93S猫头猫                lrc: lrcUrl,
383927dbe93S猫头猫            };
384927dbe93S猫头猫        }
385927dbe93S猫头猫        // 2.本地缓存
386927dbe93S猫头猫        const localLrc =
3870e4173cdS猫头猫            meta?.[internalSerializeKey]?.local?.localLrc ||
3880e4173cdS猫头猫            cache?.[internalSerializeKey]?.local?.localLrc;
389927dbe93S猫头猫        if (localLrc && (await exists(localLrc))) {
390927dbe93S猫头猫            rawLrc = await readFile(localLrc, 'utf8');
391927dbe93S猫头猫            return {
392927dbe93S猫头猫                rawLrc,
393927dbe93S猫头猫                lrc: lrcUrl,
394927dbe93S猫头猫            };
395927dbe93S猫头猫        }
396927dbe93S猫头猫        // 3.优先使用url
397927dbe93S猫头猫        if (lrcUrl) {
398927dbe93S猫头猫            try {
399927dbe93S猫头猫                // 需要超时时间 axios timeout 但是没生效
4002a3194f5S猫头猫                rawLrc = (await axios.get(lrcUrl, {timeout: 1500})).data;
401927dbe93S猫头猫                return {
402927dbe93S猫头猫                    rawLrc,
403927dbe93S猫头猫                    lrc: lrcUrl,
404927dbe93S猫头猫                };
405927dbe93S猫头猫            } catch {
406927dbe93S猫头猫                lrcUrl = undefined;
407927dbe93S猫头猫            }
408927dbe93S猫头猫        }
409927dbe93S猫头猫        // 4. 如果地址失效
410927dbe93S猫头猫        if (!lrcUrl) {
411927dbe93S猫头猫            // 插件获得url
412927dbe93S猫头猫            try {
4137a91f04fS猫头猫                let lrcSource;
4147a91f04fS猫头猫                if (from) {
4157a91f04fS猫头猫                    lrcSource = await PluginManager.getByMedia(
4167a91f04fS猫头猫                        musicItem,
4177a91f04fS猫头猫                    )?.instance?.getLyric?.(
418927dbe93S猫头猫                        resetMediaItem(musicItem, undefined, true),
419927dbe93S猫头猫                    );
4207a91f04fS猫头猫                } else {
4217a91f04fS猫头猫                    lrcSource = await this.plugin.instance?.getLyric?.(
4227a91f04fS猫头猫                        resetMediaItem(musicItem, undefined, true),
4237a91f04fS猫头猫                    );
4247a91f04fS猫头猫                }
4257a91f04fS猫头猫
426927dbe93S猫头猫                rawLrc = lrcSource?.rawLrc;
427927dbe93S猫头猫                lrcUrl = lrcSource?.lrc;
428927dbe93S猫头猫            } catch (e: any) {
429927dbe93S猫头猫                trace('插件获取歌词失败', e?.message, 'error');
430ea6d708fS猫头猫                devLog('error', '插件获取歌词失败', e, e?.message);
431927dbe93S猫头猫            }
432927dbe93S猫头猫        }
433927dbe93S猫头猫        // 5. 最后一次请求
434927dbe93S猫头猫        if (rawLrc || lrcUrl) {
435927dbe93S猫头猫            const filename = `${pathConst.lrcCachePath}${nanoid()}.lrc`;
436927dbe93S猫头猫            if (lrcUrl) {
437927dbe93S猫头猫                try {
4382a3194f5S猫头猫                    rawLrc = (await axios.get(lrcUrl, {timeout: 1500})).data;
439927dbe93S猫头猫                } catch {}
440927dbe93S猫头猫            }
441927dbe93S猫头猫            if (rawLrc) {
442927dbe93S猫头猫                await writeFile(filename, rawLrc, 'utf8');
443927dbe93S猫头猫                // 写入缓存
444927dbe93S猫头猫                Cache.update(musicItem, [
4450e4173cdS猫头猫                    [`${internalSerializeKey}.local.localLrc`, filename],
446927dbe93S猫头猫                ]);
447927dbe93S猫头猫                // 如果有meta
448927dbe93S猫头猫                if (meta) {
449927dbe93S猫头猫                    MediaMeta.update(musicItem, [
4500e4173cdS猫头猫                        [`${internalSerializeKey}.local.localLrc`, filename],
451927dbe93S猫头猫                    ]);
452927dbe93S猫头猫                }
453927dbe93S猫头猫                return {
454927dbe93S猫头猫                    rawLrc,
455927dbe93S猫头猫                    lrc: lrcUrl,
456927dbe93S猫头猫                };
457927dbe93S猫头猫            }
458927dbe93S猫头猫        }
4593a6f67b1S猫头猫        // 6. 如果是本地文件
4603a6f67b1S猫头猫        const isDownloaded = LocalMusicSheet.isLocalMusic(musicItem);
4613a6f67b1S猫头猫        if (musicItem.platform !== localPluginPlatform && isDownloaded) {
4623a6f67b1S猫头猫            const res = await localFilePlugin.instance!.getLyric!(isDownloaded);
4633a6f67b1S猫头猫            if (res) {
4643a6f67b1S猫头猫                return res;
4653a6f67b1S猫头猫            }
4663a6f67b1S猫头猫        }
467ea6d708fS猫头猫        devLog('warn', '无歌词');
468927dbe93S猫头猫
469927dbe93S猫头猫        return null;
470927dbe93S猫头猫    }
471927dbe93S猫头猫
472927dbe93S猫头猫    /** 获取歌词文本 */
473927dbe93S猫头猫    async getLyricText(
474927dbe93S猫头猫        musicItem: IMusic.IMusicItem,
475927dbe93S猫头猫    ): Promise<string | undefined> {
476927dbe93S猫头猫        return (await this.getLyric(musicItem))?.rawLrc;
477927dbe93S猫头猫    }
478927dbe93S猫头猫
479927dbe93S猫头猫    /** 获取专辑信息 */
480927dbe93S猫头猫    async getAlbumInfo(
481927dbe93S猫头猫        albumItem: IAlbum.IAlbumItemBase,
482927dbe93S猫头猫    ): Promise<IAlbum.IAlbumItem | null> {
483927dbe93S猫头猫        if (!this.plugin.instance.getAlbumInfo) {
484927dbe93S猫头猫            return {...albumItem, musicList: []};
485927dbe93S猫头猫        }
486927dbe93S猫头猫        try {
487927dbe93S猫头猫            const result = await this.plugin.instance.getAlbumInfo(
488927dbe93S猫头猫                resetMediaItem(albumItem, undefined, true),
489927dbe93S猫头猫            );
4905276aef9S猫头猫            if (!result) {
4915276aef9S猫头猫                throw new Error();
4925276aef9S猫头猫            }
493927dbe93S猫头猫            result?.musicList?.forEach(_ => {
494927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
495927dbe93S猫头猫            });
4965276aef9S猫头猫
4975276aef9S猫头猫            return {...albumItem, ...result};
4984394410dS猫头猫        } catch (e: any) {
4994394410dS猫头猫            trace('获取专辑信息失败', e?.message);
500ea6d708fS猫头猫            devLog('error', '获取专辑信息失败', e, e?.message);
501ea6d708fS猫头猫
502927dbe93S猫头猫            return {...albumItem, musicList: []};
503927dbe93S猫头猫        }
504927dbe93S猫头猫    }
505927dbe93S猫头猫
506927dbe93S猫头猫    /** 查询作者信息 */
507efb9da24S猫头猫    async getArtistWorks<T extends IArtist.ArtistMediaType>(
508927dbe93S猫头猫        artistItem: IArtist.IArtistItem,
509927dbe93S猫头猫        page: number,
510927dbe93S猫头猫        type: T,
511927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
512efb9da24S猫头猫        if (!this.plugin.instance.getArtistWorks) {
513927dbe93S猫头猫            return {
514927dbe93S猫头猫                isEnd: true,
515927dbe93S猫头猫                data: [],
516927dbe93S猫头猫            };
517927dbe93S猫头猫        }
518927dbe93S猫头猫        try {
519efb9da24S猫头猫            const result = await this.plugin.instance.getArtistWorks(
520927dbe93S猫头猫                artistItem,
521927dbe93S猫头猫                page,
522927dbe93S猫头猫                type,
523927dbe93S猫头猫            );
524927dbe93S猫头猫            if (!result.data) {
525927dbe93S猫头猫                return {
526927dbe93S猫头猫                    isEnd: true,
527927dbe93S猫头猫                    data: [],
528927dbe93S猫头猫                };
529927dbe93S猫头猫            }
530927dbe93S猫头猫            result.data?.forEach(_ => resetMediaItem(_, this.plugin.name));
531927dbe93S猫头猫            return {
532927dbe93S猫头猫                isEnd: result.isEnd ?? true,
533927dbe93S猫头猫                data: result.data,
534927dbe93S猫头猫            };
5354394410dS猫头猫        } catch (e: any) {
5364394410dS猫头猫            trace('查询作者信息失败', e?.message);
537ea6d708fS猫头猫            devLog('error', '查询作者信息失败', e, e?.message);
538ea6d708fS猫头猫
539927dbe93S猫头猫            throw e;
540927dbe93S猫头猫        }
541927dbe93S猫头猫    }
54208380090S猫头猫
54308380090S猫头猫    /** 导入歌单 */
54408380090S猫头猫    async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> {
54508380090S猫头猫        try {
54608380090S猫头猫            const result =
54708380090S猫头猫                (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? [];
54808380090S猫头猫            result.forEach(_ => resetMediaItem(_, this.plugin.name));
54908380090S猫头猫            return result;
550ea6d708fS猫头猫        } catch (e: any) {
5510e4173cdS猫头猫            console.log(e);
552ea6d708fS猫头猫            devLog('error', '导入歌单失败', e, e?.message);
553ea6d708fS猫头猫
55408380090S猫头猫            return [];
55508380090S猫头猫        }
55608380090S猫头猫    }
5574d9d3c4cS猫头猫    /** 导入单曲 */
5584d9d3c4cS猫头猫    async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> {
5594d9d3c4cS猫头猫        try {
5604d9d3c4cS猫头猫            const result = await this.plugin.instance?.importMusicItem?.(
5614d9d3c4cS猫头猫                urlLike,
5624d9d3c4cS猫头猫            );
5634d9d3c4cS猫头猫            if (!result) {
5644d9d3c4cS猫头猫                throw new Error();
5654d9d3c4cS猫头猫            }
5664d9d3c4cS猫头猫            resetMediaItem(result, this.plugin.name);
5674d9d3c4cS猫头猫            return result;
568ea6d708fS猫头猫        } catch (e: any) {
569ea6d708fS猫头猫            devLog('error', '导入单曲失败', e, e?.message);
570ea6d708fS猫头猫
5714d9d3c4cS猫头猫            return null;
5724d9d3c4cS猫头猫        }
5734d9d3c4cS猫头猫    }
574d52aa40eS猫头猫    /** 获取榜单 */
575d52aa40eS猫头猫    async getTopLists(): Promise<IMusic.IMusicTopListGroupItem[]> {
576d52aa40eS猫头猫        try {
577d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopLists?.();
578d52aa40eS猫头猫            if (!result) {
579d52aa40eS猫头猫                throw new Error();
580d52aa40eS猫头猫            }
581d52aa40eS猫头猫            return result;
582d52aa40eS猫头猫        } catch (e: any) {
583d52aa40eS猫头猫            devLog('error', '获取榜单失败', e, e?.message);
584d52aa40eS猫头猫            return [];
585d52aa40eS猫头猫        }
586d52aa40eS猫头猫    }
587d52aa40eS猫头猫    /** 获取榜单详情 */
588d52aa40eS猫头猫    async getTopListDetail(
589d52aa40eS猫头猫        topListItem: IMusic.IMusicTopListItem,
590d52aa40eS猫头猫    ): Promise<ICommon.WithMusicList<IMusic.IMusicTopListItem>> {
591d52aa40eS猫头猫        try {
592d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopListDetail?.(
593d52aa40eS猫头猫                topListItem,
594d52aa40eS猫头猫            );
595d52aa40eS猫头猫            if (!result) {
596d52aa40eS猫头猫                throw new Error();
597d52aa40eS猫头猫            }
598d384662fS猫头猫            if (result.musicList) {
599d384662fS猫头猫                result.musicList.forEach(_ =>
600d384662fS猫头猫                    resetMediaItem(_, this.plugin.name),
601d384662fS猫头猫                );
602d384662fS猫头猫            }
603d52aa40eS猫头猫            return result;
604d52aa40eS猫头猫        } catch (e: any) {
605d52aa40eS猫头猫            devLog('error', '获取榜单详情失败', e, e?.message);
606d52aa40eS猫头猫            return {
607d52aa40eS猫头猫                ...topListItem,
608d52aa40eS猫头猫                musicList: [],
609d52aa40eS猫头猫            };
610d52aa40eS猫头猫        }
611d52aa40eS猫头猫    }
612927dbe93S猫头猫}
613d5bfeb7eS猫头猫//#endregion
6141a5528a0S猫头猫
615927dbe93S猫头猫let plugins: Array<Plugin> = [];
616927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins);
61774d0cf81S猫头猫
618d5bfeb7eS猫头猫//#region 本地音乐插件
61974d0cf81S猫头猫/** 本地插件 */
62074d0cf81S猫头猫const localFilePlugin = new Plugin(function () {
6210e4173cdS猫头猫    return {
622d5bfeb7eS猫头猫        platform: localPluginPlatform,
62374d0cf81S猫头猫        _path: '',
62474d0cf81S猫头猫        async getMusicInfo(musicBase) {
62574d0cf81S猫头猫            const localPath = getInternalData<string>(
62674d0cf81S猫头猫                musicBase,
62774d0cf81S猫头猫                InternalDataType.LOCALPATH,
6280e4173cdS猫头猫            );
62974d0cf81S猫头猫            if (localPath) {
63074d0cf81S猫头猫                const coverImg = await Mp3Util.getMediaCoverImg(localPath);
63174d0cf81S猫头猫                return {
63274d0cf81S猫头猫                    artwork: coverImg,
63374d0cf81S猫头猫                };
63474d0cf81S猫头猫            }
63574d0cf81S猫头猫            return null;
63674d0cf81S猫头猫        },
6377993f90eS猫头猫        async getLyric(musicBase) {
6387993f90eS猫头猫            const localPath = getInternalData<string>(
6397993f90eS猫头猫                musicBase,
6407993f90eS猫头猫                InternalDataType.LOCALPATH,
6417993f90eS猫头猫            );
6423a6f67b1S猫头猫            let rawLrc: string | null = null;
6437993f90eS猫头猫            if (localPath) {
6443a6f67b1S猫头猫                // 读取内嵌歌词
6453a6f67b1S猫头猫                try {
6463a6f67b1S猫头猫                    rawLrc = await Mp3Util.getLyric(localPath);
6473a6f67b1S猫头猫                } catch (e) {
6483a6f67b1S猫头猫                    console.log('e', e);
6497993f90eS猫头猫                }
6503a6f67b1S猫头猫                if (!rawLrc) {
6513a6f67b1S猫头猫                    // 读取配置歌词
6523a6f67b1S猫头猫                    const lastDot = localPath.lastIndexOf('.');
6533a6f67b1S猫头猫                    const lrcPath = localPath.slice(0, lastDot) + '.lrc';
6543a6f67b1S猫头猫
6553a6f67b1S猫头猫                    try {
6563a6f67b1S猫头猫                        if (await exists(lrcPath)) {
6573a6f67b1S猫头猫                            rawLrc = await readFile(lrcPath, 'utf8');
6583a6f67b1S猫头猫                        }
6593a6f67b1S猫头猫                    } catch {}
6603a6f67b1S猫头猫                }
6613a6f67b1S猫头猫            }
6623a6f67b1S猫头猫
6633a6f67b1S猫头猫            return rawLrc
6643a6f67b1S猫头猫                ? {
6653a6f67b1S猫头猫                      rawLrc,
6663a6f67b1S猫头猫                  }
6673a6f67b1S猫头猫                : null;
6687993f90eS猫头猫        },
66974d0cf81S猫头猫    };
67074d0cf81S猫头猫}, '');
6717993f90eS猫头猫localFilePlugin.hash = localPluginHash;
672927dbe93S猫头猫
673d5bfeb7eS猫头猫//#endregion
674d5bfeb7eS猫头猫
675927dbe93S猫头猫async function setup() {
676927dbe93S猫头猫    const _plugins: Array<Plugin> = [];
677927dbe93S猫头猫    try {
678927dbe93S猫头猫        // 加载插件
679927dbe93S猫头猫        const pluginsPaths = await readDir(pathConst.pluginPath);
680927dbe93S猫头猫        for (let i = 0; i < pluginsPaths.length; ++i) {
681927dbe93S猫头猫            const _pluginUrl = pluginsPaths[i];
6821e263108S猫头猫            trace('初始化插件', _pluginUrl);
6831e263108S猫头猫            if (
6841e263108S猫头猫                _pluginUrl.isFile() &&
6851e263108S猫头猫                (_pluginUrl.name?.endsWith?.('.js') ||
6861e263108S猫头猫                    _pluginUrl.path?.endsWith?.('.js'))
6871e263108S猫头猫            ) {
688927dbe93S猫头猫                const funcCode = await readFile(_pluginUrl.path, 'utf8');
689927dbe93S猫头猫                const plugin = new Plugin(funcCode, _pluginUrl.path);
6904060c00aS猫头猫                const _pluginIndex = _plugins.findIndex(
6914060c00aS猫头猫                    p => p.hash === plugin.hash,
6924060c00aS猫头猫                );
693927dbe93S猫头猫                if (_pluginIndex !== -1) {
694927dbe93S猫头猫                    // 重复插件,直接忽略
695927dbe93S猫头猫                    return;
696927dbe93S猫头猫                }
697927dbe93S猫头猫                plugin.hash !== '' && _plugins.push(plugin);
698927dbe93S猫头猫            }
699927dbe93S猫头猫        }
700927dbe93S猫头猫
701927dbe93S猫头猫        plugins = _plugins;
702927dbe93S猫头猫        pluginStateMapper.notify();
703e08d37a3S猫头猫        /** 初始化meta信息 */
704e08d37a3S猫头猫        PluginMeta.setupMeta(plugins.map(_ => _.name));
705927dbe93S猫头猫    } catch (e: any) {
7064060c00aS猫头猫        ToastAndroid.show(
7074060c00aS猫头猫            `插件初始化失败:${e?.message ?? e}`,
7084060c00aS猫头猫            ToastAndroid.LONG,
7094060c00aS猫头猫        );
7101a5528a0S猫头猫        errorLog('插件初始化失败', e?.message);
711927dbe93S猫头猫        throw e;
712927dbe93S猫头猫    }
713927dbe93S猫头猫}
714927dbe93S猫头猫
715927dbe93S猫头猫// 安装插件
716927dbe93S猫头猫async function installPlugin(pluginPath: string) {
71722c09412S猫头猫    // if (pluginPath.endsWith('.js')) {
718927dbe93S猫头猫    const funcCode = await readFile(pluginPath, 'utf8');
719927dbe93S猫头猫    const plugin = new Plugin(funcCode, pluginPath);
720927dbe93S猫头猫    const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
721927dbe93S猫头猫    if (_pluginIndex !== -1) {
7224d9d3c4cS猫头猫        throw new Error('插件已安装');
723927dbe93S猫头猫    }
724927dbe93S猫头猫    if (plugin.hash !== '') {
725927dbe93S猫头猫        const fn = nanoid();
726927dbe93S猫头猫        const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
727927dbe93S猫头猫        await copyFile(pluginPath, _pluginPath);
728927dbe93S猫头猫        plugin.path = _pluginPath;
729927dbe93S猫头猫        plugins = plugins.concat(plugin);
730927dbe93S猫头猫        pluginStateMapper.notify();
7314d9d3c4cS猫头猫        return;
732927dbe93S猫头猫    }
7334d9d3c4cS猫头猫    throw new Error('插件无法解析');
73422c09412S猫头猫    // }
73522c09412S猫头猫    // throw new Error('插件不存在');
736927dbe93S猫头猫}
737927dbe93S猫头猫
73858992c6bS猫头猫async function installPluginFromUrl(url: string) {
73958992c6bS猫头猫    try {
74058992c6bS猫头猫        const funcCode = (await axios.get(url)).data;
74158992c6bS猫头猫        if (funcCode) {
74258992c6bS猫头猫            const plugin = new Plugin(funcCode, '');
74358992c6bS猫头猫            const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
74458992c6bS猫头猫            if (_pluginIndex !== -1) {
7458b7ddca8S猫头猫                // 静默忽略
7468b7ddca8S猫头猫                return;
74758992c6bS猫头猫            }
74825c1bd29S猫头猫            const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
74925c1bd29S猫头猫            if (oldVersionPlugin) {
75025c1bd29S猫头猫                if (
75125c1bd29S猫头猫                    compare(
75225c1bd29S猫头猫                        oldVersionPlugin.instance.version ?? '',
75325c1bd29S猫头猫                        plugin.instance.version ?? '',
75425c1bd29S猫头猫                        '>',
75525c1bd29S猫头猫                    )
75625c1bd29S猫头猫                ) {
75725c1bd29S猫头猫                    throw new Error('已安装更新版本的插件');
75825c1bd29S猫头猫                }
75925c1bd29S猫头猫            }
76025c1bd29S猫头猫
76158992c6bS猫头猫            if (plugin.hash !== '') {
76258992c6bS猫头猫                const fn = nanoid();
76358992c6bS猫头猫                const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
76458992c6bS猫头猫                await writeFile(_pluginPath, funcCode, 'utf8');
76558992c6bS猫头猫                plugin.path = _pluginPath;
76658992c6bS猫头猫                plugins = plugins.concat(plugin);
76725c1bd29S猫头猫                if (oldVersionPlugin) {
76825c1bd29S猫头猫                    plugins = plugins.filter(
76925c1bd29S猫头猫                        _ => _.hash !== oldVersionPlugin.hash,
77025c1bd29S猫头猫                    );
77125c1bd29S猫头猫                    try {
77225c1bd29S猫头猫                        await unlink(oldVersionPlugin.path);
77325c1bd29S猫头猫                    } catch {}
77425c1bd29S猫头猫                }
77558992c6bS猫头猫                pluginStateMapper.notify();
77658992c6bS猫头猫                return;
77758992c6bS猫头猫            }
77874acbfc0S猫头猫            throw new Error('插件无法解析!');
77958992c6bS猫头猫        }
78025c1bd29S猫头猫    } catch (e: any) {
781ea6d708fS猫头猫        devLog('error', 'URL安装插件失败', e, e?.message);
78258992c6bS猫头猫        errorLog('URL安装插件失败', e);
78325c1bd29S猫头猫        throw new Error(e?.message ?? '');
78458992c6bS猫头猫    }
78558992c6bS猫头猫}
78658992c6bS猫头猫
787927dbe93S猫头猫/** 卸载插件 */
788927dbe93S猫头猫async function uninstallPlugin(hash: string) {
789927dbe93S猫头猫    const targetIndex = plugins.findIndex(_ => _.hash === hash);
790927dbe93S猫头猫    if (targetIndex !== -1) {
791927dbe93S猫头猫        try {
79224e5e74aS猫头猫            const pluginName = plugins[targetIndex].name;
793927dbe93S猫头猫            await unlink(plugins[targetIndex].path);
794927dbe93S猫头猫            plugins = plugins.filter(_ => _.hash !== hash);
795927dbe93S猫头猫            pluginStateMapper.notify();
79624e5e74aS猫头猫            if (plugins.every(_ => _.name !== pluginName)) {
79724e5e74aS猫头猫                await MediaMeta.removePlugin(pluginName);
79824e5e74aS猫头猫            }
799927dbe93S猫头猫        } catch {}
800927dbe93S猫头猫    }
801927dbe93S猫头猫}
802927dbe93S猫头猫
80308882a77S猫头猫async function uninstallAllPlugins() {
80408882a77S猫头猫    await Promise.all(
80508882a77S猫头猫        plugins.map(async plugin => {
80608882a77S猫头猫            try {
80708882a77S猫头猫                const pluginName = plugin.name;
80808882a77S猫头猫                await unlink(plugin.path);
80908882a77S猫头猫                await MediaMeta.removePlugin(pluginName);
81008882a77S猫头猫            } catch (e) {}
81108882a77S猫头猫        }),
81208882a77S猫头猫    );
81308882a77S猫头猫    plugins = [];
81408882a77S猫头猫    pluginStateMapper.notify();
815e08d37a3S猫头猫
816e08d37a3S猫头猫    /** 清除空余文件,异步做就可以了 */
817e08d37a3S猫头猫    readDir(pathConst.pluginPath)
818e08d37a3S猫头猫        .then(fns => {
819e08d37a3S猫头猫            fns.forEach(fn => {
820e08d37a3S猫头猫                unlink(fn.path).catch(emptyFunction);
821e08d37a3S猫头猫            });
822e08d37a3S猫头猫        })
823e08d37a3S猫头猫        .catch(emptyFunction);
82408882a77S猫头猫}
82508882a77S猫头猫
82625c1bd29S猫头猫async function updatePlugin(plugin: Plugin) {
82725c1bd29S猫头猫    const updateUrl = plugin.instance.srcUrl;
82825c1bd29S猫头猫    if (!updateUrl) {
82925c1bd29S猫头猫        throw new Error('没有更新源');
83025c1bd29S猫头猫    }
83125c1bd29S猫头猫    try {
83225c1bd29S猫头猫        await installPluginFromUrl(updateUrl);
83325c1bd29S猫头猫    } catch (e: any) {
83425c1bd29S猫头猫        if (e.message === '插件已安装') {
83525c1bd29S猫头猫            throw new Error('当前已是最新版本');
83625c1bd29S猫头猫        } else {
83725c1bd29S猫头猫            throw e;
83825c1bd29S猫头猫        }
83925c1bd29S猫头猫    }
84025c1bd29S猫头猫}
84125c1bd29S猫头猫
842927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) {
8432c595535S猫头猫    return getByName(mediaItem?.platform);
844927dbe93S猫头猫}
845927dbe93S猫头猫
846927dbe93S猫头猫function getByHash(hash: string) {
8477993f90eS猫头猫    return hash === localPluginHash
8487993f90eS猫头猫        ? localFilePlugin
8497993f90eS猫头猫        : plugins.find(_ => _.hash === hash);
850927dbe93S猫头猫}
851927dbe93S猫头猫
852927dbe93S猫头猫function getByName(name: string) {
8537993f90eS猫头猫    return name === localPluginPlatform
8540e4173cdS猫头猫        ? localFilePlugin
8550e4173cdS猫头猫        : plugins.find(_ => _.name === name);
856927dbe93S猫头猫}
857927dbe93S猫头猫
858927dbe93S猫头猫function getValidPlugins() {
859927dbe93S猫头猫    return plugins.filter(_ => _.state === 'enabled');
860927dbe93S猫头猫}
861927dbe93S猫头猫
862efb9da24S猫头猫function getSearchablePlugins() {
863efb9da24S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.search);
864efb9da24S猫头猫}
865efb9da24S猫头猫
866e08d37a3S猫头猫function getSortedSearchablePlugins() {
867e08d37a3S猫头猫    return getSearchablePlugins().sort((a, b) =>
868e08d37a3S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
869e08d37a3S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
870e08d37a3S猫头猫        0
871e08d37a3S猫头猫            ? -1
872e08d37a3S猫头猫            : 1,
873e08d37a3S猫头猫    );
874e08d37a3S猫头猫}
875e08d37a3S猫头猫
87615feccc1S猫头猫function getTopListsablePlugins() {
87715feccc1S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists);
87815feccc1S猫头猫}
87915feccc1S猫头猫
88015feccc1S猫头猫function getSortedTopListsablePlugins() {
88115feccc1S猫头猫    return getTopListsablePlugins().sort((a, b) =>
88215feccc1S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
88315feccc1S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
88415feccc1S猫头猫        0
88515feccc1S猫头猫            ? -1
88615feccc1S猫头猫            : 1,
88715feccc1S猫头猫    );
88815feccc1S猫头猫}
88915feccc1S猫头猫
890e08d37a3S猫头猫function useSortedPlugins() {
891e08d37a3S猫头猫    const _plugins = pluginStateMapper.useMappedState();
892e08d37a3S猫头猫    const _pluginMetaAll = PluginMeta.usePluginMetaAll();
893e08d37a3S猫头猫
89434588741S猫头猫    const [sortedPlugins, setSortedPlugins] = useState(
89534588741S猫头猫        [..._plugins].sort((a, b) =>
896e08d37a3S猫头猫            (_pluginMetaAll[a.name]?.order ?? Infinity) -
897e08d37a3S猫头猫                (_pluginMetaAll[b.name]?.order ?? Infinity) <
898e08d37a3S猫头猫            0
899e08d37a3S猫头猫                ? -1
900e08d37a3S猫头猫                : 1,
90134588741S猫头猫        ),
902e08d37a3S猫头猫    );
90334588741S猫头猫
90434588741S猫头猫    useEffect(() => {
905d4cd40d8S猫头猫        InteractionManager.runAfterInteractions(() => {
90634588741S猫头猫            setSortedPlugins(
90734588741S猫头猫                [..._plugins].sort((a, b) =>
90834588741S猫头猫                    (_pluginMetaAll[a.name]?.order ?? Infinity) -
90934588741S猫头猫                        (_pluginMetaAll[b.name]?.order ?? Infinity) <
91034588741S猫头猫                    0
91134588741S猫头猫                        ? -1
91234588741S猫头猫                        : 1,
91334588741S猫头猫                ),
91434588741S猫头猫            );
915d4cd40d8S猫头猫        });
91634588741S猫头猫    }, [_plugins, _pluginMetaAll]);
91734588741S猫头猫
91834588741S猫头猫    return sortedPlugins;
919e08d37a3S猫头猫}
920e08d37a3S猫头猫
921927dbe93S猫头猫const PluginManager = {
922927dbe93S猫头猫    setup,
923927dbe93S猫头猫    installPlugin,
92458992c6bS猫头猫    installPluginFromUrl,
92525c1bd29S猫头猫    updatePlugin,
926927dbe93S猫头猫    uninstallPlugin,
927927dbe93S猫头猫    getByMedia,
928927dbe93S猫头猫    getByHash,
929927dbe93S猫头猫    getByName,
930927dbe93S猫头猫    getValidPlugins,
931efb9da24S猫头猫    getSearchablePlugins,
932e08d37a3S猫头猫    getSortedSearchablePlugins,
93315feccc1S猫头猫    getTopListsablePlugins,
93415feccc1S猫头猫    getSortedTopListsablePlugins,
9355276aef9S猫头猫    usePlugins: pluginStateMapper.useMappedState,
936e08d37a3S猫头猫    useSortedPlugins,
93708882a77S猫头猫    uninstallAllPlugins,
9385276aef9S猫头猫};
939927dbe93S猫头猫
940927dbe93S猫头猫export default PluginManager;
941