xref: /MusicFree/src/core/pluginManager.ts (revision 5353b47372c7f041c41058084167f60418a3c9fc)
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';
205589cdf3S猫头猫import MediaExtra 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';
42*5353b473S猫头猫import {getInfoAsync} from 'expo-file-system';
4374d0cf81S猫头猫import Mp3Util from '@/native/mp3Util';
44e08d37a3S猫头猫import {PluginMeta} from './pluginMeta';
4534588741S猫头猫import {useEffect, useState} from 'react';
4611908939S猫头猫import {addFileScheme, getFileName} from '@/utils/fileUtils';
47a7b42a4cS猫头猫import {URL} from 'react-native-url-polyfill';
48a7b42a4cS猫头猫import Base64 from '@/utils/base64';
4943eb30bfS猫头猫import MediaCache from './mediaCache';
505589cdf3S猫头猫import {produce} from 'immer';
5143eb30bfS猫头猫import objectPath from 'object-path';
52927dbe93S猫头猫
5361aca335S猫头猫axios.defaults.timeout = 2000;
54927dbe93S猫头猫
55927dbe93S猫头猫const sha256 = CryptoJs.SHA256;
56927dbe93S猫头猫
57cfa0fc07S猫头猫export enum PluginStateCode {
58927dbe93S猫头猫    /** 版本不匹配 */
59927dbe93S猫头猫    VersionNotMatch = 'VERSION NOT MATCH',
60927dbe93S猫头猫    /** 无法解析 */
61927dbe93S猫头猫    CannotParse = 'CANNOT PARSE',
62927dbe93S猫头猫}
63927dbe93S猫头猫
649c34d637S猫头猫const packages: Record<string, any> = {
659c34d637S猫头猫    cheerio,
669c34d637S猫头猫    'crypto-js': CryptoJs,
679c34d637S猫头猫    axios,
689c34d637S猫头猫    dayjs,
699c34d637S猫头猫    'big-integer': bigInt,
709c34d637S猫头猫    qs,
719c34d637S猫头猫    he,
723b3d6357S猫头猫    '@react-native-cookies/cookies': CookieManager,
7328ccceb7S猫头猫    webdav,
749c34d637S猫头猫};
759c34d637S猫头猫
76b43683eaS猫头猫const _require = (packageName: string) => {
77b43683eaS猫头猫    let pkg = packages[packageName];
78b43683eaS猫头猫    pkg.default = pkg;
79b43683eaS猫头猫    return pkg;
80b43683eaS猫头猫};
819c34d637S猫头猫
8253f8cd8eS猫头猫const _consoleBind = function (
8353f8cd8eS猫头猫    method: 'log' | 'error' | 'info' | 'warn',
8453f8cd8eS猫头猫    ...args: any
8553f8cd8eS猫头猫) {
8653f8cd8eS猫头猫    const fn = console[method];
8753f8cd8eS猫头猫    if (fn) {
8853f8cd8eS猫头猫        fn(...args);
8953f8cd8eS猫头猫        devLog(method, ...args);
9053f8cd8eS猫头猫    }
9153f8cd8eS猫头猫};
9253f8cd8eS猫头猫
9353f8cd8eS猫头猫const _console = {
9453f8cd8eS猫头猫    log: _consoleBind.bind(null, 'log'),
9553f8cd8eS猫头猫    warn: _consoleBind.bind(null, 'warn'),
9653f8cd8eS猫头猫    info: _consoleBind.bind(null, 'info'),
9753f8cd8eS猫头猫    error: _consoleBind.bind(null, 'error'),
9853f8cd8eS猫头猫};
9953f8cd8eS猫头猫
100a7b42a4cS猫头猫function formatAuthUrl(url: string) {
101a7b42a4cS猫头猫    const urlObj = new URL(url);
102a7b42a4cS猫头猫
103a7b42a4cS猫头猫    try {
104a7b42a4cS猫头猫        if (urlObj.username && urlObj.password) {
105a7b42a4cS猫头猫            const auth = `Basic ${Base64.btoa(
106a7b42a4cS猫头猫                `${decodeURIComponent(urlObj.username)}:${decodeURIComponent(
107a7b42a4cS猫头猫                    urlObj.password,
108a7b42a4cS猫头猫                )}`,
109a7b42a4cS猫头猫            )}`;
110a7b42a4cS猫头猫            urlObj.username = '';
111a7b42a4cS猫头猫            urlObj.password = '';
112a7b42a4cS猫头猫
113a7b42a4cS猫头猫            return {
114a7b42a4cS猫头猫                url: urlObj.toString(),
115a7b42a4cS猫头猫                auth,
116a7b42a4cS猫头猫            };
117a7b42a4cS猫头猫        }
118a7b42a4cS猫头猫    } catch (e) {
119a7b42a4cS猫头猫        return {
120a7b42a4cS猫头猫            url,
121a7b42a4cS猫头猫        };
122a7b42a4cS猫头猫    }
123a7b42a4cS猫头猫    return {
124a7b42a4cS猫头猫        url,
125a7b42a4cS猫头猫    };
126a7b42a4cS猫头猫}
127a7b42a4cS猫头猫
128d5bfeb7eS猫头猫//#region 插件类
129927dbe93S猫头猫export class Plugin {
130927dbe93S猫头猫    /** 插件名 */
131927dbe93S猫头猫    public name: string;
132927dbe93S猫头猫    /** 插件的hash,作为唯一id */
133927dbe93S猫头猫    public hash: string;
134927dbe93S猫头猫    /** 插件状态:激活、关闭、错误 */
135927dbe93S猫头猫    public state: 'enabled' | 'disabled' | 'error';
136927dbe93S猫头猫    /** 插件状态信息 */
137927dbe93S猫头猫    public stateCode?: PluginStateCode;
138927dbe93S猫头猫    /** 插件的实例 */
139927dbe93S猫头猫    public instance: IPlugin.IPluginInstance;
140927dbe93S猫头猫    /** 插件路径 */
141927dbe93S猫头猫    public path: string;
142927dbe93S猫头猫    /** 插件方法 */
143927dbe93S猫头猫    public methods: PluginMethods;
144927dbe93S猫头猫
14574d0cf81S猫头猫    constructor(
14674d0cf81S猫头猫        funcCode: string | (() => IPlugin.IPluginInstance),
14774d0cf81S猫头猫        pluginPath: string,
14874d0cf81S猫头猫    ) {
149927dbe93S猫头猫        this.state = 'enabled';
150927dbe93S猫头猫        let _instance: IPlugin.IPluginInstance;
1513b3d6357S猫头猫        const _module: any = {exports: {}};
152927dbe93S猫头猫        try {
15374d0cf81S猫头猫            if (typeof funcCode === 'string') {
154e0caf342S猫头猫                // 插件的环境变量
155e0caf342S猫头猫                const env = {
156e0caf342S猫头猫                    getUserVariables: () => {
157e0caf342S猫头猫                        return (
158e0caf342S猫头猫                            PluginMeta.getPluginMeta(this)?.userVariables ?? {}
159e0caf342S猫头猫                        );
160e0caf342S猫头猫                    },
161e3fa9b3cS猫头猫                    os: 'android',
162e0caf342S猫头猫                };
163e0caf342S猫头猫
1644060c00aS猫头猫                // eslint-disable-next-line no-new-func
165927dbe93S猫头猫                _instance = Function(`
166927dbe93S猫头猫                    'use strict';
16791eb8fa8S猫头猫                    return function(require, __musicfree_require, module, exports, console, env, URL) {
1689c34d637S猫头猫                        ${funcCode}
169927dbe93S猫头猫                    }
170e0caf342S猫头猫                `)()(
171e0caf342S猫头猫                    _require,
172e0caf342S猫头猫                    _require,
173e0caf342S猫头猫                    _module,
174e0caf342S猫头猫                    _module.exports,
175e0caf342S猫头猫                    _console,
176e0caf342S猫头猫                    env,
17791eb8fa8S猫头猫                    URL,
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就好了
30343eb30bfS猫头猫        const mediaExtra = MediaExtra.get(musicItem);
304927dbe93S猫头猫        const localPath =
30543eb30bfS猫头猫            mediaExtra?.localPath ||
30643eb30bfS猫头猫            getInternalData<string>(musicItem, InternalDataType.LOCALPATH) ||
3070e4173cdS猫头猫            getInternalData<string>(
3080e4173cdS猫头猫                LocalMusicSheet.isLocalMusic(musicItem),
3090e4173cdS猫头猫                InternalDataType.LOCALPATH,
3100e4173cdS猫头猫            );
311*5353b473S猫头猫        if (localPath && (await getInfoAsync(localPath)).exists) {
3120e4173cdS猫头猫            trace('本地播放', localPath);
31343eb30bfS猫头猫            if (mediaExtra && mediaExtra.localPath !== localPath) {
31443eb30bfS猫头猫                // 修正一下本地数据
31543eb30bfS猫头猫                MediaExtra.update(musicItem, {
31643eb30bfS猫头猫                    localPath,
31743eb30bfS猫头猫                });
31843eb30bfS猫头猫            }
319927dbe93S猫头猫            return {
32011908939S猫头猫                url: addFileScheme(localPath),
321927dbe93S猫头猫            };
32243eb30bfS猫头猫        } else if (mediaExtra?.localPath) {
32343eb30bfS猫头猫            MediaExtra.update(musicItem, {
32443eb30bfS猫头猫                localPath: undefined,
32543eb30bfS猫头猫            });
326927dbe93S猫头猫        }
327a84a85c5S猫头猫
3287993f90eS猫头猫        if (musicItem.platform === localPluginPlatform) {
329f5935920S猫头猫            throw new Error('本地音乐不存在');
330f5935920S猫头猫        }
331927dbe93S猫头猫        // 2. 缓存播放
33243eb30bfS猫头猫        const mediaCache = MediaCache.getMediaCache(
33343eb30bfS猫头猫            musicItem,
33443eb30bfS猫头猫        ) as IMusic.IMusicItem | null;
335985f8e75S猫头猫        const pluginCacheControl =
336985f8e75S猫头猫            this.plugin.instance.cacheControl ?? 'no-cache';
337cfa0fc07S猫头猫        if (
338cfa0fc07S猫头猫            mediaCache &&
33943eb30bfS猫头猫            mediaCache?.source?.[quality]?.url &&
34048f4b873S猫头猫            (pluginCacheControl === CacheControl.Cache ||
34148f4b873S猫头猫                (pluginCacheControl === CacheControl.NoCache &&
342ef714860S猫头猫                    Network.isOffline()))
343cfa0fc07S猫头猫        ) {
3445276aef9S猫头猫            trace('播放', '缓存播放');
34543eb30bfS猫头猫            const qualityInfo = mediaCache.source[quality];
346927dbe93S猫头猫            return {
34743eb30bfS猫头猫                url: qualityInfo!.url,
348927dbe93S猫头猫                headers: mediaCache.headers,
3494060c00aS猫头猫                userAgent:
3504060c00aS猫头猫                    mediaCache.userAgent ?? mediaCache.headers?.['user-agent'],
351927dbe93S猫头猫            };
352927dbe93S猫头猫        }
353927dbe93S猫头猫        // 3. 插件解析
35420e6a092S猫头猫        if (!this.plugin.instance.getMediaSource) {
355a7b42a4cS猫头猫            const {url, auth} = formatAuthUrl(
356a7b42a4cS猫头猫                musicItem?.qualities?.[quality]?.url ?? musicItem.url,
357a7b42a4cS猫头猫            );
358a7b42a4cS猫头猫            return {
359a7b42a4cS猫头猫                url: url,
360a7b42a4cS猫头猫                headers: auth
361a7b42a4cS猫头猫                    ? {
362a7b42a4cS猫头猫                          Authorization: auth,
363a7b42a4cS猫头猫                      }
364a7b42a4cS猫头猫                    : undefined,
365a7b42a4cS猫头猫            };
366927dbe93S猫头猫        }
367927dbe93S猫头猫        try {
368abaede57S猫头猫            const {url, headers} = (await this.plugin.instance.getMediaSource(
369abaede57S猫头猫                musicItem,
370abaede57S猫头猫                quality,
371abaede57S猫头猫            )) ?? {url: musicItem?.qualities?.[quality]?.url};
372927dbe93S猫头猫            if (!url) {
373a28eac61S猫头猫                throw new Error('NOT RETRY');
374927dbe93S猫头猫            }
3755276aef9S猫头猫            trace('播放', '插件播放');
376927dbe93S猫头猫            const result = {
377927dbe93S猫头猫                url,
378927dbe93S猫头猫                headers,
379927dbe93S猫头猫                userAgent: headers?.['user-agent'],
380cfa0fc07S猫头猫            } as IPlugin.IMediaSourceResult;
381a7b42a4cS猫头猫            const authFormattedResult = formatAuthUrl(result.url!);
382a7b42a4cS猫头猫            if (authFormattedResult.auth) {
383a7b42a4cS猫头猫                result.url = authFormattedResult.url;
384a7b42a4cS猫头猫                result.headers = {
385a7b42a4cS猫头猫                    ...(result.headers ?? {}),
386a7b42a4cS猫头猫                    Authorization: authFormattedResult.auth,
387a7b42a4cS猫头猫                };
388a7b42a4cS猫头猫            }
389927dbe93S猫头猫
390dc160d50S猫头猫            if (
391dc160d50S猫头猫                pluginCacheControl !== CacheControl.NoStore &&
392dc160d50S猫头猫                !notUpdateCache
393dc160d50S猫头猫            ) {
39443eb30bfS猫头猫                // 更新缓存
39543eb30bfS猫头猫                const cacheSource = {
39643eb30bfS猫头猫                    headers: result.headers,
39743eb30bfS猫头猫                    userAgent: result.userAgent,
39843eb30bfS猫头猫                    url,
39943eb30bfS猫头猫                };
40043eb30bfS猫头猫                let realMusicItem = {
40143eb30bfS猫头猫                    ...musicItem,
40243eb30bfS猫头猫                    ...(mediaCache || {}),
40343eb30bfS猫头猫                };
40443eb30bfS猫头猫                realMusicItem.source = {
40543eb30bfS猫头猫                    ...(realMusicItem.source || {}),
40643eb30bfS猫头猫                    [quality]: cacheSource,
40743eb30bfS猫头猫                };
40843eb30bfS猫头猫
40943eb30bfS猫头猫                MediaCache.setMediaCache(realMusicItem);
410752ffc5aS猫头猫            }
411927dbe93S猫头猫            return result;
412927dbe93S猫头猫        } catch (e: any) {
413a28eac61S猫头猫            if (retryCount > 0 && e?.message !== 'NOT RETRY') {
414927dbe93S猫头猫                await delay(150);
415abaede57S猫头猫                return this.getMediaSource(musicItem, quality, --retryCount);
416927dbe93S猫头猫            }
417927dbe93S猫头猫            errorLog('获取真实源失败', e?.message);
418ea6d708fS猫头猫            devLog('error', '获取真实源失败', e, e?.message);
419192ae2b0S猫头猫            return null;
420927dbe93S猫头猫        }
421927dbe93S猫头猫    }
422927dbe93S猫头猫
423927dbe93S猫头猫    /** 获取音乐详情 */
424927dbe93S猫头猫    async getMusicInfo(
425927dbe93S猫头猫        musicItem: ICommon.IMediaBase,
42674d0cf81S猫头猫    ): Promise<Partial<IMusic.IMusicItem> | null> {
427927dbe93S猫头猫        if (!this.plugin.instance.getMusicInfo) {
428d704daedS猫头猫            return null;
429927dbe93S猫头猫        }
43074d0cf81S猫头猫        try {
431927dbe93S猫头猫            return (
432927dbe93S猫头猫                this.plugin.instance.getMusicInfo(
4337993f90eS猫头猫                    resetMediaItem(musicItem, undefined, true),
434d704daedS猫头猫                ) ?? null
435927dbe93S猫头猫            );
436ea6d708fS猫头猫        } catch (e: any) {
437ea6d708fS猫头猫            devLog('error', '获取音乐详情失败', e, e?.message);
438d704daedS猫头猫            return null;
43974d0cf81S猫头猫        }
440927dbe93S猫头猫    }
441927dbe93S猫头猫
4427e883dbbS猫头猫    /**
4437e883dbbS猫头猫     *
4447e883dbbS猫头猫     * getLyric(musicItem) => {
4457e883dbbS猫头猫     *      lyric: string;
4467e883dbbS猫头猫     *      trans: string;
4477e883dbbS猫头猫     * }
4487e883dbbS猫头猫     *
4497e883dbbS猫头猫     */
450927dbe93S猫头猫    /** 获取歌词 */
451927dbe93S猫头猫    async getLyric(
45243eb30bfS猫头猫        originalMusicItem: IMusic.IMusicItemBase,
453927dbe93S猫头猫    ): Promise<ILyric.ILyricSource | null> {
45443eb30bfS猫头猫        // 1.额外存储的meta信息(关联歌词)
4555589cdf3S猫头猫        const meta = MediaExtra.get(originalMusicItem);
45643eb30bfS猫头猫        let musicItem: IMusic.IMusicItem;
457927dbe93S猫头猫        if (meta && meta.associatedLrc) {
45843eb30bfS猫头猫            musicItem = meta.associatedLrc as IMusic.IMusicItem;
45943eb30bfS猫头猫        } else {
46043eb30bfS猫头猫            musicItem = originalMusicItem as IMusic.IMusicItem;
461927dbe93S猫头猫        }
46243eb30bfS猫头猫
46343eb30bfS猫头猫        const musicItemCache = MediaCache.getMediaCache(
4647a91f04fS猫头猫            musicItem,
46543eb30bfS猫头猫        ) as IMusic.IMusicItemCache | null;
46643eb30bfS猫头猫
46743eb30bfS猫头猫        /** 原始歌词文本 */
4687e883dbbS猫头猫        let rawLrc: string | null = musicItem.rawLrc || null;
4697e883dbbS猫头猫        let translation: string | null = null;
47043eb30bfS猫头猫
47143eb30bfS猫头猫        // 2. 缓存歌词 / 对象上本身的歌词
47243eb30bfS猫头猫        if (musicItemCache?.lyric) {
47343eb30bfS猫头猫            // 缓存的远程结果
47443eb30bfS猫头猫            let cacheLyric: ILyric.ILyricSource | null =
47543eb30bfS猫头猫                musicItemCache.lyric || null;
47643eb30bfS猫头猫            // 缓存的本地结果
47743eb30bfS猫头猫            let localLyric: ILyric.ILyricSource | null =
47843eb30bfS猫头猫                musicItemCache.$localLyric || null;
47943eb30bfS猫头猫
4807e883dbbS猫头猫            // 优先用缓存的结果
4817e883dbbS猫头猫            if (cacheLyric.rawLrc || cacheLyric.translation) {
48243eb30bfS猫头猫                return {
4837e883dbbS猫头猫                    rawLrc: cacheLyric.rawLrc,
4847e883dbbS猫头猫                    translation: cacheLyric.translation,
48543eb30bfS猫头猫                };
48643eb30bfS猫头猫            }
48743eb30bfS猫头猫
4887e883dbbS猫头猫            // 本地其实是缓存的路径
4897e883dbbS猫头猫            if (localLyric) {
4907e883dbbS猫头猫                let needRefetch = false;
4917e883dbbS猫头猫                if (localLyric.rawLrc && (await exists(localLyric.rawLrc))) {
4927e883dbbS猫头猫                    rawLrc = await readFile(localLyric.rawLrc, 'utf8');
4937e883dbbS猫头猫                } else if (localLyric.rawLrc) {
4947e883dbbS猫头猫                    needRefetch = true;
4957e883dbbS猫头猫                }
4967e883dbbS猫头猫                if (
4977e883dbbS猫头猫                    localLyric.translation &&
4987e883dbbS猫头猫                    (await exists(localLyric.translation))
4997e883dbbS猫头猫                ) {
5007e883dbbS猫头猫                    translation = await readFile(
5017e883dbbS猫头猫                        localLyric.translation,
5027e883dbbS猫头猫                        'utf8',
5037e883dbbS猫头猫                    );
5047e883dbbS猫头猫                } else if (localLyric.translation) {
5057e883dbbS猫头猫                    needRefetch = true;
50643eb30bfS猫头猫                }
50743eb30bfS猫头猫
5087e883dbbS猫头猫                if (!needRefetch && (rawLrc || translation)) {
50943eb30bfS猫头猫                    return {
5107e883dbbS猫头猫                        rawLrc: rawLrc || undefined,
5117e883dbbS猫头猫                        translation: translation || undefined,
51243eb30bfS猫头猫                    };
51343eb30bfS猫头猫                }
51443eb30bfS猫头猫            }
51543eb30bfS猫头猫        }
51643eb30bfS猫头猫
51743eb30bfS猫头猫        // 3. 无缓存歌词/无自带歌词/无本地歌词
51843eb30bfS猫头猫        let lrcSource: ILyric.ILyricSource | null;
51943eb30bfS猫头猫        if (isSameMediaItem(originalMusicItem, musicItem)) {
52043eb30bfS猫头猫            lrcSource =
52143eb30bfS猫头猫                (await this.plugin.instance
5227e883dbbS猫头猫                    ?.getLyric?.(resetMediaItem(musicItem, undefined, true))
52343eb30bfS猫头猫                    ?.catch(() => null)) || null;
52443eb30bfS猫头猫        } else {
52543eb30bfS猫头猫            lrcSource =
52643eb30bfS猫头猫                (await PluginManager.getByMedia(musicItem)
52743eb30bfS猫头猫                    ?.instance?.getLyric?.(
52843eb30bfS猫头猫                        resetMediaItem(musicItem, undefined, true),
52943eb30bfS猫头猫                    )
53043eb30bfS猫头猫                    ?.catch(() => null)) || null;
53143eb30bfS猫头猫        }
53243eb30bfS猫头猫
53343eb30bfS猫头猫        if (lrcSource) {
5347e883dbbS猫头猫            rawLrc = lrcSource?.rawLrc || rawLrc;
5357e883dbbS猫头猫            translation = lrcSource?.translation || null;
53643eb30bfS猫头猫
5377e883dbbS猫头猫            const deprecatedLrcUrl = lrcSource?.lrc || musicItem.lrc;
53843eb30bfS猫头猫
5397e883dbbS猫头猫            // 本地的文件名
5407e883dbbS猫头猫            let filename: string | undefined = `${
5417e883dbbS猫头猫                pathConst.lrcCachePath
5427e883dbbS猫头猫            }${nanoid()}.lrc`;
5437e883dbbS猫头猫            let filenameTrans: string | undefined = `${
5447e883dbbS猫头猫                pathConst.lrcCachePath
5457e883dbbS猫头猫            }${nanoid()}.lrc`;
54643eb30bfS猫头猫
5477e883dbbS猫头猫            // 旧版本兼容
5487e883dbbS猫头猫            if (!(rawLrc || translation)) {
5497e883dbbS猫头猫                if (deprecatedLrcUrl) {
55043eb30bfS猫头猫                    rawLrc = (
5517e883dbbS猫头猫                        await axios
5527e883dbbS猫头猫                            .get(deprecatedLrcUrl, {timeout: 3000})
5537e883dbbS猫头猫                            .catch(() => null)
55443eb30bfS猫头猫                    )?.data;
5557e883dbbS猫头猫                } else if (musicItem.rawLrc) {
5567e883dbbS猫头猫                    rawLrc = musicItem.rawLrc;
5577e883dbbS猫头猫                }
55843eb30bfS猫头猫            }
55943eb30bfS猫头猫
56043eb30bfS猫头猫            if (rawLrc) {
56143eb30bfS猫头猫                await writeFile(filename, rawLrc, 'utf8');
56243eb30bfS猫头猫            } else {
5637e883dbbS猫头猫                filename = undefined;
5647e883dbbS猫头猫            }
5657e883dbbS猫头猫            if (translation) {
5667e883dbbS猫头猫                await writeFile(filenameTrans, translation, 'utf8');
5677e883dbbS猫头猫            } else {
5687e883dbbS猫头猫                filenameTrans = undefined;
5697a91f04fS猫头猫            }
5707a91f04fS猫头猫
5717e883dbbS猫头猫            if (rawLrc || translation) {
5727e883dbbS猫头猫                MediaCache.setMediaCache(
5737e883dbbS猫头猫                    produce(musicItemCache || musicItem, draft => {
5747e883dbbS猫头猫                        musicItemCache?.$localLyric?.rawLrc;
5757e883dbbS猫头猫                        objectPath.set(draft, '$localLyric.rawLrc', filename);
57643eb30bfS猫头猫                        objectPath.set(
57743eb30bfS猫头猫                            draft,
5787e883dbbS猫头猫                            '$localLyric.translation',
5797e883dbbS猫头猫                            filenameTrans,
58043eb30bfS猫头猫                        );
58143eb30bfS猫头猫                        return draft;
58243eb30bfS猫头猫                    }),
58343eb30bfS猫头猫                );
584927dbe93S猫头猫                return {
5857e883dbbS猫头猫                    rawLrc: rawLrc || undefined,
5867e883dbbS猫头猫                    translation: translation || undefined,
587927dbe93S猫头猫                };
588927dbe93S猫头猫            }
589927dbe93S猫头猫        }
59043eb30bfS猫头猫
5913a6f67b1S猫头猫        // 6. 如果是本地文件
59243eb30bfS猫头猫        const isDownloaded = LocalMusicSheet.isLocalMusic(originalMusicItem);
59343eb30bfS猫头猫        if (
59443eb30bfS猫头猫            originalMusicItem.platform !== localPluginPlatform &&
59543eb30bfS猫头猫            isDownloaded
59643eb30bfS猫头猫        ) {
5977e883dbbS猫头猫            const res = await localFilePlugin.instance!.getLyric!(isDownloaded);
59843eb30bfS猫头猫
59943eb30bfS猫头猫            console.log('本地文件歌词');
60043eb30bfS猫头猫
6013a6f67b1S猫头猫            if (res) {
6023a6f67b1S猫头猫                return res;
6033a6f67b1S猫头猫            }
6043a6f67b1S猫头猫        }
605ea6d708fS猫头猫        devLog('warn', '无歌词');
606927dbe93S猫头猫
607927dbe93S猫头猫        return null;
608927dbe93S猫头猫    }
609927dbe93S猫头猫
610927dbe93S猫头猫    /** 获取歌词文本 */
611927dbe93S猫头猫    async getLyricText(
612927dbe93S猫头猫        musicItem: IMusic.IMusicItem,
613927dbe93S猫头猫    ): Promise<string | undefined> {
6147e883dbbS猫头猫        return (await this.getLyric(musicItem))?.rawLrc;
615927dbe93S猫头猫    }
616927dbe93S猫头猫
617927dbe93S猫头猫    /** 获取专辑信息 */
618927dbe93S猫头猫    async getAlbumInfo(
619927dbe93S猫头猫        albumItem: IAlbum.IAlbumItemBase,
620f9afcc0dS猫头猫        page: number = 1,
621f9afcc0dS猫头猫    ): Promise<IPlugin.IAlbumInfoResult | null> {
622927dbe93S猫头猫        if (!this.plugin.instance.getAlbumInfo) {
623f9afcc0dS猫头猫            return {
624f9afcc0dS猫头猫                albumItem,
625f2a4767cS猫头猫                musicList: (albumItem?.musicList ?? []).map(
626f2a4767cS猫头猫                    resetMediaItem,
627f2a4767cS猫头猫                    this.plugin.name,
628f2a4767cS猫头猫                    true,
629f2a4767cS猫头猫                ),
630f9afcc0dS猫头猫                isEnd: true,
631f9afcc0dS猫头猫            };
632927dbe93S猫头猫        }
633927dbe93S猫头猫        try {
634927dbe93S猫头猫            const result = await this.plugin.instance.getAlbumInfo(
635927dbe93S猫头猫                resetMediaItem(albumItem, undefined, true),
636f9afcc0dS猫头猫                page,
637927dbe93S猫头猫            );
6385276aef9S猫头猫            if (!result) {
6395276aef9S猫头猫                throw new Error();
6405276aef9S猫头猫            }
641927dbe93S猫头猫            result?.musicList?.forEach(_ => {
642927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
64396744680S猫头猫                _.album = albumItem.title;
644927dbe93S猫头猫            });
6455276aef9S猫头猫
646f9afcc0dS猫头猫            if (page <= 1) {
647f9afcc0dS猫头猫                // 合并信息
648f9afcc0dS猫头猫                return {
649f9afcc0dS猫头猫                    albumItem: {...albumItem, ...(result?.albumItem ?? {})},
650f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
651f9afcc0dS猫头猫                    musicList: result.musicList,
652f9afcc0dS猫头猫                };
653f9afcc0dS猫头猫            } else {
654f9afcc0dS猫头猫                return {
655f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
656f9afcc0dS猫头猫                    musicList: result.musicList,
657f9afcc0dS猫头猫                };
658f9afcc0dS猫头猫            }
6594394410dS猫头猫        } catch (e: any) {
6604394410dS猫头猫            trace('获取专辑信息失败', e?.message);
661ea6d708fS猫头猫            devLog('error', '获取专辑信息失败', e, e?.message);
662ea6d708fS猫头猫
663f9afcc0dS猫头猫            return null;
664927dbe93S猫头猫        }
665927dbe93S猫头猫    }
666927dbe93S猫头猫
6675830c002S猫头猫    /** 获取歌单信息 */
6685830c002S猫头猫    async getMusicSheetInfo(
6695830c002S猫头猫        sheetItem: IMusic.IMusicSheetItem,
6705830c002S猫头猫        page: number = 1,
6715830c002S猫头猫    ): Promise<IPlugin.ISheetInfoResult | null> {
6725281926bS猫头猫        if (!this.plugin.instance.getMusicSheetInfo) {
6735830c002S猫头猫            return {
6745830c002S猫头猫                sheetItem,
6755830c002S猫头猫                musicList: sheetItem?.musicList ?? [],
6765830c002S猫头猫                isEnd: true,
6775830c002S猫头猫            };
6785830c002S猫头猫        }
6795830c002S猫头猫        try {
6805830c002S猫头猫            const result = await this.plugin.instance?.getMusicSheetInfo?.(
6815830c002S猫头猫                resetMediaItem(sheetItem, undefined, true),
6825830c002S猫头猫                page,
6835830c002S猫头猫            );
6845830c002S猫头猫            if (!result) {
6855830c002S猫头猫                throw new Error();
6865830c002S猫头猫            }
6875830c002S猫头猫            result?.musicList?.forEach(_ => {
6885830c002S猫头猫                resetMediaItem(_, this.plugin.name);
6895830c002S猫头猫            });
6905830c002S猫头猫
6915830c002S猫头猫            if (page <= 1) {
6925830c002S猫头猫                // 合并信息
6935830c002S猫头猫                return {
6945830c002S猫头猫                    sheetItem: {...sheetItem, ...(result?.sheetItem ?? {})},
6955830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
6965830c002S猫头猫                    musicList: result.musicList,
6975830c002S猫头猫                };
6985830c002S猫头猫            } else {
6995830c002S猫头猫                return {
7005830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
7015830c002S猫头猫                    musicList: result.musicList,
7025830c002S猫头猫                };
7035830c002S猫头猫            }
7045830c002S猫头猫        } catch (e: any) {
7055830c002S猫头猫            trace('获取歌单信息失败', e, e?.message);
7065830c002S猫头猫            devLog('error', '获取歌单信息失败', e, e?.message);
7075830c002S猫头猫
7085830c002S猫头猫            return null;
7095830c002S猫头猫        }
7105830c002S猫头猫    }
7115830c002S猫头猫
712927dbe93S猫头猫    /** 查询作者信息 */
713efb9da24S猫头猫    async getArtistWorks<T extends IArtist.ArtistMediaType>(
714927dbe93S猫头猫        artistItem: IArtist.IArtistItem,
715927dbe93S猫头猫        page: number,
716927dbe93S猫头猫        type: T,
717927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
718efb9da24S猫头猫        if (!this.plugin.instance.getArtistWorks) {
719927dbe93S猫头猫            return {
720927dbe93S猫头猫                isEnd: true,
721927dbe93S猫头猫                data: [],
722927dbe93S猫头猫            };
723927dbe93S猫头猫        }
724927dbe93S猫头猫        try {
725efb9da24S猫头猫            const result = await this.plugin.instance.getArtistWorks(
726927dbe93S猫头猫                artistItem,
727927dbe93S猫头猫                page,
728927dbe93S猫头猫                type,
729927dbe93S猫头猫            );
730927dbe93S猫头猫            if (!result.data) {
731927dbe93S猫头猫                return {
732927dbe93S猫头猫                    isEnd: true,
733927dbe93S猫头猫                    data: [],
734927dbe93S猫头猫                };
735927dbe93S猫头猫            }
736927dbe93S猫头猫            result.data?.forEach(_ => resetMediaItem(_, this.plugin.name));
737927dbe93S猫头猫            return {
738927dbe93S猫头猫                isEnd: result.isEnd ?? true,
739927dbe93S猫头猫                data: result.data,
740927dbe93S猫头猫            };
7414394410dS猫头猫        } catch (e: any) {
7424394410dS猫头猫            trace('查询作者信息失败', e?.message);
743ea6d708fS猫头猫            devLog('error', '查询作者信息失败', e, e?.message);
744ea6d708fS猫头猫
745927dbe93S猫头猫            throw e;
746927dbe93S猫头猫        }
747927dbe93S猫头猫    }
74808380090S猫头猫
74908380090S猫头猫    /** 导入歌单 */
75008380090S猫头猫    async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> {
75108380090S猫头猫        try {
75208380090S猫头猫            const result =
75308380090S猫头猫                (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? [];
75408380090S猫头猫            result.forEach(_ => resetMediaItem(_, this.plugin.name));
75508380090S猫头猫            return result;
756ea6d708fS猫头猫        } catch (e: any) {
7570e4173cdS猫头猫            console.log(e);
758ea6d708fS猫头猫            devLog('error', '导入歌单失败', e, e?.message);
759ea6d708fS猫头猫
76008380090S猫头猫            return [];
76108380090S猫头猫        }
76208380090S猫头猫    }
7634d9d3c4cS猫头猫    /** 导入单曲 */
7644d9d3c4cS猫头猫    async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> {
7654d9d3c4cS猫头猫        try {
7664d9d3c4cS猫头猫            const result = await this.plugin.instance?.importMusicItem?.(
7674d9d3c4cS猫头猫                urlLike,
7684d9d3c4cS猫头猫            );
7694d9d3c4cS猫头猫            if (!result) {
7704d9d3c4cS猫头猫                throw new Error();
7714d9d3c4cS猫头猫            }
7724d9d3c4cS猫头猫            resetMediaItem(result, this.plugin.name);
7734d9d3c4cS猫头猫            return result;
774ea6d708fS猫头猫        } catch (e: any) {
775ea6d708fS猫头猫            devLog('error', '导入单曲失败', e, e?.message);
776ea6d708fS猫头猫
7774d9d3c4cS猫头猫            return null;
7784d9d3c4cS猫头猫        }
7794d9d3c4cS猫头猫    }
780d52aa40eS猫头猫    /** 获取榜单 */
78192b6c95aS猫头猫    async getTopLists(): Promise<IMusic.IMusicSheetGroupItem[]> {
782d52aa40eS猫头猫        try {
783d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopLists?.();
784d52aa40eS猫头猫            if (!result) {
785d52aa40eS猫头猫                throw new Error();
786d52aa40eS猫头猫            }
787d52aa40eS猫头猫            return result;
788d52aa40eS猫头猫        } catch (e: any) {
789d52aa40eS猫头猫            devLog('error', '获取榜单失败', e, e?.message);
790d52aa40eS猫头猫            return [];
791d52aa40eS猫头猫        }
792d52aa40eS猫头猫    }
793d52aa40eS猫头猫    /** 获取榜单详情 */
794d52aa40eS猫头猫    async getTopListDetail(
79592b6c95aS猫头猫        topListItem: IMusic.IMusicSheetItemBase,
796956ee1b7S猫头猫        page: number,
797956ee1b7S猫头猫    ): Promise<IPlugin.ITopListInfoResult> {
798d52aa40eS猫头猫        try {
799d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopListDetail?.(
800d52aa40eS猫头猫                topListItem,
801956ee1b7S猫头猫                page,
802d52aa40eS猫头猫            );
803d52aa40eS猫头猫            if (!result) {
804d52aa40eS猫头猫                throw new Error();
805d52aa40eS猫头猫            }
806d384662fS猫头猫            if (result.musicList) {
807d384662fS猫头猫                result.musicList.forEach(_ =>
808d384662fS猫头猫                    resetMediaItem(_, this.plugin.name),
809d384662fS猫头猫                );
810d384662fS猫头猫            }
811956ee1b7S猫头猫            if (result.isEnd !== false) {
812956ee1b7S猫头猫                result.isEnd = true;
813956ee1b7S猫头猫            }
814d52aa40eS猫头猫            return result;
815d52aa40eS猫头猫        } catch (e: any) {
816d52aa40eS猫头猫            devLog('error', '获取榜单详情失败', e, e?.message);
817d52aa40eS猫头猫            return {
818956ee1b7S猫头猫                isEnd: true,
819956ee1b7S猫头猫                topListItem: topListItem as IMusic.IMusicSheetItem,
820d52aa40eS猫头猫                musicList: [],
821d52aa40eS猫头猫            };
822d52aa40eS猫头猫        }
823d52aa40eS猫头猫    }
824ceb900cdS猫头猫
8255830c002S猫头猫    /** 获取推荐歌单的tag */
826ceb900cdS猫头猫    async getRecommendSheetTags(): Promise<IPlugin.IGetRecommendSheetTagsResult> {
827ceb900cdS猫头猫        try {
828ceb900cdS猫头猫            const result =
829ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetTags?.();
830ceb900cdS猫头猫            if (!result) {
831ceb900cdS猫头猫                throw new Error();
832ceb900cdS猫头猫            }
833ceb900cdS猫头猫            return result;
834ceb900cdS猫头猫        } catch (e: any) {
835ceb900cdS猫头猫            devLog('error', '获取推荐歌单失败', e, e?.message);
836ceb900cdS猫头猫            return {
837ceb900cdS猫头猫                data: [],
838ceb900cdS猫头猫            };
839ceb900cdS猫头猫        }
840ceb900cdS猫头猫    }
8415830c002S猫头猫    /** 获取某个tag的推荐歌单 */
842ceb900cdS猫头猫    async getRecommendSheetsByTag(
843ceb900cdS猫头猫        tagItem: ICommon.IUnique,
844ceb900cdS猫头猫        page?: number,
845ceb900cdS猫头猫    ): Promise<ICommon.PaginationResponse<IMusic.IMusicSheetItemBase>> {
846ceb900cdS猫头猫        try {
847ceb900cdS猫头猫            const result =
848ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetsByTag?.(
849ceb900cdS猫头猫                    tagItem,
850ceb900cdS猫头猫                    page ?? 1,
851ceb900cdS猫头猫                );
852ceb900cdS猫头猫            if (!result) {
853ceb900cdS猫头猫                throw new Error();
854ceb900cdS猫头猫            }
855ceb900cdS猫头猫            if (result.isEnd !== false) {
856ceb900cdS猫头猫                result.isEnd = true;
857ceb900cdS猫头猫            }
858ceb900cdS猫头猫            if (!result.data) {
859ceb900cdS猫头猫                result.data = [];
860ceb900cdS猫头猫            }
861ceb900cdS猫头猫            result.data.forEach(item => resetMediaItem(item, this.plugin.name));
862ceb900cdS猫头猫
863ceb900cdS猫头猫            return result;
864ceb900cdS猫头猫        } catch (e: any) {
865ceb900cdS猫头猫            devLog('error', '获取推荐歌单详情失败', e, e?.message);
866ceb900cdS猫头猫            return {
867ceb900cdS猫头猫                isEnd: true,
868ceb900cdS猫头猫                data: [],
869ceb900cdS猫头猫            };
870ceb900cdS猫头猫        }
871ceb900cdS猫头猫    }
872927dbe93S猫头猫}
873d5bfeb7eS猫头猫//#endregion
8741a5528a0S猫头猫
875927dbe93S猫头猫let plugins: Array<Plugin> = [];
876927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins);
87774d0cf81S猫头猫
878d5bfeb7eS猫头猫//#region 本地音乐插件
87974d0cf81S猫头猫/** 本地插件 */
88074d0cf81S猫头猫const localFilePlugin = new Plugin(function () {
8810e4173cdS猫头猫    return {
882d5bfeb7eS猫头猫        platform: localPluginPlatform,
88374d0cf81S猫头猫        _path: '',
88474d0cf81S猫头猫        async getMusicInfo(musicBase) {
88574d0cf81S猫头猫            const localPath = getInternalData<string>(
88674d0cf81S猫头猫                musicBase,
88774d0cf81S猫头猫                InternalDataType.LOCALPATH,
8880e4173cdS猫头猫            );
88974d0cf81S猫头猫            if (localPath) {
89074d0cf81S猫头猫                const coverImg = await Mp3Util.getMediaCoverImg(localPath);
89174d0cf81S猫头猫                return {
89274d0cf81S猫头猫                    artwork: coverImg,
89374d0cf81S猫头猫                };
89474d0cf81S猫头猫            }
89574d0cf81S猫头猫            return null;
89674d0cf81S猫头猫        },
8977993f90eS猫头猫        async getLyric(musicBase) {
8987993f90eS猫头猫            const localPath = getInternalData<string>(
8997993f90eS猫头猫                musicBase,
9007993f90eS猫头猫                InternalDataType.LOCALPATH,
9017993f90eS猫头猫            );
9023a6f67b1S猫头猫            let rawLrc: string | null = null;
9037993f90eS猫头猫            if (localPath) {
9043a6f67b1S猫头猫                // 读取内嵌歌词
9053a6f67b1S猫头猫                try {
9063a6f67b1S猫头猫                    rawLrc = await Mp3Util.getLyric(localPath);
9073a6f67b1S猫头猫                } catch (e) {
908a84a85c5S猫头猫                    console.log('读取内嵌歌词失败', e);
9097993f90eS猫头猫                }
9103a6f67b1S猫头猫                if (!rawLrc) {
9113a6f67b1S猫头猫                    // 读取配置歌词
9123a6f67b1S猫头猫                    const lastDot = localPath.lastIndexOf('.');
9133a6f67b1S猫头猫                    const lrcPath = localPath.slice(0, lastDot) + '.lrc';
9143a6f67b1S猫头猫
9153a6f67b1S猫头猫                    try {
9163a6f67b1S猫头猫                        if (await exists(lrcPath)) {
9173a6f67b1S猫头猫                            rawLrc = await readFile(lrcPath, 'utf8');
9183a6f67b1S猫头猫                        }
9193a6f67b1S猫头猫                    } catch {}
9203a6f67b1S猫头猫                }
9213a6f67b1S猫头猫            }
9223a6f67b1S猫头猫
9233a6f67b1S猫头猫            return rawLrc
9243a6f67b1S猫头猫                ? {
9253a6f67b1S猫头猫                      rawLrc,
9263a6f67b1S猫头猫                  }
9273a6f67b1S猫头猫                : null;
9287993f90eS猫头猫        },
929a84a85c5S猫头猫        async importMusicItem(urlLike) {
930a84a85c5S猫头猫            let meta: any = {};
931a84a85c5S猫头猫            try {
932a84a85c5S猫头猫                meta = await Mp3Util.getBasicMeta(urlLike);
933a84a85c5S猫头猫            } catch {}
934*5353b473S猫头猫            const stat = await getInfoAsync(urlLike, {
935*5353b473S猫头猫                md5: true,
936*5353b473S猫头猫            });
937*5353b473S猫头猫            let id: string;
938*5353b473S猫头猫            if (stat.exists) {
939*5353b473S猫头猫                id = stat.md5 || nanoid();
940*5353b473S猫头猫            } else {
941*5353b473S猫头猫                id = nanoid();
942*5353b473S猫头猫            }
943a84a85c5S猫头猫            return {
944a84a85c5S猫头猫                id: id,
945a84a85c5S猫头猫                platform: '本地',
946a84a85c5S猫头猫                title: meta?.title ?? getFileName(urlLike),
947a84a85c5S猫头猫                artist: meta?.artist ?? '未知歌手',
948*5353b473S猫头猫                duration: parseInt(meta?.duration ?? '0', 10) / 1000,
949a84a85c5S猫头猫                album: meta?.album ?? '未知专辑',
950a84a85c5S猫头猫                artwork: '',
951a84a85c5S猫头猫                [internalSerializeKey]: {
952a84a85c5S猫头猫                    localPath: urlLike,
953a84a85c5S猫头猫                },
954a84a85c5S猫头猫            };
955a84a85c5S猫头猫        },
95611908939S猫头猫        async getMediaSource(musicItem, quality) {
95711908939S猫头猫            if (quality === 'standard') {
95811908939S猫头猫                return {
95911908939S猫头猫                    url: addFileScheme(musicItem.$?.localPath || musicItem.url),
96011908939S猫头猫                };
96111908939S猫头猫            }
96211908939S猫头猫            return null;
96311908939S猫头猫        },
96474d0cf81S猫头猫    };
96574d0cf81S猫头猫}, '');
9667993f90eS猫头猫localFilePlugin.hash = localPluginHash;
967927dbe93S猫头猫
968d5bfeb7eS猫头猫//#endregion
969d5bfeb7eS猫头猫
970927dbe93S猫头猫async function setup() {
971927dbe93S猫头猫    const _plugins: Array<Plugin> = [];
972927dbe93S猫头猫    try {
973927dbe93S猫头猫        // 加载插件
974927dbe93S猫头猫        const pluginsPaths = await readDir(pathConst.pluginPath);
975927dbe93S猫头猫        for (let i = 0; i < pluginsPaths.length; ++i) {
976927dbe93S猫头猫            const _pluginUrl = pluginsPaths[i];
9771e263108S猫头猫            trace('初始化插件', _pluginUrl);
9781e263108S猫头猫            if (
9791e263108S猫头猫                _pluginUrl.isFile() &&
9801e263108S猫头猫                (_pluginUrl.name?.endsWith?.('.js') ||
9811e263108S猫头猫                    _pluginUrl.path?.endsWith?.('.js'))
9821e263108S猫头猫            ) {
983927dbe93S猫头猫                const funcCode = await readFile(_pluginUrl.path, 'utf8');
984927dbe93S猫头猫                const plugin = new Plugin(funcCode, _pluginUrl.path);
9854060c00aS猫头猫                const _pluginIndex = _plugins.findIndex(
9864060c00aS猫头猫                    p => p.hash === plugin.hash,
9874060c00aS猫头猫                );
988927dbe93S猫头猫                if (_pluginIndex !== -1) {
989927dbe93S猫头猫                    // 重复插件,直接忽略
9900c266394S猫头猫                    continue;
991927dbe93S猫头猫                }
992927dbe93S猫头猫                plugin.hash !== '' && _plugins.push(plugin);
993927dbe93S猫头猫            }
994927dbe93S猫头猫        }
995927dbe93S猫头猫
996927dbe93S猫头猫        plugins = _plugins;
997e08d37a3S猫头猫        /** 初始化meta信息 */
998c2b3a262S猫头猫        await PluginMeta.setupMeta(plugins.map(_ => _.name));
999c2b3a262S猫头猫        /** 查看一下是否有禁用的标记 */
1000c2b3a262S猫头猫        const allMeta = PluginMeta.getPluginMetaAll() ?? {};
1001c2b3a262S猫头猫        for (let plugin of plugins) {
1002c2b3a262S猫头猫            if (allMeta[plugin.name]?.enabled === false) {
1003c2b3a262S猫头猫                plugin.state = 'disabled';
1004c2b3a262S猫头猫            }
1005c2b3a262S猫头猫        }
1006c2b3a262S猫头猫        pluginStateMapper.notify();
1007927dbe93S猫头猫    } catch (e: any) {
10084060c00aS猫头猫        ToastAndroid.show(
10094060c00aS猫头猫            `插件初始化失败:${e?.message ?? e}`,
10104060c00aS猫头猫            ToastAndroid.LONG,
10114060c00aS猫头猫        );
10121a5528a0S猫头猫        errorLog('插件初始化失败', e?.message);
1013927dbe93S猫头猫        throw e;
1014927dbe93S猫头猫    }
1015927dbe93S猫头猫}
1016927dbe93S猫头猫
1017e36e2599S猫头猫interface IInstallPluginConfig {
1018e36e2599S猫头猫    notCheckVersion?: boolean;
1019e36e2599S猫头猫}
1020e36e2599S猫头猫
1021927dbe93S猫头猫// 安装插件
1022e36e2599S猫头猫async function installPlugin(
1023e36e2599S猫头猫    pluginPath: string,
1024e36e2599S猫头猫    config?: IInstallPluginConfig,
1025e36e2599S猫头猫) {
102622c09412S猫头猫    // if (pluginPath.endsWith('.js')) {
1027927dbe93S猫头猫    const funcCode = await readFile(pluginPath, 'utf8');
1028e36e2599S猫头猫
1029e36e2599S猫头猫    if (funcCode) {
1030927dbe93S猫头猫        const plugin = new Plugin(funcCode, pluginPath);
1031927dbe93S猫头猫        const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
1032927dbe93S猫头猫        if (_pluginIndex !== -1) {
1033e36e2599S猫头猫            // 静默忽略
1034e36e2599S猫头猫            return plugin;
1035927dbe93S猫头猫        }
1036e36e2599S猫头猫        const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
1037e36e2599S猫头猫        if (oldVersionPlugin && !config?.notCheckVersion) {
1038e36e2599S猫头猫            if (
1039e36e2599S猫头猫                compare(
1040e36e2599S猫头猫                    oldVersionPlugin.instance.version ?? '',
1041e36e2599S猫头猫                    plugin.instance.version ?? '',
1042e36e2599S猫头猫                    '>',
1043e36e2599S猫头猫                )
1044e36e2599S猫头猫            ) {
1045e36e2599S猫头猫                throw new Error('已安装更新版本的插件');
1046e36e2599S猫头猫            }
1047e36e2599S猫头猫        }
1048e36e2599S猫头猫
1049927dbe93S猫头猫        if (plugin.hash !== '') {
1050927dbe93S猫头猫            const fn = nanoid();
1051e36e2599S猫头猫            if (oldVersionPlugin) {
1052e36e2599S猫头猫                plugins = plugins.filter(_ => _.hash !== oldVersionPlugin.hash);
1053e36e2599S猫头猫                try {
1054e36e2599S猫头猫                    await unlink(oldVersionPlugin.path);
1055e36e2599S猫头猫                } catch {}
1056e36e2599S猫头猫            }
1057927dbe93S猫头猫            const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
1058927dbe93S猫头猫            await copyFile(pluginPath, _pluginPath);
1059927dbe93S猫头猫            plugin.path = _pluginPath;
1060927dbe93S猫头猫            plugins = plugins.concat(plugin);
1061927dbe93S猫头猫            pluginStateMapper.notify();
1062a84a85c5S猫头猫            return plugin;
1063927dbe93S猫头猫        }
1064e36e2599S猫头猫        throw new Error('插件无法解析!');
1065927dbe93S猫头猫    }
1066e36e2599S猫头猫    throw new Error('插件无法识别!');
1067c2b3a262S猫头猫}
1068c2b3a262S猫头猫
1069ee781061S猫头猫const reqHeaders = {
1070ee781061S猫头猫    'Cache-Control': 'no-cache',
1071ee781061S猫头猫    Pragma: 'no-cache',
1072ee781061S猫头猫    Expires: '0',
1073ee781061S猫头猫};
1074ee781061S猫头猫
1075c2b3a262S猫头猫async function installPluginFromUrl(
1076c2b3a262S猫头猫    url: string,
1077c2b3a262S猫头猫    config?: IInstallPluginConfig,
1078c2b3a262S猫头猫) {
107958992c6bS猫头猫    try {
1080ee781061S猫头猫        const funcCode = (
1081ee781061S猫头猫            await axios.get(url, {
1082ee781061S猫头猫                headers: reqHeaders,
1083ee781061S猫头猫            })
1084ee781061S猫头猫        ).data;
108558992c6bS猫头猫        if (funcCode) {
108658992c6bS猫头猫            const plugin = new Plugin(funcCode, '');
108758992c6bS猫头猫            const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
108858992c6bS猫头猫            if (_pluginIndex !== -1) {
10898b7ddca8S猫头猫                // 静默忽略
10908b7ddca8S猫头猫                return;
109158992c6bS猫头猫            }
109225c1bd29S猫头猫            const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
1093c2b3a262S猫头猫            if (oldVersionPlugin && !config?.notCheckVersion) {
109425c1bd29S猫头猫                if (
109525c1bd29S猫头猫                    compare(
109625c1bd29S猫头猫                        oldVersionPlugin.instance.version ?? '',
109725c1bd29S猫头猫                        plugin.instance.version ?? '',
109825c1bd29S猫头猫                        '>',
109925c1bd29S猫头猫                    )
110025c1bd29S猫头猫                ) {
110125c1bd29S猫头猫                    throw new Error('已安装更新版本的插件');
110225c1bd29S猫头猫                }
110325c1bd29S猫头猫            }
110425c1bd29S猫头猫
110558992c6bS猫头猫            if (plugin.hash !== '') {
110658992c6bS猫头猫                const fn = nanoid();
110758992c6bS猫头猫                const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
110858992c6bS猫头猫                await writeFile(_pluginPath, funcCode, 'utf8');
110958992c6bS猫头猫                plugin.path = _pluginPath;
111058992c6bS猫头猫                plugins = plugins.concat(plugin);
111125c1bd29S猫头猫                if (oldVersionPlugin) {
111225c1bd29S猫头猫                    plugins = plugins.filter(
111325c1bd29S猫头猫                        _ => _.hash !== oldVersionPlugin.hash,
111425c1bd29S猫头猫                    );
111525c1bd29S猫头猫                    try {
111625c1bd29S猫头猫                        await unlink(oldVersionPlugin.path);
111725c1bd29S猫头猫                    } catch {}
111825c1bd29S猫头猫                }
111958992c6bS猫头猫                pluginStateMapper.notify();
112058992c6bS猫头猫                return;
112158992c6bS猫头猫            }
112274acbfc0S猫头猫            throw new Error('插件无法解析!');
112358992c6bS猫头猫        }
112425c1bd29S猫头猫    } catch (e: any) {
1125ea6d708fS猫头猫        devLog('error', 'URL安装插件失败', e, e?.message);
112658992c6bS猫头猫        errorLog('URL安装插件失败', e);
112725c1bd29S猫头猫        throw new Error(e?.message ?? '');
112858992c6bS猫头猫    }
112958992c6bS猫头猫}
113058992c6bS猫头猫
1131927dbe93S猫头猫/** 卸载插件 */
1132927dbe93S猫头猫async function uninstallPlugin(hash: string) {
1133927dbe93S猫头猫    const targetIndex = plugins.findIndex(_ => _.hash === hash);
1134927dbe93S猫头猫    if (targetIndex !== -1) {
1135927dbe93S猫头猫        try {
113624e5e74aS猫头猫            const pluginName = plugins[targetIndex].name;
1137927dbe93S猫头猫            await unlink(plugins[targetIndex].path);
1138927dbe93S猫头猫            plugins = plugins.filter(_ => _.hash !== hash);
1139927dbe93S猫头猫            pluginStateMapper.notify();
114043eb30bfS猫头猫            // 防止其他重名
114124e5e74aS猫头猫            if (plugins.every(_ => _.name !== pluginName)) {
11425589cdf3S猫头猫                MediaExtra.removeAll(pluginName);
114324e5e74aS猫头猫            }
1144927dbe93S猫头猫        } catch {}
1145927dbe93S猫头猫    }
1146927dbe93S猫头猫}
1147927dbe93S猫头猫
114808882a77S猫头猫async function uninstallAllPlugins() {
114908882a77S猫头猫    await Promise.all(
115008882a77S猫头猫        plugins.map(async plugin => {
115108882a77S猫头猫            try {
115208882a77S猫头猫                const pluginName = plugin.name;
115308882a77S猫头猫                await unlink(plugin.path);
11545589cdf3S猫头猫                MediaExtra.removeAll(pluginName);
115508882a77S猫头猫            } catch (e) {}
115608882a77S猫头猫        }),
115708882a77S猫头猫    );
115808882a77S猫头猫    plugins = [];
115908882a77S猫头猫    pluginStateMapper.notify();
1160e08d37a3S猫头猫
1161e08d37a3S猫头猫    /** 清除空余文件,异步做就可以了 */
1162e08d37a3S猫头猫    readDir(pathConst.pluginPath)
1163e08d37a3S猫头猫        .then(fns => {
1164e08d37a3S猫头猫            fns.forEach(fn => {
1165e08d37a3S猫头猫                unlink(fn.path).catch(emptyFunction);
1166e08d37a3S猫头猫            });
1167e08d37a3S猫头猫        })
1168e08d37a3S猫头猫        .catch(emptyFunction);
116908882a77S猫头猫}
117008882a77S猫头猫
117125c1bd29S猫头猫async function updatePlugin(plugin: Plugin) {
117225c1bd29S猫头猫    const updateUrl = plugin.instance.srcUrl;
117325c1bd29S猫头猫    if (!updateUrl) {
117425c1bd29S猫头猫        throw new Error('没有更新源');
117525c1bd29S猫头猫    }
117625c1bd29S猫头猫    try {
117725c1bd29S猫头猫        await installPluginFromUrl(updateUrl);
117825c1bd29S猫头猫    } catch (e: any) {
117925c1bd29S猫头猫        if (e.message === '插件已安装') {
118025c1bd29S猫头猫            throw new Error('当前已是最新版本');
118125c1bd29S猫头猫        } else {
118225c1bd29S猫头猫            throw e;
118325c1bd29S猫头猫        }
118425c1bd29S猫头猫    }
118525c1bd29S猫头猫}
118625c1bd29S猫头猫
1187927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) {
11882c595535S猫头猫    return getByName(mediaItem?.platform);
1189927dbe93S猫头猫}
1190927dbe93S猫头猫
1191927dbe93S猫头猫function getByHash(hash: string) {
11927993f90eS猫头猫    return hash === localPluginHash
11937993f90eS猫头猫        ? localFilePlugin
11947993f90eS猫头猫        : plugins.find(_ => _.hash === hash);
1195927dbe93S猫头猫}
1196927dbe93S猫头猫
1197927dbe93S猫头猫function getByName(name: string) {
11987993f90eS猫头猫    return name === localPluginPlatform
11990e4173cdS猫头猫        ? localFilePlugin
12000e4173cdS猫头猫        : plugins.find(_ => _.name === name);
1201927dbe93S猫头猫}
1202927dbe93S猫头猫
1203927dbe93S猫头猫function getValidPlugins() {
1204927dbe93S猫头猫    return plugins.filter(_ => _.state === 'enabled');
1205927dbe93S猫头猫}
1206927dbe93S猫头猫
12072b80a429S猫头猫function getSearchablePlugins(supportedSearchType?: ICommon.SupportMediaType) {
12082b80a429S猫头猫    return plugins.filter(
12092b80a429S猫头猫        _ =>
12102b80a429S猫头猫            _.state === 'enabled' &&
12112b80a429S猫头猫            _.instance.search &&
121239ac60f7S猫头猫            (supportedSearchType && _.instance.supportedSearchType
121339ac60f7S猫头猫                ? _.instance.supportedSearchType.includes(supportedSearchType)
12142b80a429S猫头猫                : true),
12152b80a429S猫头猫    );
1216efb9da24S猫头猫}
1217efb9da24S猫头猫
12182b80a429S猫头猫function getSortedSearchablePlugins(
12192b80a429S猫头猫    supportedSearchType?: ICommon.SupportMediaType,
12202b80a429S猫头猫) {
12212b80a429S猫头猫    return getSearchablePlugins(supportedSearchType).sort((a, b) =>
1222e08d37a3S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1223e08d37a3S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1224e08d37a3S猫头猫        0
1225e08d37a3S猫头猫            ? -1
1226e08d37a3S猫头猫            : 1,
1227e08d37a3S猫头猫    );
1228e08d37a3S猫头猫}
1229e08d37a3S猫头猫
123015feccc1S猫头猫function getTopListsablePlugins() {
123115feccc1S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists);
123215feccc1S猫头猫}
123315feccc1S猫头猫
123415feccc1S猫头猫function getSortedTopListsablePlugins() {
123515feccc1S猫头猫    return getTopListsablePlugins().sort((a, b) =>
123615feccc1S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
123715feccc1S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
123815feccc1S猫头猫        0
123915feccc1S猫头猫            ? -1
124015feccc1S猫头猫            : 1,
124115feccc1S猫头猫    );
124215feccc1S猫头猫}
124315feccc1S猫头猫
1244ceb900cdS猫头猫function getRecommendSheetablePlugins() {
1245ceb900cdS猫头猫    return plugins.filter(
1246ceb900cdS猫头猫        _ => _.state === 'enabled' && _.instance.getRecommendSheetsByTag,
1247ceb900cdS猫头猫    );
1248ceb900cdS猫头猫}
1249ceb900cdS猫头猫
1250ceb900cdS猫头猫function getSortedRecommendSheetablePlugins() {
1251ceb900cdS猫头猫    return getRecommendSheetablePlugins().sort((a, b) =>
1252ceb900cdS猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1253ceb900cdS猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1254ceb900cdS猫头猫        0
1255ceb900cdS猫头猫            ? -1
1256ceb900cdS猫头猫            : 1,
1257ceb900cdS猫头猫    );
1258ceb900cdS猫头猫}
1259ceb900cdS猫头猫
1260e08d37a3S猫头猫function useSortedPlugins() {
1261e08d37a3S猫头猫    const _plugins = pluginStateMapper.useMappedState();
1262e08d37a3S猫头猫    const _pluginMetaAll = PluginMeta.usePluginMetaAll();
1263e08d37a3S猫头猫
126434588741S猫头猫    const [sortedPlugins, setSortedPlugins] = useState(
126534588741S猫头猫        [..._plugins].sort((a, b) =>
1266e08d37a3S猫头猫            (_pluginMetaAll[a.name]?.order ?? Infinity) -
1267e08d37a3S猫头猫                (_pluginMetaAll[b.name]?.order ?? Infinity) <
1268e08d37a3S猫头猫            0
1269e08d37a3S猫头猫                ? -1
1270e08d37a3S猫头猫                : 1,
127134588741S猫头猫        ),
1272e08d37a3S猫头猫    );
127334588741S猫头猫
127434588741S猫头猫    useEffect(() => {
1275d4cd40d8S猫头猫        InteractionManager.runAfterInteractions(() => {
127634588741S猫头猫            setSortedPlugins(
127734588741S猫头猫                [..._plugins].sort((a, b) =>
127834588741S猫头猫                    (_pluginMetaAll[a.name]?.order ?? Infinity) -
127934588741S猫头猫                        (_pluginMetaAll[b.name]?.order ?? Infinity) <
128034588741S猫头猫                    0
128134588741S猫头猫                        ? -1
128234588741S猫头猫                        : 1,
128334588741S猫头猫                ),
128434588741S猫头猫            );
1285d4cd40d8S猫头猫        });
128634588741S猫头猫    }, [_plugins, _pluginMetaAll]);
128734588741S猫头猫
128834588741S猫头猫    return sortedPlugins;
1289e08d37a3S猫头猫}
1290e08d37a3S猫头猫
1291c2b3a262S猫头猫async function setPluginEnabled(plugin: Plugin, enabled?: boolean) {
1292c2b3a262S猫头猫    const target = plugins.find(it => it.hash === plugin.hash);
1293c2b3a262S猫头猫    if (target) {
1294c2b3a262S猫头猫        target.state = enabled ? 'enabled' : 'disabled';
1295c2b3a262S猫头猫        plugins = [...plugins];
1296c2b3a262S猫头猫        pluginStateMapper.notify();
1297c2b3a262S猫头猫        PluginMeta.setPluginMetaProp(plugin, 'enabled', enabled);
1298c2b3a262S猫头猫    }
1299c2b3a262S猫头猫}
1300c2b3a262S猫头猫
1301927dbe93S猫头猫const PluginManager = {
1302927dbe93S猫头猫    setup,
1303927dbe93S猫头猫    installPlugin,
130458992c6bS猫头猫    installPluginFromUrl,
130525c1bd29S猫头猫    updatePlugin,
1306927dbe93S猫头猫    uninstallPlugin,
1307927dbe93S猫头猫    getByMedia,
1308927dbe93S猫头猫    getByHash,
1309927dbe93S猫头猫    getByName,
1310927dbe93S猫头猫    getValidPlugins,
1311efb9da24S猫头猫    getSearchablePlugins,
1312e08d37a3S猫头猫    getSortedSearchablePlugins,
131315feccc1S猫头猫    getTopListsablePlugins,
1314ceb900cdS猫头猫    getSortedRecommendSheetablePlugins,
131515feccc1S猫头猫    getSortedTopListsablePlugins,
13165276aef9S猫头猫    usePlugins: pluginStateMapper.useMappedState,
1317e08d37a3S猫头猫    useSortedPlugins,
131808882a77S猫头猫    uninstallAllPlugins,
1319c2b3a262S猫头猫    setPluginEnabled,
13205276aef9S猫头猫};
1321927dbe93S猫头猫
1322927dbe93S猫头猫export default PluginManager;
1323