xref: /MusicFree/src/core/pluginManager.ts (revision ab8941d9b1f653e4ea0d5299223020581c617744)
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猫头猫
75d5bfeb7eS猫头猫//#region 插件类
76927dbe93S猫头猫export class Plugin {
77927dbe93S猫头猫    /** 插件名 */
78927dbe93S猫头猫    public name: string;
79927dbe93S猫头猫    /** 插件的hash,作为唯一id */
80927dbe93S猫头猫    public hash: string;
81927dbe93S猫头猫    /** 插件状态:激活、关闭、错误 */
82927dbe93S猫头猫    public state: 'enabled' | 'disabled' | 'error';
83927dbe93S猫头猫    /** 插件支持的搜索类型 */
84927dbe93S猫头猫    public supportedSearchType?: string;
85927dbe93S猫头猫    /** 插件状态信息 */
86927dbe93S猫头猫    public stateCode?: PluginStateCode;
87927dbe93S猫头猫    /** 插件的实例 */
88927dbe93S猫头猫    public instance: IPlugin.IPluginInstance;
89927dbe93S猫头猫    /** 插件路径 */
90927dbe93S猫头猫    public path: string;
91927dbe93S猫头猫    /** 插件方法 */
92927dbe93S猫头猫    public methods: PluginMethods;
93d5bfeb7eS猫头猫    /** 用户输入 */
94d5bfeb7eS猫头猫    public userEnv?: Record<string, string>;
95927dbe93S猫头猫
9674d0cf81S猫头猫    constructor(
9774d0cf81S猫头猫        funcCode: string | (() => IPlugin.IPluginInstance),
9874d0cf81S猫头猫        pluginPath: string,
9974d0cf81S猫头猫    ) {
100927dbe93S猫头猫        this.state = 'enabled';
101927dbe93S猫头猫        let _instance: IPlugin.IPluginInstance;
1023b3d6357S猫头猫        const _module: any = {exports: {}};
103927dbe93S猫头猫        try {
10474d0cf81S猫头猫            if (typeof funcCode === 'string') {
1054060c00aS猫头猫                // eslint-disable-next-line no-new-func
106927dbe93S猫头猫                _instance = Function(`
107927dbe93S猫头猫                    'use strict';
1089c34d637S猫头猫                    return function(require, __musicfree_require, module, exports) {
1099c34d637S猫头猫                        ${funcCode}
110927dbe93S猫头猫                    }
1119c34d637S猫头猫                `)()(_require, _require, _module, _module.exports);
1123b3d6357S猫头猫                if (_module.exports.default) {
1133b3d6357S猫头猫                    _instance = _module.exports
1143b3d6357S猫头猫                        .default as IPlugin.IPluginInstance;
1153b3d6357S猫头猫                } else {
1169c34d637S猫头猫                    _instance = _module.exports as IPlugin.IPluginInstance;
1173b3d6357S猫头猫                }
11874d0cf81S猫头猫            } else {
11974d0cf81S猫头猫                _instance = funcCode();
12074d0cf81S猫头猫            }
121927dbe93S猫头猫            this.checkValid(_instance);
122927dbe93S猫头猫        } catch (e: any) {
123b43683eaS猫头猫            console.log(e);
124927dbe93S猫头猫            this.state = 'error';
125927dbe93S猫头猫            this.stateCode = PluginStateCode.CannotParse;
126927dbe93S猫头猫            if (e?.stateCode) {
127927dbe93S猫头猫                this.stateCode = e.stateCode;
128927dbe93S猫头猫            }
129927dbe93S猫头猫            errorLog(`${pluginPath}插件无法解析 `, {
130927dbe93S猫头猫                stateCode: this.stateCode,
131927dbe93S猫头猫                message: e?.message,
132927dbe93S猫头猫                stack: e?.stack,
133927dbe93S猫头猫            });
134927dbe93S猫头猫            _instance = e?.instance ?? {
135927dbe93S猫头猫                _path: '',
136927dbe93S猫头猫                platform: '',
137927dbe93S猫头猫                appVersion: '',
13820e6a092S猫头猫                async getMediaSource() {
139927dbe93S猫头猫                    return null;
140927dbe93S猫头猫                },
141927dbe93S猫头猫                async search() {
142927dbe93S猫头猫                    return {};
143927dbe93S猫头猫                },
144927dbe93S猫头猫                async getAlbumInfo() {
145927dbe93S猫头猫                    return null;
146927dbe93S猫头猫                },
147927dbe93S猫头猫            };
148927dbe93S猫头猫        }
149927dbe93S猫头猫        this.instance = _instance;
150927dbe93S猫头猫        this.path = pluginPath;
151927dbe93S猫头猫        this.name = _instance.platform;
152*ab8941d9S猫头猫        if (
153*ab8941d9S猫头猫            this.instance.platform === '' ||
154*ab8941d9S猫头猫            this.instance.platform === undefined
155*ab8941d9S猫头猫        ) {
156927dbe93S猫头猫            this.hash = '';
157927dbe93S猫头猫        } else {
15874d0cf81S猫头猫            if (typeof funcCode === 'string') {
159927dbe93S猫头猫                this.hash = sha256(funcCode).toString();
16074d0cf81S猫头猫            } else {
16174d0cf81S猫头猫                this.hash = sha256(funcCode.toString()).toString();
16274d0cf81S猫头猫            }
163927dbe93S猫头猫        }
164927dbe93S猫头猫
165927dbe93S猫头猫        // 放在最后
166927dbe93S猫头猫        this.methods = new PluginMethods(this);
167927dbe93S猫头猫    }
168927dbe93S猫头猫
169927dbe93S猫头猫    private checkValid(_instance: IPlugin.IPluginInstance) {
170927dbe93S猫头猫        /** 版本号校验 */
171927dbe93S猫头猫        if (
172927dbe93S猫头猫            _instance.appVersion &&
173927dbe93S猫头猫            !satisfies(DeviceInfo.getVersion(), _instance.appVersion)
174927dbe93S猫头猫        ) {
175927dbe93S猫头猫            throw {
176927dbe93S猫头猫                instance: _instance,
177927dbe93S猫头猫                stateCode: PluginStateCode.VersionNotMatch,
178927dbe93S猫头猫            };
179927dbe93S猫头猫        }
180927dbe93S猫头猫        return true;
181927dbe93S猫头猫    }
182927dbe93S猫头猫}
183d5bfeb7eS猫头猫//#endregion
184927dbe93S猫头猫
185d5bfeb7eS猫头猫//#region 基于插件类封装的方法,供给APP侧直接调用
186927dbe93S猫头猫/** 有缓存等信息 */
187927dbe93S猫头猫class PluginMethods implements IPlugin.IPluginInstanceMethods {
188927dbe93S猫头猫    private plugin;
189927dbe93S猫头猫    constructor(plugin: Plugin) {
190927dbe93S猫头猫        this.plugin = plugin;
191927dbe93S猫头猫    }
192927dbe93S猫头猫    /** 搜索 */
193927dbe93S猫头猫    async search<T extends ICommon.SupportMediaType>(
194927dbe93S猫头猫        query: string,
195927dbe93S猫头猫        page: number,
196927dbe93S猫头猫        type: T,
197927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
198927dbe93S猫头猫        if (!this.plugin.instance.search) {
199927dbe93S猫头猫            return {
200927dbe93S猫头猫                isEnd: true,
201927dbe93S猫头猫                data: [],
202927dbe93S猫头猫            };
203927dbe93S猫头猫        }
204927dbe93S猫头猫
2054060c00aS猫头猫        const result =
2064060c00aS猫头猫            (await this.plugin.instance.search(query, page, type)) ?? {};
207927dbe93S猫头猫        if (Array.isArray(result.data)) {
208927dbe93S猫头猫            result.data.forEach(_ => {
209927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
210927dbe93S猫头猫            });
211927dbe93S猫头猫            return {
212927dbe93S猫头猫                isEnd: result.isEnd ?? true,
213927dbe93S猫头猫                data: result.data,
214927dbe93S猫头猫            };
215927dbe93S猫头猫        }
216927dbe93S猫头猫        return {
217927dbe93S猫头猫            isEnd: true,
218927dbe93S猫头猫            data: [],
219927dbe93S猫头猫        };
220927dbe93S猫头猫    }
221927dbe93S猫头猫
222927dbe93S猫头猫    /** 获取真实源 */
22320e6a092S猫头猫    async getMediaSource(
224927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
225abaede57S猫头猫        quality: IMusic.IQualityKey = 'standard',
226927dbe93S猫头猫        retryCount = 1,
227dc160d50S猫头猫        notUpdateCache = false,
228192ae2b0S猫头猫    ): Promise<IPlugin.IMediaSourceResult | null> {
229927dbe93S猫头猫        // 1. 本地搜索 其实直接读mediameta就好了
230927dbe93S猫头猫        const localPath =
2310e4173cdS猫头猫            getInternalData<string>(musicItem, InternalDataType.LOCALPATH) ??
2320e4173cdS猫头猫            getInternalData<string>(
2330e4173cdS猫头猫                LocalMusicSheet.isLocalMusic(musicItem),
2340e4173cdS猫头猫                InternalDataType.LOCALPATH,
2350e4173cdS猫头猫            );
2360e4173cdS猫头猫        if (localPath && (await FileSystem.exists(localPath))) {
2370e4173cdS猫头猫            trace('本地播放', localPath);
238927dbe93S猫头猫            return {
239927dbe93S猫头猫                url: localPath,
240927dbe93S猫头猫            };
241927dbe93S猫头猫        }
2427993f90eS猫头猫        if (musicItem.platform === localPluginPlatform) {
243f5935920S猫头猫            throw new Error('本地音乐不存在');
244f5935920S猫头猫        }
245927dbe93S猫头猫        // 2. 缓存播放
246927dbe93S猫头猫        const mediaCache = Cache.get(musicItem);
247985f8e75S猫头猫        const pluginCacheControl =
248985f8e75S猫头猫            this.plugin.instance.cacheControl ?? 'no-cache';
249cfa0fc07S猫头猫        if (
250cfa0fc07S猫头猫            mediaCache &&
251abaede57S猫头猫            mediaCache?.qualities?.[quality]?.url &&
25248f4b873S猫头猫            (pluginCacheControl === CacheControl.Cache ||
25348f4b873S猫头猫                (pluginCacheControl === CacheControl.NoCache &&
254ef714860S猫头猫                    Network.isOffline()))
255cfa0fc07S猫头猫        ) {
2565276aef9S猫头猫            trace('播放', '缓存播放');
257abaede57S猫头猫            const qualityInfo = mediaCache.qualities[quality];
258927dbe93S猫头猫            return {
259abaede57S猫头猫                url: qualityInfo.url,
260927dbe93S猫头猫                headers: mediaCache.headers,
2614060c00aS猫头猫                userAgent:
2624060c00aS猫头猫                    mediaCache.userAgent ?? mediaCache.headers?.['user-agent'],
263927dbe93S猫头猫            };
264927dbe93S猫头猫        }
265927dbe93S猫头猫        // 3. 插件解析
26620e6a092S猫头猫        if (!this.plugin.instance.getMediaSource) {
267abaede57S猫头猫            return {url: musicItem?.qualities?.[quality]?.url ?? musicItem.url};
268927dbe93S猫头猫        }
269927dbe93S猫头猫        try {
270abaede57S猫头猫            const {url, headers} = (await this.plugin.instance.getMediaSource(
271abaede57S猫头猫                musicItem,
272abaede57S猫头猫                quality,
273abaede57S猫头猫            )) ?? {url: musicItem?.qualities?.[quality]?.url};
274927dbe93S猫头猫            if (!url) {
275a28eac61S猫头猫                throw new Error('NOT RETRY');
276927dbe93S猫头猫            }
2775276aef9S猫头猫            trace('播放', '插件播放');
278927dbe93S猫头猫            const result = {
279927dbe93S猫头猫                url,
280927dbe93S猫头猫                headers,
281927dbe93S猫头猫                userAgent: headers?.['user-agent'],
282cfa0fc07S猫头猫            } as IPlugin.IMediaSourceResult;
283927dbe93S猫头猫
284dc160d50S猫头猫            if (
285dc160d50S猫头猫                pluginCacheControl !== CacheControl.NoStore &&
286dc160d50S猫头猫                !notUpdateCache
287dc160d50S猫头猫            ) {
288abaede57S猫头猫                Cache.update(musicItem, [
289abaede57S猫头猫                    ['headers', result.headers],
290abaede57S猫头猫                    ['userAgent', result.userAgent],
291abaede57S猫头猫                    [`qualities.${quality}.url`, url],
292abaede57S猫头猫                ]);
293752ffc5aS猫头猫            }
294cfa0fc07S猫头猫
295927dbe93S猫头猫            return result;
296927dbe93S猫头猫        } catch (e: any) {
297a28eac61S猫头猫            if (retryCount > 0 && e?.message !== 'NOT RETRY') {
298927dbe93S猫头猫                await delay(150);
299abaede57S猫头猫                return this.getMediaSource(musicItem, quality, --retryCount);
300927dbe93S猫头猫            }
301927dbe93S猫头猫            errorLog('获取真实源失败', e?.message);
302ea6d708fS猫头猫            devLog('error', '获取真实源失败', e, e?.message);
303192ae2b0S猫头猫            return null;
304927dbe93S猫头猫        }
305927dbe93S猫头猫    }
306927dbe93S猫头猫
307927dbe93S猫头猫    /** 获取音乐详情 */
308927dbe93S猫头猫    async getMusicInfo(
309927dbe93S猫头猫        musicItem: ICommon.IMediaBase,
31074d0cf81S猫头猫    ): Promise<Partial<IMusic.IMusicItem> | null> {
311927dbe93S猫头猫        if (!this.plugin.instance.getMusicInfo) {
312d704daedS猫头猫            return null;
313927dbe93S猫头猫        }
31474d0cf81S猫头猫        try {
315927dbe93S猫头猫            return (
316927dbe93S猫头猫                this.plugin.instance.getMusicInfo(
3177993f90eS猫头猫                    resetMediaItem(musicItem, undefined, true),
318d704daedS猫头猫                ) ?? null
319927dbe93S猫头猫            );
320ea6d708fS猫头猫        } catch (e: any) {
321ea6d708fS猫头猫            devLog('error', '获取音乐详情失败', e, e?.message);
322d704daedS猫头猫            return null;
32374d0cf81S猫头猫        }
324927dbe93S猫头猫    }
325927dbe93S猫头猫
326927dbe93S猫头猫    /** 获取歌词 */
327927dbe93S猫头猫    async getLyric(
328927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
329927dbe93S猫头猫        from?: IMusic.IMusicItemBase,
330927dbe93S猫头猫    ): Promise<ILyric.ILyricSource | null> {
331927dbe93S猫头猫        // 1.额外存储的meta信息
332927dbe93S猫头猫        const meta = MediaMeta.get(musicItem);
333927dbe93S猫头猫        if (meta && meta.associatedLrc) {
334927dbe93S猫头猫            // 有关联歌词
335927dbe93S猫头猫            if (
336927dbe93S猫头猫                isSameMediaItem(musicItem, from) ||
337927dbe93S猫头猫                isSameMediaItem(meta.associatedLrc, musicItem)
338927dbe93S猫头猫            ) {
339927dbe93S猫头猫                // 形成环路,断开当前的环
340927dbe93S猫头猫                await MediaMeta.update(musicItem, {
341927dbe93S猫头猫                    associatedLrc: undefined,
342927dbe93S猫头猫                });
343927dbe93S猫头猫                // 无歌词
344927dbe93S猫头猫                return null;
345927dbe93S猫头猫            }
346927dbe93S猫头猫            // 获取关联歌词
3477a91f04fS猫头猫            const associatedMeta = MediaMeta.get(meta.associatedLrc) ?? {};
3484060c00aS猫头猫            const result = await this.getLyric(
3497a91f04fS猫头猫                {...meta.associatedLrc, ...associatedMeta},
3504060c00aS猫头猫                from ?? musicItem,
3514060c00aS猫头猫            );
352927dbe93S猫头猫            if (result) {
353927dbe93S猫头猫                // 如果有关联歌词,就返回关联歌词,深度优先
354927dbe93S猫头猫                return result;
355927dbe93S猫头猫            }
356927dbe93S猫头猫        }
357927dbe93S猫头猫        const cache = Cache.get(musicItem);
358927dbe93S猫头猫        let rawLrc = meta?.rawLrc || musicItem.rawLrc || cache?.rawLrc;
359927dbe93S猫头猫        let lrcUrl = meta?.lrc || musicItem.lrc || cache?.lrc;
360927dbe93S猫头猫        // 如果存在文本
361927dbe93S猫头猫        if (rawLrc) {
362927dbe93S猫头猫            return {
363927dbe93S猫头猫                rawLrc,
364927dbe93S猫头猫                lrc: lrcUrl,
365927dbe93S猫头猫            };
366927dbe93S猫头猫        }
367927dbe93S猫头猫        // 2.本地缓存
368927dbe93S猫头猫        const localLrc =
3690e4173cdS猫头猫            meta?.[internalSerializeKey]?.local?.localLrc ||
3700e4173cdS猫头猫            cache?.[internalSerializeKey]?.local?.localLrc;
371927dbe93S猫头猫        if (localLrc && (await exists(localLrc))) {
372927dbe93S猫头猫            rawLrc = await readFile(localLrc, 'utf8');
373927dbe93S猫头猫            return {
374927dbe93S猫头猫                rawLrc,
375927dbe93S猫头猫                lrc: lrcUrl,
376927dbe93S猫头猫            };
377927dbe93S猫头猫        }
378927dbe93S猫头猫        // 3.优先使用url
379927dbe93S猫头猫        if (lrcUrl) {
380927dbe93S猫头猫            try {
381927dbe93S猫头猫                // 需要超时时间 axios timeout 但是没生效
3822a3194f5S猫头猫                rawLrc = (await axios.get(lrcUrl, {timeout: 1500})).data;
383927dbe93S猫头猫                return {
384927dbe93S猫头猫                    rawLrc,
385927dbe93S猫头猫                    lrc: lrcUrl,
386927dbe93S猫头猫                };
387927dbe93S猫头猫            } catch {
388927dbe93S猫头猫                lrcUrl = undefined;
389927dbe93S猫头猫            }
390927dbe93S猫头猫        }
391927dbe93S猫头猫        // 4. 如果地址失效
392927dbe93S猫头猫        if (!lrcUrl) {
393927dbe93S猫头猫            // 插件获得url
394927dbe93S猫头猫            try {
3957a91f04fS猫头猫                let lrcSource;
3967a91f04fS猫头猫                if (from) {
3977a91f04fS猫头猫                    lrcSource = await PluginManager.getByMedia(
3987a91f04fS猫头猫                        musicItem,
3997a91f04fS猫头猫                    )?.instance?.getLyric?.(
400927dbe93S猫头猫                        resetMediaItem(musicItem, undefined, true),
401927dbe93S猫头猫                    );
4027a91f04fS猫头猫                } else {
4037a91f04fS猫头猫                    lrcSource = await this.plugin.instance?.getLyric?.(
4047a91f04fS猫头猫                        resetMediaItem(musicItem, undefined, true),
4057a91f04fS猫头猫                    );
4067a91f04fS猫头猫                }
4077a91f04fS猫头猫
408927dbe93S猫头猫                rawLrc = lrcSource?.rawLrc;
409927dbe93S猫头猫                lrcUrl = lrcSource?.lrc;
410927dbe93S猫头猫            } catch (e: any) {
411927dbe93S猫头猫                trace('插件获取歌词失败', e?.message, 'error');
412ea6d708fS猫头猫                devLog('error', '插件获取歌词失败', e, e?.message);
413927dbe93S猫头猫            }
414927dbe93S猫头猫        }
415927dbe93S猫头猫        // 5. 最后一次请求
416927dbe93S猫头猫        if (rawLrc || lrcUrl) {
417927dbe93S猫头猫            const filename = `${pathConst.lrcCachePath}${nanoid()}.lrc`;
418927dbe93S猫头猫            if (lrcUrl) {
419927dbe93S猫头猫                try {
4202a3194f5S猫头猫                    rawLrc = (await axios.get(lrcUrl, {timeout: 1500})).data;
421927dbe93S猫头猫                } catch {}
422927dbe93S猫头猫            }
423927dbe93S猫头猫            if (rawLrc) {
424927dbe93S猫头猫                await writeFile(filename, rawLrc, 'utf8');
425927dbe93S猫头猫                // 写入缓存
426927dbe93S猫头猫                Cache.update(musicItem, [
4270e4173cdS猫头猫                    [`${internalSerializeKey}.local.localLrc`, filename],
428927dbe93S猫头猫                ]);
429927dbe93S猫头猫                // 如果有meta
430927dbe93S猫头猫                if (meta) {
431927dbe93S猫头猫                    MediaMeta.update(musicItem, [
4320e4173cdS猫头猫                        [`${internalSerializeKey}.local.localLrc`, filename],
433927dbe93S猫头猫                    ]);
434927dbe93S猫头猫                }
435927dbe93S猫头猫                return {
436927dbe93S猫头猫                    rawLrc,
437927dbe93S猫头猫                    lrc: lrcUrl,
438927dbe93S猫头猫                };
439927dbe93S猫头猫            }
440927dbe93S猫头猫        }
4413a6f67b1S猫头猫        // 6. 如果是本地文件
4423a6f67b1S猫头猫        const isDownloaded = LocalMusicSheet.isLocalMusic(musicItem);
4433a6f67b1S猫头猫        if (musicItem.platform !== localPluginPlatform && isDownloaded) {
4443a6f67b1S猫头猫            const res = await localFilePlugin.instance!.getLyric!(isDownloaded);
4453a6f67b1S猫头猫            if (res) {
4463a6f67b1S猫头猫                return res;
4473a6f67b1S猫头猫            }
4483a6f67b1S猫头猫        }
449ea6d708fS猫头猫        devLog('warn', '无歌词');
450927dbe93S猫头猫
451927dbe93S猫头猫        return null;
452927dbe93S猫头猫    }
453927dbe93S猫头猫
454927dbe93S猫头猫    /** 获取歌词文本 */
455927dbe93S猫头猫    async getLyricText(
456927dbe93S猫头猫        musicItem: IMusic.IMusicItem,
457927dbe93S猫头猫    ): Promise<string | undefined> {
458927dbe93S猫头猫        return (await this.getLyric(musicItem))?.rawLrc;
459927dbe93S猫头猫    }
460927dbe93S猫头猫
461927dbe93S猫头猫    /** 获取专辑信息 */
462927dbe93S猫头猫    async getAlbumInfo(
463927dbe93S猫头猫        albumItem: IAlbum.IAlbumItemBase,
464927dbe93S猫头猫    ): Promise<IAlbum.IAlbumItem | null> {
465927dbe93S猫头猫        if (!this.plugin.instance.getAlbumInfo) {
466927dbe93S猫头猫            return {...albumItem, musicList: []};
467927dbe93S猫头猫        }
468927dbe93S猫头猫        try {
469927dbe93S猫头猫            const result = await this.plugin.instance.getAlbumInfo(
470927dbe93S猫头猫                resetMediaItem(albumItem, undefined, true),
471927dbe93S猫头猫            );
4725276aef9S猫头猫            if (!result) {
4735276aef9S猫头猫                throw new Error();
4745276aef9S猫头猫            }
475927dbe93S猫头猫            result?.musicList?.forEach(_ => {
476927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
477927dbe93S猫头猫            });
4785276aef9S猫头猫
4795276aef9S猫头猫            return {...albumItem, ...result};
4804394410dS猫头猫        } catch (e: any) {
4814394410dS猫头猫            trace('获取专辑信息失败', e?.message);
482ea6d708fS猫头猫            devLog('error', '获取专辑信息失败', e, e?.message);
483ea6d708fS猫头猫
484927dbe93S猫头猫            return {...albumItem, musicList: []};
485927dbe93S猫头猫        }
486927dbe93S猫头猫    }
487927dbe93S猫头猫
488927dbe93S猫头猫    /** 查询作者信息 */
489efb9da24S猫头猫    async getArtistWorks<T extends IArtist.ArtistMediaType>(
490927dbe93S猫头猫        artistItem: IArtist.IArtistItem,
491927dbe93S猫头猫        page: number,
492927dbe93S猫头猫        type: T,
493927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
494efb9da24S猫头猫        if (!this.plugin.instance.getArtistWorks) {
495927dbe93S猫头猫            return {
496927dbe93S猫头猫                isEnd: true,
497927dbe93S猫头猫                data: [],
498927dbe93S猫头猫            };
499927dbe93S猫头猫        }
500927dbe93S猫头猫        try {
501efb9da24S猫头猫            const result = await this.plugin.instance.getArtistWorks(
502927dbe93S猫头猫                artistItem,
503927dbe93S猫头猫                page,
504927dbe93S猫头猫                type,
505927dbe93S猫头猫            );
506927dbe93S猫头猫            if (!result.data) {
507927dbe93S猫头猫                return {
508927dbe93S猫头猫                    isEnd: true,
509927dbe93S猫头猫                    data: [],
510927dbe93S猫头猫                };
511927dbe93S猫头猫            }
512927dbe93S猫头猫            result.data?.forEach(_ => resetMediaItem(_, this.plugin.name));
513927dbe93S猫头猫            return {
514927dbe93S猫头猫                isEnd: result.isEnd ?? true,
515927dbe93S猫头猫                data: result.data,
516927dbe93S猫头猫            };
5174394410dS猫头猫        } catch (e: any) {
5184394410dS猫头猫            trace('查询作者信息失败', e?.message);
519ea6d708fS猫头猫            devLog('error', '查询作者信息失败', e, e?.message);
520ea6d708fS猫头猫
521927dbe93S猫头猫            throw e;
522927dbe93S猫头猫        }
523927dbe93S猫头猫    }
52408380090S猫头猫
52508380090S猫头猫    /** 导入歌单 */
52608380090S猫头猫    async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> {
52708380090S猫头猫        try {
52808380090S猫头猫            const result =
52908380090S猫头猫                (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? [];
53008380090S猫头猫            result.forEach(_ => resetMediaItem(_, this.plugin.name));
53108380090S猫头猫            return result;
532ea6d708fS猫头猫        } catch (e: any) {
5330e4173cdS猫头猫            console.log(e);
534ea6d708fS猫头猫            devLog('error', '导入歌单失败', e, e?.message);
535ea6d708fS猫头猫
53608380090S猫头猫            return [];
53708380090S猫头猫        }
53808380090S猫头猫    }
5394d9d3c4cS猫头猫    /** 导入单曲 */
5404d9d3c4cS猫头猫    async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> {
5414d9d3c4cS猫头猫        try {
5424d9d3c4cS猫头猫            const result = await this.plugin.instance?.importMusicItem?.(
5434d9d3c4cS猫头猫                urlLike,
5444d9d3c4cS猫头猫            );
5454d9d3c4cS猫头猫            if (!result) {
5464d9d3c4cS猫头猫                throw new Error();
5474d9d3c4cS猫头猫            }
5484d9d3c4cS猫头猫            resetMediaItem(result, this.plugin.name);
5494d9d3c4cS猫头猫            return result;
550ea6d708fS猫头猫        } catch (e: any) {
551ea6d708fS猫头猫            devLog('error', '导入单曲失败', e, e?.message);
552ea6d708fS猫头猫
5534d9d3c4cS猫头猫            return null;
5544d9d3c4cS猫头猫        }
5554d9d3c4cS猫头猫    }
556d52aa40eS猫头猫    /** 获取榜单 */
557d52aa40eS猫头猫    async getTopLists(): Promise<IMusic.IMusicTopListGroupItem[]> {
558d52aa40eS猫头猫        try {
559d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopLists?.();
560d52aa40eS猫头猫            if (!result) {
561d52aa40eS猫头猫                throw new Error();
562d52aa40eS猫头猫            }
563d52aa40eS猫头猫            return result;
564d52aa40eS猫头猫        } catch (e: any) {
565d52aa40eS猫头猫            devLog('error', '获取榜单失败', e, e?.message);
566d52aa40eS猫头猫            return [];
567d52aa40eS猫头猫        }
568d52aa40eS猫头猫    }
569d52aa40eS猫头猫    /** 获取榜单详情 */
570d52aa40eS猫头猫    async getTopListDetail(
571d52aa40eS猫头猫        topListItem: IMusic.IMusicTopListItem,
572d52aa40eS猫头猫    ): Promise<ICommon.WithMusicList<IMusic.IMusicTopListItem>> {
573d52aa40eS猫头猫        try {
574d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopListDetail?.(
575d52aa40eS猫头猫                topListItem,
576d52aa40eS猫头猫            );
577d52aa40eS猫头猫            if (!result) {
578d52aa40eS猫头猫                throw new Error();
579d52aa40eS猫头猫            }
580d384662fS猫头猫            if (result.musicList) {
581d384662fS猫头猫                result.musicList.forEach(_ =>
582d384662fS猫头猫                    resetMediaItem(_, this.plugin.name),
583d384662fS猫头猫                );
584d384662fS猫头猫            }
585d52aa40eS猫头猫            return result;
586d52aa40eS猫头猫        } catch (e: any) {
587d52aa40eS猫头猫            devLog('error', '获取榜单详情失败', e, e?.message);
588d52aa40eS猫头猫            return {
589d52aa40eS猫头猫                ...topListItem,
590d52aa40eS猫头猫                musicList: [],
591d52aa40eS猫头猫            };
592d52aa40eS猫头猫        }
593d52aa40eS猫头猫    }
594927dbe93S猫头猫}
595d5bfeb7eS猫头猫//#endregion
5961a5528a0S猫头猫
597927dbe93S猫头猫let plugins: Array<Plugin> = [];
598927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins);
59974d0cf81S猫头猫
600d5bfeb7eS猫头猫//#region 本地音乐插件
60174d0cf81S猫头猫/** 本地插件 */
60274d0cf81S猫头猫const localFilePlugin = new Plugin(function () {
6030e4173cdS猫头猫    return {
604d5bfeb7eS猫头猫        platform: localPluginPlatform,
60574d0cf81S猫头猫        _path: '',
60674d0cf81S猫头猫        async getMusicInfo(musicBase) {
60774d0cf81S猫头猫            const localPath = getInternalData<string>(
60874d0cf81S猫头猫                musicBase,
60974d0cf81S猫头猫                InternalDataType.LOCALPATH,
6100e4173cdS猫头猫            );
61174d0cf81S猫头猫            if (localPath) {
61274d0cf81S猫头猫                const coverImg = await Mp3Util.getMediaCoverImg(localPath);
61374d0cf81S猫头猫                return {
61474d0cf81S猫头猫                    artwork: coverImg,
61574d0cf81S猫头猫                };
61674d0cf81S猫头猫            }
61774d0cf81S猫头猫            return null;
61874d0cf81S猫头猫        },
6197993f90eS猫头猫        async getLyric(musicBase) {
6207993f90eS猫头猫            const localPath = getInternalData<string>(
6217993f90eS猫头猫                musicBase,
6227993f90eS猫头猫                InternalDataType.LOCALPATH,
6237993f90eS猫头猫            );
6243a6f67b1S猫头猫            let rawLrc: string | null = null;
6257993f90eS猫头猫            if (localPath) {
6263a6f67b1S猫头猫                // 读取内嵌歌词
6273a6f67b1S猫头猫                try {
6283a6f67b1S猫头猫                    rawLrc = await Mp3Util.getLyric(localPath);
6293a6f67b1S猫头猫                } catch (e) {
6303a6f67b1S猫头猫                    console.log('e', e);
6317993f90eS猫头猫                }
6323a6f67b1S猫头猫                if (!rawLrc) {
6333a6f67b1S猫头猫                    // 读取配置歌词
6343a6f67b1S猫头猫                    const lastDot = localPath.lastIndexOf('.');
6353a6f67b1S猫头猫                    const lrcPath = localPath.slice(0, lastDot) + '.lrc';
6363a6f67b1S猫头猫
6373a6f67b1S猫头猫                    try {
6383a6f67b1S猫头猫                        if (await exists(lrcPath)) {
6393a6f67b1S猫头猫                            rawLrc = await readFile(lrcPath, 'utf8');
6403a6f67b1S猫头猫                        }
6413a6f67b1S猫头猫                    } catch {}
6423a6f67b1S猫头猫                }
6433a6f67b1S猫头猫            }
6443a6f67b1S猫头猫
6453a6f67b1S猫头猫            return rawLrc
6463a6f67b1S猫头猫                ? {
6473a6f67b1S猫头猫                      rawLrc,
6483a6f67b1S猫头猫                  }
6493a6f67b1S猫头猫                : null;
6507993f90eS猫头猫        },
65174d0cf81S猫头猫    };
65274d0cf81S猫头猫}, '');
6537993f90eS猫头猫localFilePlugin.hash = localPluginHash;
654927dbe93S猫头猫
655d5bfeb7eS猫头猫//#endregion
656d5bfeb7eS猫头猫
657927dbe93S猫头猫async function setup() {
658927dbe93S猫头猫    const _plugins: Array<Plugin> = [];
659927dbe93S猫头猫    try {
660927dbe93S猫头猫        // 加载插件
661927dbe93S猫头猫        const pluginsPaths = await readDir(pathConst.pluginPath);
662927dbe93S猫头猫        for (let i = 0; i < pluginsPaths.length; ++i) {
663927dbe93S猫头猫            const _pluginUrl = pluginsPaths[i];
6641e263108S猫头猫            trace('初始化插件', _pluginUrl);
6651e263108S猫头猫            if (
6661e263108S猫头猫                _pluginUrl.isFile() &&
6671e263108S猫头猫                (_pluginUrl.name?.endsWith?.('.js') ||
6681e263108S猫头猫                    _pluginUrl.path?.endsWith?.('.js'))
6691e263108S猫头猫            ) {
670927dbe93S猫头猫                const funcCode = await readFile(_pluginUrl.path, 'utf8');
671927dbe93S猫头猫                const plugin = new Plugin(funcCode, _pluginUrl.path);
6724060c00aS猫头猫                const _pluginIndex = _plugins.findIndex(
6734060c00aS猫头猫                    p => p.hash === plugin.hash,
6744060c00aS猫头猫                );
675927dbe93S猫头猫                if (_pluginIndex !== -1) {
676927dbe93S猫头猫                    // 重复插件,直接忽略
677927dbe93S猫头猫                    return;
678927dbe93S猫头猫                }
679927dbe93S猫头猫                plugin.hash !== '' && _plugins.push(plugin);
680927dbe93S猫头猫            }
681927dbe93S猫头猫        }
682927dbe93S猫头猫
683927dbe93S猫头猫        plugins = _plugins;
684927dbe93S猫头猫        pluginStateMapper.notify();
685e08d37a3S猫头猫        /** 初始化meta信息 */
686e08d37a3S猫头猫        PluginMeta.setupMeta(plugins.map(_ => _.name));
687927dbe93S猫头猫    } catch (e: any) {
6884060c00aS猫头猫        ToastAndroid.show(
6894060c00aS猫头猫            `插件初始化失败:${e?.message ?? e}`,
6904060c00aS猫头猫            ToastAndroid.LONG,
6914060c00aS猫头猫        );
6921a5528a0S猫头猫        errorLog('插件初始化失败', e?.message);
693927dbe93S猫头猫        throw e;
694927dbe93S猫头猫    }
695927dbe93S猫头猫}
696927dbe93S猫头猫
697927dbe93S猫头猫// 安装插件
698927dbe93S猫头猫async function installPlugin(pluginPath: string) {
69922c09412S猫头猫    // if (pluginPath.endsWith('.js')) {
700927dbe93S猫头猫    const funcCode = await readFile(pluginPath, 'utf8');
701927dbe93S猫头猫    const plugin = new Plugin(funcCode, pluginPath);
702927dbe93S猫头猫    const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
703927dbe93S猫头猫    if (_pluginIndex !== -1) {
7044d9d3c4cS猫头猫        throw new Error('插件已安装');
705927dbe93S猫头猫    }
706927dbe93S猫头猫    if (plugin.hash !== '') {
707927dbe93S猫头猫        const fn = nanoid();
708927dbe93S猫头猫        const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
709927dbe93S猫头猫        await copyFile(pluginPath, _pluginPath);
710927dbe93S猫头猫        plugin.path = _pluginPath;
711927dbe93S猫头猫        plugins = plugins.concat(plugin);
712927dbe93S猫头猫        pluginStateMapper.notify();
7134d9d3c4cS猫头猫        return;
714927dbe93S猫头猫    }
7154d9d3c4cS猫头猫    throw new Error('插件无法解析');
71622c09412S猫头猫    // }
71722c09412S猫头猫    // throw new Error('插件不存在');
718927dbe93S猫头猫}
719927dbe93S猫头猫
72058992c6bS猫头猫async function installPluginFromUrl(url: string) {
72158992c6bS猫头猫    try {
72258992c6bS猫头猫        const funcCode = (await axios.get(url)).data;
72358992c6bS猫头猫        if (funcCode) {
72458992c6bS猫头猫            const plugin = new Plugin(funcCode, '');
72558992c6bS猫头猫            const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
72658992c6bS猫头猫            if (_pluginIndex !== -1) {
7278b7ddca8S猫头猫                // 静默忽略
7288b7ddca8S猫头猫                return;
72958992c6bS猫头猫            }
73025c1bd29S猫头猫            const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
73125c1bd29S猫头猫            if (oldVersionPlugin) {
73225c1bd29S猫头猫                if (
73325c1bd29S猫头猫                    compare(
73425c1bd29S猫头猫                        oldVersionPlugin.instance.version ?? '',
73525c1bd29S猫头猫                        plugin.instance.version ?? '',
73625c1bd29S猫头猫                        '>',
73725c1bd29S猫头猫                    )
73825c1bd29S猫头猫                ) {
73925c1bd29S猫头猫                    throw new Error('已安装更新版本的插件');
74025c1bd29S猫头猫                }
74125c1bd29S猫头猫            }
74225c1bd29S猫头猫
74358992c6bS猫头猫            if (plugin.hash !== '') {
74458992c6bS猫头猫                const fn = nanoid();
74558992c6bS猫头猫                const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
74658992c6bS猫头猫                await writeFile(_pluginPath, funcCode, 'utf8');
74758992c6bS猫头猫                plugin.path = _pluginPath;
74858992c6bS猫头猫                plugins = plugins.concat(plugin);
74925c1bd29S猫头猫                if (oldVersionPlugin) {
75025c1bd29S猫头猫                    plugins = plugins.filter(
75125c1bd29S猫头猫                        _ => _.hash !== oldVersionPlugin.hash,
75225c1bd29S猫头猫                    );
75325c1bd29S猫头猫                    try {
75425c1bd29S猫头猫                        await unlink(oldVersionPlugin.path);
75525c1bd29S猫头猫                    } catch {}
75625c1bd29S猫头猫                }
75758992c6bS猫头猫                pluginStateMapper.notify();
75858992c6bS猫头猫                return;
75958992c6bS猫头猫            }
76074acbfc0S猫头猫            throw new Error('插件无法解析!');
76158992c6bS猫头猫        }
76225c1bd29S猫头猫    } catch (e: any) {
763ea6d708fS猫头猫        devLog('error', 'URL安装插件失败', e, e?.message);
76458992c6bS猫头猫        errorLog('URL安装插件失败', e);
76525c1bd29S猫头猫        throw new Error(e?.message ?? '');
76658992c6bS猫头猫    }
76758992c6bS猫头猫}
76858992c6bS猫头猫
769927dbe93S猫头猫/** 卸载插件 */
770927dbe93S猫头猫async function uninstallPlugin(hash: string) {
771927dbe93S猫头猫    const targetIndex = plugins.findIndex(_ => _.hash === hash);
772927dbe93S猫头猫    if (targetIndex !== -1) {
773927dbe93S猫头猫        try {
77424e5e74aS猫头猫            const pluginName = plugins[targetIndex].name;
775927dbe93S猫头猫            await unlink(plugins[targetIndex].path);
776927dbe93S猫头猫            plugins = plugins.filter(_ => _.hash !== hash);
777927dbe93S猫头猫            pluginStateMapper.notify();
77824e5e74aS猫头猫            if (plugins.every(_ => _.name !== pluginName)) {
77924e5e74aS猫头猫                await MediaMeta.removePlugin(pluginName);
78024e5e74aS猫头猫            }
781927dbe93S猫头猫        } catch {}
782927dbe93S猫头猫    }
783927dbe93S猫头猫}
784927dbe93S猫头猫
78508882a77S猫头猫async function uninstallAllPlugins() {
78608882a77S猫头猫    await Promise.all(
78708882a77S猫头猫        plugins.map(async plugin => {
78808882a77S猫头猫            try {
78908882a77S猫头猫                const pluginName = plugin.name;
79008882a77S猫头猫                await unlink(plugin.path);
79108882a77S猫头猫                await MediaMeta.removePlugin(pluginName);
79208882a77S猫头猫            } catch (e) {}
79308882a77S猫头猫        }),
79408882a77S猫头猫    );
79508882a77S猫头猫    plugins = [];
79608882a77S猫头猫    pluginStateMapper.notify();
797e08d37a3S猫头猫
798e08d37a3S猫头猫    /** 清除空余文件,异步做就可以了 */
799e08d37a3S猫头猫    readDir(pathConst.pluginPath)
800e08d37a3S猫头猫        .then(fns => {
801e08d37a3S猫头猫            fns.forEach(fn => {
802e08d37a3S猫头猫                unlink(fn.path).catch(emptyFunction);
803e08d37a3S猫头猫            });
804e08d37a3S猫头猫        })
805e08d37a3S猫头猫        .catch(emptyFunction);
80608882a77S猫头猫}
80708882a77S猫头猫
80825c1bd29S猫头猫async function updatePlugin(plugin: Plugin) {
80925c1bd29S猫头猫    const updateUrl = plugin.instance.srcUrl;
81025c1bd29S猫头猫    if (!updateUrl) {
81125c1bd29S猫头猫        throw new Error('没有更新源');
81225c1bd29S猫头猫    }
81325c1bd29S猫头猫    try {
81425c1bd29S猫头猫        await installPluginFromUrl(updateUrl);
81525c1bd29S猫头猫    } catch (e: any) {
81625c1bd29S猫头猫        if (e.message === '插件已安装') {
81725c1bd29S猫头猫            throw new Error('当前已是最新版本');
81825c1bd29S猫头猫        } else {
81925c1bd29S猫头猫            throw e;
82025c1bd29S猫头猫        }
82125c1bd29S猫头猫    }
82225c1bd29S猫头猫}
82325c1bd29S猫头猫
824927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) {
8252c595535S猫头猫    return getByName(mediaItem?.platform);
826927dbe93S猫头猫}
827927dbe93S猫头猫
828927dbe93S猫头猫function getByHash(hash: string) {
8297993f90eS猫头猫    return hash === localPluginHash
8307993f90eS猫头猫        ? localFilePlugin
8317993f90eS猫头猫        : plugins.find(_ => _.hash === hash);
832927dbe93S猫头猫}
833927dbe93S猫头猫
834927dbe93S猫头猫function getByName(name: string) {
8357993f90eS猫头猫    return name === localPluginPlatform
8360e4173cdS猫头猫        ? localFilePlugin
8370e4173cdS猫头猫        : plugins.find(_ => _.name === name);
838927dbe93S猫头猫}
839927dbe93S猫头猫
840927dbe93S猫头猫function getValidPlugins() {
841927dbe93S猫头猫    return plugins.filter(_ => _.state === 'enabled');
842927dbe93S猫头猫}
843927dbe93S猫头猫
844efb9da24S猫头猫function getSearchablePlugins() {
845efb9da24S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.search);
846efb9da24S猫头猫}
847efb9da24S猫头猫
848e08d37a3S猫头猫function getSortedSearchablePlugins() {
849e08d37a3S猫头猫    return getSearchablePlugins().sort((a, b) =>
850e08d37a3S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
851e08d37a3S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
852e08d37a3S猫头猫        0
853e08d37a3S猫头猫            ? -1
854e08d37a3S猫头猫            : 1,
855e08d37a3S猫头猫    );
856e08d37a3S猫头猫}
857e08d37a3S猫头猫
85815feccc1S猫头猫function getTopListsablePlugins() {
85915feccc1S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists);
86015feccc1S猫头猫}
86115feccc1S猫头猫
86215feccc1S猫头猫function getSortedTopListsablePlugins() {
86315feccc1S猫头猫    return getTopListsablePlugins().sort((a, b) =>
86415feccc1S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
86515feccc1S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
86615feccc1S猫头猫        0
86715feccc1S猫头猫            ? -1
86815feccc1S猫头猫            : 1,
86915feccc1S猫头猫    );
87015feccc1S猫头猫}
87115feccc1S猫头猫
872e08d37a3S猫头猫function useSortedPlugins() {
873e08d37a3S猫头猫    const _plugins = pluginStateMapper.useMappedState();
874e08d37a3S猫头猫    const _pluginMetaAll = PluginMeta.usePluginMetaAll();
875e08d37a3S猫头猫
87634588741S猫头猫    const [sortedPlugins, setSortedPlugins] = useState(
87734588741S猫头猫        [..._plugins].sort((a, b) =>
878e08d37a3S猫头猫            (_pluginMetaAll[a.name]?.order ?? Infinity) -
879e08d37a3S猫头猫                (_pluginMetaAll[b.name]?.order ?? Infinity) <
880e08d37a3S猫头猫            0
881e08d37a3S猫头猫                ? -1
882e08d37a3S猫头猫                : 1,
88334588741S猫头猫        ),
884e08d37a3S猫头猫    );
88534588741S猫头猫
88634588741S猫头猫    useEffect(() => {
887d4cd40d8S猫头猫        InteractionManager.runAfterInteractions(() => {
88834588741S猫头猫            setSortedPlugins(
88934588741S猫头猫                [..._plugins].sort((a, b) =>
89034588741S猫头猫                    (_pluginMetaAll[a.name]?.order ?? Infinity) -
89134588741S猫头猫                        (_pluginMetaAll[b.name]?.order ?? Infinity) <
89234588741S猫头猫                    0
89334588741S猫头猫                        ? -1
89434588741S猫头猫                        : 1,
89534588741S猫头猫                ),
89634588741S猫头猫            );
897d4cd40d8S猫头猫        });
89834588741S猫头猫    }, [_plugins, _pluginMetaAll]);
89934588741S猫头猫
90034588741S猫头猫    return sortedPlugins;
901e08d37a3S猫头猫}
902e08d37a3S猫头猫
903927dbe93S猫头猫const PluginManager = {
904927dbe93S猫头猫    setup,
905927dbe93S猫头猫    installPlugin,
90658992c6bS猫头猫    installPluginFromUrl,
90725c1bd29S猫头猫    updatePlugin,
908927dbe93S猫头猫    uninstallPlugin,
909927dbe93S猫头猫    getByMedia,
910927dbe93S猫头猫    getByHash,
911927dbe93S猫头猫    getByName,
912927dbe93S猫头猫    getValidPlugins,
913efb9da24S猫头猫    getSearchablePlugins,
914e08d37a3S猫头猫    getSortedSearchablePlugins,
91515feccc1S猫头猫    getTopListsablePlugins,
91615feccc1S猫头猫    getSortedTopListsablePlugins,
9175276aef9S猫头猫    usePlugins: pluginStateMapper.useMappedState,
918e08d37a3S猫头猫    useSortedPlugins,
91908882a77S猫头猫    uninstallAllPlugins,
9205276aef9S猫头猫};
921927dbe93S猫头猫
922927dbe93S猫头猫export default PluginManager;
923