xref: /MusicFree/src/core/pluginManager.ts (revision 3b3d635780587a33a7dddaf7f5758aac0125f022)
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,
66*3b3d6357S猫头猫    '@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;
102*3b3d6357S猫头猫        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);
112*3b3d6357S猫头猫                if (_module.exports.default) {
113*3b3d6357S猫头猫                    _instance = _module.exports
114*3b3d6357S猫头猫                        .default as IPlugin.IPluginInstance;
115*3b3d6357S猫头猫                } else {
1169c34d637S猫头猫                    _instance = _module.exports as IPlugin.IPluginInstance;
117*3b3d6357S猫头猫                }
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;
152927dbe93S猫头猫        if (this.instance.platform === '') {
153927dbe93S猫头猫            this.hash = '';
154927dbe93S猫头猫        } else {
15574d0cf81S猫头猫            if (typeof funcCode === 'string') {
156927dbe93S猫头猫                this.hash = sha256(funcCode).toString();
15774d0cf81S猫头猫            } else {
15874d0cf81S猫头猫                this.hash = sha256(funcCode.toString()).toString();
15974d0cf81S猫头猫            }
160927dbe93S猫头猫        }
161927dbe93S猫头猫
162927dbe93S猫头猫        // 放在最后
163927dbe93S猫头猫        this.methods = new PluginMethods(this);
164927dbe93S猫头猫    }
165927dbe93S猫头猫
166927dbe93S猫头猫    private checkValid(_instance: IPlugin.IPluginInstance) {
167927dbe93S猫头猫        /** 版本号校验 */
168927dbe93S猫头猫        if (
169927dbe93S猫头猫            _instance.appVersion &&
170927dbe93S猫头猫            !satisfies(DeviceInfo.getVersion(), _instance.appVersion)
171927dbe93S猫头猫        ) {
172927dbe93S猫头猫            throw {
173927dbe93S猫头猫                instance: _instance,
174927dbe93S猫头猫                stateCode: PluginStateCode.VersionNotMatch,
175927dbe93S猫头猫            };
176927dbe93S猫头猫        }
177927dbe93S猫头猫        return true;
178927dbe93S猫头猫    }
179927dbe93S猫头猫}
180d5bfeb7eS猫头猫//#endregion
181927dbe93S猫头猫
182d5bfeb7eS猫头猫//#region 基于插件类封装的方法,供给APP侧直接调用
183927dbe93S猫头猫/** 有缓存等信息 */
184927dbe93S猫头猫class PluginMethods implements IPlugin.IPluginInstanceMethods {
185927dbe93S猫头猫    private plugin;
186927dbe93S猫头猫    constructor(plugin: Plugin) {
187927dbe93S猫头猫        this.plugin = plugin;
188927dbe93S猫头猫    }
189927dbe93S猫头猫    /** 搜索 */
190927dbe93S猫头猫    async search<T extends ICommon.SupportMediaType>(
191927dbe93S猫头猫        query: string,
192927dbe93S猫头猫        page: number,
193927dbe93S猫头猫        type: T,
194927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
195927dbe93S猫头猫        if (!this.plugin.instance.search) {
196927dbe93S猫头猫            return {
197927dbe93S猫头猫                isEnd: true,
198927dbe93S猫头猫                data: [],
199927dbe93S猫头猫            };
200927dbe93S猫头猫        }
201927dbe93S猫头猫
2024060c00aS猫头猫        const result =
2034060c00aS猫头猫            (await this.plugin.instance.search(query, page, type)) ?? {};
204927dbe93S猫头猫        if (Array.isArray(result.data)) {
205927dbe93S猫头猫            result.data.forEach(_ => {
206927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
207927dbe93S猫头猫            });
208927dbe93S猫头猫            return {
209927dbe93S猫头猫                isEnd: result.isEnd ?? true,
210927dbe93S猫头猫                data: result.data,
211927dbe93S猫头猫            };
212927dbe93S猫头猫        }
213927dbe93S猫头猫        return {
214927dbe93S猫头猫            isEnd: true,
215927dbe93S猫头猫            data: [],
216927dbe93S猫头猫        };
217927dbe93S猫头猫    }
218927dbe93S猫头猫
219927dbe93S猫头猫    /** 获取真实源 */
22020e6a092S猫头猫    async getMediaSource(
221927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
222abaede57S猫头猫        quality: IMusic.IQualityKey = 'standard',
223927dbe93S猫头猫        retryCount = 1,
224dc160d50S猫头猫        notUpdateCache = false,
225192ae2b0S猫头猫    ): Promise<IPlugin.IMediaSourceResult | null> {
226927dbe93S猫头猫        // 1. 本地搜索 其实直接读mediameta就好了
227927dbe93S猫头猫        const localPath =
2280e4173cdS猫头猫            getInternalData<string>(musicItem, InternalDataType.LOCALPATH) ??
2290e4173cdS猫头猫            getInternalData<string>(
2300e4173cdS猫头猫                LocalMusicSheet.isLocalMusic(musicItem),
2310e4173cdS猫头猫                InternalDataType.LOCALPATH,
2320e4173cdS猫头猫            );
2330e4173cdS猫头猫        if (localPath && (await FileSystem.exists(localPath))) {
2340e4173cdS猫头猫            trace('本地播放', localPath);
235927dbe93S猫头猫            return {
236927dbe93S猫头猫                url: localPath,
237927dbe93S猫头猫            };
238927dbe93S猫头猫        }
2397993f90eS猫头猫        if (musicItem.platform === localPluginPlatform) {
240f5935920S猫头猫            throw new Error('本地音乐不存在');
241f5935920S猫头猫        }
242927dbe93S猫头猫        // 2. 缓存播放
243927dbe93S猫头猫        const mediaCache = Cache.get(musicItem);
244985f8e75S猫头猫        const pluginCacheControl =
245985f8e75S猫头猫            this.plugin.instance.cacheControl ?? 'no-cache';
246cfa0fc07S猫头猫        if (
247cfa0fc07S猫头猫            mediaCache &&
248abaede57S猫头猫            mediaCache?.qualities?.[quality]?.url &&
24948f4b873S猫头猫            (pluginCacheControl === CacheControl.Cache ||
25048f4b873S猫头猫                (pluginCacheControl === CacheControl.NoCache &&
251ef714860S猫头猫                    Network.isOffline()))
252cfa0fc07S猫头猫        ) {
2535276aef9S猫头猫            trace('播放', '缓存播放');
254abaede57S猫头猫            const qualityInfo = mediaCache.qualities[quality];
255927dbe93S猫头猫            return {
256abaede57S猫头猫                url: qualityInfo.url,
257927dbe93S猫头猫                headers: mediaCache.headers,
2584060c00aS猫头猫                userAgent:
2594060c00aS猫头猫                    mediaCache.userAgent ?? mediaCache.headers?.['user-agent'],
260927dbe93S猫头猫            };
261927dbe93S猫头猫        }
262927dbe93S猫头猫        // 3. 插件解析
26320e6a092S猫头猫        if (!this.plugin.instance.getMediaSource) {
264abaede57S猫头猫            return {url: musicItem?.qualities?.[quality]?.url ?? musicItem.url};
265927dbe93S猫头猫        }
266927dbe93S猫头猫        try {
267abaede57S猫头猫            const {url, headers} = (await this.plugin.instance.getMediaSource(
268abaede57S猫头猫                musicItem,
269abaede57S猫头猫                quality,
270abaede57S猫头猫            )) ?? {url: musicItem?.qualities?.[quality]?.url};
271927dbe93S猫头猫            if (!url) {
272a28eac61S猫头猫                throw new Error('NOT RETRY');
273927dbe93S猫头猫            }
2745276aef9S猫头猫            trace('播放', '插件播放');
275927dbe93S猫头猫            const result = {
276927dbe93S猫头猫                url,
277927dbe93S猫头猫                headers,
278927dbe93S猫头猫                userAgent: headers?.['user-agent'],
279cfa0fc07S猫头猫            } as IPlugin.IMediaSourceResult;
280927dbe93S猫头猫
281dc160d50S猫头猫            if (
282dc160d50S猫头猫                pluginCacheControl !== CacheControl.NoStore &&
283dc160d50S猫头猫                !notUpdateCache
284dc160d50S猫头猫            ) {
285abaede57S猫头猫                Cache.update(musicItem, [
286abaede57S猫头猫                    ['headers', result.headers],
287abaede57S猫头猫                    ['userAgent', result.userAgent],
288abaede57S猫头猫                    [`qualities.${quality}.url`, url],
289abaede57S猫头猫                ]);
290752ffc5aS猫头猫            }
291cfa0fc07S猫头猫
292927dbe93S猫头猫            return result;
293927dbe93S猫头猫        } catch (e: any) {
294a28eac61S猫头猫            if (retryCount > 0 && e?.message !== 'NOT RETRY') {
295927dbe93S猫头猫                await delay(150);
296abaede57S猫头猫                return this.getMediaSource(musicItem, quality, --retryCount);
297927dbe93S猫头猫            }
298927dbe93S猫头猫            errorLog('获取真实源失败', e?.message);
299ea6d708fS猫头猫            devLog('error', '获取真实源失败', e, e?.message);
300192ae2b0S猫头猫            return null;
301927dbe93S猫头猫        }
302927dbe93S猫头猫    }
303927dbe93S猫头猫
304927dbe93S猫头猫    /** 获取音乐详情 */
305927dbe93S猫头猫    async getMusicInfo(
306927dbe93S猫头猫        musicItem: ICommon.IMediaBase,
30774d0cf81S猫头猫    ): Promise<Partial<IMusic.IMusicItem> | null> {
308927dbe93S猫头猫        if (!this.plugin.instance.getMusicInfo) {
309d704daedS猫头猫            return null;
310927dbe93S猫头猫        }
31174d0cf81S猫头猫        try {
312927dbe93S猫头猫            return (
313927dbe93S猫头猫                this.plugin.instance.getMusicInfo(
3147993f90eS猫头猫                    resetMediaItem(musicItem, undefined, true),
315d704daedS猫头猫                ) ?? null
316927dbe93S猫头猫            );
317ea6d708fS猫头猫        } catch (e: any) {
318ea6d708fS猫头猫            devLog('error', '获取音乐详情失败', e, e?.message);
319d704daedS猫头猫            return null;
32074d0cf81S猫头猫        }
321927dbe93S猫头猫    }
322927dbe93S猫头猫
323927dbe93S猫头猫    /** 获取歌词 */
324927dbe93S猫头猫    async getLyric(
325927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
326927dbe93S猫头猫        from?: IMusic.IMusicItemBase,
327927dbe93S猫头猫    ): Promise<ILyric.ILyricSource | null> {
328927dbe93S猫头猫        // 1.额外存储的meta信息
329927dbe93S猫头猫        const meta = MediaMeta.get(musicItem);
330927dbe93S猫头猫        if (meta && meta.associatedLrc) {
331927dbe93S猫头猫            // 有关联歌词
332927dbe93S猫头猫            if (
333927dbe93S猫头猫                isSameMediaItem(musicItem, from) ||
334927dbe93S猫头猫                isSameMediaItem(meta.associatedLrc, musicItem)
335927dbe93S猫头猫            ) {
336927dbe93S猫头猫                // 形成环路,断开当前的环
337927dbe93S猫头猫                await MediaMeta.update(musicItem, {
338927dbe93S猫头猫                    associatedLrc: undefined,
339927dbe93S猫头猫                });
340927dbe93S猫头猫                // 无歌词
341927dbe93S猫头猫                return null;
342927dbe93S猫头猫            }
343927dbe93S猫头猫            // 获取关联歌词
3447a91f04fS猫头猫            const associatedMeta = MediaMeta.get(meta.associatedLrc) ?? {};
3454060c00aS猫头猫            const result = await this.getLyric(
3467a91f04fS猫头猫                {...meta.associatedLrc, ...associatedMeta},
3474060c00aS猫头猫                from ?? musicItem,
3484060c00aS猫头猫            );
349927dbe93S猫头猫            if (result) {
350927dbe93S猫头猫                // 如果有关联歌词,就返回关联歌词,深度优先
351927dbe93S猫头猫                return result;
352927dbe93S猫头猫            }
353927dbe93S猫头猫        }
354927dbe93S猫头猫        const cache = Cache.get(musicItem);
355927dbe93S猫头猫        let rawLrc = meta?.rawLrc || musicItem.rawLrc || cache?.rawLrc;
356927dbe93S猫头猫        let lrcUrl = meta?.lrc || musicItem.lrc || cache?.lrc;
357927dbe93S猫头猫        // 如果存在文本
358927dbe93S猫头猫        if (rawLrc) {
359927dbe93S猫头猫            return {
360927dbe93S猫头猫                rawLrc,
361927dbe93S猫头猫                lrc: lrcUrl,
362927dbe93S猫头猫            };
363927dbe93S猫头猫        }
364927dbe93S猫头猫        // 2.本地缓存
365927dbe93S猫头猫        const localLrc =
3660e4173cdS猫头猫            meta?.[internalSerializeKey]?.local?.localLrc ||
3670e4173cdS猫头猫            cache?.[internalSerializeKey]?.local?.localLrc;
368927dbe93S猫头猫        if (localLrc && (await exists(localLrc))) {
369927dbe93S猫头猫            rawLrc = await readFile(localLrc, 'utf8');
370927dbe93S猫头猫            return {
371927dbe93S猫头猫                rawLrc,
372927dbe93S猫头猫                lrc: lrcUrl,
373927dbe93S猫头猫            };
374927dbe93S猫头猫        }
375927dbe93S猫头猫        // 3.优先使用url
376927dbe93S猫头猫        if (lrcUrl) {
377927dbe93S猫头猫            try {
378927dbe93S猫头猫                // 需要超时时间 axios timeout 但是没生效
3792a3194f5S猫头猫                rawLrc = (await axios.get(lrcUrl, {timeout: 1500})).data;
380927dbe93S猫头猫                return {
381927dbe93S猫头猫                    rawLrc,
382927dbe93S猫头猫                    lrc: lrcUrl,
383927dbe93S猫头猫                };
384927dbe93S猫头猫            } catch {
385927dbe93S猫头猫                lrcUrl = undefined;
386927dbe93S猫头猫            }
387927dbe93S猫头猫        }
388927dbe93S猫头猫        // 4. 如果地址失效
389927dbe93S猫头猫        if (!lrcUrl) {
390927dbe93S猫头猫            // 插件获得url
391927dbe93S猫头猫            try {
3927a91f04fS猫头猫                let lrcSource;
3937a91f04fS猫头猫                if (from) {
3947a91f04fS猫头猫                    lrcSource = await PluginManager.getByMedia(
3957a91f04fS猫头猫                        musicItem,
3967a91f04fS猫头猫                    )?.instance?.getLyric?.(
397927dbe93S猫头猫                        resetMediaItem(musicItem, undefined, true),
398927dbe93S猫头猫                    );
3997a91f04fS猫头猫                } else {
4007a91f04fS猫头猫                    lrcSource = await this.plugin.instance?.getLyric?.(
4017a91f04fS猫头猫                        resetMediaItem(musicItem, undefined, true),
4027a91f04fS猫头猫                    );
4037a91f04fS猫头猫                }
4047a91f04fS猫头猫
405927dbe93S猫头猫                rawLrc = lrcSource?.rawLrc;
406927dbe93S猫头猫                lrcUrl = lrcSource?.lrc;
407927dbe93S猫头猫            } catch (e: any) {
408927dbe93S猫头猫                trace('插件获取歌词失败', e?.message, 'error');
409ea6d708fS猫头猫                devLog('error', '插件获取歌词失败', e, e?.message);
410927dbe93S猫头猫            }
411927dbe93S猫头猫        }
412927dbe93S猫头猫        // 5. 最后一次请求
413927dbe93S猫头猫        if (rawLrc || lrcUrl) {
414927dbe93S猫头猫            const filename = `${pathConst.lrcCachePath}${nanoid()}.lrc`;
415927dbe93S猫头猫            if (lrcUrl) {
416927dbe93S猫头猫                try {
4172a3194f5S猫头猫                    rawLrc = (await axios.get(lrcUrl, {timeout: 1500})).data;
418927dbe93S猫头猫                } catch {}
419927dbe93S猫头猫            }
420927dbe93S猫头猫            if (rawLrc) {
421927dbe93S猫头猫                await writeFile(filename, rawLrc, 'utf8');
422927dbe93S猫头猫                // 写入缓存
423927dbe93S猫头猫                Cache.update(musicItem, [
4240e4173cdS猫头猫                    [`${internalSerializeKey}.local.localLrc`, filename],
425927dbe93S猫头猫                ]);
426927dbe93S猫头猫                // 如果有meta
427927dbe93S猫头猫                if (meta) {
428927dbe93S猫头猫                    MediaMeta.update(musicItem, [
4290e4173cdS猫头猫                        [`${internalSerializeKey}.local.localLrc`, filename],
430927dbe93S猫头猫                    ]);
431927dbe93S猫头猫                }
432927dbe93S猫头猫                return {
433927dbe93S猫头猫                    rawLrc,
434927dbe93S猫头猫                    lrc: lrcUrl,
435927dbe93S猫头猫                };
436927dbe93S猫头猫            }
437927dbe93S猫头猫        }
4383a6f67b1S猫头猫        // 6. 如果是本地文件
4393a6f67b1S猫头猫        const isDownloaded = LocalMusicSheet.isLocalMusic(musicItem);
4403a6f67b1S猫头猫        if (musicItem.platform !== localPluginPlatform && isDownloaded) {
4413a6f67b1S猫头猫            const res = await localFilePlugin.instance!.getLyric!(isDownloaded);
4423a6f67b1S猫头猫            if (res) {
4433a6f67b1S猫头猫                return res;
4443a6f67b1S猫头猫            }
4453a6f67b1S猫头猫        }
446ea6d708fS猫头猫        devLog('warn', '无歌词');
447927dbe93S猫头猫
448927dbe93S猫头猫        return null;
449927dbe93S猫头猫    }
450927dbe93S猫头猫
451927dbe93S猫头猫    /** 获取歌词文本 */
452927dbe93S猫头猫    async getLyricText(
453927dbe93S猫头猫        musicItem: IMusic.IMusicItem,
454927dbe93S猫头猫    ): Promise<string | undefined> {
455927dbe93S猫头猫        return (await this.getLyric(musicItem))?.rawLrc;
456927dbe93S猫头猫    }
457927dbe93S猫头猫
458927dbe93S猫头猫    /** 获取专辑信息 */
459927dbe93S猫头猫    async getAlbumInfo(
460927dbe93S猫头猫        albumItem: IAlbum.IAlbumItemBase,
461927dbe93S猫头猫    ): Promise<IAlbum.IAlbumItem | null> {
462927dbe93S猫头猫        if (!this.plugin.instance.getAlbumInfo) {
463927dbe93S猫头猫            return {...albumItem, musicList: []};
464927dbe93S猫头猫        }
465927dbe93S猫头猫        try {
466927dbe93S猫头猫            const result = await this.plugin.instance.getAlbumInfo(
467927dbe93S猫头猫                resetMediaItem(albumItem, undefined, true),
468927dbe93S猫头猫            );
4695276aef9S猫头猫            if (!result) {
4705276aef9S猫头猫                throw new Error();
4715276aef9S猫头猫            }
472927dbe93S猫头猫            result?.musicList?.forEach(_ => {
473927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
474927dbe93S猫头猫            });
4755276aef9S猫头猫
4765276aef9S猫头猫            return {...albumItem, ...result};
4774394410dS猫头猫        } catch (e: any) {
4784394410dS猫头猫            trace('获取专辑信息失败', e?.message);
479ea6d708fS猫头猫            devLog('error', '获取专辑信息失败', e, e?.message);
480ea6d708fS猫头猫
481927dbe93S猫头猫            return {...albumItem, musicList: []};
482927dbe93S猫头猫        }
483927dbe93S猫头猫    }
484927dbe93S猫头猫
485927dbe93S猫头猫    /** 查询作者信息 */
486efb9da24S猫头猫    async getArtistWorks<T extends IArtist.ArtistMediaType>(
487927dbe93S猫头猫        artistItem: IArtist.IArtistItem,
488927dbe93S猫头猫        page: number,
489927dbe93S猫头猫        type: T,
490927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
491efb9da24S猫头猫        if (!this.plugin.instance.getArtistWorks) {
492927dbe93S猫头猫            return {
493927dbe93S猫头猫                isEnd: true,
494927dbe93S猫头猫                data: [],
495927dbe93S猫头猫            };
496927dbe93S猫头猫        }
497927dbe93S猫头猫        try {
498efb9da24S猫头猫            const result = await this.plugin.instance.getArtistWorks(
499927dbe93S猫头猫                artistItem,
500927dbe93S猫头猫                page,
501927dbe93S猫头猫                type,
502927dbe93S猫头猫            );
503927dbe93S猫头猫            if (!result.data) {
504927dbe93S猫头猫                return {
505927dbe93S猫头猫                    isEnd: true,
506927dbe93S猫头猫                    data: [],
507927dbe93S猫头猫                };
508927dbe93S猫头猫            }
509927dbe93S猫头猫            result.data?.forEach(_ => resetMediaItem(_, this.plugin.name));
510927dbe93S猫头猫            return {
511927dbe93S猫头猫                isEnd: result.isEnd ?? true,
512927dbe93S猫头猫                data: result.data,
513927dbe93S猫头猫            };
5144394410dS猫头猫        } catch (e: any) {
5154394410dS猫头猫            trace('查询作者信息失败', e?.message);
516ea6d708fS猫头猫            devLog('error', '查询作者信息失败', e, e?.message);
517ea6d708fS猫头猫
518927dbe93S猫头猫            throw e;
519927dbe93S猫头猫        }
520927dbe93S猫头猫    }
52108380090S猫头猫
52208380090S猫头猫    /** 导入歌单 */
52308380090S猫头猫    async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> {
52408380090S猫头猫        try {
52508380090S猫头猫            const result =
52608380090S猫头猫                (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? [];
52708380090S猫头猫            result.forEach(_ => resetMediaItem(_, this.plugin.name));
52808380090S猫头猫            return result;
529ea6d708fS猫头猫        } catch (e: any) {
5300e4173cdS猫头猫            console.log(e);
531ea6d708fS猫头猫            devLog('error', '导入歌单失败', e, e?.message);
532ea6d708fS猫头猫
53308380090S猫头猫            return [];
53408380090S猫头猫        }
53508380090S猫头猫    }
5364d9d3c4cS猫头猫    /** 导入单曲 */
5374d9d3c4cS猫头猫    async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> {
5384d9d3c4cS猫头猫        try {
5394d9d3c4cS猫头猫            const result = await this.plugin.instance?.importMusicItem?.(
5404d9d3c4cS猫头猫                urlLike,
5414d9d3c4cS猫头猫            );
5424d9d3c4cS猫头猫            if (!result) {
5434d9d3c4cS猫头猫                throw new Error();
5444d9d3c4cS猫头猫            }
5454d9d3c4cS猫头猫            resetMediaItem(result, this.plugin.name);
5464d9d3c4cS猫头猫            return result;
547ea6d708fS猫头猫        } catch (e: any) {
548ea6d708fS猫头猫            devLog('error', '导入单曲失败', e, e?.message);
549ea6d708fS猫头猫
5504d9d3c4cS猫头猫            return null;
5514d9d3c4cS猫头猫        }
5524d9d3c4cS猫头猫    }
553d52aa40eS猫头猫    /** 获取榜单 */
554d52aa40eS猫头猫    async getTopLists(): Promise<IMusic.IMusicTopListGroupItem[]> {
555d52aa40eS猫头猫        try {
556d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopLists?.();
557d52aa40eS猫头猫            if (!result) {
558d52aa40eS猫头猫                throw new Error();
559d52aa40eS猫头猫            }
560d52aa40eS猫头猫            return result;
561d52aa40eS猫头猫        } catch (e: any) {
562d52aa40eS猫头猫            devLog('error', '获取榜单失败', e, e?.message);
563d52aa40eS猫头猫            return [];
564d52aa40eS猫头猫        }
565d52aa40eS猫头猫    }
566d52aa40eS猫头猫    /** 获取榜单详情 */
567d52aa40eS猫头猫    async getTopListDetail(
568d52aa40eS猫头猫        topListItem: IMusic.IMusicTopListItem,
569d52aa40eS猫头猫    ): Promise<ICommon.WithMusicList<IMusic.IMusicTopListItem>> {
570d52aa40eS猫头猫        try {
571d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopListDetail?.(
572d52aa40eS猫头猫                topListItem,
573d52aa40eS猫头猫            );
574d52aa40eS猫头猫            if (!result) {
575d52aa40eS猫头猫                throw new Error();
576d52aa40eS猫头猫            }
577d384662fS猫头猫            if (result.musicList) {
578d384662fS猫头猫                result.musicList.forEach(_ =>
579d384662fS猫头猫                    resetMediaItem(_, this.plugin.name),
580d384662fS猫头猫                );
581d384662fS猫头猫            }
582d52aa40eS猫头猫            return result;
583d52aa40eS猫头猫        } catch (e: any) {
584d52aa40eS猫头猫            devLog('error', '获取榜单详情失败', e, e?.message);
585d52aa40eS猫头猫            return {
586d52aa40eS猫头猫                ...topListItem,
587d52aa40eS猫头猫                musicList: [],
588d52aa40eS猫头猫            };
589d52aa40eS猫头猫        }
590d52aa40eS猫头猫    }
591927dbe93S猫头猫}
592d5bfeb7eS猫头猫//#endregion
5931a5528a0S猫头猫
594927dbe93S猫头猫let plugins: Array<Plugin> = [];
595927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins);
59674d0cf81S猫头猫
597d5bfeb7eS猫头猫//#region 本地音乐插件
59874d0cf81S猫头猫/** 本地插件 */
59974d0cf81S猫头猫const localFilePlugin = new Plugin(function () {
6000e4173cdS猫头猫    return {
601d5bfeb7eS猫头猫        platform: localPluginPlatform,
60274d0cf81S猫头猫        _path: '',
60374d0cf81S猫头猫        async getMusicInfo(musicBase) {
60474d0cf81S猫头猫            const localPath = getInternalData<string>(
60574d0cf81S猫头猫                musicBase,
60674d0cf81S猫头猫                InternalDataType.LOCALPATH,
6070e4173cdS猫头猫            );
60874d0cf81S猫头猫            if (localPath) {
60974d0cf81S猫头猫                const coverImg = await Mp3Util.getMediaCoverImg(localPath);
61074d0cf81S猫头猫                return {
61174d0cf81S猫头猫                    artwork: coverImg,
61274d0cf81S猫头猫                };
61374d0cf81S猫头猫            }
61474d0cf81S猫头猫            return null;
61574d0cf81S猫头猫        },
6167993f90eS猫头猫        async getLyric(musicBase) {
6177993f90eS猫头猫            const localPath = getInternalData<string>(
6187993f90eS猫头猫                musicBase,
6197993f90eS猫头猫                InternalDataType.LOCALPATH,
6207993f90eS猫头猫            );
6213a6f67b1S猫头猫            let rawLrc: string | null = null;
6227993f90eS猫头猫            if (localPath) {
6233a6f67b1S猫头猫                // 读取内嵌歌词
6243a6f67b1S猫头猫                try {
6253a6f67b1S猫头猫                    rawLrc = await Mp3Util.getLyric(localPath);
6263a6f67b1S猫头猫                } catch (e) {
6273a6f67b1S猫头猫                    console.log('e', e);
6287993f90eS猫头猫                }
6293a6f67b1S猫头猫                if (!rawLrc) {
6303a6f67b1S猫头猫                    // 读取配置歌词
6313a6f67b1S猫头猫                    const lastDot = localPath.lastIndexOf('.');
6323a6f67b1S猫头猫                    const lrcPath = localPath.slice(0, lastDot) + '.lrc';
6333a6f67b1S猫头猫
6343a6f67b1S猫头猫                    try {
6353a6f67b1S猫头猫                        if (await exists(lrcPath)) {
6363a6f67b1S猫头猫                            rawLrc = await readFile(lrcPath, 'utf8');
6373a6f67b1S猫头猫                        }
6383a6f67b1S猫头猫                    } catch {}
6393a6f67b1S猫头猫                }
6403a6f67b1S猫头猫            }
6413a6f67b1S猫头猫
6423a6f67b1S猫头猫            return rawLrc
6433a6f67b1S猫头猫                ? {
6443a6f67b1S猫头猫                      rawLrc,
6453a6f67b1S猫头猫                  }
6463a6f67b1S猫头猫                : null;
6477993f90eS猫头猫        },
64874d0cf81S猫头猫    };
64974d0cf81S猫头猫}, '');
6507993f90eS猫头猫localFilePlugin.hash = localPluginHash;
651927dbe93S猫头猫
652d5bfeb7eS猫头猫//#endregion
653d5bfeb7eS猫头猫
654927dbe93S猫头猫async function setup() {
655927dbe93S猫头猫    const _plugins: Array<Plugin> = [];
656927dbe93S猫头猫    try {
657927dbe93S猫头猫        // 加载插件
658927dbe93S猫头猫        const pluginsPaths = await readDir(pathConst.pluginPath);
659927dbe93S猫头猫        for (let i = 0; i < pluginsPaths.length; ++i) {
660927dbe93S猫头猫            const _pluginUrl = pluginsPaths[i];
6611e263108S猫头猫            trace('初始化插件', _pluginUrl);
6621e263108S猫头猫            if (
6631e263108S猫头猫                _pluginUrl.isFile() &&
6641e263108S猫头猫                (_pluginUrl.name?.endsWith?.('.js') ||
6651e263108S猫头猫                    _pluginUrl.path?.endsWith?.('.js'))
6661e263108S猫头猫            ) {
667927dbe93S猫头猫                const funcCode = await readFile(_pluginUrl.path, 'utf8');
668927dbe93S猫头猫                const plugin = new Plugin(funcCode, _pluginUrl.path);
6694060c00aS猫头猫                const _pluginIndex = _plugins.findIndex(
6704060c00aS猫头猫                    p => p.hash === plugin.hash,
6714060c00aS猫头猫                );
672927dbe93S猫头猫                if (_pluginIndex !== -1) {
673927dbe93S猫头猫                    // 重复插件,直接忽略
674927dbe93S猫头猫                    return;
675927dbe93S猫头猫                }
676927dbe93S猫头猫                plugin.hash !== '' && _plugins.push(plugin);
677927dbe93S猫头猫            }
678927dbe93S猫头猫        }
679927dbe93S猫头猫
680927dbe93S猫头猫        plugins = _plugins;
681927dbe93S猫头猫        pluginStateMapper.notify();
682e08d37a3S猫头猫        /** 初始化meta信息 */
683e08d37a3S猫头猫        PluginMeta.setupMeta(plugins.map(_ => _.name));
684927dbe93S猫头猫    } catch (e: any) {
6854060c00aS猫头猫        ToastAndroid.show(
6864060c00aS猫头猫            `插件初始化失败:${e?.message ?? e}`,
6874060c00aS猫头猫            ToastAndroid.LONG,
6884060c00aS猫头猫        );
6891a5528a0S猫头猫        errorLog('插件初始化失败', e?.message);
690927dbe93S猫头猫        throw e;
691927dbe93S猫头猫    }
692927dbe93S猫头猫}
693927dbe93S猫头猫
694927dbe93S猫头猫// 安装插件
695927dbe93S猫头猫async function installPlugin(pluginPath: string) {
69622c09412S猫头猫    // if (pluginPath.endsWith('.js')) {
697927dbe93S猫头猫    const funcCode = await readFile(pluginPath, 'utf8');
698927dbe93S猫头猫    const plugin = new Plugin(funcCode, pluginPath);
699927dbe93S猫头猫    const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
700927dbe93S猫头猫    if (_pluginIndex !== -1) {
7014d9d3c4cS猫头猫        throw new Error('插件已安装');
702927dbe93S猫头猫    }
703927dbe93S猫头猫    if (plugin.hash !== '') {
704927dbe93S猫头猫        const fn = nanoid();
705927dbe93S猫头猫        const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
706927dbe93S猫头猫        await copyFile(pluginPath, _pluginPath);
707927dbe93S猫头猫        plugin.path = _pluginPath;
708927dbe93S猫头猫        plugins = plugins.concat(plugin);
709927dbe93S猫头猫        pluginStateMapper.notify();
7104d9d3c4cS猫头猫        return;
711927dbe93S猫头猫    }
7124d9d3c4cS猫头猫    throw new Error('插件无法解析');
71322c09412S猫头猫    // }
71422c09412S猫头猫    // throw new Error('插件不存在');
715927dbe93S猫头猫}
716927dbe93S猫头猫
71758992c6bS猫头猫async function installPluginFromUrl(url: string) {
71858992c6bS猫头猫    try {
71958992c6bS猫头猫        const funcCode = (await axios.get(url)).data;
72058992c6bS猫头猫        if (funcCode) {
72158992c6bS猫头猫            const plugin = new Plugin(funcCode, '');
72258992c6bS猫头猫            const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
72358992c6bS猫头猫            if (_pluginIndex !== -1) {
7248b7ddca8S猫头猫                // 静默忽略
7258b7ddca8S猫头猫                return;
72658992c6bS猫头猫            }
72725c1bd29S猫头猫            const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
72825c1bd29S猫头猫            if (oldVersionPlugin) {
72925c1bd29S猫头猫                if (
73025c1bd29S猫头猫                    compare(
73125c1bd29S猫头猫                        oldVersionPlugin.instance.version ?? '',
73225c1bd29S猫头猫                        plugin.instance.version ?? '',
73325c1bd29S猫头猫                        '>',
73425c1bd29S猫头猫                    )
73525c1bd29S猫头猫                ) {
73625c1bd29S猫头猫                    throw new Error('已安装更新版本的插件');
73725c1bd29S猫头猫                }
73825c1bd29S猫头猫            }
73925c1bd29S猫头猫
74058992c6bS猫头猫            if (plugin.hash !== '') {
74158992c6bS猫头猫                const fn = nanoid();
74258992c6bS猫头猫                const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
74358992c6bS猫头猫                await writeFile(_pluginPath, funcCode, 'utf8');
74458992c6bS猫头猫                plugin.path = _pluginPath;
74558992c6bS猫头猫                plugins = plugins.concat(plugin);
74625c1bd29S猫头猫                if (oldVersionPlugin) {
74725c1bd29S猫头猫                    plugins = plugins.filter(
74825c1bd29S猫头猫                        _ => _.hash !== oldVersionPlugin.hash,
74925c1bd29S猫头猫                    );
75025c1bd29S猫头猫                    try {
75125c1bd29S猫头猫                        await unlink(oldVersionPlugin.path);
75225c1bd29S猫头猫                    } catch {}
75325c1bd29S猫头猫                }
75458992c6bS猫头猫                pluginStateMapper.notify();
75558992c6bS猫头猫                return;
75658992c6bS猫头猫            }
75774acbfc0S猫头猫            throw new Error('插件无法解析!');
75858992c6bS猫头猫        }
75925c1bd29S猫头猫    } catch (e: any) {
760ea6d708fS猫头猫        devLog('error', 'URL安装插件失败', e, e?.message);
76158992c6bS猫头猫        errorLog('URL安装插件失败', e);
76225c1bd29S猫头猫        throw new Error(e?.message ?? '');
76358992c6bS猫头猫    }
76458992c6bS猫头猫}
76558992c6bS猫头猫
766927dbe93S猫头猫/** 卸载插件 */
767927dbe93S猫头猫async function uninstallPlugin(hash: string) {
768927dbe93S猫头猫    const targetIndex = plugins.findIndex(_ => _.hash === hash);
769927dbe93S猫头猫    if (targetIndex !== -1) {
770927dbe93S猫头猫        try {
77124e5e74aS猫头猫            const pluginName = plugins[targetIndex].name;
772927dbe93S猫头猫            await unlink(plugins[targetIndex].path);
773927dbe93S猫头猫            plugins = plugins.filter(_ => _.hash !== hash);
774927dbe93S猫头猫            pluginStateMapper.notify();
77524e5e74aS猫头猫            if (plugins.every(_ => _.name !== pluginName)) {
77624e5e74aS猫头猫                await MediaMeta.removePlugin(pluginName);
77724e5e74aS猫头猫            }
778927dbe93S猫头猫        } catch {}
779927dbe93S猫头猫    }
780927dbe93S猫头猫}
781927dbe93S猫头猫
78208882a77S猫头猫async function uninstallAllPlugins() {
78308882a77S猫头猫    await Promise.all(
78408882a77S猫头猫        plugins.map(async plugin => {
78508882a77S猫头猫            try {
78608882a77S猫头猫                const pluginName = plugin.name;
78708882a77S猫头猫                await unlink(plugin.path);
78808882a77S猫头猫                await MediaMeta.removePlugin(pluginName);
78908882a77S猫头猫            } catch (e) {}
79008882a77S猫头猫        }),
79108882a77S猫头猫    );
79208882a77S猫头猫    plugins = [];
79308882a77S猫头猫    pluginStateMapper.notify();
794e08d37a3S猫头猫
795e08d37a3S猫头猫    /** 清除空余文件,异步做就可以了 */
796e08d37a3S猫头猫    readDir(pathConst.pluginPath)
797e08d37a3S猫头猫        .then(fns => {
798e08d37a3S猫头猫            fns.forEach(fn => {
799e08d37a3S猫头猫                unlink(fn.path).catch(emptyFunction);
800e08d37a3S猫头猫            });
801e08d37a3S猫头猫        })
802e08d37a3S猫头猫        .catch(emptyFunction);
80308882a77S猫头猫}
80408882a77S猫头猫
80525c1bd29S猫头猫async function updatePlugin(plugin: Plugin) {
80625c1bd29S猫头猫    const updateUrl = plugin.instance.srcUrl;
80725c1bd29S猫头猫    if (!updateUrl) {
80825c1bd29S猫头猫        throw new Error('没有更新源');
80925c1bd29S猫头猫    }
81025c1bd29S猫头猫    try {
81125c1bd29S猫头猫        await installPluginFromUrl(updateUrl);
81225c1bd29S猫头猫    } catch (e: any) {
81325c1bd29S猫头猫        if (e.message === '插件已安装') {
81425c1bd29S猫头猫            throw new Error('当前已是最新版本');
81525c1bd29S猫头猫        } else {
81625c1bd29S猫头猫            throw e;
81725c1bd29S猫头猫        }
81825c1bd29S猫头猫    }
81925c1bd29S猫头猫}
82025c1bd29S猫头猫
821927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) {
8222c595535S猫头猫    return getByName(mediaItem?.platform);
823927dbe93S猫头猫}
824927dbe93S猫头猫
825927dbe93S猫头猫function getByHash(hash: string) {
8267993f90eS猫头猫    return hash === localPluginHash
8277993f90eS猫头猫        ? localFilePlugin
8287993f90eS猫头猫        : plugins.find(_ => _.hash === hash);
829927dbe93S猫头猫}
830927dbe93S猫头猫
831927dbe93S猫头猫function getByName(name: string) {
8327993f90eS猫头猫    return name === localPluginPlatform
8330e4173cdS猫头猫        ? localFilePlugin
8340e4173cdS猫头猫        : plugins.find(_ => _.name === name);
835927dbe93S猫头猫}
836927dbe93S猫头猫
837927dbe93S猫头猫function getValidPlugins() {
838927dbe93S猫头猫    return plugins.filter(_ => _.state === 'enabled');
839927dbe93S猫头猫}
840927dbe93S猫头猫
841efb9da24S猫头猫function getSearchablePlugins() {
842efb9da24S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.search);
843efb9da24S猫头猫}
844efb9da24S猫头猫
845e08d37a3S猫头猫function getSortedSearchablePlugins() {
846e08d37a3S猫头猫    return getSearchablePlugins().sort((a, b) =>
847e08d37a3S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
848e08d37a3S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
849e08d37a3S猫头猫        0
850e08d37a3S猫头猫            ? -1
851e08d37a3S猫头猫            : 1,
852e08d37a3S猫头猫    );
853e08d37a3S猫头猫}
854e08d37a3S猫头猫
85515feccc1S猫头猫function getTopListsablePlugins() {
85615feccc1S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists);
85715feccc1S猫头猫}
85815feccc1S猫头猫
85915feccc1S猫头猫function getSortedTopListsablePlugins() {
86015feccc1S猫头猫    return getTopListsablePlugins().sort((a, b) =>
86115feccc1S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
86215feccc1S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
86315feccc1S猫头猫        0
86415feccc1S猫头猫            ? -1
86515feccc1S猫头猫            : 1,
86615feccc1S猫头猫    );
86715feccc1S猫头猫}
86815feccc1S猫头猫
869e08d37a3S猫头猫function useSortedPlugins() {
870e08d37a3S猫头猫    const _plugins = pluginStateMapper.useMappedState();
871e08d37a3S猫头猫    const _pluginMetaAll = PluginMeta.usePluginMetaAll();
872e08d37a3S猫头猫
87334588741S猫头猫    const [sortedPlugins, setSortedPlugins] = useState(
87434588741S猫头猫        [..._plugins].sort((a, b) =>
875e08d37a3S猫头猫            (_pluginMetaAll[a.name]?.order ?? Infinity) -
876e08d37a3S猫头猫                (_pluginMetaAll[b.name]?.order ?? Infinity) <
877e08d37a3S猫头猫            0
878e08d37a3S猫头猫                ? -1
879e08d37a3S猫头猫                : 1,
88034588741S猫头猫        ),
881e08d37a3S猫头猫    );
88234588741S猫头猫
88334588741S猫头猫    useEffect(() => {
884d4cd40d8S猫头猫        InteractionManager.runAfterInteractions(() => {
88534588741S猫头猫            setSortedPlugins(
88634588741S猫头猫                [..._plugins].sort((a, b) =>
88734588741S猫头猫                    (_pluginMetaAll[a.name]?.order ?? Infinity) -
88834588741S猫头猫                        (_pluginMetaAll[b.name]?.order ?? Infinity) <
88934588741S猫头猫                    0
89034588741S猫头猫                        ? -1
89134588741S猫头猫                        : 1,
89234588741S猫头猫                ),
89334588741S猫头猫            );
894d4cd40d8S猫头猫        });
89534588741S猫头猫    }, [_plugins, _pluginMetaAll]);
89634588741S猫头猫
89734588741S猫头猫    return sortedPlugins;
898e08d37a3S猫头猫}
899e08d37a3S猫头猫
900927dbe93S猫头猫const PluginManager = {
901927dbe93S猫头猫    setup,
902927dbe93S猫头猫    installPlugin,
90358992c6bS猫头猫    installPluginFromUrl,
90425c1bd29S猫头猫    updatePlugin,
905927dbe93S猫头猫    uninstallPlugin,
906927dbe93S猫头猫    getByMedia,
907927dbe93S猫头猫    getByHash,
908927dbe93S猫头猫    getByName,
909927dbe93S猫头猫    getValidPlugins,
910efb9da24S猫头猫    getSearchablePlugins,
911e08d37a3S猫头猫    getSortedSearchablePlugins,
91215feccc1S猫头猫    getTopListsablePlugins,
91315feccc1S猫头猫    getSortedTopListsablePlugins,
9145276aef9S猫头猫    usePlugins: pluginStateMapper.useMappedState,
915e08d37a3S猫头猫    useSortedPlugins,
91608882a77S猫头猫    uninstallAllPlugins,
9175276aef9S猫头猫};
918927dbe93S猫头猫
919927dbe93S猫头猫export default PluginManager;
920