xref: /MusicFree/src/core/pluginManager.ts (revision 9c34d6375a46216b079eac044e4397b909d83a3b)
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猫头猫
58*9c34d637S猫头猫const packages: Record<string, any> = {
59*9c34d637S猫头猫    cheerio,
60*9c34d637S猫头猫    'crypto-js': CryptoJs,
61*9c34d637S猫头猫    axios,
62*9c34d637S猫头猫    dayjs,
63*9c34d637S猫头猫    'big-integer': bigInt,
64*9c34d637S猫头猫    qs,
65*9c34d637S猫头猫    he,
66*9c34d637S猫头猫    '@react-native-cookies/cookies': {
67*9c34d637S猫头猫        flush: CookieManager.flush,
68*9c34d637S猫头猫        get: CookieManager.get,
69*9c34d637S猫头猫    },
70*9c34d637S猫头猫};
71*9c34d637S猫头猫
72*9c34d637S猫头猫const _require = (packageName: string) => packages[packageName];
73*9c34d637S猫头猫
74d5bfeb7eS猫头猫//#region 插件类
75927dbe93S猫头猫export class Plugin {
76927dbe93S猫头猫    /** 插件名 */
77927dbe93S猫头猫    public name: string;
78927dbe93S猫头猫    /** 插件的hash,作为唯一id */
79927dbe93S猫头猫    public hash: string;
80927dbe93S猫头猫    /** 插件状态:激活、关闭、错误 */
81927dbe93S猫头猫    public state: 'enabled' | 'disabled' | 'error';
82927dbe93S猫头猫    /** 插件支持的搜索类型 */
83927dbe93S猫头猫    public supportedSearchType?: string;
84927dbe93S猫头猫    /** 插件状态信息 */
85927dbe93S猫头猫    public stateCode?: PluginStateCode;
86927dbe93S猫头猫    /** 插件的实例 */
87927dbe93S猫头猫    public instance: IPlugin.IPluginInstance;
88927dbe93S猫头猫    /** 插件路径 */
89927dbe93S猫头猫    public path: string;
90927dbe93S猫头猫    /** 插件方法 */
91927dbe93S猫头猫    public methods: PluginMethods;
92d5bfeb7eS猫头猫    /** 用户输入 */
93d5bfeb7eS猫头猫    public userEnv?: Record<string, string>;
94927dbe93S猫头猫
9574d0cf81S猫头猫    constructor(
9674d0cf81S猫头猫        funcCode: string | (() => IPlugin.IPluginInstance),
9774d0cf81S猫头猫        pluginPath: string,
9874d0cf81S猫头猫    ) {
99927dbe93S猫头猫        this.state = 'enabled';
100927dbe93S猫头猫        let _instance: IPlugin.IPluginInstance;
101*9c34d637S猫头猫        const _module = {exports: {}};
102927dbe93S猫头猫        try {
10374d0cf81S猫头猫            if (typeof funcCode === 'string') {
1044060c00aS猫头猫                // eslint-disable-next-line no-new-func
105927dbe93S猫头猫                _instance = Function(`
106927dbe93S猫头猫                    'use strict';
107*9c34d637S猫头猫                    return function(require, __musicfree_require, module, exports) {
108*9c34d637S猫头猫                        ${funcCode}
109927dbe93S猫头猫                    }
110*9c34d637S猫头猫                `)()(_require, _require, _module, _module.exports);
111*9c34d637S猫头猫                _instance = _module.exports as IPlugin.IPluginInstance;
11274d0cf81S猫头猫            } else {
11374d0cf81S猫头猫                _instance = funcCode();
11474d0cf81S猫头猫            }
115927dbe93S猫头猫            this.checkValid(_instance);
116927dbe93S猫头猫        } catch (e: any) {
117927dbe93S猫头猫            this.state = 'error';
118927dbe93S猫头猫            this.stateCode = PluginStateCode.CannotParse;
119927dbe93S猫头猫            if (e?.stateCode) {
120927dbe93S猫头猫                this.stateCode = e.stateCode;
121927dbe93S猫头猫            }
122927dbe93S猫头猫            errorLog(`${pluginPath}插件无法解析 `, {
123927dbe93S猫头猫                stateCode: this.stateCode,
124927dbe93S猫头猫                message: e?.message,
125927dbe93S猫头猫                stack: e?.stack,
126927dbe93S猫头猫            });
127927dbe93S猫头猫            _instance = e?.instance ?? {
128927dbe93S猫头猫                _path: '',
129927dbe93S猫头猫                platform: '',
130927dbe93S猫头猫                appVersion: '',
13120e6a092S猫头猫                async getMediaSource() {
132927dbe93S猫头猫                    return null;
133927dbe93S猫头猫                },
134927dbe93S猫头猫                async search() {
135927dbe93S猫头猫                    return {};
136927dbe93S猫头猫                },
137927dbe93S猫头猫                async getAlbumInfo() {
138927dbe93S猫头猫                    return null;
139927dbe93S猫头猫                },
140927dbe93S猫头猫            };
141927dbe93S猫头猫        }
142927dbe93S猫头猫        this.instance = _instance;
143927dbe93S猫头猫        this.path = pluginPath;
144927dbe93S猫头猫        this.name = _instance.platform;
145927dbe93S猫头猫        if (this.instance.platform === '') {
146927dbe93S猫头猫            this.hash = '';
147927dbe93S猫头猫        } else {
14874d0cf81S猫头猫            if (typeof funcCode === 'string') {
149927dbe93S猫头猫                this.hash = sha256(funcCode).toString();
15074d0cf81S猫头猫            } else {
15174d0cf81S猫头猫                this.hash = sha256(funcCode.toString()).toString();
15274d0cf81S猫头猫            }
153927dbe93S猫头猫        }
154927dbe93S猫头猫
155927dbe93S猫头猫        // 放在最后
156927dbe93S猫头猫        this.methods = new PluginMethods(this);
157927dbe93S猫头猫    }
158927dbe93S猫头猫
159927dbe93S猫头猫    private checkValid(_instance: IPlugin.IPluginInstance) {
160927dbe93S猫头猫        /** 版本号校验 */
161927dbe93S猫头猫        if (
162927dbe93S猫头猫            _instance.appVersion &&
163927dbe93S猫头猫            !satisfies(DeviceInfo.getVersion(), _instance.appVersion)
164927dbe93S猫头猫        ) {
165927dbe93S猫头猫            throw {
166927dbe93S猫头猫                instance: _instance,
167927dbe93S猫头猫                stateCode: PluginStateCode.VersionNotMatch,
168927dbe93S猫头猫            };
169927dbe93S猫头猫        }
170927dbe93S猫头猫        return true;
171927dbe93S猫头猫    }
172927dbe93S猫头猫}
173d5bfeb7eS猫头猫//#endregion
174927dbe93S猫头猫
175d5bfeb7eS猫头猫//#region 基于插件类封装的方法,供给APP侧直接调用
176927dbe93S猫头猫/** 有缓存等信息 */
177927dbe93S猫头猫class PluginMethods implements IPlugin.IPluginInstanceMethods {
178927dbe93S猫头猫    private plugin;
179927dbe93S猫头猫    constructor(plugin: Plugin) {
180927dbe93S猫头猫        this.plugin = plugin;
181927dbe93S猫头猫    }
182927dbe93S猫头猫    /** 搜索 */
183927dbe93S猫头猫    async search<T extends ICommon.SupportMediaType>(
184927dbe93S猫头猫        query: string,
185927dbe93S猫头猫        page: number,
186927dbe93S猫头猫        type: T,
187927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
188927dbe93S猫头猫        if (!this.plugin.instance.search) {
189927dbe93S猫头猫            return {
190927dbe93S猫头猫                isEnd: true,
191927dbe93S猫头猫                data: [],
192927dbe93S猫头猫            };
193927dbe93S猫头猫        }
194927dbe93S猫头猫
1954060c00aS猫头猫        const result =
1964060c00aS猫头猫            (await this.plugin.instance.search(query, page, type)) ?? {};
197927dbe93S猫头猫        if (Array.isArray(result.data)) {
198927dbe93S猫头猫            result.data.forEach(_ => {
199927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
200927dbe93S猫头猫            });
201927dbe93S猫头猫            return {
202927dbe93S猫头猫                isEnd: result.isEnd ?? true,
203927dbe93S猫头猫                data: result.data,
204927dbe93S猫头猫            };
205927dbe93S猫头猫        }
206927dbe93S猫头猫        return {
207927dbe93S猫头猫            isEnd: true,
208927dbe93S猫头猫            data: [],
209927dbe93S猫头猫        };
210927dbe93S猫头猫    }
211927dbe93S猫头猫
212927dbe93S猫头猫    /** 获取真实源 */
21320e6a092S猫头猫    async getMediaSource(
214927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
215abaede57S猫头猫        quality: IMusic.IQualityKey = 'standard',
216927dbe93S猫头猫        retryCount = 1,
217dc160d50S猫头猫        notUpdateCache = false,
218192ae2b0S猫头猫    ): Promise<IPlugin.IMediaSourceResult | null> {
219927dbe93S猫头猫        // 1. 本地搜索 其实直接读mediameta就好了
220927dbe93S猫头猫        const localPath =
2210e4173cdS猫头猫            getInternalData<string>(musicItem, InternalDataType.LOCALPATH) ??
2220e4173cdS猫头猫            getInternalData<string>(
2230e4173cdS猫头猫                LocalMusicSheet.isLocalMusic(musicItem),
2240e4173cdS猫头猫                InternalDataType.LOCALPATH,
2250e4173cdS猫头猫            );
2260e4173cdS猫头猫        if (localPath && (await FileSystem.exists(localPath))) {
2270e4173cdS猫头猫            trace('本地播放', localPath);
228927dbe93S猫头猫            return {
229927dbe93S猫头猫                url: localPath,
230927dbe93S猫头猫            };
231927dbe93S猫头猫        }
2327993f90eS猫头猫        if (musicItem.platform === localPluginPlatform) {
233f5935920S猫头猫            throw new Error('本地音乐不存在');
234f5935920S猫头猫        }
235927dbe93S猫头猫        // 2. 缓存播放
236927dbe93S猫头猫        const mediaCache = Cache.get(musicItem);
237985f8e75S猫头猫        const pluginCacheControl =
238985f8e75S猫头猫            this.plugin.instance.cacheControl ?? 'no-cache';
239cfa0fc07S猫头猫        if (
240cfa0fc07S猫头猫            mediaCache &&
241abaede57S猫头猫            mediaCache?.qualities?.[quality]?.url &&
24248f4b873S猫头猫            (pluginCacheControl === CacheControl.Cache ||
24348f4b873S猫头猫                (pluginCacheControl === CacheControl.NoCache &&
244ef714860S猫头猫                    Network.isOffline()))
245cfa0fc07S猫头猫        ) {
2465276aef9S猫头猫            trace('播放', '缓存播放');
247abaede57S猫头猫            const qualityInfo = mediaCache.qualities[quality];
248927dbe93S猫头猫            return {
249abaede57S猫头猫                url: qualityInfo.url,
250927dbe93S猫头猫                headers: mediaCache.headers,
2514060c00aS猫头猫                userAgent:
2524060c00aS猫头猫                    mediaCache.userAgent ?? mediaCache.headers?.['user-agent'],
253927dbe93S猫头猫            };
254927dbe93S猫头猫        }
255927dbe93S猫头猫        // 3. 插件解析
25620e6a092S猫头猫        if (!this.plugin.instance.getMediaSource) {
257abaede57S猫头猫            return {url: musicItem?.qualities?.[quality]?.url ?? musicItem.url};
258927dbe93S猫头猫        }
259927dbe93S猫头猫        try {
260abaede57S猫头猫            const {url, headers} = (await this.plugin.instance.getMediaSource(
261abaede57S猫头猫                musicItem,
262abaede57S猫头猫                quality,
263abaede57S猫头猫            )) ?? {url: musicItem?.qualities?.[quality]?.url};
264927dbe93S猫头猫            if (!url) {
265a28eac61S猫头猫                throw new Error('NOT RETRY');
266927dbe93S猫头猫            }
2675276aef9S猫头猫            trace('播放', '插件播放');
268927dbe93S猫头猫            const result = {
269927dbe93S猫头猫                url,
270927dbe93S猫头猫                headers,
271927dbe93S猫头猫                userAgent: headers?.['user-agent'],
272cfa0fc07S猫头猫            } as IPlugin.IMediaSourceResult;
273927dbe93S猫头猫
274dc160d50S猫头猫            if (
275dc160d50S猫头猫                pluginCacheControl !== CacheControl.NoStore &&
276dc160d50S猫头猫                !notUpdateCache
277dc160d50S猫头猫            ) {
278abaede57S猫头猫                Cache.update(musicItem, [
279abaede57S猫头猫                    ['headers', result.headers],
280abaede57S猫头猫                    ['userAgent', result.userAgent],
281abaede57S猫头猫                    [`qualities.${quality}.url`, url],
282abaede57S猫头猫                ]);
283752ffc5aS猫头猫            }
284cfa0fc07S猫头猫
285927dbe93S猫头猫            return result;
286927dbe93S猫头猫        } catch (e: any) {
287a28eac61S猫头猫            if (retryCount > 0 && e?.message !== 'NOT RETRY') {
288927dbe93S猫头猫                await delay(150);
289abaede57S猫头猫                return this.getMediaSource(musicItem, quality, --retryCount);
290927dbe93S猫头猫            }
291927dbe93S猫头猫            errorLog('获取真实源失败', e?.message);
292ea6d708fS猫头猫            devLog('error', '获取真实源失败', e, e?.message);
293192ae2b0S猫头猫            return null;
294927dbe93S猫头猫        }
295927dbe93S猫头猫    }
296927dbe93S猫头猫
297927dbe93S猫头猫    /** 获取音乐详情 */
298927dbe93S猫头猫    async getMusicInfo(
299927dbe93S猫头猫        musicItem: ICommon.IMediaBase,
30074d0cf81S猫头猫    ): Promise<Partial<IMusic.IMusicItem> | null> {
301927dbe93S猫头猫        if (!this.plugin.instance.getMusicInfo) {
302d704daedS猫头猫            return null;
303927dbe93S猫头猫        }
30474d0cf81S猫头猫        try {
305927dbe93S猫头猫            return (
306927dbe93S猫头猫                this.plugin.instance.getMusicInfo(
3077993f90eS猫头猫                    resetMediaItem(musicItem, undefined, true),
308d704daedS猫头猫                ) ?? null
309927dbe93S猫头猫            );
310ea6d708fS猫头猫        } catch (e: any) {
311ea6d708fS猫头猫            devLog('error', '获取音乐详情失败', e, e?.message);
312d704daedS猫头猫            return null;
31374d0cf81S猫头猫        }
314927dbe93S猫头猫    }
315927dbe93S猫头猫
316927dbe93S猫头猫    /** 获取歌词 */
317927dbe93S猫头猫    async getLyric(
318927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
319927dbe93S猫头猫        from?: IMusic.IMusicItemBase,
320927dbe93S猫头猫    ): Promise<ILyric.ILyricSource | null> {
321927dbe93S猫头猫        // 1.额外存储的meta信息
322927dbe93S猫头猫        const meta = MediaMeta.get(musicItem);
323927dbe93S猫头猫        if (meta && meta.associatedLrc) {
324927dbe93S猫头猫            // 有关联歌词
325927dbe93S猫头猫            if (
326927dbe93S猫头猫                isSameMediaItem(musicItem, from) ||
327927dbe93S猫头猫                isSameMediaItem(meta.associatedLrc, musicItem)
328927dbe93S猫头猫            ) {
329927dbe93S猫头猫                // 形成环路,断开当前的环
330927dbe93S猫头猫                await MediaMeta.update(musicItem, {
331927dbe93S猫头猫                    associatedLrc: undefined,
332927dbe93S猫头猫                });
333927dbe93S猫头猫                // 无歌词
334927dbe93S猫头猫                return null;
335927dbe93S猫头猫            }
336927dbe93S猫头猫            // 获取关联歌词
3377a91f04fS猫头猫            const associatedMeta = MediaMeta.get(meta.associatedLrc) ?? {};
3384060c00aS猫头猫            const result = await this.getLyric(
3397a91f04fS猫头猫                {...meta.associatedLrc, ...associatedMeta},
3404060c00aS猫头猫                from ?? musicItem,
3414060c00aS猫头猫            );
342927dbe93S猫头猫            if (result) {
343927dbe93S猫头猫                // 如果有关联歌词,就返回关联歌词,深度优先
344927dbe93S猫头猫                return result;
345927dbe93S猫头猫            }
346927dbe93S猫头猫        }
347927dbe93S猫头猫        const cache = Cache.get(musicItem);
348927dbe93S猫头猫        let rawLrc = meta?.rawLrc || musicItem.rawLrc || cache?.rawLrc;
349927dbe93S猫头猫        let lrcUrl = meta?.lrc || musicItem.lrc || cache?.lrc;
350927dbe93S猫头猫        // 如果存在文本
351927dbe93S猫头猫        if (rawLrc) {
352927dbe93S猫头猫            return {
353927dbe93S猫头猫                rawLrc,
354927dbe93S猫头猫                lrc: lrcUrl,
355927dbe93S猫头猫            };
356927dbe93S猫头猫        }
357927dbe93S猫头猫        // 2.本地缓存
358927dbe93S猫头猫        const localLrc =
3590e4173cdS猫头猫            meta?.[internalSerializeKey]?.local?.localLrc ||
3600e4173cdS猫头猫            cache?.[internalSerializeKey]?.local?.localLrc;
361927dbe93S猫头猫        if (localLrc && (await exists(localLrc))) {
362927dbe93S猫头猫            rawLrc = await readFile(localLrc, 'utf8');
363927dbe93S猫头猫            return {
364927dbe93S猫头猫                rawLrc,
365927dbe93S猫头猫                lrc: lrcUrl,
366927dbe93S猫头猫            };
367927dbe93S猫头猫        }
368927dbe93S猫头猫        // 3.优先使用url
369927dbe93S猫头猫        if (lrcUrl) {
370927dbe93S猫头猫            try {
371927dbe93S猫头猫                // 需要超时时间 axios timeout 但是没生效
3722a3194f5S猫头猫                rawLrc = (await axios.get(lrcUrl, {timeout: 1500})).data;
373927dbe93S猫头猫                return {
374927dbe93S猫头猫                    rawLrc,
375927dbe93S猫头猫                    lrc: lrcUrl,
376927dbe93S猫头猫                };
377927dbe93S猫头猫            } catch {
378927dbe93S猫头猫                lrcUrl = undefined;
379927dbe93S猫头猫            }
380927dbe93S猫头猫        }
381927dbe93S猫头猫        // 4. 如果地址失效
382927dbe93S猫头猫        if (!lrcUrl) {
383927dbe93S猫头猫            // 插件获得url
384927dbe93S猫头猫            try {
3857a91f04fS猫头猫                let lrcSource;
3867a91f04fS猫头猫                if (from) {
3877a91f04fS猫头猫                    lrcSource = await PluginManager.getByMedia(
3887a91f04fS猫头猫                        musicItem,
3897a91f04fS猫头猫                    )?.instance?.getLyric?.(
390927dbe93S猫头猫                        resetMediaItem(musicItem, undefined, true),
391927dbe93S猫头猫                    );
3927a91f04fS猫头猫                } else {
3937a91f04fS猫头猫                    lrcSource = await this.plugin.instance?.getLyric?.(
3947a91f04fS猫头猫                        resetMediaItem(musicItem, undefined, true),
3957a91f04fS猫头猫                    );
3967a91f04fS猫头猫                }
3977a91f04fS猫头猫
398927dbe93S猫头猫                rawLrc = lrcSource?.rawLrc;
399927dbe93S猫头猫                lrcUrl = lrcSource?.lrc;
400927dbe93S猫头猫            } catch (e: any) {
401927dbe93S猫头猫                trace('插件获取歌词失败', e?.message, 'error');
402ea6d708fS猫头猫                devLog('error', '插件获取歌词失败', e, e?.message);
403927dbe93S猫头猫            }
404927dbe93S猫头猫        }
405927dbe93S猫头猫        // 5. 最后一次请求
406927dbe93S猫头猫        if (rawLrc || lrcUrl) {
407927dbe93S猫头猫            const filename = `${pathConst.lrcCachePath}${nanoid()}.lrc`;
408927dbe93S猫头猫            if (lrcUrl) {
409927dbe93S猫头猫                try {
4102a3194f5S猫头猫                    rawLrc = (await axios.get(lrcUrl, {timeout: 1500})).data;
411927dbe93S猫头猫                } catch {}
412927dbe93S猫头猫            }
413927dbe93S猫头猫            if (rawLrc) {
414927dbe93S猫头猫                await writeFile(filename, rawLrc, 'utf8');
415927dbe93S猫头猫                // 写入缓存
416927dbe93S猫头猫                Cache.update(musicItem, [
4170e4173cdS猫头猫                    [`${internalSerializeKey}.local.localLrc`, filename],
418927dbe93S猫头猫                ]);
419927dbe93S猫头猫                // 如果有meta
420927dbe93S猫头猫                if (meta) {
421927dbe93S猫头猫                    MediaMeta.update(musicItem, [
4220e4173cdS猫头猫                        [`${internalSerializeKey}.local.localLrc`, filename],
423927dbe93S猫头猫                    ]);
424927dbe93S猫头猫                }
425927dbe93S猫头猫                return {
426927dbe93S猫头猫                    rawLrc,
427927dbe93S猫头猫                    lrc: lrcUrl,
428927dbe93S猫头猫                };
429927dbe93S猫头猫            }
430927dbe93S猫头猫        }
4313a6f67b1S猫头猫        // 6. 如果是本地文件
4323a6f67b1S猫头猫        const isDownloaded = LocalMusicSheet.isLocalMusic(musicItem);
4333a6f67b1S猫头猫        if (musicItem.platform !== localPluginPlatform && isDownloaded) {
4343a6f67b1S猫头猫            const res = await localFilePlugin.instance!.getLyric!(isDownloaded);
4353a6f67b1S猫头猫            if (res) {
4363a6f67b1S猫头猫                return res;
4373a6f67b1S猫头猫            }
4383a6f67b1S猫头猫        }
439ea6d708fS猫头猫        devLog('warn', '无歌词');
440927dbe93S猫头猫
441927dbe93S猫头猫        return null;
442927dbe93S猫头猫    }
443927dbe93S猫头猫
444927dbe93S猫头猫    /** 获取歌词文本 */
445927dbe93S猫头猫    async getLyricText(
446927dbe93S猫头猫        musicItem: IMusic.IMusicItem,
447927dbe93S猫头猫    ): Promise<string | undefined> {
448927dbe93S猫头猫        return (await this.getLyric(musicItem))?.rawLrc;
449927dbe93S猫头猫    }
450927dbe93S猫头猫
451927dbe93S猫头猫    /** 获取专辑信息 */
452927dbe93S猫头猫    async getAlbumInfo(
453927dbe93S猫头猫        albumItem: IAlbum.IAlbumItemBase,
454927dbe93S猫头猫    ): Promise<IAlbum.IAlbumItem | null> {
455927dbe93S猫头猫        if (!this.plugin.instance.getAlbumInfo) {
456927dbe93S猫头猫            return {...albumItem, musicList: []};
457927dbe93S猫头猫        }
458927dbe93S猫头猫        try {
459927dbe93S猫头猫            const result = await this.plugin.instance.getAlbumInfo(
460927dbe93S猫头猫                resetMediaItem(albumItem, undefined, true),
461927dbe93S猫头猫            );
4625276aef9S猫头猫            if (!result) {
4635276aef9S猫头猫                throw new Error();
4645276aef9S猫头猫            }
465927dbe93S猫头猫            result?.musicList?.forEach(_ => {
466927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
467927dbe93S猫头猫            });
4685276aef9S猫头猫
4695276aef9S猫头猫            return {...albumItem, ...result};
4704394410dS猫头猫        } catch (e: any) {
4714394410dS猫头猫            trace('获取专辑信息失败', e?.message);
472ea6d708fS猫头猫            devLog('error', '获取专辑信息失败', e, e?.message);
473ea6d708fS猫头猫
474927dbe93S猫头猫            return {...albumItem, musicList: []};
475927dbe93S猫头猫        }
476927dbe93S猫头猫    }
477927dbe93S猫头猫
478927dbe93S猫头猫    /** 查询作者信息 */
479efb9da24S猫头猫    async getArtistWorks<T extends IArtist.ArtistMediaType>(
480927dbe93S猫头猫        artistItem: IArtist.IArtistItem,
481927dbe93S猫头猫        page: number,
482927dbe93S猫头猫        type: T,
483927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
484efb9da24S猫头猫        if (!this.plugin.instance.getArtistWorks) {
485927dbe93S猫头猫            return {
486927dbe93S猫头猫                isEnd: true,
487927dbe93S猫头猫                data: [],
488927dbe93S猫头猫            };
489927dbe93S猫头猫        }
490927dbe93S猫头猫        try {
491efb9da24S猫头猫            const result = await this.plugin.instance.getArtistWorks(
492927dbe93S猫头猫                artistItem,
493927dbe93S猫头猫                page,
494927dbe93S猫头猫                type,
495927dbe93S猫头猫            );
496927dbe93S猫头猫            if (!result.data) {
497927dbe93S猫头猫                return {
498927dbe93S猫头猫                    isEnd: true,
499927dbe93S猫头猫                    data: [],
500927dbe93S猫头猫                };
501927dbe93S猫头猫            }
502927dbe93S猫头猫            result.data?.forEach(_ => resetMediaItem(_, this.plugin.name));
503927dbe93S猫头猫            return {
504927dbe93S猫头猫                isEnd: result.isEnd ?? true,
505927dbe93S猫头猫                data: result.data,
506927dbe93S猫头猫            };
5074394410dS猫头猫        } catch (e: any) {
5084394410dS猫头猫            trace('查询作者信息失败', e?.message);
509ea6d708fS猫头猫            devLog('error', '查询作者信息失败', e, e?.message);
510ea6d708fS猫头猫
511927dbe93S猫头猫            throw e;
512927dbe93S猫头猫        }
513927dbe93S猫头猫    }
51408380090S猫头猫
51508380090S猫头猫    /** 导入歌单 */
51608380090S猫头猫    async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> {
51708380090S猫头猫        try {
51808380090S猫头猫            const result =
51908380090S猫头猫                (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? [];
52008380090S猫头猫            result.forEach(_ => resetMediaItem(_, this.plugin.name));
52108380090S猫头猫            return result;
522ea6d708fS猫头猫        } catch (e: any) {
5230e4173cdS猫头猫            console.log(e);
524ea6d708fS猫头猫            devLog('error', '导入歌单失败', e, e?.message);
525ea6d708fS猫头猫
52608380090S猫头猫            return [];
52708380090S猫头猫        }
52808380090S猫头猫    }
5294d9d3c4cS猫头猫    /** 导入单曲 */
5304d9d3c4cS猫头猫    async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> {
5314d9d3c4cS猫头猫        try {
5324d9d3c4cS猫头猫            const result = await this.plugin.instance?.importMusicItem?.(
5334d9d3c4cS猫头猫                urlLike,
5344d9d3c4cS猫头猫            );
5354d9d3c4cS猫头猫            if (!result) {
5364d9d3c4cS猫头猫                throw new Error();
5374d9d3c4cS猫头猫            }
5384d9d3c4cS猫头猫            resetMediaItem(result, this.plugin.name);
5394d9d3c4cS猫头猫            return result;
540ea6d708fS猫头猫        } catch (e: any) {
541ea6d708fS猫头猫            devLog('error', '导入单曲失败', e, e?.message);
542ea6d708fS猫头猫
5434d9d3c4cS猫头猫            return null;
5444d9d3c4cS猫头猫        }
5454d9d3c4cS猫头猫    }
546d52aa40eS猫头猫    /** 获取榜单 */
547d52aa40eS猫头猫    async getTopLists(): Promise<IMusic.IMusicTopListGroupItem[]> {
548d52aa40eS猫头猫        try {
549d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopLists?.();
550d52aa40eS猫头猫            if (!result) {
551d52aa40eS猫头猫                throw new Error();
552d52aa40eS猫头猫            }
553d52aa40eS猫头猫            return result;
554d52aa40eS猫头猫        } catch (e: any) {
555d52aa40eS猫头猫            devLog('error', '获取榜单失败', e, e?.message);
556d52aa40eS猫头猫            return [];
557d52aa40eS猫头猫        }
558d52aa40eS猫头猫    }
559d52aa40eS猫头猫    /** 获取榜单详情 */
560d52aa40eS猫头猫    async getTopListDetail(
561d52aa40eS猫头猫        topListItem: IMusic.IMusicTopListItem,
562d52aa40eS猫头猫    ): Promise<ICommon.WithMusicList<IMusic.IMusicTopListItem>> {
563d52aa40eS猫头猫        try {
564d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopListDetail?.(
565d52aa40eS猫头猫                topListItem,
566d52aa40eS猫头猫            );
567d52aa40eS猫头猫            if (!result) {
568d52aa40eS猫头猫                throw new Error();
569d52aa40eS猫头猫            }
570d384662fS猫头猫            if (result.musicList) {
571d384662fS猫头猫                result.musicList.forEach(_ =>
572d384662fS猫头猫                    resetMediaItem(_, this.plugin.name),
573d384662fS猫头猫                );
574d384662fS猫头猫            }
575d52aa40eS猫头猫            return result;
576d52aa40eS猫头猫        } catch (e: any) {
577d52aa40eS猫头猫            devLog('error', '获取榜单详情失败', e, e?.message);
578d52aa40eS猫头猫            return {
579d52aa40eS猫头猫                ...topListItem,
580d52aa40eS猫头猫                musicList: [],
581d52aa40eS猫头猫            };
582d52aa40eS猫头猫        }
583d52aa40eS猫头猫    }
584927dbe93S猫头猫}
585d5bfeb7eS猫头猫//#endregion
5861a5528a0S猫头猫
587927dbe93S猫头猫let plugins: Array<Plugin> = [];
588927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins);
58974d0cf81S猫头猫
590d5bfeb7eS猫头猫//#region 本地音乐插件
59174d0cf81S猫头猫/** 本地插件 */
59274d0cf81S猫头猫const localFilePlugin = new Plugin(function () {
5930e4173cdS猫头猫    return {
594d5bfeb7eS猫头猫        platform: localPluginPlatform,
59574d0cf81S猫头猫        _path: '',
59674d0cf81S猫头猫        async getMusicInfo(musicBase) {
59774d0cf81S猫头猫            const localPath = getInternalData<string>(
59874d0cf81S猫头猫                musicBase,
59974d0cf81S猫头猫                InternalDataType.LOCALPATH,
6000e4173cdS猫头猫            );
60174d0cf81S猫头猫            if (localPath) {
60274d0cf81S猫头猫                const coverImg = await Mp3Util.getMediaCoverImg(localPath);
60374d0cf81S猫头猫                return {
60474d0cf81S猫头猫                    artwork: coverImg,
60574d0cf81S猫头猫                };
60674d0cf81S猫头猫            }
60774d0cf81S猫头猫            return null;
60874d0cf81S猫头猫        },
6097993f90eS猫头猫        async getLyric(musicBase) {
6107993f90eS猫头猫            const localPath = getInternalData<string>(
6117993f90eS猫头猫                musicBase,
6127993f90eS猫头猫                InternalDataType.LOCALPATH,
6137993f90eS猫头猫            );
6143a6f67b1S猫头猫            let rawLrc: string | null = null;
6157993f90eS猫头猫            if (localPath) {
6163a6f67b1S猫头猫                // 读取内嵌歌词
6173a6f67b1S猫头猫                try {
6183a6f67b1S猫头猫                    rawLrc = await Mp3Util.getLyric(localPath);
6193a6f67b1S猫头猫                } catch (e) {
6203a6f67b1S猫头猫                    console.log('e', e);
6217993f90eS猫头猫                }
6223a6f67b1S猫头猫                if (!rawLrc) {
6233a6f67b1S猫头猫                    // 读取配置歌词
6243a6f67b1S猫头猫                    const lastDot = localPath.lastIndexOf('.');
6253a6f67b1S猫头猫                    const lrcPath = localPath.slice(0, lastDot) + '.lrc';
6263a6f67b1S猫头猫
6273a6f67b1S猫头猫                    try {
6283a6f67b1S猫头猫                        if (await exists(lrcPath)) {
6293a6f67b1S猫头猫                            rawLrc = await readFile(lrcPath, 'utf8');
6303a6f67b1S猫头猫                        }
6313a6f67b1S猫头猫                    } catch {}
6323a6f67b1S猫头猫                }
6333a6f67b1S猫头猫            }
6343a6f67b1S猫头猫
6353a6f67b1S猫头猫            return rawLrc
6363a6f67b1S猫头猫                ? {
6373a6f67b1S猫头猫                      rawLrc,
6383a6f67b1S猫头猫                  }
6393a6f67b1S猫头猫                : null;
6407993f90eS猫头猫        },
64174d0cf81S猫头猫    };
64274d0cf81S猫头猫}, '');
6437993f90eS猫头猫localFilePlugin.hash = localPluginHash;
644927dbe93S猫头猫
645d5bfeb7eS猫头猫//#endregion
646d5bfeb7eS猫头猫
647927dbe93S猫头猫async function setup() {
648927dbe93S猫头猫    const _plugins: Array<Plugin> = [];
649927dbe93S猫头猫    try {
650927dbe93S猫头猫        // 加载插件
651927dbe93S猫头猫        const pluginsPaths = await readDir(pathConst.pluginPath);
652927dbe93S猫头猫        for (let i = 0; i < pluginsPaths.length; ++i) {
653927dbe93S猫头猫            const _pluginUrl = pluginsPaths[i];
6541e263108S猫头猫            trace('初始化插件', _pluginUrl);
6551e263108S猫头猫            if (
6561e263108S猫头猫                _pluginUrl.isFile() &&
6571e263108S猫头猫                (_pluginUrl.name?.endsWith?.('.js') ||
6581e263108S猫头猫                    _pluginUrl.path?.endsWith?.('.js'))
6591e263108S猫头猫            ) {
660927dbe93S猫头猫                const funcCode = await readFile(_pluginUrl.path, 'utf8');
661927dbe93S猫头猫                const plugin = new Plugin(funcCode, _pluginUrl.path);
6624060c00aS猫头猫                const _pluginIndex = _plugins.findIndex(
6634060c00aS猫头猫                    p => p.hash === plugin.hash,
6644060c00aS猫头猫                );
665927dbe93S猫头猫                if (_pluginIndex !== -1) {
666927dbe93S猫头猫                    // 重复插件,直接忽略
667927dbe93S猫头猫                    return;
668927dbe93S猫头猫                }
669927dbe93S猫头猫                plugin.hash !== '' && _plugins.push(plugin);
670927dbe93S猫头猫            }
671927dbe93S猫头猫        }
672927dbe93S猫头猫
673927dbe93S猫头猫        plugins = _plugins;
674927dbe93S猫头猫        pluginStateMapper.notify();
675e08d37a3S猫头猫        /** 初始化meta信息 */
676e08d37a3S猫头猫        PluginMeta.setupMeta(plugins.map(_ => _.name));
677927dbe93S猫头猫    } catch (e: any) {
6784060c00aS猫头猫        ToastAndroid.show(
6794060c00aS猫头猫            `插件初始化失败:${e?.message ?? e}`,
6804060c00aS猫头猫            ToastAndroid.LONG,
6814060c00aS猫头猫        );
6821a5528a0S猫头猫        errorLog('插件初始化失败', e?.message);
683927dbe93S猫头猫        throw e;
684927dbe93S猫头猫    }
685927dbe93S猫头猫}
686927dbe93S猫头猫
687927dbe93S猫头猫// 安装插件
688927dbe93S猫头猫async function installPlugin(pluginPath: string) {
68922c09412S猫头猫    // if (pluginPath.endsWith('.js')) {
690927dbe93S猫头猫    const funcCode = await readFile(pluginPath, 'utf8');
691927dbe93S猫头猫    const plugin = new Plugin(funcCode, pluginPath);
692927dbe93S猫头猫    const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
693927dbe93S猫头猫    if (_pluginIndex !== -1) {
6944d9d3c4cS猫头猫        throw new Error('插件已安装');
695927dbe93S猫头猫    }
696927dbe93S猫头猫    if (plugin.hash !== '') {
697927dbe93S猫头猫        const fn = nanoid();
698927dbe93S猫头猫        const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
699927dbe93S猫头猫        await copyFile(pluginPath, _pluginPath);
700927dbe93S猫头猫        plugin.path = _pluginPath;
701927dbe93S猫头猫        plugins = plugins.concat(plugin);
702927dbe93S猫头猫        pluginStateMapper.notify();
7034d9d3c4cS猫头猫        return;
704927dbe93S猫头猫    }
7054d9d3c4cS猫头猫    throw new Error('插件无法解析');
70622c09412S猫头猫    // }
70722c09412S猫头猫    // throw new Error('插件不存在');
708927dbe93S猫头猫}
709927dbe93S猫头猫
71058992c6bS猫头猫async function installPluginFromUrl(url: string) {
71158992c6bS猫头猫    try {
71258992c6bS猫头猫        const funcCode = (await axios.get(url)).data;
71358992c6bS猫头猫        if (funcCode) {
71458992c6bS猫头猫            const plugin = new Plugin(funcCode, '');
71558992c6bS猫头猫            const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
71658992c6bS猫头猫            if (_pluginIndex !== -1) {
7178b7ddca8S猫头猫                // 静默忽略
7188b7ddca8S猫头猫                return;
71958992c6bS猫头猫            }
72025c1bd29S猫头猫            const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
72125c1bd29S猫头猫            if (oldVersionPlugin) {
72225c1bd29S猫头猫                if (
72325c1bd29S猫头猫                    compare(
72425c1bd29S猫头猫                        oldVersionPlugin.instance.version ?? '',
72525c1bd29S猫头猫                        plugin.instance.version ?? '',
72625c1bd29S猫头猫                        '>',
72725c1bd29S猫头猫                    )
72825c1bd29S猫头猫                ) {
72925c1bd29S猫头猫                    throw new Error('已安装更新版本的插件');
73025c1bd29S猫头猫                }
73125c1bd29S猫头猫            }
73225c1bd29S猫头猫
73358992c6bS猫头猫            if (plugin.hash !== '') {
73458992c6bS猫头猫                const fn = nanoid();
73558992c6bS猫头猫                const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
73658992c6bS猫头猫                await writeFile(_pluginPath, funcCode, 'utf8');
73758992c6bS猫头猫                plugin.path = _pluginPath;
73858992c6bS猫头猫                plugins = plugins.concat(plugin);
73925c1bd29S猫头猫                if (oldVersionPlugin) {
74025c1bd29S猫头猫                    plugins = plugins.filter(
74125c1bd29S猫头猫                        _ => _.hash !== oldVersionPlugin.hash,
74225c1bd29S猫头猫                    );
74325c1bd29S猫头猫                    try {
74425c1bd29S猫头猫                        await unlink(oldVersionPlugin.path);
74525c1bd29S猫头猫                    } catch {}
74625c1bd29S猫头猫                }
74758992c6bS猫头猫                pluginStateMapper.notify();
74858992c6bS猫头猫                return;
74958992c6bS猫头猫            }
75074acbfc0S猫头猫            throw new Error('插件无法解析!');
75158992c6bS猫头猫        }
75225c1bd29S猫头猫    } catch (e: any) {
753ea6d708fS猫头猫        devLog('error', 'URL安装插件失败', e, e?.message);
75458992c6bS猫头猫        errorLog('URL安装插件失败', e);
75525c1bd29S猫头猫        throw new Error(e?.message ?? '');
75658992c6bS猫头猫    }
75758992c6bS猫头猫}
75858992c6bS猫头猫
759927dbe93S猫头猫/** 卸载插件 */
760927dbe93S猫头猫async function uninstallPlugin(hash: string) {
761927dbe93S猫头猫    const targetIndex = plugins.findIndex(_ => _.hash === hash);
762927dbe93S猫头猫    if (targetIndex !== -1) {
763927dbe93S猫头猫        try {
76424e5e74aS猫头猫            const pluginName = plugins[targetIndex].name;
765927dbe93S猫头猫            await unlink(plugins[targetIndex].path);
766927dbe93S猫头猫            plugins = plugins.filter(_ => _.hash !== hash);
767927dbe93S猫头猫            pluginStateMapper.notify();
76824e5e74aS猫头猫            if (plugins.every(_ => _.name !== pluginName)) {
76924e5e74aS猫头猫                await MediaMeta.removePlugin(pluginName);
77024e5e74aS猫头猫            }
771927dbe93S猫头猫        } catch {}
772927dbe93S猫头猫    }
773927dbe93S猫头猫}
774927dbe93S猫头猫
77508882a77S猫头猫async function uninstallAllPlugins() {
77608882a77S猫头猫    await Promise.all(
77708882a77S猫头猫        plugins.map(async plugin => {
77808882a77S猫头猫            try {
77908882a77S猫头猫                const pluginName = plugin.name;
78008882a77S猫头猫                await unlink(plugin.path);
78108882a77S猫头猫                await MediaMeta.removePlugin(pluginName);
78208882a77S猫头猫            } catch (e) {}
78308882a77S猫头猫        }),
78408882a77S猫头猫    );
78508882a77S猫头猫    plugins = [];
78608882a77S猫头猫    pluginStateMapper.notify();
787e08d37a3S猫头猫
788e08d37a3S猫头猫    /** 清除空余文件,异步做就可以了 */
789e08d37a3S猫头猫    readDir(pathConst.pluginPath)
790e08d37a3S猫头猫        .then(fns => {
791e08d37a3S猫头猫            fns.forEach(fn => {
792e08d37a3S猫头猫                unlink(fn.path).catch(emptyFunction);
793e08d37a3S猫头猫            });
794e08d37a3S猫头猫        })
795e08d37a3S猫头猫        .catch(emptyFunction);
79608882a77S猫头猫}
79708882a77S猫头猫
79825c1bd29S猫头猫async function updatePlugin(plugin: Plugin) {
79925c1bd29S猫头猫    const updateUrl = plugin.instance.srcUrl;
80025c1bd29S猫头猫    if (!updateUrl) {
80125c1bd29S猫头猫        throw new Error('没有更新源');
80225c1bd29S猫头猫    }
80325c1bd29S猫头猫    try {
80425c1bd29S猫头猫        await installPluginFromUrl(updateUrl);
80525c1bd29S猫头猫    } catch (e: any) {
80625c1bd29S猫头猫        if (e.message === '插件已安装') {
80725c1bd29S猫头猫            throw new Error('当前已是最新版本');
80825c1bd29S猫头猫        } else {
80925c1bd29S猫头猫            throw e;
81025c1bd29S猫头猫        }
81125c1bd29S猫头猫    }
81225c1bd29S猫头猫}
81325c1bd29S猫头猫
814927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) {
8152c595535S猫头猫    return getByName(mediaItem?.platform);
816927dbe93S猫头猫}
817927dbe93S猫头猫
818927dbe93S猫头猫function getByHash(hash: string) {
8197993f90eS猫头猫    return hash === localPluginHash
8207993f90eS猫头猫        ? localFilePlugin
8217993f90eS猫头猫        : plugins.find(_ => _.hash === hash);
822927dbe93S猫头猫}
823927dbe93S猫头猫
824927dbe93S猫头猫function getByName(name: string) {
8257993f90eS猫头猫    return name === localPluginPlatform
8260e4173cdS猫头猫        ? localFilePlugin
8270e4173cdS猫头猫        : plugins.find(_ => _.name === name);
828927dbe93S猫头猫}
829927dbe93S猫头猫
830927dbe93S猫头猫function getValidPlugins() {
831927dbe93S猫头猫    return plugins.filter(_ => _.state === 'enabled');
832927dbe93S猫头猫}
833927dbe93S猫头猫
834efb9da24S猫头猫function getSearchablePlugins() {
835efb9da24S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.search);
836efb9da24S猫头猫}
837efb9da24S猫头猫
838e08d37a3S猫头猫function getSortedSearchablePlugins() {
839e08d37a3S猫头猫    return getSearchablePlugins().sort((a, b) =>
840e08d37a3S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
841e08d37a3S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
842e08d37a3S猫头猫        0
843e08d37a3S猫头猫            ? -1
844e08d37a3S猫头猫            : 1,
845e08d37a3S猫头猫    );
846e08d37a3S猫头猫}
847e08d37a3S猫头猫
84815feccc1S猫头猫function getTopListsablePlugins() {
84915feccc1S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists);
85015feccc1S猫头猫}
85115feccc1S猫头猫
85215feccc1S猫头猫function getSortedTopListsablePlugins() {
85315feccc1S猫头猫    return getTopListsablePlugins().sort((a, b) =>
85415feccc1S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
85515feccc1S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
85615feccc1S猫头猫        0
85715feccc1S猫头猫            ? -1
85815feccc1S猫头猫            : 1,
85915feccc1S猫头猫    );
86015feccc1S猫头猫}
86115feccc1S猫头猫
862e08d37a3S猫头猫function useSortedPlugins() {
863e08d37a3S猫头猫    const _plugins = pluginStateMapper.useMappedState();
864e08d37a3S猫头猫    const _pluginMetaAll = PluginMeta.usePluginMetaAll();
865e08d37a3S猫头猫
86634588741S猫头猫    const [sortedPlugins, setSortedPlugins] = useState(
86734588741S猫头猫        [..._plugins].sort((a, b) =>
868e08d37a3S猫头猫            (_pluginMetaAll[a.name]?.order ?? Infinity) -
869e08d37a3S猫头猫                (_pluginMetaAll[b.name]?.order ?? Infinity) <
870e08d37a3S猫头猫            0
871e08d37a3S猫头猫                ? -1
872e08d37a3S猫头猫                : 1,
87334588741S猫头猫        ),
874e08d37a3S猫头猫    );
87534588741S猫头猫
87634588741S猫头猫    useEffect(() => {
877d4cd40d8S猫头猫        InteractionManager.runAfterInteractions(() => {
87834588741S猫头猫            setSortedPlugins(
87934588741S猫头猫                [..._plugins].sort((a, b) =>
88034588741S猫头猫                    (_pluginMetaAll[a.name]?.order ?? Infinity) -
88134588741S猫头猫                        (_pluginMetaAll[b.name]?.order ?? Infinity) <
88234588741S猫头猫                    0
88334588741S猫头猫                        ? -1
88434588741S猫头猫                        : 1,
88534588741S猫头猫                ),
88634588741S猫头猫            );
887d4cd40d8S猫头猫        });
88834588741S猫头猫    }, [_plugins, _pluginMetaAll]);
88934588741S猫头猫
89034588741S猫头猫    return sortedPlugins;
891e08d37a3S猫头猫}
892e08d37a3S猫头猫
893927dbe93S猫头猫const PluginManager = {
894927dbe93S猫头猫    setup,
895927dbe93S猫头猫    installPlugin,
89658992c6bS猫头猫    installPluginFromUrl,
89725c1bd29S猫头猫    updatePlugin,
898927dbe93S猫头猫    uninstallPlugin,
899927dbe93S猫头猫    getByMedia,
900927dbe93S猫头猫    getByHash,
901927dbe93S猫头猫    getByName,
902927dbe93S猫头猫    getValidPlugins,
903efb9da24S猫头猫    getSearchablePlugins,
904e08d37a3S猫头猫    getSortedSearchablePlugins,
90515feccc1S猫头猫    getTopListsablePlugins,
90615feccc1S猫头猫    getSortedTopListsablePlugins,
9075276aef9S猫头猫    usePlugins: pluginStateMapper.useMappedState,
908e08d37a3S猫头猫    useSortedPlugins,
90908882a77S猫头猫    uninstallAllPlugins,
9105276aef9S猫头猫};
911927dbe93S猫头猫
912927dbe93S猫头猫export default PluginManager;
913