xref: /MusicFree/src/core/pluginManager.ts (revision b43683ea3d18f404bd628ebf9e369dee7829aaef)
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,
669c34d637S猫头猫    '@react-native-cookies/cookies': {
679c34d637S猫头猫        flush: CookieManager.flush,
689c34d637S猫头猫        get: CookieManager.get,
699c34d637S猫头猫    },
709c34d637S猫头猫};
719c34d637S猫头猫
72*b43683eaS猫头猫const _require = (packageName: string) => {
73*b43683eaS猫头猫    let pkg = packages[packageName];
74*b43683eaS猫头猫    pkg.default = pkg;
75*b43683eaS猫头猫    return pkg;
76*b43683eaS猫头猫};
779c34d637S猫头猫
78d5bfeb7eS猫头猫//#region 插件类
79927dbe93S猫头猫export class Plugin {
80927dbe93S猫头猫    /** 插件名 */
81927dbe93S猫头猫    public name: string;
82927dbe93S猫头猫    /** 插件的hash,作为唯一id */
83927dbe93S猫头猫    public hash: string;
84927dbe93S猫头猫    /** 插件状态:激活、关闭、错误 */
85927dbe93S猫头猫    public state: 'enabled' | 'disabled' | 'error';
86927dbe93S猫头猫    /** 插件支持的搜索类型 */
87927dbe93S猫头猫    public supportedSearchType?: string;
88927dbe93S猫头猫    /** 插件状态信息 */
89927dbe93S猫头猫    public stateCode?: PluginStateCode;
90927dbe93S猫头猫    /** 插件的实例 */
91927dbe93S猫头猫    public instance: IPlugin.IPluginInstance;
92927dbe93S猫头猫    /** 插件路径 */
93927dbe93S猫头猫    public path: string;
94927dbe93S猫头猫    /** 插件方法 */
95927dbe93S猫头猫    public methods: PluginMethods;
96d5bfeb7eS猫头猫    /** 用户输入 */
97d5bfeb7eS猫头猫    public userEnv?: Record<string, string>;
98927dbe93S猫头猫
9974d0cf81S猫头猫    constructor(
10074d0cf81S猫头猫        funcCode: string | (() => IPlugin.IPluginInstance),
10174d0cf81S猫头猫        pluginPath: string,
10274d0cf81S猫头猫    ) {
103927dbe93S猫头猫        this.state = 'enabled';
104927dbe93S猫头猫        let _instance: IPlugin.IPluginInstance;
1059c34d637S猫头猫        const _module = {exports: {}};
106927dbe93S猫头猫        try {
10774d0cf81S猫头猫            if (typeof funcCode === 'string') {
1084060c00aS猫头猫                // eslint-disable-next-line no-new-func
109927dbe93S猫头猫                _instance = Function(`
110927dbe93S猫头猫                    'use strict';
1119c34d637S猫头猫                    return function(require, __musicfree_require, module, exports) {
1129c34d637S猫头猫                        ${funcCode}
113927dbe93S猫头猫                    }
1149c34d637S猫头猫                `)()(_require, _require, _module, _module.exports);
115*b43683eaS猫头猫                console.log(_module);
1169c34d637S猫头猫                _instance = _module.exports as IPlugin.IPluginInstance;
11774d0cf81S猫头猫            } else {
11874d0cf81S猫头猫                _instance = funcCode();
11974d0cf81S猫头猫            }
120927dbe93S猫头猫            this.checkValid(_instance);
121927dbe93S猫头猫        } catch (e: any) {
122*b43683eaS猫头猫            console.log(e);
123927dbe93S猫头猫            this.state = 'error';
124927dbe93S猫头猫            this.stateCode = PluginStateCode.CannotParse;
125927dbe93S猫头猫            if (e?.stateCode) {
126927dbe93S猫头猫                this.stateCode = e.stateCode;
127927dbe93S猫头猫            }
128927dbe93S猫头猫            errorLog(`${pluginPath}插件无法解析 `, {
129927dbe93S猫头猫                stateCode: this.stateCode,
130927dbe93S猫头猫                message: e?.message,
131927dbe93S猫头猫                stack: e?.stack,
132927dbe93S猫头猫            });
133927dbe93S猫头猫            _instance = e?.instance ?? {
134927dbe93S猫头猫                _path: '',
135927dbe93S猫头猫                platform: '',
136927dbe93S猫头猫                appVersion: '',
13720e6a092S猫头猫                async getMediaSource() {
138927dbe93S猫头猫                    return null;
139927dbe93S猫头猫                },
140927dbe93S猫头猫                async search() {
141927dbe93S猫头猫                    return {};
142927dbe93S猫头猫                },
143927dbe93S猫头猫                async getAlbumInfo() {
144927dbe93S猫头猫                    return null;
145927dbe93S猫头猫                },
146927dbe93S猫头猫            };
147927dbe93S猫头猫        }
148927dbe93S猫头猫        this.instance = _instance;
149927dbe93S猫头猫        this.path = pluginPath;
150927dbe93S猫头猫        this.name = _instance.platform;
151927dbe93S猫头猫        if (this.instance.platform === '') {
152927dbe93S猫头猫            this.hash = '';
153927dbe93S猫头猫        } else {
15474d0cf81S猫头猫            if (typeof funcCode === 'string') {
155927dbe93S猫头猫                this.hash = sha256(funcCode).toString();
15674d0cf81S猫头猫            } else {
15774d0cf81S猫头猫                this.hash = sha256(funcCode.toString()).toString();
15874d0cf81S猫头猫            }
159927dbe93S猫头猫        }
160927dbe93S猫头猫
161927dbe93S猫头猫        // 放在最后
162927dbe93S猫头猫        this.methods = new PluginMethods(this);
163927dbe93S猫头猫    }
164927dbe93S猫头猫
165927dbe93S猫头猫    private checkValid(_instance: IPlugin.IPluginInstance) {
166927dbe93S猫头猫        /** 版本号校验 */
167927dbe93S猫头猫        if (
168927dbe93S猫头猫            _instance.appVersion &&
169927dbe93S猫头猫            !satisfies(DeviceInfo.getVersion(), _instance.appVersion)
170927dbe93S猫头猫        ) {
171927dbe93S猫头猫            throw {
172927dbe93S猫头猫                instance: _instance,
173927dbe93S猫头猫                stateCode: PluginStateCode.VersionNotMatch,
174927dbe93S猫头猫            };
175927dbe93S猫头猫        }
176927dbe93S猫头猫        return true;
177927dbe93S猫头猫    }
178927dbe93S猫头猫}
179d5bfeb7eS猫头猫//#endregion
180927dbe93S猫头猫
181d5bfeb7eS猫头猫//#region 基于插件类封装的方法,供给APP侧直接调用
182927dbe93S猫头猫/** 有缓存等信息 */
183927dbe93S猫头猫class PluginMethods implements IPlugin.IPluginInstanceMethods {
184927dbe93S猫头猫    private plugin;
185927dbe93S猫头猫    constructor(plugin: Plugin) {
186927dbe93S猫头猫        this.plugin = plugin;
187927dbe93S猫头猫    }
188927dbe93S猫头猫    /** 搜索 */
189927dbe93S猫头猫    async search<T extends ICommon.SupportMediaType>(
190927dbe93S猫头猫        query: string,
191927dbe93S猫头猫        page: number,
192927dbe93S猫头猫        type: T,
193927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
194927dbe93S猫头猫        if (!this.plugin.instance.search) {
195927dbe93S猫头猫            return {
196927dbe93S猫头猫                isEnd: true,
197927dbe93S猫头猫                data: [],
198927dbe93S猫头猫            };
199927dbe93S猫头猫        }
200927dbe93S猫头猫
2014060c00aS猫头猫        const result =
2024060c00aS猫头猫            (await this.plugin.instance.search(query, page, type)) ?? {};
203927dbe93S猫头猫        if (Array.isArray(result.data)) {
204927dbe93S猫头猫            result.data.forEach(_ => {
205927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
206927dbe93S猫头猫            });
207927dbe93S猫头猫            return {
208927dbe93S猫头猫                isEnd: result.isEnd ?? true,
209927dbe93S猫头猫                data: result.data,
210927dbe93S猫头猫            };
211927dbe93S猫头猫        }
212927dbe93S猫头猫        return {
213927dbe93S猫头猫            isEnd: true,
214927dbe93S猫头猫            data: [],
215927dbe93S猫头猫        };
216927dbe93S猫头猫    }
217927dbe93S猫头猫
218927dbe93S猫头猫    /** 获取真实源 */
21920e6a092S猫头猫    async getMediaSource(
220927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
221abaede57S猫头猫        quality: IMusic.IQualityKey = 'standard',
222927dbe93S猫头猫        retryCount = 1,
223dc160d50S猫头猫        notUpdateCache = false,
224192ae2b0S猫头猫    ): Promise<IPlugin.IMediaSourceResult | null> {
225927dbe93S猫头猫        // 1. 本地搜索 其实直接读mediameta就好了
226927dbe93S猫头猫        const localPath =
2270e4173cdS猫头猫            getInternalData<string>(musicItem, InternalDataType.LOCALPATH) ??
2280e4173cdS猫头猫            getInternalData<string>(
2290e4173cdS猫头猫                LocalMusicSheet.isLocalMusic(musicItem),
2300e4173cdS猫头猫                InternalDataType.LOCALPATH,
2310e4173cdS猫头猫            );
2320e4173cdS猫头猫        if (localPath && (await FileSystem.exists(localPath))) {
2330e4173cdS猫头猫            trace('本地播放', localPath);
234927dbe93S猫头猫            return {
235927dbe93S猫头猫                url: localPath,
236927dbe93S猫头猫            };
237927dbe93S猫头猫        }
2387993f90eS猫头猫        if (musicItem.platform === localPluginPlatform) {
239f5935920S猫头猫            throw new Error('本地音乐不存在');
240f5935920S猫头猫        }
241927dbe93S猫头猫        // 2. 缓存播放
242927dbe93S猫头猫        const mediaCache = Cache.get(musicItem);
243985f8e75S猫头猫        const pluginCacheControl =
244985f8e75S猫头猫            this.plugin.instance.cacheControl ?? 'no-cache';
245cfa0fc07S猫头猫        if (
246cfa0fc07S猫头猫            mediaCache &&
247abaede57S猫头猫            mediaCache?.qualities?.[quality]?.url &&
24848f4b873S猫头猫            (pluginCacheControl === CacheControl.Cache ||
24948f4b873S猫头猫                (pluginCacheControl === CacheControl.NoCache &&
250ef714860S猫头猫                    Network.isOffline()))
251cfa0fc07S猫头猫        ) {
2525276aef9S猫头猫            trace('播放', '缓存播放');
253abaede57S猫头猫            const qualityInfo = mediaCache.qualities[quality];
254927dbe93S猫头猫            return {
255abaede57S猫头猫                url: qualityInfo.url,
256927dbe93S猫头猫                headers: mediaCache.headers,
2574060c00aS猫头猫                userAgent:
2584060c00aS猫头猫                    mediaCache.userAgent ?? mediaCache.headers?.['user-agent'],
259927dbe93S猫头猫            };
260927dbe93S猫头猫        }
261927dbe93S猫头猫        // 3. 插件解析
26220e6a092S猫头猫        if (!this.plugin.instance.getMediaSource) {
263abaede57S猫头猫            return {url: musicItem?.qualities?.[quality]?.url ?? musicItem.url};
264927dbe93S猫头猫        }
265927dbe93S猫头猫        try {
266abaede57S猫头猫            const {url, headers} = (await this.plugin.instance.getMediaSource(
267abaede57S猫头猫                musicItem,
268abaede57S猫头猫                quality,
269abaede57S猫头猫            )) ?? {url: musicItem?.qualities?.[quality]?.url};
270927dbe93S猫头猫            if (!url) {
271a28eac61S猫头猫                throw new Error('NOT RETRY');
272927dbe93S猫头猫            }
2735276aef9S猫头猫            trace('播放', '插件播放');
274927dbe93S猫头猫            const result = {
275927dbe93S猫头猫                url,
276927dbe93S猫头猫                headers,
277927dbe93S猫头猫                userAgent: headers?.['user-agent'],
278cfa0fc07S猫头猫            } as IPlugin.IMediaSourceResult;
279927dbe93S猫头猫
280dc160d50S猫头猫            if (
281dc160d50S猫头猫                pluginCacheControl !== CacheControl.NoStore &&
282dc160d50S猫头猫                !notUpdateCache
283dc160d50S猫头猫            ) {
284abaede57S猫头猫                Cache.update(musicItem, [
285abaede57S猫头猫                    ['headers', result.headers],
286abaede57S猫头猫                    ['userAgent', result.userAgent],
287abaede57S猫头猫                    [`qualities.${quality}.url`, url],
288abaede57S猫头猫                ]);
289752ffc5aS猫头猫            }
290cfa0fc07S猫头猫
291927dbe93S猫头猫            return result;
292927dbe93S猫头猫        } catch (e: any) {
293a28eac61S猫头猫            if (retryCount > 0 && e?.message !== 'NOT RETRY') {
294927dbe93S猫头猫                await delay(150);
295abaede57S猫头猫                return this.getMediaSource(musicItem, quality, --retryCount);
296927dbe93S猫头猫            }
297927dbe93S猫头猫            errorLog('获取真实源失败', e?.message);
298ea6d708fS猫头猫            devLog('error', '获取真实源失败', e, e?.message);
299192ae2b0S猫头猫            return null;
300927dbe93S猫头猫        }
301927dbe93S猫头猫    }
302927dbe93S猫头猫
303927dbe93S猫头猫    /** 获取音乐详情 */
304927dbe93S猫头猫    async getMusicInfo(
305927dbe93S猫头猫        musicItem: ICommon.IMediaBase,
30674d0cf81S猫头猫    ): Promise<Partial<IMusic.IMusicItem> | null> {
307927dbe93S猫头猫        if (!this.plugin.instance.getMusicInfo) {
308d704daedS猫头猫            return null;
309927dbe93S猫头猫        }
31074d0cf81S猫头猫        try {
311927dbe93S猫头猫            return (
312927dbe93S猫头猫                this.plugin.instance.getMusicInfo(
3137993f90eS猫头猫                    resetMediaItem(musicItem, undefined, true),
314d704daedS猫头猫                ) ?? null
315927dbe93S猫头猫            );
316ea6d708fS猫头猫        } catch (e: any) {
317ea6d708fS猫头猫            devLog('error', '获取音乐详情失败', e, e?.message);
318d704daedS猫头猫            return null;
31974d0cf81S猫头猫        }
320927dbe93S猫头猫    }
321927dbe93S猫头猫
322927dbe93S猫头猫    /** 获取歌词 */
323927dbe93S猫头猫    async getLyric(
324927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
325927dbe93S猫头猫        from?: IMusic.IMusicItemBase,
326927dbe93S猫头猫    ): Promise<ILyric.ILyricSource | null> {
327927dbe93S猫头猫        // 1.额外存储的meta信息
328927dbe93S猫头猫        const meta = MediaMeta.get(musicItem);
329927dbe93S猫头猫        if (meta && meta.associatedLrc) {
330927dbe93S猫头猫            // 有关联歌词
331927dbe93S猫头猫            if (
332927dbe93S猫头猫                isSameMediaItem(musicItem, from) ||
333927dbe93S猫头猫                isSameMediaItem(meta.associatedLrc, musicItem)
334927dbe93S猫头猫            ) {
335927dbe93S猫头猫                // 形成环路,断开当前的环
336927dbe93S猫头猫                await MediaMeta.update(musicItem, {
337927dbe93S猫头猫                    associatedLrc: undefined,
338927dbe93S猫头猫                });
339927dbe93S猫头猫                // 无歌词
340927dbe93S猫头猫                return null;
341927dbe93S猫头猫            }
342927dbe93S猫头猫            // 获取关联歌词
3437a91f04fS猫头猫            const associatedMeta = MediaMeta.get(meta.associatedLrc) ?? {};
3444060c00aS猫头猫            const result = await this.getLyric(
3457a91f04fS猫头猫                {...meta.associatedLrc, ...associatedMeta},
3464060c00aS猫头猫                from ?? musicItem,
3474060c00aS猫头猫            );
348927dbe93S猫头猫            if (result) {
349927dbe93S猫头猫                // 如果有关联歌词,就返回关联歌词,深度优先
350927dbe93S猫头猫                return result;
351927dbe93S猫头猫            }
352927dbe93S猫头猫        }
353927dbe93S猫头猫        const cache = Cache.get(musicItem);
354927dbe93S猫头猫        let rawLrc = meta?.rawLrc || musicItem.rawLrc || cache?.rawLrc;
355927dbe93S猫头猫        let lrcUrl = meta?.lrc || musicItem.lrc || cache?.lrc;
356927dbe93S猫头猫        // 如果存在文本
357927dbe93S猫头猫        if (rawLrc) {
358927dbe93S猫头猫            return {
359927dbe93S猫头猫                rawLrc,
360927dbe93S猫头猫                lrc: lrcUrl,
361927dbe93S猫头猫            };
362927dbe93S猫头猫        }
363927dbe93S猫头猫        // 2.本地缓存
364927dbe93S猫头猫        const localLrc =
3650e4173cdS猫头猫            meta?.[internalSerializeKey]?.local?.localLrc ||
3660e4173cdS猫头猫            cache?.[internalSerializeKey]?.local?.localLrc;
367927dbe93S猫头猫        if (localLrc && (await exists(localLrc))) {
368927dbe93S猫头猫            rawLrc = await readFile(localLrc, 'utf8');
369927dbe93S猫头猫            return {
370927dbe93S猫头猫                rawLrc,
371927dbe93S猫头猫                lrc: lrcUrl,
372927dbe93S猫头猫            };
373927dbe93S猫头猫        }
374927dbe93S猫头猫        // 3.优先使用url
375927dbe93S猫头猫        if (lrcUrl) {
376927dbe93S猫头猫            try {
377927dbe93S猫头猫                // 需要超时时间 axios timeout 但是没生效
3782a3194f5S猫头猫                rawLrc = (await axios.get(lrcUrl, {timeout: 1500})).data;
379927dbe93S猫头猫                return {
380927dbe93S猫头猫                    rawLrc,
381927dbe93S猫头猫                    lrc: lrcUrl,
382927dbe93S猫头猫                };
383927dbe93S猫头猫            } catch {
384927dbe93S猫头猫                lrcUrl = undefined;
385927dbe93S猫头猫            }
386927dbe93S猫头猫        }
387927dbe93S猫头猫        // 4. 如果地址失效
388927dbe93S猫头猫        if (!lrcUrl) {
389927dbe93S猫头猫            // 插件获得url
390927dbe93S猫头猫            try {
3917a91f04fS猫头猫                let lrcSource;
3927a91f04fS猫头猫                if (from) {
3937a91f04fS猫头猫                    lrcSource = await PluginManager.getByMedia(
3947a91f04fS猫头猫                        musicItem,
3957a91f04fS猫头猫                    )?.instance?.getLyric?.(
396927dbe93S猫头猫                        resetMediaItem(musicItem, undefined, true),
397927dbe93S猫头猫                    );
3987a91f04fS猫头猫                } else {
3997a91f04fS猫头猫                    lrcSource = await this.plugin.instance?.getLyric?.(
4007a91f04fS猫头猫                        resetMediaItem(musicItem, undefined, true),
4017a91f04fS猫头猫                    );
4027a91f04fS猫头猫                }
4037a91f04fS猫头猫
404927dbe93S猫头猫                rawLrc = lrcSource?.rawLrc;
405927dbe93S猫头猫                lrcUrl = lrcSource?.lrc;
406927dbe93S猫头猫            } catch (e: any) {
407927dbe93S猫头猫                trace('插件获取歌词失败', e?.message, 'error');
408ea6d708fS猫头猫                devLog('error', '插件获取歌词失败', e, e?.message);
409927dbe93S猫头猫            }
410927dbe93S猫头猫        }
411927dbe93S猫头猫        // 5. 最后一次请求
412927dbe93S猫头猫        if (rawLrc || lrcUrl) {
413927dbe93S猫头猫            const filename = `${pathConst.lrcCachePath}${nanoid()}.lrc`;
414927dbe93S猫头猫            if (lrcUrl) {
415927dbe93S猫头猫                try {
4162a3194f5S猫头猫                    rawLrc = (await axios.get(lrcUrl, {timeout: 1500})).data;
417927dbe93S猫头猫                } catch {}
418927dbe93S猫头猫            }
419927dbe93S猫头猫            if (rawLrc) {
420927dbe93S猫头猫                await writeFile(filename, rawLrc, 'utf8');
421927dbe93S猫头猫                // 写入缓存
422927dbe93S猫头猫                Cache.update(musicItem, [
4230e4173cdS猫头猫                    [`${internalSerializeKey}.local.localLrc`, filename],
424927dbe93S猫头猫                ]);
425927dbe93S猫头猫                // 如果有meta
426927dbe93S猫头猫                if (meta) {
427927dbe93S猫头猫                    MediaMeta.update(musicItem, [
4280e4173cdS猫头猫                        [`${internalSerializeKey}.local.localLrc`, filename],
429927dbe93S猫头猫                    ]);
430927dbe93S猫头猫                }
431927dbe93S猫头猫                return {
432927dbe93S猫头猫                    rawLrc,
433927dbe93S猫头猫                    lrc: lrcUrl,
434927dbe93S猫头猫                };
435927dbe93S猫头猫            }
436927dbe93S猫头猫        }
4373a6f67b1S猫头猫        // 6. 如果是本地文件
4383a6f67b1S猫头猫        const isDownloaded = LocalMusicSheet.isLocalMusic(musicItem);
4393a6f67b1S猫头猫        if (musicItem.platform !== localPluginPlatform && isDownloaded) {
4403a6f67b1S猫头猫            const res = await localFilePlugin.instance!.getLyric!(isDownloaded);
4413a6f67b1S猫头猫            if (res) {
4423a6f67b1S猫头猫                return res;
4433a6f67b1S猫头猫            }
4443a6f67b1S猫头猫        }
445ea6d708fS猫头猫        devLog('warn', '无歌词');
446927dbe93S猫头猫
447927dbe93S猫头猫        return null;
448927dbe93S猫头猫    }
449927dbe93S猫头猫
450927dbe93S猫头猫    /** 获取歌词文本 */
451927dbe93S猫头猫    async getLyricText(
452927dbe93S猫头猫        musicItem: IMusic.IMusicItem,
453927dbe93S猫头猫    ): Promise<string | undefined> {
454927dbe93S猫头猫        return (await this.getLyric(musicItem))?.rawLrc;
455927dbe93S猫头猫    }
456927dbe93S猫头猫
457927dbe93S猫头猫    /** 获取专辑信息 */
458927dbe93S猫头猫    async getAlbumInfo(
459927dbe93S猫头猫        albumItem: IAlbum.IAlbumItemBase,
460927dbe93S猫头猫    ): Promise<IAlbum.IAlbumItem | null> {
461927dbe93S猫头猫        if (!this.plugin.instance.getAlbumInfo) {
462927dbe93S猫头猫            return {...albumItem, musicList: []};
463927dbe93S猫头猫        }
464927dbe93S猫头猫        try {
465927dbe93S猫头猫            const result = await this.plugin.instance.getAlbumInfo(
466927dbe93S猫头猫                resetMediaItem(albumItem, undefined, true),
467927dbe93S猫头猫            );
4685276aef9S猫头猫            if (!result) {
4695276aef9S猫头猫                throw new Error();
4705276aef9S猫头猫            }
471927dbe93S猫头猫            result?.musicList?.forEach(_ => {
472927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
473927dbe93S猫头猫            });
4745276aef9S猫头猫
4755276aef9S猫头猫            return {...albumItem, ...result};
4764394410dS猫头猫        } catch (e: any) {
4774394410dS猫头猫            trace('获取专辑信息失败', e?.message);
478ea6d708fS猫头猫            devLog('error', '获取专辑信息失败', e, e?.message);
479ea6d708fS猫头猫
480927dbe93S猫头猫            return {...albumItem, musicList: []};
481927dbe93S猫头猫        }
482927dbe93S猫头猫    }
483927dbe93S猫头猫
484927dbe93S猫头猫    /** 查询作者信息 */
485efb9da24S猫头猫    async getArtistWorks<T extends IArtist.ArtistMediaType>(
486927dbe93S猫头猫        artistItem: IArtist.IArtistItem,
487927dbe93S猫头猫        page: number,
488927dbe93S猫头猫        type: T,
489927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
490efb9da24S猫头猫        if (!this.plugin.instance.getArtistWorks) {
491927dbe93S猫头猫            return {
492927dbe93S猫头猫                isEnd: true,
493927dbe93S猫头猫                data: [],
494927dbe93S猫头猫            };
495927dbe93S猫头猫        }
496927dbe93S猫头猫        try {
497efb9da24S猫头猫            const result = await this.plugin.instance.getArtistWorks(
498927dbe93S猫头猫                artistItem,
499927dbe93S猫头猫                page,
500927dbe93S猫头猫                type,
501927dbe93S猫头猫            );
502927dbe93S猫头猫            if (!result.data) {
503927dbe93S猫头猫                return {
504927dbe93S猫头猫                    isEnd: true,
505927dbe93S猫头猫                    data: [],
506927dbe93S猫头猫                };
507927dbe93S猫头猫            }
508927dbe93S猫头猫            result.data?.forEach(_ => resetMediaItem(_, this.plugin.name));
509927dbe93S猫头猫            return {
510927dbe93S猫头猫                isEnd: result.isEnd ?? true,
511927dbe93S猫头猫                data: result.data,
512927dbe93S猫头猫            };
5134394410dS猫头猫        } catch (e: any) {
5144394410dS猫头猫            trace('查询作者信息失败', e?.message);
515ea6d708fS猫头猫            devLog('error', '查询作者信息失败', e, e?.message);
516ea6d708fS猫头猫
517927dbe93S猫头猫            throw e;
518927dbe93S猫头猫        }
519927dbe93S猫头猫    }
52008380090S猫头猫
52108380090S猫头猫    /** 导入歌单 */
52208380090S猫头猫    async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> {
52308380090S猫头猫        try {
52408380090S猫头猫            const result =
52508380090S猫头猫                (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? [];
52608380090S猫头猫            result.forEach(_ => resetMediaItem(_, this.plugin.name));
52708380090S猫头猫            return result;
528ea6d708fS猫头猫        } catch (e: any) {
5290e4173cdS猫头猫            console.log(e);
530ea6d708fS猫头猫            devLog('error', '导入歌单失败', e, e?.message);
531ea6d708fS猫头猫
53208380090S猫头猫            return [];
53308380090S猫头猫        }
53408380090S猫头猫    }
5354d9d3c4cS猫头猫    /** 导入单曲 */
5364d9d3c4cS猫头猫    async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> {
5374d9d3c4cS猫头猫        try {
5384d9d3c4cS猫头猫            const result = await this.plugin.instance?.importMusicItem?.(
5394d9d3c4cS猫头猫                urlLike,
5404d9d3c4cS猫头猫            );
5414d9d3c4cS猫头猫            if (!result) {
5424d9d3c4cS猫头猫                throw new Error();
5434d9d3c4cS猫头猫            }
5444d9d3c4cS猫头猫            resetMediaItem(result, this.plugin.name);
5454d9d3c4cS猫头猫            return result;
546ea6d708fS猫头猫        } catch (e: any) {
547ea6d708fS猫头猫            devLog('error', '导入单曲失败', e, e?.message);
548ea6d708fS猫头猫
5494d9d3c4cS猫头猫            return null;
5504d9d3c4cS猫头猫        }
5514d9d3c4cS猫头猫    }
552d52aa40eS猫头猫    /** 获取榜单 */
553d52aa40eS猫头猫    async getTopLists(): Promise<IMusic.IMusicTopListGroupItem[]> {
554d52aa40eS猫头猫        try {
555d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopLists?.();
556d52aa40eS猫头猫            if (!result) {
557d52aa40eS猫头猫                throw new Error();
558d52aa40eS猫头猫            }
559d52aa40eS猫头猫            return result;
560d52aa40eS猫头猫        } catch (e: any) {
561d52aa40eS猫头猫            devLog('error', '获取榜单失败', e, e?.message);
562d52aa40eS猫头猫            return [];
563d52aa40eS猫头猫        }
564d52aa40eS猫头猫    }
565d52aa40eS猫头猫    /** 获取榜单详情 */
566d52aa40eS猫头猫    async getTopListDetail(
567d52aa40eS猫头猫        topListItem: IMusic.IMusicTopListItem,
568d52aa40eS猫头猫    ): Promise<ICommon.WithMusicList<IMusic.IMusicTopListItem>> {
569d52aa40eS猫头猫        try {
570d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopListDetail?.(
571d52aa40eS猫头猫                topListItem,
572d52aa40eS猫头猫            );
573d52aa40eS猫头猫            if (!result) {
574d52aa40eS猫头猫                throw new Error();
575d52aa40eS猫头猫            }
576d384662fS猫头猫            if (result.musicList) {
577d384662fS猫头猫                result.musicList.forEach(_ =>
578d384662fS猫头猫                    resetMediaItem(_, this.plugin.name),
579d384662fS猫头猫                );
580d384662fS猫头猫            }
581d52aa40eS猫头猫            return result;
582d52aa40eS猫头猫        } catch (e: any) {
583d52aa40eS猫头猫            devLog('error', '获取榜单详情失败', e, e?.message);
584d52aa40eS猫头猫            return {
585d52aa40eS猫头猫                ...topListItem,
586d52aa40eS猫头猫                musicList: [],
587d52aa40eS猫头猫            };
588d52aa40eS猫头猫        }
589d52aa40eS猫头猫    }
590927dbe93S猫头猫}
591d5bfeb7eS猫头猫//#endregion
5921a5528a0S猫头猫
593927dbe93S猫头猫let plugins: Array<Plugin> = [];
594927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins);
59574d0cf81S猫头猫
596d5bfeb7eS猫头猫//#region 本地音乐插件
59774d0cf81S猫头猫/** 本地插件 */
59874d0cf81S猫头猫const localFilePlugin = new Plugin(function () {
5990e4173cdS猫头猫    return {
600d5bfeb7eS猫头猫        platform: localPluginPlatform,
60174d0cf81S猫头猫        _path: '',
60274d0cf81S猫头猫        async getMusicInfo(musicBase) {
60374d0cf81S猫头猫            const localPath = getInternalData<string>(
60474d0cf81S猫头猫                musicBase,
60574d0cf81S猫头猫                InternalDataType.LOCALPATH,
6060e4173cdS猫头猫            );
60774d0cf81S猫头猫            if (localPath) {
60874d0cf81S猫头猫                const coverImg = await Mp3Util.getMediaCoverImg(localPath);
60974d0cf81S猫头猫                return {
61074d0cf81S猫头猫                    artwork: coverImg,
61174d0cf81S猫头猫                };
61274d0cf81S猫头猫            }
61374d0cf81S猫头猫            return null;
61474d0cf81S猫头猫        },
6157993f90eS猫头猫        async getLyric(musicBase) {
6167993f90eS猫头猫            const localPath = getInternalData<string>(
6177993f90eS猫头猫                musicBase,
6187993f90eS猫头猫                InternalDataType.LOCALPATH,
6197993f90eS猫头猫            );
6203a6f67b1S猫头猫            let rawLrc: string | null = null;
6217993f90eS猫头猫            if (localPath) {
6223a6f67b1S猫头猫                // 读取内嵌歌词
6233a6f67b1S猫头猫                try {
6243a6f67b1S猫头猫                    rawLrc = await Mp3Util.getLyric(localPath);
6253a6f67b1S猫头猫                } catch (e) {
6263a6f67b1S猫头猫                    console.log('e', e);
6277993f90eS猫头猫                }
6283a6f67b1S猫头猫                if (!rawLrc) {
6293a6f67b1S猫头猫                    // 读取配置歌词
6303a6f67b1S猫头猫                    const lastDot = localPath.lastIndexOf('.');
6313a6f67b1S猫头猫                    const lrcPath = localPath.slice(0, lastDot) + '.lrc';
6323a6f67b1S猫头猫
6333a6f67b1S猫头猫                    try {
6343a6f67b1S猫头猫                        if (await exists(lrcPath)) {
6353a6f67b1S猫头猫                            rawLrc = await readFile(lrcPath, 'utf8');
6363a6f67b1S猫头猫                        }
6373a6f67b1S猫头猫                    } catch {}
6383a6f67b1S猫头猫                }
6393a6f67b1S猫头猫            }
6403a6f67b1S猫头猫
6413a6f67b1S猫头猫            return rawLrc
6423a6f67b1S猫头猫                ? {
6433a6f67b1S猫头猫                      rawLrc,
6443a6f67b1S猫头猫                  }
6453a6f67b1S猫头猫                : null;
6467993f90eS猫头猫        },
64774d0cf81S猫头猫    };
64874d0cf81S猫头猫}, '');
6497993f90eS猫头猫localFilePlugin.hash = localPluginHash;
650927dbe93S猫头猫
651d5bfeb7eS猫头猫//#endregion
652d5bfeb7eS猫头猫
653927dbe93S猫头猫async function setup() {
654927dbe93S猫头猫    const _plugins: Array<Plugin> = [];
655927dbe93S猫头猫    try {
656927dbe93S猫头猫        // 加载插件
657927dbe93S猫头猫        const pluginsPaths = await readDir(pathConst.pluginPath);
658927dbe93S猫头猫        for (let i = 0; i < pluginsPaths.length; ++i) {
659927dbe93S猫头猫            const _pluginUrl = pluginsPaths[i];
6601e263108S猫头猫            trace('初始化插件', _pluginUrl);
6611e263108S猫头猫            if (
6621e263108S猫头猫                _pluginUrl.isFile() &&
6631e263108S猫头猫                (_pluginUrl.name?.endsWith?.('.js') ||
6641e263108S猫头猫                    _pluginUrl.path?.endsWith?.('.js'))
6651e263108S猫头猫            ) {
666927dbe93S猫头猫                const funcCode = await readFile(_pluginUrl.path, 'utf8');
667927dbe93S猫头猫                const plugin = new Plugin(funcCode, _pluginUrl.path);
6684060c00aS猫头猫                const _pluginIndex = _plugins.findIndex(
6694060c00aS猫头猫                    p => p.hash === plugin.hash,
6704060c00aS猫头猫                );
671927dbe93S猫头猫                if (_pluginIndex !== -1) {
672927dbe93S猫头猫                    // 重复插件,直接忽略
673927dbe93S猫头猫                    return;
674927dbe93S猫头猫                }
675927dbe93S猫头猫                plugin.hash !== '' && _plugins.push(plugin);
676927dbe93S猫头猫            }
677927dbe93S猫头猫        }
678927dbe93S猫头猫
679927dbe93S猫头猫        plugins = _plugins;
680927dbe93S猫头猫        pluginStateMapper.notify();
681e08d37a3S猫头猫        /** 初始化meta信息 */
682e08d37a3S猫头猫        PluginMeta.setupMeta(plugins.map(_ => _.name));
683927dbe93S猫头猫    } catch (e: any) {
6844060c00aS猫头猫        ToastAndroid.show(
6854060c00aS猫头猫            `插件初始化失败:${e?.message ?? e}`,
6864060c00aS猫头猫            ToastAndroid.LONG,
6874060c00aS猫头猫        );
6881a5528a0S猫头猫        errorLog('插件初始化失败', e?.message);
689927dbe93S猫头猫        throw e;
690927dbe93S猫头猫    }
691927dbe93S猫头猫}
692927dbe93S猫头猫
693927dbe93S猫头猫// 安装插件
694927dbe93S猫头猫async function installPlugin(pluginPath: string) {
69522c09412S猫头猫    // if (pluginPath.endsWith('.js')) {
696927dbe93S猫头猫    const funcCode = await readFile(pluginPath, 'utf8');
697927dbe93S猫头猫    const plugin = new Plugin(funcCode, pluginPath);
698927dbe93S猫头猫    const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
699927dbe93S猫头猫    if (_pluginIndex !== -1) {
7004d9d3c4cS猫头猫        throw new Error('插件已安装');
701927dbe93S猫头猫    }
702927dbe93S猫头猫    if (plugin.hash !== '') {
703927dbe93S猫头猫        const fn = nanoid();
704927dbe93S猫头猫        const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
705927dbe93S猫头猫        await copyFile(pluginPath, _pluginPath);
706927dbe93S猫头猫        plugin.path = _pluginPath;
707927dbe93S猫头猫        plugins = plugins.concat(plugin);
708927dbe93S猫头猫        pluginStateMapper.notify();
7094d9d3c4cS猫头猫        return;
710927dbe93S猫头猫    }
7114d9d3c4cS猫头猫    throw new Error('插件无法解析');
71222c09412S猫头猫    // }
71322c09412S猫头猫    // throw new Error('插件不存在');
714927dbe93S猫头猫}
715927dbe93S猫头猫
71658992c6bS猫头猫async function installPluginFromUrl(url: string) {
71758992c6bS猫头猫    try {
71858992c6bS猫头猫        const funcCode = (await axios.get(url)).data;
71958992c6bS猫头猫        if (funcCode) {
72058992c6bS猫头猫            const plugin = new Plugin(funcCode, '');
72158992c6bS猫头猫            const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
72258992c6bS猫头猫            if (_pluginIndex !== -1) {
7238b7ddca8S猫头猫                // 静默忽略
7248b7ddca8S猫头猫                return;
72558992c6bS猫头猫            }
72625c1bd29S猫头猫            const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
72725c1bd29S猫头猫            if (oldVersionPlugin) {
72825c1bd29S猫头猫                if (
72925c1bd29S猫头猫                    compare(
73025c1bd29S猫头猫                        oldVersionPlugin.instance.version ?? '',
73125c1bd29S猫头猫                        plugin.instance.version ?? '',
73225c1bd29S猫头猫                        '>',
73325c1bd29S猫头猫                    )
73425c1bd29S猫头猫                ) {
73525c1bd29S猫头猫                    throw new Error('已安装更新版本的插件');
73625c1bd29S猫头猫                }
73725c1bd29S猫头猫            }
73825c1bd29S猫头猫
73958992c6bS猫头猫            if (plugin.hash !== '') {
74058992c6bS猫头猫                const fn = nanoid();
74158992c6bS猫头猫                const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
74258992c6bS猫头猫                await writeFile(_pluginPath, funcCode, 'utf8');
74358992c6bS猫头猫                plugin.path = _pluginPath;
74458992c6bS猫头猫                plugins = plugins.concat(plugin);
74525c1bd29S猫头猫                if (oldVersionPlugin) {
74625c1bd29S猫头猫                    plugins = plugins.filter(
74725c1bd29S猫头猫                        _ => _.hash !== oldVersionPlugin.hash,
74825c1bd29S猫头猫                    );
74925c1bd29S猫头猫                    try {
75025c1bd29S猫头猫                        await unlink(oldVersionPlugin.path);
75125c1bd29S猫头猫                    } catch {}
75225c1bd29S猫头猫                }
75358992c6bS猫头猫                pluginStateMapper.notify();
75458992c6bS猫头猫                return;
75558992c6bS猫头猫            }
75674acbfc0S猫头猫            throw new Error('插件无法解析!');
75758992c6bS猫头猫        }
75825c1bd29S猫头猫    } catch (e: any) {
759ea6d708fS猫头猫        devLog('error', 'URL安装插件失败', e, e?.message);
76058992c6bS猫头猫        errorLog('URL安装插件失败', e);
76125c1bd29S猫头猫        throw new Error(e?.message ?? '');
76258992c6bS猫头猫    }
76358992c6bS猫头猫}
76458992c6bS猫头猫
765927dbe93S猫头猫/** 卸载插件 */
766927dbe93S猫头猫async function uninstallPlugin(hash: string) {
767927dbe93S猫头猫    const targetIndex = plugins.findIndex(_ => _.hash === hash);
768927dbe93S猫头猫    if (targetIndex !== -1) {
769927dbe93S猫头猫        try {
77024e5e74aS猫头猫            const pluginName = plugins[targetIndex].name;
771927dbe93S猫头猫            await unlink(plugins[targetIndex].path);
772927dbe93S猫头猫            plugins = plugins.filter(_ => _.hash !== hash);
773927dbe93S猫头猫            pluginStateMapper.notify();
77424e5e74aS猫头猫            if (plugins.every(_ => _.name !== pluginName)) {
77524e5e74aS猫头猫                await MediaMeta.removePlugin(pluginName);
77624e5e74aS猫头猫            }
777927dbe93S猫头猫        } catch {}
778927dbe93S猫头猫    }
779927dbe93S猫头猫}
780927dbe93S猫头猫
78108882a77S猫头猫async function uninstallAllPlugins() {
78208882a77S猫头猫    await Promise.all(
78308882a77S猫头猫        plugins.map(async plugin => {
78408882a77S猫头猫            try {
78508882a77S猫头猫                const pluginName = plugin.name;
78608882a77S猫头猫                await unlink(plugin.path);
78708882a77S猫头猫                await MediaMeta.removePlugin(pluginName);
78808882a77S猫头猫            } catch (e) {}
78908882a77S猫头猫        }),
79008882a77S猫头猫    );
79108882a77S猫头猫    plugins = [];
79208882a77S猫头猫    pluginStateMapper.notify();
793e08d37a3S猫头猫
794e08d37a3S猫头猫    /** 清除空余文件,异步做就可以了 */
795e08d37a3S猫头猫    readDir(pathConst.pluginPath)
796e08d37a3S猫头猫        .then(fns => {
797e08d37a3S猫头猫            fns.forEach(fn => {
798e08d37a3S猫头猫                unlink(fn.path).catch(emptyFunction);
799e08d37a3S猫头猫            });
800e08d37a3S猫头猫        })
801e08d37a3S猫头猫        .catch(emptyFunction);
80208882a77S猫头猫}
80308882a77S猫头猫
80425c1bd29S猫头猫async function updatePlugin(plugin: Plugin) {
80525c1bd29S猫头猫    const updateUrl = plugin.instance.srcUrl;
80625c1bd29S猫头猫    if (!updateUrl) {
80725c1bd29S猫头猫        throw new Error('没有更新源');
80825c1bd29S猫头猫    }
80925c1bd29S猫头猫    try {
81025c1bd29S猫头猫        await installPluginFromUrl(updateUrl);
81125c1bd29S猫头猫    } catch (e: any) {
81225c1bd29S猫头猫        if (e.message === '插件已安装') {
81325c1bd29S猫头猫            throw new Error('当前已是最新版本');
81425c1bd29S猫头猫        } else {
81525c1bd29S猫头猫            throw e;
81625c1bd29S猫头猫        }
81725c1bd29S猫头猫    }
81825c1bd29S猫头猫}
81925c1bd29S猫头猫
820927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) {
8212c595535S猫头猫    return getByName(mediaItem?.platform);
822927dbe93S猫头猫}
823927dbe93S猫头猫
824927dbe93S猫头猫function getByHash(hash: string) {
8257993f90eS猫头猫    return hash === localPluginHash
8267993f90eS猫头猫        ? localFilePlugin
8277993f90eS猫头猫        : plugins.find(_ => _.hash === hash);
828927dbe93S猫头猫}
829927dbe93S猫头猫
830927dbe93S猫头猫function getByName(name: string) {
8317993f90eS猫头猫    return name === localPluginPlatform
8320e4173cdS猫头猫        ? localFilePlugin
8330e4173cdS猫头猫        : plugins.find(_ => _.name === name);
834927dbe93S猫头猫}
835927dbe93S猫头猫
836927dbe93S猫头猫function getValidPlugins() {
837927dbe93S猫头猫    return plugins.filter(_ => _.state === 'enabled');
838927dbe93S猫头猫}
839927dbe93S猫头猫
840efb9da24S猫头猫function getSearchablePlugins() {
841efb9da24S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.search);
842efb9da24S猫头猫}
843efb9da24S猫头猫
844e08d37a3S猫头猫function getSortedSearchablePlugins() {
845e08d37a3S猫头猫    return getSearchablePlugins().sort((a, b) =>
846e08d37a3S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
847e08d37a3S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
848e08d37a3S猫头猫        0
849e08d37a3S猫头猫            ? -1
850e08d37a3S猫头猫            : 1,
851e08d37a3S猫头猫    );
852e08d37a3S猫头猫}
853e08d37a3S猫头猫
85415feccc1S猫头猫function getTopListsablePlugins() {
85515feccc1S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists);
85615feccc1S猫头猫}
85715feccc1S猫头猫
85815feccc1S猫头猫function getSortedTopListsablePlugins() {
85915feccc1S猫头猫    return getTopListsablePlugins().sort((a, b) =>
86015feccc1S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
86115feccc1S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
86215feccc1S猫头猫        0
86315feccc1S猫头猫            ? -1
86415feccc1S猫头猫            : 1,
86515feccc1S猫头猫    );
86615feccc1S猫头猫}
86715feccc1S猫头猫
868e08d37a3S猫头猫function useSortedPlugins() {
869e08d37a3S猫头猫    const _plugins = pluginStateMapper.useMappedState();
870e08d37a3S猫头猫    const _pluginMetaAll = PluginMeta.usePluginMetaAll();
871e08d37a3S猫头猫
87234588741S猫头猫    const [sortedPlugins, setSortedPlugins] = useState(
87334588741S猫头猫        [..._plugins].sort((a, b) =>
874e08d37a3S猫头猫            (_pluginMetaAll[a.name]?.order ?? Infinity) -
875e08d37a3S猫头猫                (_pluginMetaAll[b.name]?.order ?? Infinity) <
876e08d37a3S猫头猫            0
877e08d37a3S猫头猫                ? -1
878e08d37a3S猫头猫                : 1,
87934588741S猫头猫        ),
880e08d37a3S猫头猫    );
88134588741S猫头猫
88234588741S猫头猫    useEffect(() => {
883d4cd40d8S猫头猫        InteractionManager.runAfterInteractions(() => {
88434588741S猫头猫            setSortedPlugins(
88534588741S猫头猫                [..._plugins].sort((a, b) =>
88634588741S猫头猫                    (_pluginMetaAll[a.name]?.order ?? Infinity) -
88734588741S猫头猫                        (_pluginMetaAll[b.name]?.order ?? Infinity) <
88834588741S猫头猫                    0
88934588741S猫头猫                        ? -1
89034588741S猫头猫                        : 1,
89134588741S猫头猫                ),
89234588741S猫头猫            );
893d4cd40d8S猫头猫        });
89434588741S猫头猫    }, [_plugins, _pluginMetaAll]);
89534588741S猫头猫
89634588741S猫头猫    return sortedPlugins;
897e08d37a3S猫头猫}
898e08d37a3S猫头猫
899927dbe93S猫头猫const PluginManager = {
900927dbe93S猫头猫    setup,
901927dbe93S猫头猫    installPlugin,
90258992c6bS猫头猫    installPluginFromUrl,
90325c1bd29S猫头猫    updatePlugin,
904927dbe93S猫头猫    uninstallPlugin,
905927dbe93S猫头猫    getByMedia,
906927dbe93S猫头猫    getByHash,
907927dbe93S猫头猫    getByName,
908927dbe93S猫头猫    getValidPlugins,
909efb9da24S猫头猫    getSearchablePlugins,
910e08d37a3S猫头猫    getSortedSearchablePlugins,
91115feccc1S猫头猫    getTopListsablePlugins,
91215feccc1S猫头猫    getSortedTopListsablePlugins,
9135276aef9S猫头猫    usePlugins: pluginStateMapper.useMappedState,
914e08d37a3S猫头猫    useSortedPlugins,
91508882a77S猫头猫    uninstallAllPlugins,
9165276aef9S猫头猫};
917927dbe93S猫头猫
918927dbe93S猫头猫export default PluginManager;
919