xref: /MusicFree/src/core/pluginManager.ts (revision 43eb30bf16ea88ab8e3cdff98faf307c8c8f93d3)
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';
1428ccceb7S猫头猫import * as webdav from 'webdav';
15d4cd40d8S猫头猫import {InteractionManager, ToastAndroid} from 'react-native';
16927dbe93S猫头猫import pathConst from '@/constants/pathConst';
1725c1bd29S猫头猫import {compare, satisfies} from 'compare-versions';
18927dbe93S猫头猫import DeviceInfo from 'react-native-device-info';
19927dbe93S猫头猫import StateMapper from '@/utils/stateMapper';
20*43eb30bfS猫头猫import MediaMeta from './mediaExtra';
21927dbe93S猫头猫import {nanoid} from 'nanoid';
22ea6d708fS猫头猫import {devLog, errorLog, trace} from '../utils/log';
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';
46a84a85c5S猫头猫import {getFileName} from '@/utils/fileUtils';
47a7b42a4cS猫头猫import {URL} from 'react-native-url-polyfill';
48a7b42a4cS猫头猫import Base64 from '@/utils/base64';
49*43eb30bfS猫头猫import MediaCache from './mediaCache';
50*43eb30bfS猫头猫import produce from 'immer';
51*43eb30bfS猫头猫import MediaExtra from './mediaExtra';
52*43eb30bfS猫头猫import objectPath from 'object-path';
53927dbe93S猫头猫
5461aca335S猫头猫axios.defaults.timeout = 2000;
55927dbe93S猫头猫
56927dbe93S猫头猫const sha256 = CryptoJs.SHA256;
57927dbe93S猫头猫
58cfa0fc07S猫头猫export enum PluginStateCode {
59927dbe93S猫头猫    /** 版本不匹配 */
60927dbe93S猫头猫    VersionNotMatch = 'VERSION NOT MATCH',
61927dbe93S猫头猫    /** 无法解析 */
62927dbe93S猫头猫    CannotParse = 'CANNOT PARSE',
63927dbe93S猫头猫}
64927dbe93S猫头猫
659c34d637S猫头猫const packages: Record<string, any> = {
669c34d637S猫头猫    cheerio,
679c34d637S猫头猫    'crypto-js': CryptoJs,
689c34d637S猫头猫    axios,
699c34d637S猫头猫    dayjs,
709c34d637S猫头猫    'big-integer': bigInt,
719c34d637S猫头猫    qs,
729c34d637S猫头猫    he,
733b3d6357S猫头猫    '@react-native-cookies/cookies': CookieManager,
7428ccceb7S猫头猫    webdav,
759c34d637S猫头猫};
769c34d637S猫头猫
77b43683eaS猫头猫const _require = (packageName: string) => {
78b43683eaS猫头猫    let pkg = packages[packageName];
79b43683eaS猫头猫    pkg.default = pkg;
80b43683eaS猫头猫    return pkg;
81b43683eaS猫头猫};
829c34d637S猫头猫
8353f8cd8eS猫头猫const _consoleBind = function (
8453f8cd8eS猫头猫    method: 'log' | 'error' | 'info' | 'warn',
8553f8cd8eS猫头猫    ...args: any
8653f8cd8eS猫头猫) {
8753f8cd8eS猫头猫    const fn = console[method];
8853f8cd8eS猫头猫    if (fn) {
8953f8cd8eS猫头猫        fn(...args);
9053f8cd8eS猫头猫        devLog(method, ...args);
9153f8cd8eS猫头猫    }
9253f8cd8eS猫头猫};
9353f8cd8eS猫头猫
9453f8cd8eS猫头猫const _console = {
9553f8cd8eS猫头猫    log: _consoleBind.bind(null, 'log'),
9653f8cd8eS猫头猫    warn: _consoleBind.bind(null, 'warn'),
9753f8cd8eS猫头猫    info: _consoleBind.bind(null, 'info'),
9853f8cd8eS猫头猫    error: _consoleBind.bind(null, 'error'),
9953f8cd8eS猫头猫};
10053f8cd8eS猫头猫
101a7b42a4cS猫头猫function formatAuthUrl(url: string) {
102a7b42a4cS猫头猫    const urlObj = new URL(url);
103a7b42a4cS猫头猫
104a7b42a4cS猫头猫    try {
105a7b42a4cS猫头猫        if (urlObj.username && urlObj.password) {
106a7b42a4cS猫头猫            const auth = `Basic ${Base64.btoa(
107a7b42a4cS猫头猫                `${decodeURIComponent(urlObj.username)}:${decodeURIComponent(
108a7b42a4cS猫头猫                    urlObj.password,
109a7b42a4cS猫头猫                )}`,
110a7b42a4cS猫头猫            )}`;
111a7b42a4cS猫头猫            urlObj.username = '';
112a7b42a4cS猫头猫            urlObj.password = '';
113a7b42a4cS猫头猫
114a7b42a4cS猫头猫            return {
115a7b42a4cS猫头猫                url: urlObj.toString(),
116a7b42a4cS猫头猫                auth,
117a7b42a4cS猫头猫            };
118a7b42a4cS猫头猫        }
119a7b42a4cS猫头猫    } catch (e) {
120a7b42a4cS猫头猫        return {
121a7b42a4cS猫头猫            url,
122a7b42a4cS猫头猫        };
123a7b42a4cS猫头猫    }
124a7b42a4cS猫头猫    return {
125a7b42a4cS猫头猫        url,
126a7b42a4cS猫头猫    };
127a7b42a4cS猫头猫}
128a7b42a4cS猫头猫
129d5bfeb7eS猫头猫//#region 插件类
130927dbe93S猫头猫export class Plugin {
131927dbe93S猫头猫    /** 插件名 */
132927dbe93S猫头猫    public name: string;
133927dbe93S猫头猫    /** 插件的hash,作为唯一id */
134927dbe93S猫头猫    public hash: string;
135927dbe93S猫头猫    /** 插件状态:激活、关闭、错误 */
136927dbe93S猫头猫    public state: 'enabled' | 'disabled' | 'error';
137927dbe93S猫头猫    /** 插件状态信息 */
138927dbe93S猫头猫    public stateCode?: PluginStateCode;
139927dbe93S猫头猫    /** 插件的实例 */
140927dbe93S猫头猫    public instance: IPlugin.IPluginInstance;
141927dbe93S猫头猫    /** 插件路径 */
142927dbe93S猫头猫    public path: string;
143927dbe93S猫头猫    /** 插件方法 */
144927dbe93S猫头猫    public methods: PluginMethods;
145927dbe93S猫头猫
14674d0cf81S猫头猫    constructor(
14774d0cf81S猫头猫        funcCode: string | (() => IPlugin.IPluginInstance),
14874d0cf81S猫头猫        pluginPath: string,
14974d0cf81S猫头猫    ) {
150927dbe93S猫头猫        this.state = 'enabled';
151927dbe93S猫头猫        let _instance: IPlugin.IPluginInstance;
1523b3d6357S猫头猫        const _module: any = {exports: {}};
153927dbe93S猫头猫        try {
15474d0cf81S猫头猫            if (typeof funcCode === 'string') {
155e0caf342S猫头猫                // 插件的环境变量
156e0caf342S猫头猫                const env = {
157e0caf342S猫头猫                    getUserVariables: () => {
158e0caf342S猫头猫                        return (
159e0caf342S猫头猫                            PluginMeta.getPluginMeta(this)?.userVariables ?? {}
160e0caf342S猫头猫                        );
161e0caf342S猫头猫                    },
162e3fa9b3cS猫头猫                    os: 'android',
163e0caf342S猫头猫                };
164e0caf342S猫头猫
1654060c00aS猫头猫                // eslint-disable-next-line no-new-func
166927dbe93S猫头猫                _instance = Function(`
167927dbe93S猫头猫                    'use strict';
168e0caf342S猫头猫                    return function(require, __musicfree_require, module, exports, console, env) {
1699c34d637S猫头猫                        ${funcCode}
170927dbe93S猫头猫                    }
171e0caf342S猫头猫                `)()(
172e0caf342S猫头猫                    _require,
173e0caf342S猫头猫                    _require,
174e0caf342S猫头猫                    _module,
175e0caf342S猫头猫                    _module.exports,
176e0caf342S猫头猫                    _console,
177e0caf342S猫头猫                    env,
178e0caf342S猫头猫                );
1793b3d6357S猫头猫                if (_module.exports.default) {
1803b3d6357S猫头猫                    _instance = _module.exports
1813b3d6357S猫头猫                        .default as IPlugin.IPluginInstance;
1823b3d6357S猫头猫                } else {
1839c34d637S猫头猫                    _instance = _module.exports as IPlugin.IPluginInstance;
1843b3d6357S猫头猫                }
18574d0cf81S猫头猫            } else {
18674d0cf81S猫头猫                _instance = funcCode();
18774d0cf81S猫头猫            }
188c2b3a262S猫头猫            // 插件初始化后的一些操作
18995297592S猫头猫            if (Array.isArray(_instance.userVariables)) {
19095297592S猫头猫                _instance.userVariables = _instance.userVariables.filter(
19195297592S猫头猫                    it => it?.key,
19295297592S猫头猫                );
19395297592S猫头猫            }
194927dbe93S猫头猫            this.checkValid(_instance);
195927dbe93S猫头猫        } catch (e: any) {
196b43683eaS猫头猫            console.log(e);
197927dbe93S猫头猫            this.state = 'error';
198927dbe93S猫头猫            this.stateCode = PluginStateCode.CannotParse;
199927dbe93S猫头猫            if (e?.stateCode) {
200927dbe93S猫头猫                this.stateCode = e.stateCode;
201927dbe93S猫头猫            }
202927dbe93S猫头猫            errorLog(`${pluginPath}插件无法解析 `, {
203927dbe93S猫头猫                stateCode: this.stateCode,
204927dbe93S猫头猫                message: e?.message,
205927dbe93S猫头猫                stack: e?.stack,
206927dbe93S猫头猫            });
207927dbe93S猫头猫            _instance = e?.instance ?? {
208927dbe93S猫头猫                _path: '',
209927dbe93S猫头猫                platform: '',
210927dbe93S猫头猫                appVersion: '',
21120e6a092S猫头猫                async getMediaSource() {
212927dbe93S猫头猫                    return null;
213927dbe93S猫头猫                },
214927dbe93S猫头猫                async search() {
215927dbe93S猫头猫                    return {};
216927dbe93S猫头猫                },
217927dbe93S猫头猫                async getAlbumInfo() {
218927dbe93S猫头猫                    return null;
219927dbe93S猫头猫                },
220927dbe93S猫头猫            };
221927dbe93S猫头猫        }
222927dbe93S猫头猫        this.instance = _instance;
223927dbe93S猫头猫        this.path = pluginPath;
224927dbe93S猫头猫        this.name = _instance.platform;
225ab8941d9S猫头猫        if (
226ab8941d9S猫头猫            this.instance.platform === '' ||
227ab8941d9S猫头猫            this.instance.platform === undefined
228ab8941d9S猫头猫        ) {
229927dbe93S猫头猫            this.hash = '';
230927dbe93S猫头猫        } else {
23174d0cf81S猫头猫            if (typeof funcCode === 'string') {
232927dbe93S猫头猫                this.hash = sha256(funcCode).toString();
23374d0cf81S猫头猫            } else {
23474d0cf81S猫头猫                this.hash = sha256(funcCode.toString()).toString();
23574d0cf81S猫头猫            }
236927dbe93S猫头猫        }
237927dbe93S猫头猫
238927dbe93S猫头猫        // 放在最后
239927dbe93S猫头猫        this.methods = new PluginMethods(this);
240927dbe93S猫头猫    }
241927dbe93S猫头猫
242927dbe93S猫头猫    private checkValid(_instance: IPlugin.IPluginInstance) {
243927dbe93S猫头猫        /** 版本号校验 */
244927dbe93S猫头猫        if (
245927dbe93S猫头猫            _instance.appVersion &&
246927dbe93S猫头猫            !satisfies(DeviceInfo.getVersion(), _instance.appVersion)
247927dbe93S猫头猫        ) {
248927dbe93S猫头猫            throw {
249927dbe93S猫头猫                instance: _instance,
250927dbe93S猫头猫                stateCode: PluginStateCode.VersionNotMatch,
251927dbe93S猫头猫            };
252927dbe93S猫头猫        }
253927dbe93S猫头猫        return true;
254927dbe93S猫头猫    }
255927dbe93S猫头猫}
256d5bfeb7eS猫头猫//#endregion
257927dbe93S猫头猫
258d5bfeb7eS猫头猫//#region 基于插件类封装的方法,供给APP侧直接调用
259927dbe93S猫头猫/** 有缓存等信息 */
260927dbe93S猫头猫class PluginMethods implements IPlugin.IPluginInstanceMethods {
261927dbe93S猫头猫    private plugin;
262927dbe93S猫头猫    constructor(plugin: Plugin) {
263927dbe93S猫头猫        this.plugin = plugin;
264927dbe93S猫头猫    }
265927dbe93S猫头猫    /** 搜索 */
266927dbe93S猫头猫    async search<T extends ICommon.SupportMediaType>(
267927dbe93S猫头猫        query: string,
268927dbe93S猫头猫        page: number,
269927dbe93S猫头猫        type: T,
270927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
271927dbe93S猫头猫        if (!this.plugin.instance.search) {
272927dbe93S猫头猫            return {
273927dbe93S猫头猫                isEnd: true,
274927dbe93S猫头猫                data: [],
275927dbe93S猫头猫            };
276927dbe93S猫头猫        }
277927dbe93S猫头猫
2784060c00aS猫头猫        const result =
2794060c00aS猫头猫            (await this.plugin.instance.search(query, page, type)) ?? {};
280927dbe93S猫头猫        if (Array.isArray(result.data)) {
281927dbe93S猫头猫            result.data.forEach(_ => {
282927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
283927dbe93S猫头猫            });
284927dbe93S猫头猫            return {
285927dbe93S猫头猫                isEnd: result.isEnd ?? true,
286927dbe93S猫头猫                data: result.data,
287927dbe93S猫头猫            };
288927dbe93S猫头猫        }
289927dbe93S猫头猫        return {
290927dbe93S猫头猫            isEnd: true,
291927dbe93S猫头猫            data: [],
292927dbe93S猫头猫        };
293927dbe93S猫头猫    }
294927dbe93S猫头猫
295927dbe93S猫头猫    /** 获取真实源 */
29620e6a092S猫头猫    async getMediaSource(
297927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
298abaede57S猫头猫        quality: IMusic.IQualityKey = 'standard',
299927dbe93S猫头猫        retryCount = 1,
300dc160d50S猫头猫        notUpdateCache = false,
301192ae2b0S猫头猫    ): Promise<IPlugin.IMediaSourceResult | null> {
302927dbe93S猫头猫        // 1. 本地搜索 其实直接读mediameta就好了
303*43eb30bfS猫头猫        const mediaExtra = MediaExtra.get(musicItem);
304927dbe93S猫头猫        const localPath =
305*43eb30bfS猫头猫            mediaExtra?.localPath ||
306*43eb30bfS猫头猫            getInternalData<string>(musicItem, InternalDataType.LOCALPATH) ||
3070e4173cdS猫头猫            getInternalData<string>(
3080e4173cdS猫头猫                LocalMusicSheet.isLocalMusic(musicItem),
3090e4173cdS猫头猫                InternalDataType.LOCALPATH,
3100e4173cdS猫头猫            );
311a84a85c5S猫头猫        if (
312a84a85c5S猫头猫            localPath &&
313a84a85c5S猫头猫            (localPath.startsWith('content://') ||
314a84a85c5S猫头猫                (await FileSystem.exists(localPath)))
315a84a85c5S猫头猫        ) {
3160e4173cdS猫头猫            trace('本地播放', localPath);
317*43eb30bfS猫头猫            if (mediaExtra && mediaExtra.localPath !== localPath) {
318*43eb30bfS猫头猫                // 修正一下本地数据
319*43eb30bfS猫头猫                MediaExtra.update(musicItem, {
320*43eb30bfS猫头猫                    localPath,
321*43eb30bfS猫头猫                });
322*43eb30bfS猫头猫            }
323927dbe93S猫头猫            return {
324927dbe93S猫头猫                url: localPath,
325927dbe93S猫头猫            };
326*43eb30bfS猫头猫        } else if (mediaExtra?.localPath) {
327*43eb30bfS猫头猫            MediaExtra.update(musicItem, {
328*43eb30bfS猫头猫                localPath: undefined,
329*43eb30bfS猫头猫            });
330927dbe93S猫头猫        }
331a84a85c5S猫头猫
3327993f90eS猫头猫        if (musicItem.platform === localPluginPlatform) {
333f5935920S猫头猫            throw new Error('本地音乐不存在');
334f5935920S猫头猫        }
335927dbe93S猫头猫        // 2. 缓存播放
336*43eb30bfS猫头猫        const mediaCache = MediaCache.getMediaCache(
337*43eb30bfS猫头猫            musicItem,
338*43eb30bfS猫头猫        ) as IMusic.IMusicItem | null;
339985f8e75S猫头猫        const pluginCacheControl =
340985f8e75S猫头猫            this.plugin.instance.cacheControl ?? 'no-cache';
341cfa0fc07S猫头猫        if (
342cfa0fc07S猫头猫            mediaCache &&
343*43eb30bfS猫头猫            mediaCache?.source?.[quality]?.url &&
34448f4b873S猫头猫            (pluginCacheControl === CacheControl.Cache ||
34548f4b873S猫头猫                (pluginCacheControl === CacheControl.NoCache &&
346ef714860S猫头猫                    Network.isOffline()))
347cfa0fc07S猫头猫        ) {
3485276aef9S猫头猫            trace('播放', '缓存播放');
349*43eb30bfS猫头猫            const qualityInfo = mediaCache.source[quality];
350927dbe93S猫头猫            return {
351*43eb30bfS猫头猫                url: qualityInfo!.url,
352927dbe93S猫头猫                headers: mediaCache.headers,
3534060c00aS猫头猫                userAgent:
3544060c00aS猫头猫                    mediaCache.userAgent ?? mediaCache.headers?.['user-agent'],
355927dbe93S猫头猫            };
356927dbe93S猫头猫        }
357927dbe93S猫头猫        // 3. 插件解析
35820e6a092S猫头猫        if (!this.plugin.instance.getMediaSource) {
359a7b42a4cS猫头猫            const {url, auth} = formatAuthUrl(
360a7b42a4cS猫头猫                musicItem?.qualities?.[quality]?.url ?? musicItem.url,
361a7b42a4cS猫头猫            );
362a7b42a4cS猫头猫            return {
363a7b42a4cS猫头猫                url: url,
364a7b42a4cS猫头猫                headers: auth
365a7b42a4cS猫头猫                    ? {
366a7b42a4cS猫头猫                          Authorization: auth,
367a7b42a4cS猫头猫                      }
368a7b42a4cS猫头猫                    : undefined,
369a7b42a4cS猫头猫            };
370927dbe93S猫头猫        }
371927dbe93S猫头猫        try {
372abaede57S猫头猫            const {url, headers} = (await this.plugin.instance.getMediaSource(
373abaede57S猫头猫                musicItem,
374abaede57S猫头猫                quality,
375abaede57S猫头猫            )) ?? {url: musicItem?.qualities?.[quality]?.url};
376927dbe93S猫头猫            if (!url) {
377a28eac61S猫头猫                throw new Error('NOT RETRY');
378927dbe93S猫头猫            }
3795276aef9S猫头猫            trace('播放', '插件播放');
380927dbe93S猫头猫            const result = {
381927dbe93S猫头猫                url,
382927dbe93S猫头猫                headers,
383927dbe93S猫头猫                userAgent: headers?.['user-agent'],
384cfa0fc07S猫头猫            } as IPlugin.IMediaSourceResult;
385a7b42a4cS猫头猫            const authFormattedResult = formatAuthUrl(result.url!);
386a7b42a4cS猫头猫            if (authFormattedResult.auth) {
387a7b42a4cS猫头猫                result.url = authFormattedResult.url;
388a7b42a4cS猫头猫                result.headers = {
389a7b42a4cS猫头猫                    ...(result.headers ?? {}),
390a7b42a4cS猫头猫                    Authorization: authFormattedResult.auth,
391a7b42a4cS猫头猫                };
392a7b42a4cS猫头猫            }
393927dbe93S猫头猫
394dc160d50S猫头猫            if (
395dc160d50S猫头猫                pluginCacheControl !== CacheControl.NoStore &&
396dc160d50S猫头猫                !notUpdateCache
397dc160d50S猫头猫            ) {
398*43eb30bfS猫头猫                // 更新缓存
399*43eb30bfS猫头猫                const cacheSource = {
400*43eb30bfS猫头猫                    headers: result.headers,
401*43eb30bfS猫头猫                    userAgent: result.userAgent,
402*43eb30bfS猫头猫                    url,
403*43eb30bfS猫头猫                };
404*43eb30bfS猫头猫                let realMusicItem = {
405*43eb30bfS猫头猫                    ...musicItem,
406*43eb30bfS猫头猫                    ...(mediaCache || {}),
407*43eb30bfS猫头猫                };
408*43eb30bfS猫头猫                realMusicItem.source = {
409*43eb30bfS猫头猫                    ...(realMusicItem.source || {}),
410*43eb30bfS猫头猫                    [quality]: cacheSource,
411*43eb30bfS猫头猫                };
412*43eb30bfS猫头猫
413*43eb30bfS猫头猫                MediaCache.setMediaCache(realMusicItem);
414752ffc5aS猫头猫            }
415927dbe93S猫头猫            return result;
416927dbe93S猫头猫        } catch (e: any) {
417a28eac61S猫头猫            if (retryCount > 0 && e?.message !== 'NOT RETRY') {
418927dbe93S猫头猫                await delay(150);
419abaede57S猫头猫                return this.getMediaSource(musicItem, quality, --retryCount);
420927dbe93S猫头猫            }
421927dbe93S猫头猫            errorLog('获取真实源失败', e?.message);
422ea6d708fS猫头猫            devLog('error', '获取真实源失败', e, e?.message);
423192ae2b0S猫头猫            return null;
424927dbe93S猫头猫        }
425927dbe93S猫头猫    }
426927dbe93S猫头猫
427927dbe93S猫头猫    /** 获取音乐详情 */
428927dbe93S猫头猫    async getMusicInfo(
429927dbe93S猫头猫        musicItem: ICommon.IMediaBase,
43074d0cf81S猫头猫    ): Promise<Partial<IMusic.IMusicItem> | null> {
431927dbe93S猫头猫        if (!this.plugin.instance.getMusicInfo) {
432d704daedS猫头猫            return null;
433927dbe93S猫头猫        }
43474d0cf81S猫头猫        try {
435927dbe93S猫头猫            return (
436927dbe93S猫头猫                this.plugin.instance.getMusicInfo(
4377993f90eS猫头猫                    resetMediaItem(musicItem, undefined, true),
438d704daedS猫头猫                ) ?? null
439927dbe93S猫头猫            );
440ea6d708fS猫头猫        } catch (e: any) {
441ea6d708fS猫头猫            devLog('error', '获取音乐详情失败', e, e?.message);
442d704daedS猫头猫            return null;
44374d0cf81S猫头猫        }
444927dbe93S猫头猫    }
445927dbe93S猫头猫
446927dbe93S猫头猫    /** 获取歌词 */
447927dbe93S猫头猫    async getLyric(
448*43eb30bfS猫头猫        originalMusicItem: IMusic.IMusicItemBase,
449*43eb30bfS猫头猫        lang: string, // 语言
450927dbe93S猫头猫    ): Promise<ILyric.ILyricSource | null> {
451*43eb30bfS猫头猫        // 1.额外存储的meta信息(关联歌词)
452*43eb30bfS猫头猫        const meta = MediaMeta.get(originalMusicItem);
453*43eb30bfS猫头猫        let musicItem: IMusic.IMusicItem;
454927dbe93S猫头猫        if (meta && meta.associatedLrc) {
455*43eb30bfS猫头猫            musicItem = meta.associatedLrc as IMusic.IMusicItem;
456*43eb30bfS猫头猫        } else {
457*43eb30bfS猫头猫            musicItem = originalMusicItem as IMusic.IMusicItem;
458927dbe93S猫头猫        }
459*43eb30bfS猫头猫
460*43eb30bfS猫头猫        const musicItemCache = MediaCache.getMediaCache(
4617a91f04fS猫头猫            musicItem,
462*43eb30bfS猫头猫        ) as IMusic.IMusicItemCache | null;
463*43eb30bfS猫头猫
464*43eb30bfS猫头猫        /** 原始歌词文本 */
465*43eb30bfS猫头猫        let rawLrc: string | null = null;
466*43eb30bfS猫头猫        /** 歌词URL */
467*43eb30bfS猫头猫        let lrcUrl: string | null = null;
468*43eb30bfS猫头猫
469*43eb30bfS猫头猫        // 本地的文件名
470*43eb30bfS猫头猫        const filename = `${pathConst.lrcCachePath}${nanoid()}.lrc`;
471*43eb30bfS猫头猫
472*43eb30bfS猫头猫        // 2. 缓存歌词 / 对象上本身的歌词
473*43eb30bfS猫头猫        if (musicItemCache?.lyric) {
474*43eb30bfS猫头猫            let lyric: ILyric.ILyricSource | null = musicItem.lyric || null;
475*43eb30bfS猫头猫            // 缓存的远程结果
476*43eb30bfS猫头猫            let cacheLyric: ILyric.ILyricSource | null =
477*43eb30bfS猫头猫                musicItemCache.lyric || null;
478*43eb30bfS猫头猫            // 缓存的本地结果
479*43eb30bfS猫头猫            let localLyric: ILyric.ILyricSource | null =
480*43eb30bfS猫头猫                musicItemCache.$localLyric || null;
481*43eb30bfS猫头猫
482*43eb30bfS猫头猫            // 如果存在本地歌词,语言非默认
483*43eb30bfS猫头猫            if (lang && lang !== 'default') {
484*43eb30bfS猫头猫                cacheLyric = cacheLyric?.versions?.[lang] || null;
485*43eb30bfS猫头猫                localLyric = localLyric?.versions?.[lang] || null;
486*43eb30bfS猫头猫                lyric = lyric?.versions?.[lang] || null;
487*43eb30bfS猫头猫            } else {
488*43eb30bfS猫头猫                cacheLyric = {
489*43eb30bfS猫头猫                    lrc: cacheLyric?.lrc || musicItemCache.lrc,
490*43eb30bfS猫头猫                    rawLrc: cacheLyric?.rawLrc || musicItemCache.rawLrc,
491*43eb30bfS猫头猫                };
492*43eb30bfS猫头猫                lyric = {
493*43eb30bfS猫头猫                    lrc: lyric?.lrc || musicItem.lrc,
494*43eb30bfS猫头猫                    rawLrc: lyric?.rawLrc ?? musicItem.rawLrc,
495*43eb30bfS猫头猫                };
496*43eb30bfS猫头猫            }
497*43eb30bfS猫头猫
498*43eb30bfS猫头猫            // 返回本地歌词
499*43eb30bfS猫头猫            if (localLyric?.lrc && (await exists(localLyric.lrc))) {
500*43eb30bfS猫头猫                // TODO 清理缓存,不管也行
501*43eb30bfS猫头猫                rawLrc = await readFile(localLyric.lrc, 'utf8');
502*43eb30bfS猫头猫
503*43eb30bfS猫头猫                console.log('本地缓存的歌词');
504*43eb30bfS猫头猫                return {
505*43eb30bfS猫头猫                    rawLrc,
506*43eb30bfS猫头猫                    lang,
507*43eb30bfS猫头猫                    versions: cacheLyric?.versions,
508*43eb30bfS猫头猫                };
509*43eb30bfS猫头猫            }
510*43eb30bfS猫头猫
511*43eb30bfS猫头猫            // 缓存的搜索结果
512*43eb30bfS猫头猫            if (cacheLyric?.rawLrc || lyric?.rawLrc) {
513*43eb30bfS猫头猫                console.log('缓存结果的歌词1');
514*43eb30bfS猫头猫                return {
515*43eb30bfS猫头猫                    rawLrc: cacheLyric?.rawLrc || lyric?.rawLrc,
516*43eb30bfS猫头猫                    lang,
517*43eb30bfS猫头猫                    versions: cacheLyric?.versions || lyric?.versions,
518*43eb30bfS猫头猫                };
519*43eb30bfS猫头猫            }
520*43eb30bfS猫头猫
521*43eb30bfS猫头猫            if (cacheLyric?.lrc || lyric?.lrc) {
522*43eb30bfS猫头猫                // 加载远程歌词
523*43eb30bfS猫头猫                rawLrc = (
524*43eb30bfS猫头猫                    await axios
525*43eb30bfS猫头猫                        .get((cacheLyric?.lrc || lyric?.lrc)!, {timeout: 3000})
526*43eb30bfS猫头猫                        .catch(() => null)
527*43eb30bfS猫头猫                )?.data;
528*43eb30bfS猫头猫
529*43eb30bfS猫头猫                if (rawLrc) {
530*43eb30bfS猫头猫                    // 写本地歌词缓存
531*43eb30bfS猫头猫                    await writeFile(filename, rawLrc, 'utf8');
532*43eb30bfS猫头猫                    MediaCache.setMediaCache(
533*43eb30bfS猫头猫                        produce(musicItemCache, draft => {
534*43eb30bfS猫头猫                            if (!lang || lang === 'default') {
535*43eb30bfS猫头猫                                objectPath.set(
536*43eb30bfS猫头猫                                    draft,
537*43eb30bfS猫头猫                                    '$localLyric.lrc',
538*43eb30bfS猫头猫                                    filename,
539927dbe93S猫头猫                                );
5407a91f04fS猫头猫                            } else {
541*43eb30bfS猫头猫                                objectPath.set(
542*43eb30bfS猫头猫                                    draft,
543*43eb30bfS猫头猫                                    `$localLyric.lrc.versions.${lang}.lrc`,
544*43eb30bfS猫头猫                                    filename,
545*43eb30bfS猫头猫                                );
546*43eb30bfS猫头猫                            }
547*43eb30bfS猫头猫                            return draft;
548*43eb30bfS猫头猫                        }),
549*43eb30bfS猫头猫                    );
550*43eb30bfS猫头猫
551*43eb30bfS猫头猫                    console.log('缓存结果的歌词2');
552*43eb30bfS猫头猫
553*43eb30bfS猫头猫                    return {
554*43eb30bfS猫头猫                        rawLrc,
555*43eb30bfS猫头猫                        lang,
556*43eb30bfS猫头猫                        lrc: cacheLyric?.lrc || lyric?.lrc,
557*43eb30bfS猫头猫                        versions: cacheLyric?.versions || lyric?.versions,
558*43eb30bfS猫头猫                    };
559*43eb30bfS猫头猫                }
560*43eb30bfS猫头猫            }
561*43eb30bfS猫头猫        }
562*43eb30bfS猫头猫
563*43eb30bfS猫头猫        // 3. 无缓存歌词/无自带歌词/无本地歌词
564*43eb30bfS猫头猫        let lrcSource: ILyric.ILyricSource | null;
565*43eb30bfS猫头猫        if (isSameMediaItem(originalMusicItem, musicItem)) {
566*43eb30bfS猫头猫            lrcSource =
567*43eb30bfS猫头猫                (await this.plugin.instance
568*43eb30bfS猫头猫                    ?.getLyric?.(
5697a91f04fS猫头猫                        resetMediaItem(musicItem, undefined, true),
570*43eb30bfS猫头猫                        lang,
571*43eb30bfS猫头猫                    )
572*43eb30bfS猫头猫                    ?.catch(() => null)) || null;
573*43eb30bfS猫头猫        } else {
574*43eb30bfS猫头猫            lrcSource =
575*43eb30bfS猫头猫                (await PluginManager.getByMedia(musicItem)
576*43eb30bfS猫头猫                    ?.instance?.getLyric?.(
577*43eb30bfS猫头猫                        resetMediaItem(musicItem, undefined, true),
578*43eb30bfS猫头猫                        lang,
579*43eb30bfS猫头猫                    )
580*43eb30bfS猫头猫                    ?.catch(() => null)) || null;
581*43eb30bfS猫头猫        }
582*43eb30bfS猫头猫
583*43eb30bfS猫头猫        if (lrcSource) {
584*43eb30bfS猫头猫            rawLrc = lrcSource?.rawLrc || null;
585*43eb30bfS猫头猫            lrcUrl = lrcSource?.lrc || null;
586*43eb30bfS猫头猫
587*43eb30bfS猫头猫            console.log('远程歌词3');
588*43eb30bfS猫头猫
589*43eb30bfS猫头猫            // 只返回了个versions
590*43eb30bfS猫头猫            if (
591*43eb30bfS猫头猫                !rawLrc &&
592*43eb30bfS猫头猫                !lrcUrl &&
593*43eb30bfS猫头猫                lrcSource?.versions?.[lang || 'default']
594*43eb30bfS猫头猫            ) {
595*43eb30bfS猫头猫                rawLrc =
596*43eb30bfS猫头猫                    lrcSource?.versions?.[lang || 'default']?.rawLrc || null;
597*43eb30bfS猫头猫                lrcUrl = lrcSource?.versions?.[lang || 'default']?.lrc || null;
598*43eb30bfS猫头猫            }
599*43eb30bfS猫头猫
600*43eb30bfS猫头猫            if (!rawLrc && lrcUrl) {
601*43eb30bfS猫头猫                rawLrc = (
602*43eb30bfS猫头猫                    await axios.get(lrcUrl, {timeout: 3000}).catch(() => null)
603*43eb30bfS猫头猫                )?.data;
604*43eb30bfS猫头猫            }
605*43eb30bfS猫头猫
606*43eb30bfS猫头猫            if (rawLrc) {
607*43eb30bfS猫头猫                await writeFile(filename, rawLrc, 'utf8');
608*43eb30bfS猫头猫                MediaCache.setMediaCache(
609*43eb30bfS猫头猫                    produce(musicItemCache || musicItem, draft => {
610*43eb30bfS猫头猫                        if (!lang || lang === 'default') {
611*43eb30bfS猫头猫                            objectPath.set(draft, '$localLyric.lrc', filename);
612*43eb30bfS猫头猫                        } else {
613*43eb30bfS猫头猫                            objectPath.set(
614*43eb30bfS猫头猫                                draft,
615*43eb30bfS猫头猫                                `$localLyric.lrc.versions.${lang}.lrc`,
616*43eb30bfS猫头猫                                filename,
6177a91f04fS猫头猫                            );
6187a91f04fS猫头猫                        }
6197a91f04fS猫头猫
620*43eb30bfS猫头猫                        objectPath.set(
621*43eb30bfS猫头猫                            draft,
622*43eb30bfS猫头猫                            `lyric.versions`,
623*43eb30bfS猫头猫                            lrcSource?.versions || draft?.versions,
624*43eb30bfS猫头猫                        );
625*43eb30bfS猫头猫                        return draft;
626*43eb30bfS猫头猫                    }),
627*43eb30bfS猫头猫                );
628*43eb30bfS猫头猫
629927dbe93S猫头猫                return {
630927dbe93S猫头猫                    rawLrc,
631*43eb30bfS猫头猫                    lrc: lrcUrl || undefined,
632*43eb30bfS猫头猫                    lang,
633*43eb30bfS猫头猫                    versions: lrcSource.versions,
634927dbe93S猫头猫                };
635927dbe93S猫头猫            }
636927dbe93S猫头猫        }
637*43eb30bfS猫头猫
6383a6f67b1S猫头猫        // 6. 如果是本地文件
639*43eb30bfS猫头猫        const isDownloaded = LocalMusicSheet.isLocalMusic(originalMusicItem);
640*43eb30bfS猫头猫        if (
641*43eb30bfS猫头猫            originalMusicItem.platform !== localPluginPlatform &&
642*43eb30bfS猫头猫            isDownloaded
643*43eb30bfS猫头猫        ) {
644*43eb30bfS猫头猫            const res = await localFilePlugin.instance!.getLyric!(
645*43eb30bfS猫头猫                isDownloaded,
646*43eb30bfS猫头猫                lang,
647*43eb30bfS猫头猫            );
648*43eb30bfS猫头猫
649*43eb30bfS猫头猫            console.log('本地文件歌词');
650*43eb30bfS猫头猫
6513a6f67b1S猫头猫            if (res) {
6523a6f67b1S猫头猫                return res;
6533a6f67b1S猫头猫            }
6543a6f67b1S猫头猫        }
655ea6d708fS猫头猫        devLog('warn', '无歌词');
656927dbe93S猫头猫
657927dbe93S猫头猫        return null;
658927dbe93S猫头猫    }
659927dbe93S猫头猫
660927dbe93S猫头猫    /** 获取歌词文本 */
661927dbe93S猫头猫    async getLyricText(
662927dbe93S猫头猫        musicItem: IMusic.IMusicItem,
663*43eb30bfS猫头猫        lang = 'default',
664927dbe93S猫头猫    ): Promise<string | undefined> {
665*43eb30bfS猫头猫        return (await this.getLyric(musicItem, lang))?.rawLrc;
666927dbe93S猫头猫    }
667927dbe93S猫头猫
668927dbe93S猫头猫    /** 获取专辑信息 */
669927dbe93S猫头猫    async getAlbumInfo(
670927dbe93S猫头猫        albumItem: IAlbum.IAlbumItemBase,
671f9afcc0dS猫头猫        page: number = 1,
672f9afcc0dS猫头猫    ): Promise<IPlugin.IAlbumInfoResult | null> {
673927dbe93S猫头猫        if (!this.plugin.instance.getAlbumInfo) {
674f9afcc0dS猫头猫            return {
675f9afcc0dS猫头猫                albumItem,
676f2a4767cS猫头猫                musicList: (albumItem?.musicList ?? []).map(
677f2a4767cS猫头猫                    resetMediaItem,
678f2a4767cS猫头猫                    this.plugin.name,
679f2a4767cS猫头猫                    true,
680f2a4767cS猫头猫                ),
681f9afcc0dS猫头猫                isEnd: true,
682f9afcc0dS猫头猫            };
683927dbe93S猫头猫        }
684927dbe93S猫头猫        try {
685927dbe93S猫头猫            const result = await this.plugin.instance.getAlbumInfo(
686927dbe93S猫头猫                resetMediaItem(albumItem, undefined, true),
687f9afcc0dS猫头猫                page,
688927dbe93S猫头猫            );
6895276aef9S猫头猫            if (!result) {
6905276aef9S猫头猫                throw new Error();
6915276aef9S猫头猫            }
692927dbe93S猫头猫            result?.musicList?.forEach(_ => {
693927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
69496744680S猫头猫                _.album = albumItem.title;
695927dbe93S猫头猫            });
6965276aef9S猫头猫
697f9afcc0dS猫头猫            if (page <= 1) {
698f9afcc0dS猫头猫                // 合并信息
699f9afcc0dS猫头猫                return {
700f9afcc0dS猫头猫                    albumItem: {...albumItem, ...(result?.albumItem ?? {})},
701f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
702f9afcc0dS猫头猫                    musicList: result.musicList,
703f9afcc0dS猫头猫                };
704f9afcc0dS猫头猫            } else {
705f9afcc0dS猫头猫                return {
706f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
707f9afcc0dS猫头猫                    musicList: result.musicList,
708f9afcc0dS猫头猫                };
709f9afcc0dS猫头猫            }
7104394410dS猫头猫        } catch (e: any) {
7114394410dS猫头猫            trace('获取专辑信息失败', e?.message);
712ea6d708fS猫头猫            devLog('error', '获取专辑信息失败', e, e?.message);
713ea6d708fS猫头猫
714f9afcc0dS猫头猫            return null;
715927dbe93S猫头猫        }
716927dbe93S猫头猫    }
717927dbe93S猫头猫
7185830c002S猫头猫    /** 获取歌单信息 */
7195830c002S猫头猫    async getMusicSheetInfo(
7205830c002S猫头猫        sheetItem: IMusic.IMusicSheetItem,
7215830c002S猫头猫        page: number = 1,
7225830c002S猫头猫    ): Promise<IPlugin.ISheetInfoResult | null> {
7235281926bS猫头猫        if (!this.plugin.instance.getMusicSheetInfo) {
7245830c002S猫头猫            return {
7255830c002S猫头猫                sheetItem,
7265830c002S猫头猫                musicList: sheetItem?.musicList ?? [],
7275830c002S猫头猫                isEnd: true,
7285830c002S猫头猫            };
7295830c002S猫头猫        }
7305830c002S猫头猫        try {
7315830c002S猫头猫            const result = await this.plugin.instance?.getMusicSheetInfo?.(
7325830c002S猫头猫                resetMediaItem(sheetItem, undefined, true),
7335830c002S猫头猫                page,
7345830c002S猫头猫            );
7355830c002S猫头猫            if (!result) {
7365830c002S猫头猫                throw new Error();
7375830c002S猫头猫            }
7385830c002S猫头猫            result?.musicList?.forEach(_ => {
7395830c002S猫头猫                resetMediaItem(_, this.plugin.name);
7405830c002S猫头猫            });
7415830c002S猫头猫
7425830c002S猫头猫            if (page <= 1) {
7435830c002S猫头猫                // 合并信息
7445830c002S猫头猫                return {
7455830c002S猫头猫                    sheetItem: {...sheetItem, ...(result?.sheetItem ?? {})},
7465830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
7475830c002S猫头猫                    musicList: result.musicList,
7485830c002S猫头猫                };
7495830c002S猫头猫            } else {
7505830c002S猫头猫                return {
7515830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
7525830c002S猫头猫                    musicList: result.musicList,
7535830c002S猫头猫                };
7545830c002S猫头猫            }
7555830c002S猫头猫        } catch (e: any) {
7565830c002S猫头猫            trace('获取歌单信息失败', e, e?.message);
7575830c002S猫头猫            devLog('error', '获取歌单信息失败', e, e?.message);
7585830c002S猫头猫
7595830c002S猫头猫            return null;
7605830c002S猫头猫        }
7615830c002S猫头猫    }
7625830c002S猫头猫
763927dbe93S猫头猫    /** 查询作者信息 */
764efb9da24S猫头猫    async getArtistWorks<T extends IArtist.ArtistMediaType>(
765927dbe93S猫头猫        artistItem: IArtist.IArtistItem,
766927dbe93S猫头猫        page: number,
767927dbe93S猫头猫        type: T,
768927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
769efb9da24S猫头猫        if (!this.plugin.instance.getArtistWorks) {
770927dbe93S猫头猫            return {
771927dbe93S猫头猫                isEnd: true,
772927dbe93S猫头猫                data: [],
773927dbe93S猫头猫            };
774927dbe93S猫头猫        }
775927dbe93S猫头猫        try {
776efb9da24S猫头猫            const result = await this.plugin.instance.getArtistWorks(
777927dbe93S猫头猫                artistItem,
778927dbe93S猫头猫                page,
779927dbe93S猫头猫                type,
780927dbe93S猫头猫            );
781927dbe93S猫头猫            if (!result.data) {
782927dbe93S猫头猫                return {
783927dbe93S猫头猫                    isEnd: true,
784927dbe93S猫头猫                    data: [],
785927dbe93S猫头猫                };
786927dbe93S猫头猫            }
787927dbe93S猫头猫            result.data?.forEach(_ => resetMediaItem(_, this.plugin.name));
788927dbe93S猫头猫            return {
789927dbe93S猫头猫                isEnd: result.isEnd ?? true,
790927dbe93S猫头猫                data: result.data,
791927dbe93S猫头猫            };
7924394410dS猫头猫        } catch (e: any) {
7934394410dS猫头猫            trace('查询作者信息失败', e?.message);
794ea6d708fS猫头猫            devLog('error', '查询作者信息失败', e, e?.message);
795ea6d708fS猫头猫
796927dbe93S猫头猫            throw e;
797927dbe93S猫头猫        }
798927dbe93S猫头猫    }
79908380090S猫头猫
80008380090S猫头猫    /** 导入歌单 */
80108380090S猫头猫    async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> {
80208380090S猫头猫        try {
80308380090S猫头猫            const result =
80408380090S猫头猫                (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? [];
80508380090S猫头猫            result.forEach(_ => resetMediaItem(_, this.plugin.name));
80608380090S猫头猫            return result;
807ea6d708fS猫头猫        } catch (e: any) {
8080e4173cdS猫头猫            console.log(e);
809ea6d708fS猫头猫            devLog('error', '导入歌单失败', e, e?.message);
810ea6d708fS猫头猫
81108380090S猫头猫            return [];
81208380090S猫头猫        }
81308380090S猫头猫    }
8144d9d3c4cS猫头猫    /** 导入单曲 */
8154d9d3c4cS猫头猫    async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> {
8164d9d3c4cS猫头猫        try {
8174d9d3c4cS猫头猫            const result = await this.plugin.instance?.importMusicItem?.(
8184d9d3c4cS猫头猫                urlLike,
8194d9d3c4cS猫头猫            );
8204d9d3c4cS猫头猫            if (!result) {
8214d9d3c4cS猫头猫                throw new Error();
8224d9d3c4cS猫头猫            }
8234d9d3c4cS猫头猫            resetMediaItem(result, this.plugin.name);
8244d9d3c4cS猫头猫            return result;
825ea6d708fS猫头猫        } catch (e: any) {
826ea6d708fS猫头猫            devLog('error', '导入单曲失败', e, e?.message);
827ea6d708fS猫头猫
8284d9d3c4cS猫头猫            return null;
8294d9d3c4cS猫头猫        }
8304d9d3c4cS猫头猫    }
831d52aa40eS猫头猫    /** 获取榜单 */
83292b6c95aS猫头猫    async getTopLists(): Promise<IMusic.IMusicSheetGroupItem[]> {
833d52aa40eS猫头猫        try {
834d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopLists?.();
835d52aa40eS猫头猫            if (!result) {
836d52aa40eS猫头猫                throw new Error();
837d52aa40eS猫头猫            }
838d52aa40eS猫头猫            return result;
839d52aa40eS猫头猫        } catch (e: any) {
840d52aa40eS猫头猫            devLog('error', '获取榜单失败', e, e?.message);
841d52aa40eS猫头猫            return [];
842d52aa40eS猫头猫        }
843d52aa40eS猫头猫    }
844d52aa40eS猫头猫    /** 获取榜单详情 */
845d52aa40eS猫头猫    async getTopListDetail(
84692b6c95aS猫头猫        topListItem: IMusic.IMusicSheetItemBase,
847956ee1b7S猫头猫        page: number,
848956ee1b7S猫头猫    ): Promise<IPlugin.ITopListInfoResult> {
849d52aa40eS猫头猫        try {
850d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopListDetail?.(
851d52aa40eS猫头猫                topListItem,
852956ee1b7S猫头猫                page,
853d52aa40eS猫头猫            );
854d52aa40eS猫头猫            if (!result) {
855d52aa40eS猫头猫                throw new Error();
856d52aa40eS猫头猫            }
857d384662fS猫头猫            if (result.musicList) {
858d384662fS猫头猫                result.musicList.forEach(_ =>
859d384662fS猫头猫                    resetMediaItem(_, this.plugin.name),
860d384662fS猫头猫                );
861d384662fS猫头猫            }
862956ee1b7S猫头猫            if (result.isEnd !== false) {
863956ee1b7S猫头猫                result.isEnd = true;
864956ee1b7S猫头猫            }
865d52aa40eS猫头猫            return result;
866d52aa40eS猫头猫        } catch (e: any) {
867d52aa40eS猫头猫            devLog('error', '获取榜单详情失败', e, e?.message);
868d52aa40eS猫头猫            return {
869956ee1b7S猫头猫                isEnd: true,
870956ee1b7S猫头猫                topListItem: topListItem as IMusic.IMusicSheetItem,
871d52aa40eS猫头猫                musicList: [],
872d52aa40eS猫头猫            };
873d52aa40eS猫头猫        }
874d52aa40eS猫头猫    }
875ceb900cdS猫头猫
8765830c002S猫头猫    /** 获取推荐歌单的tag */
877ceb900cdS猫头猫    async getRecommendSheetTags(): Promise<IPlugin.IGetRecommendSheetTagsResult> {
878ceb900cdS猫头猫        try {
879ceb900cdS猫头猫            const result =
880ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetTags?.();
881ceb900cdS猫头猫            if (!result) {
882ceb900cdS猫头猫                throw new Error();
883ceb900cdS猫头猫            }
884ceb900cdS猫头猫            return result;
885ceb900cdS猫头猫        } catch (e: any) {
886ceb900cdS猫头猫            devLog('error', '获取推荐歌单失败', e, e?.message);
887ceb900cdS猫头猫            return {
888ceb900cdS猫头猫                data: [],
889ceb900cdS猫头猫            };
890ceb900cdS猫头猫        }
891ceb900cdS猫头猫    }
8925830c002S猫头猫    /** 获取某个tag的推荐歌单 */
893ceb900cdS猫头猫    async getRecommendSheetsByTag(
894ceb900cdS猫头猫        tagItem: ICommon.IUnique,
895ceb900cdS猫头猫        page?: number,
896ceb900cdS猫头猫    ): Promise<ICommon.PaginationResponse<IMusic.IMusicSheetItemBase>> {
897ceb900cdS猫头猫        try {
898ceb900cdS猫头猫            const result =
899ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetsByTag?.(
900ceb900cdS猫头猫                    tagItem,
901ceb900cdS猫头猫                    page ?? 1,
902ceb900cdS猫头猫                );
903ceb900cdS猫头猫            if (!result) {
904ceb900cdS猫头猫                throw new Error();
905ceb900cdS猫头猫            }
906ceb900cdS猫头猫            if (result.isEnd !== false) {
907ceb900cdS猫头猫                result.isEnd = true;
908ceb900cdS猫头猫            }
909ceb900cdS猫头猫            if (!result.data) {
910ceb900cdS猫头猫                result.data = [];
911ceb900cdS猫头猫            }
912ceb900cdS猫头猫            result.data.forEach(item => resetMediaItem(item, this.plugin.name));
913ceb900cdS猫头猫
914ceb900cdS猫头猫            return result;
915ceb900cdS猫头猫        } catch (e: any) {
916ceb900cdS猫头猫            devLog('error', '获取推荐歌单详情失败', e, e?.message);
917ceb900cdS猫头猫            return {
918ceb900cdS猫头猫                isEnd: true,
919ceb900cdS猫头猫                data: [],
920ceb900cdS猫头猫            };
921ceb900cdS猫头猫        }
922ceb900cdS猫头猫    }
923927dbe93S猫头猫}
924d5bfeb7eS猫头猫//#endregion
9251a5528a0S猫头猫
926927dbe93S猫头猫let plugins: Array<Plugin> = [];
927927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins);
92874d0cf81S猫头猫
929d5bfeb7eS猫头猫//#region 本地音乐插件
93074d0cf81S猫头猫/** 本地插件 */
93174d0cf81S猫头猫const localFilePlugin = new Plugin(function () {
9320e4173cdS猫头猫    return {
933d5bfeb7eS猫头猫        platform: localPluginPlatform,
93474d0cf81S猫头猫        _path: '',
93574d0cf81S猫头猫        async getMusicInfo(musicBase) {
93674d0cf81S猫头猫            const localPath = getInternalData<string>(
93774d0cf81S猫头猫                musicBase,
93874d0cf81S猫头猫                InternalDataType.LOCALPATH,
9390e4173cdS猫头猫            );
94074d0cf81S猫头猫            if (localPath) {
94174d0cf81S猫头猫                const coverImg = await Mp3Util.getMediaCoverImg(localPath);
94274d0cf81S猫头猫                return {
94374d0cf81S猫头猫                    artwork: coverImg,
94474d0cf81S猫头猫                };
94574d0cf81S猫头猫            }
94674d0cf81S猫头猫            return null;
94774d0cf81S猫头猫        },
9487993f90eS猫头猫        async getLyric(musicBase) {
9497993f90eS猫头猫            const localPath = getInternalData<string>(
9507993f90eS猫头猫                musicBase,
9517993f90eS猫头猫                InternalDataType.LOCALPATH,
9527993f90eS猫头猫            );
9533a6f67b1S猫头猫            let rawLrc: string | null = null;
9547993f90eS猫头猫            if (localPath) {
9553a6f67b1S猫头猫                // 读取内嵌歌词
9563a6f67b1S猫头猫                try {
9573a6f67b1S猫头猫                    rawLrc = await Mp3Util.getLyric(localPath);
9583a6f67b1S猫头猫                } catch (e) {
959a84a85c5S猫头猫                    console.log('读取内嵌歌词失败', e);
9607993f90eS猫头猫                }
9613a6f67b1S猫头猫                if (!rawLrc) {
9623a6f67b1S猫头猫                    // 读取配置歌词
9633a6f67b1S猫头猫                    const lastDot = localPath.lastIndexOf('.');
9643a6f67b1S猫头猫                    const lrcPath = localPath.slice(0, lastDot) + '.lrc';
9653a6f67b1S猫头猫
9663a6f67b1S猫头猫                    try {
9673a6f67b1S猫头猫                        if (await exists(lrcPath)) {
9683a6f67b1S猫头猫                            rawLrc = await readFile(lrcPath, 'utf8');
9693a6f67b1S猫头猫                        }
9703a6f67b1S猫头猫                    } catch {}
9713a6f67b1S猫头猫                }
9723a6f67b1S猫头猫            }
9733a6f67b1S猫头猫
9743a6f67b1S猫头猫            return rawLrc
9753a6f67b1S猫头猫                ? {
9763a6f67b1S猫头猫                      rawLrc,
9773a6f67b1S猫头猫                  }
9783a6f67b1S猫头猫                : null;
9797993f90eS猫头猫        },
980a84a85c5S猫头猫        async importMusicItem(urlLike) {
981a84a85c5S猫头猫            let meta: any = {};
982a84a85c5S猫头猫            try {
983a84a85c5S猫头猫                meta = await Mp3Util.getBasicMeta(urlLike);
984a84a85c5S猫头猫            } catch {}
985a84a85c5S猫头猫            const id = await FileSystem.hash(urlLike, 'MD5');
986a84a85c5S猫头猫            return {
987a84a85c5S猫头猫                id: id,
988a84a85c5S猫头猫                platform: '本地',
989a84a85c5S猫头猫                title: meta?.title ?? getFileName(urlLike),
990a84a85c5S猫头猫                artist: meta?.artist ?? '未知歌手',
991a84a85c5S猫头猫                duration: parseInt(meta?.duration ?? '0') / 1000,
992a84a85c5S猫头猫                album: meta?.album ?? '未知专辑',
993a84a85c5S猫头猫                artwork: '',
994a84a85c5S猫头猫                [internalSerializeKey]: {
995a84a85c5S猫头猫                    localPath: urlLike,
996a84a85c5S猫头猫                },
997a84a85c5S猫头猫            };
998a84a85c5S猫头猫        },
99974d0cf81S猫头猫    };
100074d0cf81S猫头猫}, '');
10017993f90eS猫头猫localFilePlugin.hash = localPluginHash;
1002927dbe93S猫头猫
1003d5bfeb7eS猫头猫//#endregion
1004d5bfeb7eS猫头猫
1005927dbe93S猫头猫async function setup() {
1006927dbe93S猫头猫    const _plugins: Array<Plugin> = [];
1007927dbe93S猫头猫    try {
1008927dbe93S猫头猫        // 加载插件
1009927dbe93S猫头猫        const pluginsPaths = await readDir(pathConst.pluginPath);
1010927dbe93S猫头猫        for (let i = 0; i < pluginsPaths.length; ++i) {
1011927dbe93S猫头猫            const _pluginUrl = pluginsPaths[i];
10121e263108S猫头猫            trace('初始化插件', _pluginUrl);
10131e263108S猫头猫            if (
10141e263108S猫头猫                _pluginUrl.isFile() &&
10151e263108S猫头猫                (_pluginUrl.name?.endsWith?.('.js') ||
10161e263108S猫头猫                    _pluginUrl.path?.endsWith?.('.js'))
10171e263108S猫头猫            ) {
1018927dbe93S猫头猫                const funcCode = await readFile(_pluginUrl.path, 'utf8');
1019927dbe93S猫头猫                const plugin = new Plugin(funcCode, _pluginUrl.path);
10204060c00aS猫头猫                const _pluginIndex = _plugins.findIndex(
10214060c00aS猫头猫                    p => p.hash === plugin.hash,
10224060c00aS猫头猫                );
1023927dbe93S猫头猫                if (_pluginIndex !== -1) {
1024927dbe93S猫头猫                    // 重复插件,直接忽略
10250c266394S猫头猫                    continue;
1026927dbe93S猫头猫                }
1027927dbe93S猫头猫                plugin.hash !== '' && _plugins.push(plugin);
1028927dbe93S猫头猫            }
1029927dbe93S猫头猫        }
1030927dbe93S猫头猫
1031927dbe93S猫头猫        plugins = _plugins;
1032e08d37a3S猫头猫        /** 初始化meta信息 */
1033c2b3a262S猫头猫        await PluginMeta.setupMeta(plugins.map(_ => _.name));
1034c2b3a262S猫头猫        /** 查看一下是否有禁用的标记 */
1035c2b3a262S猫头猫        const allMeta = PluginMeta.getPluginMetaAll() ?? {};
1036c2b3a262S猫头猫        for (let plugin of plugins) {
1037c2b3a262S猫头猫            if (allMeta[plugin.name]?.enabled === false) {
1038c2b3a262S猫头猫                plugin.state = 'disabled';
1039c2b3a262S猫头猫            }
1040c2b3a262S猫头猫        }
1041c2b3a262S猫头猫        pluginStateMapper.notify();
1042927dbe93S猫头猫    } catch (e: any) {
10434060c00aS猫头猫        ToastAndroid.show(
10444060c00aS猫头猫            `插件初始化失败:${e?.message ?? e}`,
10454060c00aS猫头猫            ToastAndroid.LONG,
10464060c00aS猫头猫        );
10471a5528a0S猫头猫        errorLog('插件初始化失败', e?.message);
1048927dbe93S猫头猫        throw e;
1049927dbe93S猫头猫    }
1050927dbe93S猫头猫}
1051927dbe93S猫头猫
1052e36e2599S猫头猫interface IInstallPluginConfig {
1053e36e2599S猫头猫    notCheckVersion?: boolean;
1054e36e2599S猫头猫}
1055e36e2599S猫头猫
1056927dbe93S猫头猫// 安装插件
1057e36e2599S猫头猫async function installPlugin(
1058e36e2599S猫头猫    pluginPath: string,
1059e36e2599S猫头猫    config?: IInstallPluginConfig,
1060e36e2599S猫头猫) {
106122c09412S猫头猫    // if (pluginPath.endsWith('.js')) {
1062927dbe93S猫头猫    const funcCode = await readFile(pluginPath, 'utf8');
1063e36e2599S猫头猫
1064e36e2599S猫头猫    if (funcCode) {
1065927dbe93S猫头猫        const plugin = new Plugin(funcCode, pluginPath);
1066927dbe93S猫头猫        const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
1067927dbe93S猫头猫        if (_pluginIndex !== -1) {
1068e36e2599S猫头猫            // 静默忽略
1069e36e2599S猫头猫            return plugin;
1070927dbe93S猫头猫        }
1071e36e2599S猫头猫        const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
1072e36e2599S猫头猫        if (oldVersionPlugin && !config?.notCheckVersion) {
1073e36e2599S猫头猫            if (
1074e36e2599S猫头猫                compare(
1075e36e2599S猫头猫                    oldVersionPlugin.instance.version ?? '',
1076e36e2599S猫头猫                    plugin.instance.version ?? '',
1077e36e2599S猫头猫                    '>',
1078e36e2599S猫头猫                )
1079e36e2599S猫头猫            ) {
1080e36e2599S猫头猫                throw new Error('已安装更新版本的插件');
1081e36e2599S猫头猫            }
1082e36e2599S猫头猫        }
1083e36e2599S猫头猫
1084927dbe93S猫头猫        if (plugin.hash !== '') {
1085927dbe93S猫头猫            const fn = nanoid();
1086e36e2599S猫头猫            if (oldVersionPlugin) {
1087e36e2599S猫头猫                plugins = plugins.filter(_ => _.hash !== oldVersionPlugin.hash);
1088e36e2599S猫头猫                try {
1089e36e2599S猫头猫                    await unlink(oldVersionPlugin.path);
1090e36e2599S猫头猫                } catch {}
1091e36e2599S猫头猫            }
1092927dbe93S猫头猫            const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
1093927dbe93S猫头猫            await copyFile(pluginPath, _pluginPath);
1094927dbe93S猫头猫            plugin.path = _pluginPath;
1095927dbe93S猫头猫            plugins = plugins.concat(plugin);
1096927dbe93S猫头猫            pluginStateMapper.notify();
1097a84a85c5S猫头猫            return plugin;
1098927dbe93S猫头猫        }
1099e36e2599S猫头猫        throw new Error('插件无法解析!');
1100927dbe93S猫头猫    }
1101e36e2599S猫头猫    throw new Error('插件无法识别!');
1102c2b3a262S猫头猫}
1103c2b3a262S猫头猫
1104c2b3a262S猫头猫async function installPluginFromUrl(
1105c2b3a262S猫头猫    url: string,
1106c2b3a262S猫头猫    config?: IInstallPluginConfig,
1107c2b3a262S猫头猫) {
110858992c6bS猫头猫    try {
110958992c6bS猫头猫        const funcCode = (await axios.get(url)).data;
111058992c6bS猫头猫        if (funcCode) {
111158992c6bS猫头猫            const plugin = new Plugin(funcCode, '');
111258992c6bS猫头猫            const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
111358992c6bS猫头猫            if (_pluginIndex !== -1) {
11148b7ddca8S猫头猫                // 静默忽略
11158b7ddca8S猫头猫                return;
111658992c6bS猫头猫            }
111725c1bd29S猫头猫            const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
1118c2b3a262S猫头猫            if (oldVersionPlugin && !config?.notCheckVersion) {
111925c1bd29S猫头猫                if (
112025c1bd29S猫头猫                    compare(
112125c1bd29S猫头猫                        oldVersionPlugin.instance.version ?? '',
112225c1bd29S猫头猫                        plugin.instance.version ?? '',
112325c1bd29S猫头猫                        '>',
112425c1bd29S猫头猫                    )
112525c1bd29S猫头猫                ) {
112625c1bd29S猫头猫                    throw new Error('已安装更新版本的插件');
112725c1bd29S猫头猫                }
112825c1bd29S猫头猫            }
112925c1bd29S猫头猫
113058992c6bS猫头猫            if (plugin.hash !== '') {
113158992c6bS猫头猫                const fn = nanoid();
113258992c6bS猫头猫                const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
113358992c6bS猫头猫                await writeFile(_pluginPath, funcCode, 'utf8');
113458992c6bS猫头猫                plugin.path = _pluginPath;
113558992c6bS猫头猫                plugins = plugins.concat(plugin);
113625c1bd29S猫头猫                if (oldVersionPlugin) {
113725c1bd29S猫头猫                    plugins = plugins.filter(
113825c1bd29S猫头猫                        _ => _.hash !== oldVersionPlugin.hash,
113925c1bd29S猫头猫                    );
114025c1bd29S猫头猫                    try {
114125c1bd29S猫头猫                        await unlink(oldVersionPlugin.path);
114225c1bd29S猫头猫                    } catch {}
114325c1bd29S猫头猫                }
114458992c6bS猫头猫                pluginStateMapper.notify();
114558992c6bS猫头猫                return;
114658992c6bS猫头猫            }
114774acbfc0S猫头猫            throw new Error('插件无法解析!');
114858992c6bS猫头猫        }
114925c1bd29S猫头猫    } catch (e: any) {
1150ea6d708fS猫头猫        devLog('error', 'URL安装插件失败', e, e?.message);
115158992c6bS猫头猫        errorLog('URL安装插件失败', e);
115225c1bd29S猫头猫        throw new Error(e?.message ?? '');
115358992c6bS猫头猫    }
115458992c6bS猫头猫}
115558992c6bS猫头猫
1156927dbe93S猫头猫/** 卸载插件 */
1157927dbe93S猫头猫async function uninstallPlugin(hash: string) {
1158927dbe93S猫头猫    const targetIndex = plugins.findIndex(_ => _.hash === hash);
1159927dbe93S猫头猫    if (targetIndex !== -1) {
1160927dbe93S猫头猫        try {
116124e5e74aS猫头猫            const pluginName = plugins[targetIndex].name;
1162927dbe93S猫头猫            await unlink(plugins[targetIndex].path);
1163927dbe93S猫头猫            plugins = plugins.filter(_ => _.hash !== hash);
1164927dbe93S猫头猫            pluginStateMapper.notify();
1165*43eb30bfS猫头猫            // 防止其他重名
116624e5e74aS猫头猫            if (plugins.every(_ => _.name !== pluginName)) {
1167*43eb30bfS猫头猫                MediaMeta.removeAll(pluginName);
116824e5e74aS猫头猫            }
1169927dbe93S猫头猫        } catch {}
1170927dbe93S猫头猫    }
1171927dbe93S猫头猫}
1172927dbe93S猫头猫
117308882a77S猫头猫async function uninstallAllPlugins() {
117408882a77S猫头猫    await Promise.all(
117508882a77S猫头猫        plugins.map(async plugin => {
117608882a77S猫头猫            try {
117708882a77S猫头猫                const pluginName = plugin.name;
117808882a77S猫头猫                await unlink(plugin.path);
1179*43eb30bfS猫头猫                MediaMeta.removeAll(pluginName);
118008882a77S猫头猫            } catch (e) {}
118108882a77S猫头猫        }),
118208882a77S猫头猫    );
118308882a77S猫头猫    plugins = [];
118408882a77S猫头猫    pluginStateMapper.notify();
1185e08d37a3S猫头猫
1186e08d37a3S猫头猫    /** 清除空余文件,异步做就可以了 */
1187e08d37a3S猫头猫    readDir(pathConst.pluginPath)
1188e08d37a3S猫头猫        .then(fns => {
1189e08d37a3S猫头猫            fns.forEach(fn => {
1190e08d37a3S猫头猫                unlink(fn.path).catch(emptyFunction);
1191e08d37a3S猫头猫            });
1192e08d37a3S猫头猫        })
1193e08d37a3S猫头猫        .catch(emptyFunction);
119408882a77S猫头猫}
119508882a77S猫头猫
119625c1bd29S猫头猫async function updatePlugin(plugin: Plugin) {
119725c1bd29S猫头猫    const updateUrl = plugin.instance.srcUrl;
119825c1bd29S猫头猫    if (!updateUrl) {
119925c1bd29S猫头猫        throw new Error('没有更新源');
120025c1bd29S猫头猫    }
120125c1bd29S猫头猫    try {
120225c1bd29S猫头猫        await installPluginFromUrl(updateUrl);
120325c1bd29S猫头猫    } catch (e: any) {
120425c1bd29S猫头猫        if (e.message === '插件已安装') {
120525c1bd29S猫头猫            throw new Error('当前已是最新版本');
120625c1bd29S猫头猫        } else {
120725c1bd29S猫头猫            throw e;
120825c1bd29S猫头猫        }
120925c1bd29S猫头猫    }
121025c1bd29S猫头猫}
121125c1bd29S猫头猫
1212927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) {
12132c595535S猫头猫    return getByName(mediaItem?.platform);
1214927dbe93S猫头猫}
1215927dbe93S猫头猫
1216927dbe93S猫头猫function getByHash(hash: string) {
12177993f90eS猫头猫    return hash === localPluginHash
12187993f90eS猫头猫        ? localFilePlugin
12197993f90eS猫头猫        : plugins.find(_ => _.hash === hash);
1220927dbe93S猫头猫}
1221927dbe93S猫头猫
1222927dbe93S猫头猫function getByName(name: string) {
12237993f90eS猫头猫    return name === localPluginPlatform
12240e4173cdS猫头猫        ? localFilePlugin
12250e4173cdS猫头猫        : plugins.find(_ => _.name === name);
1226927dbe93S猫头猫}
1227927dbe93S猫头猫
1228927dbe93S猫头猫function getValidPlugins() {
1229927dbe93S猫头猫    return plugins.filter(_ => _.state === 'enabled');
1230927dbe93S猫头猫}
1231927dbe93S猫头猫
12322b80a429S猫头猫function getSearchablePlugins(supportedSearchType?: ICommon.SupportMediaType) {
12332b80a429S猫头猫    return plugins.filter(
12342b80a429S猫头猫        _ =>
12352b80a429S猫头猫            _.state === 'enabled' &&
12362b80a429S猫头猫            _.instance.search &&
123739ac60f7S猫头猫            (supportedSearchType && _.instance.supportedSearchType
123839ac60f7S猫头猫                ? _.instance.supportedSearchType.includes(supportedSearchType)
12392b80a429S猫头猫                : true),
12402b80a429S猫头猫    );
1241efb9da24S猫头猫}
1242efb9da24S猫头猫
12432b80a429S猫头猫function getSortedSearchablePlugins(
12442b80a429S猫头猫    supportedSearchType?: ICommon.SupportMediaType,
12452b80a429S猫头猫) {
12462b80a429S猫头猫    return getSearchablePlugins(supportedSearchType).sort((a, b) =>
1247e08d37a3S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1248e08d37a3S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1249e08d37a3S猫头猫        0
1250e08d37a3S猫头猫            ? -1
1251e08d37a3S猫头猫            : 1,
1252e08d37a3S猫头猫    );
1253e08d37a3S猫头猫}
1254e08d37a3S猫头猫
125515feccc1S猫头猫function getTopListsablePlugins() {
125615feccc1S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists);
125715feccc1S猫头猫}
125815feccc1S猫头猫
125915feccc1S猫头猫function getSortedTopListsablePlugins() {
126015feccc1S猫头猫    return getTopListsablePlugins().sort((a, b) =>
126115feccc1S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
126215feccc1S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
126315feccc1S猫头猫        0
126415feccc1S猫头猫            ? -1
126515feccc1S猫头猫            : 1,
126615feccc1S猫头猫    );
126715feccc1S猫头猫}
126815feccc1S猫头猫
1269ceb900cdS猫头猫function getRecommendSheetablePlugins() {
1270ceb900cdS猫头猫    return plugins.filter(
1271ceb900cdS猫头猫        _ => _.state === 'enabled' && _.instance.getRecommendSheetsByTag,
1272ceb900cdS猫头猫    );
1273ceb900cdS猫头猫}
1274ceb900cdS猫头猫
1275ceb900cdS猫头猫function getSortedRecommendSheetablePlugins() {
1276ceb900cdS猫头猫    return getRecommendSheetablePlugins().sort((a, b) =>
1277ceb900cdS猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1278ceb900cdS猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1279ceb900cdS猫头猫        0
1280ceb900cdS猫头猫            ? -1
1281ceb900cdS猫头猫            : 1,
1282ceb900cdS猫头猫    );
1283ceb900cdS猫头猫}
1284ceb900cdS猫头猫
1285e08d37a3S猫头猫function useSortedPlugins() {
1286e08d37a3S猫头猫    const _plugins = pluginStateMapper.useMappedState();
1287e08d37a3S猫头猫    const _pluginMetaAll = PluginMeta.usePluginMetaAll();
1288e08d37a3S猫头猫
128934588741S猫头猫    const [sortedPlugins, setSortedPlugins] = useState(
129034588741S猫头猫        [..._plugins].sort((a, b) =>
1291e08d37a3S猫头猫            (_pluginMetaAll[a.name]?.order ?? Infinity) -
1292e08d37a3S猫头猫                (_pluginMetaAll[b.name]?.order ?? Infinity) <
1293e08d37a3S猫头猫            0
1294e08d37a3S猫头猫                ? -1
1295e08d37a3S猫头猫                : 1,
129634588741S猫头猫        ),
1297e08d37a3S猫头猫    );
129834588741S猫头猫
129934588741S猫头猫    useEffect(() => {
1300d4cd40d8S猫头猫        InteractionManager.runAfterInteractions(() => {
130134588741S猫头猫            setSortedPlugins(
130234588741S猫头猫                [..._plugins].sort((a, b) =>
130334588741S猫头猫                    (_pluginMetaAll[a.name]?.order ?? Infinity) -
130434588741S猫头猫                        (_pluginMetaAll[b.name]?.order ?? Infinity) <
130534588741S猫头猫                    0
130634588741S猫头猫                        ? -1
130734588741S猫头猫                        : 1,
130834588741S猫头猫                ),
130934588741S猫头猫            );
1310d4cd40d8S猫头猫        });
131134588741S猫头猫    }, [_plugins, _pluginMetaAll]);
131234588741S猫头猫
131334588741S猫头猫    return sortedPlugins;
1314e08d37a3S猫头猫}
1315e08d37a3S猫头猫
1316c2b3a262S猫头猫async function setPluginEnabled(plugin: Plugin, enabled?: boolean) {
1317c2b3a262S猫头猫    const target = plugins.find(it => it.hash === plugin.hash);
1318c2b3a262S猫头猫    if (target) {
1319c2b3a262S猫头猫        target.state = enabled ? 'enabled' : 'disabled';
1320c2b3a262S猫头猫        plugins = [...plugins];
1321c2b3a262S猫头猫        pluginStateMapper.notify();
1322c2b3a262S猫头猫        PluginMeta.setPluginMetaProp(plugin, 'enabled', enabled);
1323c2b3a262S猫头猫    }
1324c2b3a262S猫头猫}
1325c2b3a262S猫头猫
1326927dbe93S猫头猫const PluginManager = {
1327927dbe93S猫头猫    setup,
1328927dbe93S猫头猫    installPlugin,
132958992c6bS猫头猫    installPluginFromUrl,
133025c1bd29S猫头猫    updatePlugin,
1331927dbe93S猫头猫    uninstallPlugin,
1332927dbe93S猫头猫    getByMedia,
1333927dbe93S猫头猫    getByHash,
1334927dbe93S猫头猫    getByName,
1335927dbe93S猫头猫    getValidPlugins,
1336efb9da24S猫头猫    getSearchablePlugins,
1337e08d37a3S猫头猫    getSortedSearchablePlugins,
133815feccc1S猫头猫    getTopListsablePlugins,
1339ceb900cdS猫头猫    getSortedRecommendSheetablePlugins,
134015feccc1S猫头猫    getSortedTopListsablePlugins,
13415276aef9S猫头猫    usePlugins: pluginStateMapper.useMappedState,
1342e08d37a3S猫头猫    useSortedPlugins,
134308882a77S猫头猫    uninstallAllPlugins,
1344c2b3a262S猫头猫    setPluginEnabled,
13455276aef9S猫头猫};
1346927dbe93S猫头猫
1347927dbe93S猫头猫export default PluginManager;
1348