xref: /MusicFree/src/core/pluginManager.ts (revision 5589cdf32b2bb0f641e5ac7bf1f6152cd6b9b70e)
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*5589cdf3S猫头猫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';
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';
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';
50*5589cdf3S猫头猫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猫头猫            );
311a84a85c5S猫头猫        if (
312a84a85c5S猫头猫            localPath &&
313a84a85c5S猫头猫            (localPath.startsWith('content://') ||
314a84a85c5S猫头猫                (await FileSystem.exists(localPath)))
315a84a85c5S猫头猫        ) {
3160e4173cdS猫头猫            trace('本地播放', localPath);
31743eb30bfS猫头猫            if (mediaExtra && mediaExtra.localPath !== localPath) {
31843eb30bfS猫头猫                // 修正一下本地数据
31943eb30bfS猫头猫                MediaExtra.update(musicItem, {
32043eb30bfS猫头猫                    localPath,
32143eb30bfS猫头猫                });
32243eb30bfS猫头猫            }
323927dbe93S猫头猫            return {
32411908939S猫头猫                url: addFileScheme(localPath),
325927dbe93S猫头猫            };
32643eb30bfS猫头猫        } else if (mediaExtra?.localPath) {
32743eb30bfS猫头猫            MediaExtra.update(musicItem, {
32843eb30bfS猫头猫                localPath: undefined,
32943eb30bfS猫头猫            });
330927dbe93S猫头猫        }
331a84a85c5S猫头猫
3327993f90eS猫头猫        if (musicItem.platform === localPluginPlatform) {
333f5935920S猫头猫            throw new Error('本地音乐不存在');
334f5935920S猫头猫        }
335927dbe93S猫头猫        // 2. 缓存播放
33643eb30bfS猫头猫        const mediaCache = MediaCache.getMediaCache(
33743eb30bfS猫头猫            musicItem,
33843eb30bfS猫头猫        ) as IMusic.IMusicItem | null;
339985f8e75S猫头猫        const pluginCacheControl =
340985f8e75S猫头猫            this.plugin.instance.cacheControl ?? 'no-cache';
341cfa0fc07S猫头猫        if (
342cfa0fc07S猫头猫            mediaCache &&
34343eb30bfS猫头猫            mediaCache?.source?.[quality]?.url &&
34448f4b873S猫头猫            (pluginCacheControl === CacheControl.Cache ||
34548f4b873S猫头猫                (pluginCacheControl === CacheControl.NoCache &&
346ef714860S猫头猫                    Network.isOffline()))
347cfa0fc07S猫头猫        ) {
3485276aef9S猫头猫            trace('播放', '缓存播放');
34943eb30bfS猫头猫            const qualityInfo = mediaCache.source[quality];
350927dbe93S猫头猫            return {
35143eb30bfS猫头猫                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猫头猫            ) {
39843eb30bfS猫头猫                // 更新缓存
39943eb30bfS猫头猫                const cacheSource = {
40043eb30bfS猫头猫                    headers: result.headers,
40143eb30bfS猫头猫                    userAgent: result.userAgent,
40243eb30bfS猫头猫                    url,
40343eb30bfS猫头猫                };
40443eb30bfS猫头猫                let realMusicItem = {
40543eb30bfS猫头猫                    ...musicItem,
40643eb30bfS猫头猫                    ...(mediaCache || {}),
40743eb30bfS猫头猫                };
40843eb30bfS猫头猫                realMusicItem.source = {
40943eb30bfS猫头猫                    ...(realMusicItem.source || {}),
41043eb30bfS猫头猫                    [quality]: cacheSource,
41143eb30bfS猫头猫                };
41243eb30bfS猫头猫
41343eb30bfS猫头猫                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猫头猫
4467e883dbbS猫头猫    /**
4477e883dbbS猫头猫     *
4487e883dbbS猫头猫     * getLyric(musicItem) => {
4497e883dbbS猫头猫     *      lyric: string;
4507e883dbbS猫头猫     *      trans: string;
4517e883dbbS猫头猫     * }
4527e883dbbS猫头猫     *
4537e883dbbS猫头猫     */
454927dbe93S猫头猫    /** 获取歌词 */
455927dbe93S猫头猫    async getLyric(
45643eb30bfS猫头猫        originalMusicItem: IMusic.IMusicItemBase,
457927dbe93S猫头猫    ): Promise<ILyric.ILyricSource | null> {
45843eb30bfS猫头猫        // 1.额外存储的meta信息(关联歌词)
459*5589cdf3S猫头猫        const meta = MediaExtra.get(originalMusicItem);
46043eb30bfS猫头猫        let musicItem: IMusic.IMusicItem;
461927dbe93S猫头猫        if (meta && meta.associatedLrc) {
46243eb30bfS猫头猫            musicItem = meta.associatedLrc as IMusic.IMusicItem;
46343eb30bfS猫头猫        } else {
46443eb30bfS猫头猫            musicItem = originalMusicItem as IMusic.IMusicItem;
465927dbe93S猫头猫        }
46643eb30bfS猫头猫
46743eb30bfS猫头猫        const musicItemCache = MediaCache.getMediaCache(
4687a91f04fS猫头猫            musicItem,
46943eb30bfS猫头猫        ) as IMusic.IMusicItemCache | null;
47043eb30bfS猫头猫
47143eb30bfS猫头猫        /** 原始歌词文本 */
4727e883dbbS猫头猫        let rawLrc: string | null = musicItem.rawLrc || null;
4737e883dbbS猫头猫        let translation: string | null = null;
47443eb30bfS猫头猫
47543eb30bfS猫头猫        // 2. 缓存歌词 / 对象上本身的歌词
47643eb30bfS猫头猫        if (musicItemCache?.lyric) {
47743eb30bfS猫头猫            // 缓存的远程结果
47843eb30bfS猫头猫            let cacheLyric: ILyric.ILyricSource | null =
47943eb30bfS猫头猫                musicItemCache.lyric || null;
48043eb30bfS猫头猫            // 缓存的本地结果
48143eb30bfS猫头猫            let localLyric: ILyric.ILyricSource | null =
48243eb30bfS猫头猫                musicItemCache.$localLyric || null;
48343eb30bfS猫头猫
4847e883dbbS猫头猫            // 优先用缓存的结果
4857e883dbbS猫头猫            if (cacheLyric.rawLrc || cacheLyric.translation) {
48643eb30bfS猫头猫                return {
4877e883dbbS猫头猫                    rawLrc: cacheLyric.rawLrc,
4887e883dbbS猫头猫                    translation: cacheLyric.translation,
48943eb30bfS猫头猫                };
49043eb30bfS猫头猫            }
49143eb30bfS猫头猫
4927e883dbbS猫头猫            // 本地其实是缓存的路径
4937e883dbbS猫头猫            if (localLyric) {
4947e883dbbS猫头猫                let needRefetch = false;
4957e883dbbS猫头猫                if (localLyric.rawLrc && (await exists(localLyric.rawLrc))) {
4967e883dbbS猫头猫                    rawLrc = await readFile(localLyric.rawLrc, 'utf8');
4977e883dbbS猫头猫                } else if (localLyric.rawLrc) {
4987e883dbbS猫头猫                    needRefetch = true;
4997e883dbbS猫头猫                }
5007e883dbbS猫头猫                if (
5017e883dbbS猫头猫                    localLyric.translation &&
5027e883dbbS猫头猫                    (await exists(localLyric.translation))
5037e883dbbS猫头猫                ) {
5047e883dbbS猫头猫                    translation = await readFile(
5057e883dbbS猫头猫                        localLyric.translation,
5067e883dbbS猫头猫                        'utf8',
5077e883dbbS猫头猫                    );
5087e883dbbS猫头猫                } else if (localLyric.translation) {
5097e883dbbS猫头猫                    needRefetch = true;
51043eb30bfS猫头猫                }
51143eb30bfS猫头猫
5127e883dbbS猫头猫                if (!needRefetch && (rawLrc || translation)) {
51343eb30bfS猫头猫                    return {
5147e883dbbS猫头猫                        rawLrc: rawLrc || undefined,
5157e883dbbS猫头猫                        translation: translation || undefined,
51643eb30bfS猫头猫                    };
51743eb30bfS猫头猫                }
51843eb30bfS猫头猫            }
51943eb30bfS猫头猫        }
52043eb30bfS猫头猫
52143eb30bfS猫头猫        // 3. 无缓存歌词/无自带歌词/无本地歌词
52243eb30bfS猫头猫        let lrcSource: ILyric.ILyricSource | null;
52343eb30bfS猫头猫        if (isSameMediaItem(originalMusicItem, musicItem)) {
52443eb30bfS猫头猫            lrcSource =
52543eb30bfS猫头猫                (await this.plugin.instance
5267e883dbbS猫头猫                    ?.getLyric?.(resetMediaItem(musicItem, undefined, true))
52743eb30bfS猫头猫                    ?.catch(() => null)) || null;
52843eb30bfS猫头猫        } else {
52943eb30bfS猫头猫            lrcSource =
53043eb30bfS猫头猫                (await PluginManager.getByMedia(musicItem)
53143eb30bfS猫头猫                    ?.instance?.getLyric?.(
53243eb30bfS猫头猫                        resetMediaItem(musicItem, undefined, true),
53343eb30bfS猫头猫                    )
53443eb30bfS猫头猫                    ?.catch(() => null)) || null;
53543eb30bfS猫头猫        }
53643eb30bfS猫头猫
53743eb30bfS猫头猫        if (lrcSource) {
5387e883dbbS猫头猫            rawLrc = lrcSource?.rawLrc || rawLrc;
5397e883dbbS猫头猫            translation = lrcSource?.translation || null;
54043eb30bfS猫头猫
5417e883dbbS猫头猫            const deprecatedLrcUrl = lrcSource?.lrc || musicItem.lrc;
54243eb30bfS猫头猫
5437e883dbbS猫头猫            // 本地的文件名
5447e883dbbS猫头猫            let filename: string | undefined = `${
5457e883dbbS猫头猫                pathConst.lrcCachePath
5467e883dbbS猫头猫            }${nanoid()}.lrc`;
5477e883dbbS猫头猫            let filenameTrans: string | undefined = `${
5487e883dbbS猫头猫                pathConst.lrcCachePath
5497e883dbbS猫头猫            }${nanoid()}.lrc`;
55043eb30bfS猫头猫
5517e883dbbS猫头猫            // 旧版本兼容
5527e883dbbS猫头猫            if (!(rawLrc || translation)) {
5537e883dbbS猫头猫                if (deprecatedLrcUrl) {
55443eb30bfS猫头猫                    rawLrc = (
5557e883dbbS猫头猫                        await axios
5567e883dbbS猫头猫                            .get(deprecatedLrcUrl, {timeout: 3000})
5577e883dbbS猫头猫                            .catch(() => null)
55843eb30bfS猫头猫                    )?.data;
5597e883dbbS猫头猫                } else if (musicItem.rawLrc) {
5607e883dbbS猫头猫                    rawLrc = musicItem.rawLrc;
5617e883dbbS猫头猫                }
56243eb30bfS猫头猫            }
56343eb30bfS猫头猫
56443eb30bfS猫头猫            if (rawLrc) {
56543eb30bfS猫头猫                await writeFile(filename, rawLrc, 'utf8');
56643eb30bfS猫头猫            } else {
5677e883dbbS猫头猫                filename = undefined;
5687e883dbbS猫头猫            }
5697e883dbbS猫头猫            if (translation) {
5707e883dbbS猫头猫                await writeFile(filenameTrans, translation, 'utf8');
5717e883dbbS猫头猫            } else {
5727e883dbbS猫头猫                filenameTrans = undefined;
5737a91f04fS猫头猫            }
5747a91f04fS猫头猫
5757e883dbbS猫头猫            if (rawLrc || translation) {
5767e883dbbS猫头猫                MediaCache.setMediaCache(
5777e883dbbS猫头猫                    produce(musicItemCache || musicItem, draft => {
5787e883dbbS猫头猫                        musicItemCache?.$localLyric?.rawLrc;
5797e883dbbS猫头猫                        objectPath.set(draft, '$localLyric.rawLrc', filename);
58043eb30bfS猫头猫                        objectPath.set(
58143eb30bfS猫头猫                            draft,
5827e883dbbS猫头猫                            '$localLyric.translation',
5837e883dbbS猫头猫                            filenameTrans,
58443eb30bfS猫头猫                        );
58543eb30bfS猫头猫                        return draft;
58643eb30bfS猫头猫                    }),
58743eb30bfS猫头猫                );
588927dbe93S猫头猫                return {
5897e883dbbS猫头猫                    rawLrc: rawLrc || undefined,
5907e883dbbS猫头猫                    translation: translation || undefined,
591927dbe93S猫头猫                };
592927dbe93S猫头猫            }
593927dbe93S猫头猫        }
59443eb30bfS猫头猫
5953a6f67b1S猫头猫        // 6. 如果是本地文件
59643eb30bfS猫头猫        const isDownloaded = LocalMusicSheet.isLocalMusic(originalMusicItem);
59743eb30bfS猫头猫        if (
59843eb30bfS猫头猫            originalMusicItem.platform !== localPluginPlatform &&
59943eb30bfS猫头猫            isDownloaded
60043eb30bfS猫头猫        ) {
6017e883dbbS猫头猫            const res = await localFilePlugin.instance!.getLyric!(isDownloaded);
60243eb30bfS猫头猫
60343eb30bfS猫头猫            console.log('本地文件歌词');
60443eb30bfS猫头猫
6053a6f67b1S猫头猫            if (res) {
6063a6f67b1S猫头猫                return res;
6073a6f67b1S猫头猫            }
6083a6f67b1S猫头猫        }
609ea6d708fS猫头猫        devLog('warn', '无歌词');
610927dbe93S猫头猫
611927dbe93S猫头猫        return null;
612927dbe93S猫头猫    }
613927dbe93S猫头猫
614927dbe93S猫头猫    /** 获取歌词文本 */
615927dbe93S猫头猫    async getLyricText(
616927dbe93S猫头猫        musicItem: IMusic.IMusicItem,
617927dbe93S猫头猫    ): Promise<string | undefined> {
6187e883dbbS猫头猫        return (await this.getLyric(musicItem))?.rawLrc;
619927dbe93S猫头猫    }
620927dbe93S猫头猫
621927dbe93S猫头猫    /** 获取专辑信息 */
622927dbe93S猫头猫    async getAlbumInfo(
623927dbe93S猫头猫        albumItem: IAlbum.IAlbumItemBase,
624f9afcc0dS猫头猫        page: number = 1,
625f9afcc0dS猫头猫    ): Promise<IPlugin.IAlbumInfoResult | null> {
626927dbe93S猫头猫        if (!this.plugin.instance.getAlbumInfo) {
627f9afcc0dS猫头猫            return {
628f9afcc0dS猫头猫                albumItem,
629f2a4767cS猫头猫                musicList: (albumItem?.musicList ?? []).map(
630f2a4767cS猫头猫                    resetMediaItem,
631f2a4767cS猫头猫                    this.plugin.name,
632f2a4767cS猫头猫                    true,
633f2a4767cS猫头猫                ),
634f9afcc0dS猫头猫                isEnd: true,
635f9afcc0dS猫头猫            };
636927dbe93S猫头猫        }
637927dbe93S猫头猫        try {
638927dbe93S猫头猫            const result = await this.plugin.instance.getAlbumInfo(
639927dbe93S猫头猫                resetMediaItem(albumItem, undefined, true),
640f9afcc0dS猫头猫                page,
641927dbe93S猫头猫            );
6425276aef9S猫头猫            if (!result) {
6435276aef9S猫头猫                throw new Error();
6445276aef9S猫头猫            }
645927dbe93S猫头猫            result?.musicList?.forEach(_ => {
646927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
64796744680S猫头猫                _.album = albumItem.title;
648927dbe93S猫头猫            });
6495276aef9S猫头猫
650f9afcc0dS猫头猫            if (page <= 1) {
651f9afcc0dS猫头猫                // 合并信息
652f9afcc0dS猫头猫                return {
653f9afcc0dS猫头猫                    albumItem: {...albumItem, ...(result?.albumItem ?? {})},
654f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
655f9afcc0dS猫头猫                    musicList: result.musicList,
656f9afcc0dS猫头猫                };
657f9afcc0dS猫头猫            } else {
658f9afcc0dS猫头猫                return {
659f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
660f9afcc0dS猫头猫                    musicList: result.musicList,
661f9afcc0dS猫头猫                };
662f9afcc0dS猫头猫            }
6634394410dS猫头猫        } catch (e: any) {
6644394410dS猫头猫            trace('获取专辑信息失败', e?.message);
665ea6d708fS猫头猫            devLog('error', '获取专辑信息失败', e, e?.message);
666ea6d708fS猫头猫
667f9afcc0dS猫头猫            return null;
668927dbe93S猫头猫        }
669927dbe93S猫头猫    }
670927dbe93S猫头猫
6715830c002S猫头猫    /** 获取歌单信息 */
6725830c002S猫头猫    async getMusicSheetInfo(
6735830c002S猫头猫        sheetItem: IMusic.IMusicSheetItem,
6745830c002S猫头猫        page: number = 1,
6755830c002S猫头猫    ): Promise<IPlugin.ISheetInfoResult | null> {
6765281926bS猫头猫        if (!this.plugin.instance.getMusicSheetInfo) {
6775830c002S猫头猫            return {
6785830c002S猫头猫                sheetItem,
6795830c002S猫头猫                musicList: sheetItem?.musicList ?? [],
6805830c002S猫头猫                isEnd: true,
6815830c002S猫头猫            };
6825830c002S猫头猫        }
6835830c002S猫头猫        try {
6845830c002S猫头猫            const result = await this.plugin.instance?.getMusicSheetInfo?.(
6855830c002S猫头猫                resetMediaItem(sheetItem, undefined, true),
6865830c002S猫头猫                page,
6875830c002S猫头猫            );
6885830c002S猫头猫            if (!result) {
6895830c002S猫头猫                throw new Error();
6905830c002S猫头猫            }
6915830c002S猫头猫            result?.musicList?.forEach(_ => {
6925830c002S猫头猫                resetMediaItem(_, this.plugin.name);
6935830c002S猫头猫            });
6945830c002S猫头猫
6955830c002S猫头猫            if (page <= 1) {
6965830c002S猫头猫                // 合并信息
6975830c002S猫头猫                return {
6985830c002S猫头猫                    sheetItem: {...sheetItem, ...(result?.sheetItem ?? {})},
6995830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
7005830c002S猫头猫                    musicList: result.musicList,
7015830c002S猫头猫                };
7025830c002S猫头猫            } else {
7035830c002S猫头猫                return {
7045830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
7055830c002S猫头猫                    musicList: result.musicList,
7065830c002S猫头猫                };
7075830c002S猫头猫            }
7085830c002S猫头猫        } catch (e: any) {
7095830c002S猫头猫            trace('获取歌单信息失败', e, e?.message);
7105830c002S猫头猫            devLog('error', '获取歌单信息失败', e, e?.message);
7115830c002S猫头猫
7125830c002S猫头猫            return null;
7135830c002S猫头猫        }
7145830c002S猫头猫    }
7155830c002S猫头猫
716927dbe93S猫头猫    /** 查询作者信息 */
717efb9da24S猫头猫    async getArtistWorks<T extends IArtist.ArtistMediaType>(
718927dbe93S猫头猫        artistItem: IArtist.IArtistItem,
719927dbe93S猫头猫        page: number,
720927dbe93S猫头猫        type: T,
721927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
722efb9da24S猫头猫        if (!this.plugin.instance.getArtistWorks) {
723927dbe93S猫头猫            return {
724927dbe93S猫头猫                isEnd: true,
725927dbe93S猫头猫                data: [],
726927dbe93S猫头猫            };
727927dbe93S猫头猫        }
728927dbe93S猫头猫        try {
729efb9da24S猫头猫            const result = await this.plugin.instance.getArtistWorks(
730927dbe93S猫头猫                artistItem,
731927dbe93S猫头猫                page,
732927dbe93S猫头猫                type,
733927dbe93S猫头猫            );
734927dbe93S猫头猫            if (!result.data) {
735927dbe93S猫头猫                return {
736927dbe93S猫头猫                    isEnd: true,
737927dbe93S猫头猫                    data: [],
738927dbe93S猫头猫                };
739927dbe93S猫头猫            }
740927dbe93S猫头猫            result.data?.forEach(_ => resetMediaItem(_, this.plugin.name));
741927dbe93S猫头猫            return {
742927dbe93S猫头猫                isEnd: result.isEnd ?? true,
743927dbe93S猫头猫                data: result.data,
744927dbe93S猫头猫            };
7454394410dS猫头猫        } catch (e: any) {
7464394410dS猫头猫            trace('查询作者信息失败', e?.message);
747ea6d708fS猫头猫            devLog('error', '查询作者信息失败', e, e?.message);
748ea6d708fS猫头猫
749927dbe93S猫头猫            throw e;
750927dbe93S猫头猫        }
751927dbe93S猫头猫    }
75208380090S猫头猫
75308380090S猫头猫    /** 导入歌单 */
75408380090S猫头猫    async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> {
75508380090S猫头猫        try {
75608380090S猫头猫            const result =
75708380090S猫头猫                (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? [];
75808380090S猫头猫            result.forEach(_ => resetMediaItem(_, this.plugin.name));
75908380090S猫头猫            return result;
760ea6d708fS猫头猫        } catch (e: any) {
7610e4173cdS猫头猫            console.log(e);
762ea6d708fS猫头猫            devLog('error', '导入歌单失败', e, e?.message);
763ea6d708fS猫头猫
76408380090S猫头猫            return [];
76508380090S猫头猫        }
76608380090S猫头猫    }
7674d9d3c4cS猫头猫    /** 导入单曲 */
7684d9d3c4cS猫头猫    async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> {
7694d9d3c4cS猫头猫        try {
7704d9d3c4cS猫头猫            const result = await this.plugin.instance?.importMusicItem?.(
7714d9d3c4cS猫头猫                urlLike,
7724d9d3c4cS猫头猫            );
7734d9d3c4cS猫头猫            if (!result) {
7744d9d3c4cS猫头猫                throw new Error();
7754d9d3c4cS猫头猫            }
7764d9d3c4cS猫头猫            resetMediaItem(result, this.plugin.name);
7774d9d3c4cS猫头猫            return result;
778ea6d708fS猫头猫        } catch (e: any) {
779ea6d708fS猫头猫            devLog('error', '导入单曲失败', e, e?.message);
780ea6d708fS猫头猫
7814d9d3c4cS猫头猫            return null;
7824d9d3c4cS猫头猫        }
7834d9d3c4cS猫头猫    }
784d52aa40eS猫头猫    /** 获取榜单 */
78592b6c95aS猫头猫    async getTopLists(): Promise<IMusic.IMusicSheetGroupItem[]> {
786d52aa40eS猫头猫        try {
787d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopLists?.();
788d52aa40eS猫头猫            if (!result) {
789d52aa40eS猫头猫                throw new Error();
790d52aa40eS猫头猫            }
791d52aa40eS猫头猫            return result;
792d52aa40eS猫头猫        } catch (e: any) {
793d52aa40eS猫头猫            devLog('error', '获取榜单失败', e, e?.message);
794d52aa40eS猫头猫            return [];
795d52aa40eS猫头猫        }
796d52aa40eS猫头猫    }
797d52aa40eS猫头猫    /** 获取榜单详情 */
798d52aa40eS猫头猫    async getTopListDetail(
79992b6c95aS猫头猫        topListItem: IMusic.IMusicSheetItemBase,
800956ee1b7S猫头猫        page: number,
801956ee1b7S猫头猫    ): Promise<IPlugin.ITopListInfoResult> {
802d52aa40eS猫头猫        try {
803d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopListDetail?.(
804d52aa40eS猫头猫                topListItem,
805956ee1b7S猫头猫                page,
806d52aa40eS猫头猫            );
807d52aa40eS猫头猫            if (!result) {
808d52aa40eS猫头猫                throw new Error();
809d52aa40eS猫头猫            }
810d384662fS猫头猫            if (result.musicList) {
811d384662fS猫头猫                result.musicList.forEach(_ =>
812d384662fS猫头猫                    resetMediaItem(_, this.plugin.name),
813d384662fS猫头猫                );
814d384662fS猫头猫            }
815956ee1b7S猫头猫            if (result.isEnd !== false) {
816956ee1b7S猫头猫                result.isEnd = true;
817956ee1b7S猫头猫            }
818d52aa40eS猫头猫            return result;
819d52aa40eS猫头猫        } catch (e: any) {
820d52aa40eS猫头猫            devLog('error', '获取榜单详情失败', e, e?.message);
821d52aa40eS猫头猫            return {
822956ee1b7S猫头猫                isEnd: true,
823956ee1b7S猫头猫                topListItem: topListItem as IMusic.IMusicSheetItem,
824d52aa40eS猫头猫                musicList: [],
825d52aa40eS猫头猫            };
826d52aa40eS猫头猫        }
827d52aa40eS猫头猫    }
828ceb900cdS猫头猫
8295830c002S猫头猫    /** 获取推荐歌单的tag */
830ceb900cdS猫头猫    async getRecommendSheetTags(): Promise<IPlugin.IGetRecommendSheetTagsResult> {
831ceb900cdS猫头猫        try {
832ceb900cdS猫头猫            const result =
833ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetTags?.();
834ceb900cdS猫头猫            if (!result) {
835ceb900cdS猫头猫                throw new Error();
836ceb900cdS猫头猫            }
837ceb900cdS猫头猫            return result;
838ceb900cdS猫头猫        } catch (e: any) {
839ceb900cdS猫头猫            devLog('error', '获取推荐歌单失败', e, e?.message);
840ceb900cdS猫头猫            return {
841ceb900cdS猫头猫                data: [],
842ceb900cdS猫头猫            };
843ceb900cdS猫头猫        }
844ceb900cdS猫头猫    }
8455830c002S猫头猫    /** 获取某个tag的推荐歌单 */
846ceb900cdS猫头猫    async getRecommendSheetsByTag(
847ceb900cdS猫头猫        tagItem: ICommon.IUnique,
848ceb900cdS猫头猫        page?: number,
849ceb900cdS猫头猫    ): Promise<ICommon.PaginationResponse<IMusic.IMusicSheetItemBase>> {
850ceb900cdS猫头猫        try {
851ceb900cdS猫头猫            const result =
852ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetsByTag?.(
853ceb900cdS猫头猫                    tagItem,
854ceb900cdS猫头猫                    page ?? 1,
855ceb900cdS猫头猫                );
856ceb900cdS猫头猫            if (!result) {
857ceb900cdS猫头猫                throw new Error();
858ceb900cdS猫头猫            }
859ceb900cdS猫头猫            if (result.isEnd !== false) {
860ceb900cdS猫头猫                result.isEnd = true;
861ceb900cdS猫头猫            }
862ceb900cdS猫头猫            if (!result.data) {
863ceb900cdS猫头猫                result.data = [];
864ceb900cdS猫头猫            }
865ceb900cdS猫头猫            result.data.forEach(item => resetMediaItem(item, this.plugin.name));
866ceb900cdS猫头猫
867ceb900cdS猫头猫            return result;
868ceb900cdS猫头猫        } catch (e: any) {
869ceb900cdS猫头猫            devLog('error', '获取推荐歌单详情失败', e, e?.message);
870ceb900cdS猫头猫            return {
871ceb900cdS猫头猫                isEnd: true,
872ceb900cdS猫头猫                data: [],
873ceb900cdS猫头猫            };
874ceb900cdS猫头猫        }
875ceb900cdS猫头猫    }
876927dbe93S猫头猫}
877d5bfeb7eS猫头猫//#endregion
8781a5528a0S猫头猫
879927dbe93S猫头猫let plugins: Array<Plugin> = [];
880927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins);
88174d0cf81S猫头猫
882d5bfeb7eS猫头猫//#region 本地音乐插件
88374d0cf81S猫头猫/** 本地插件 */
88474d0cf81S猫头猫const localFilePlugin = new Plugin(function () {
8850e4173cdS猫头猫    return {
886d5bfeb7eS猫头猫        platform: localPluginPlatform,
88774d0cf81S猫头猫        _path: '',
88874d0cf81S猫头猫        async getMusicInfo(musicBase) {
88974d0cf81S猫头猫            const localPath = getInternalData<string>(
89074d0cf81S猫头猫                musicBase,
89174d0cf81S猫头猫                InternalDataType.LOCALPATH,
8920e4173cdS猫头猫            );
89374d0cf81S猫头猫            if (localPath) {
89474d0cf81S猫头猫                const coverImg = await Mp3Util.getMediaCoverImg(localPath);
89574d0cf81S猫头猫                return {
89674d0cf81S猫头猫                    artwork: coverImg,
89774d0cf81S猫头猫                };
89874d0cf81S猫头猫            }
89974d0cf81S猫头猫            return null;
90074d0cf81S猫头猫        },
9017993f90eS猫头猫        async getLyric(musicBase) {
9027993f90eS猫头猫            const localPath = getInternalData<string>(
9037993f90eS猫头猫                musicBase,
9047993f90eS猫头猫                InternalDataType.LOCALPATH,
9057993f90eS猫头猫            );
9063a6f67b1S猫头猫            let rawLrc: string | null = null;
9077993f90eS猫头猫            if (localPath) {
9083a6f67b1S猫头猫                // 读取内嵌歌词
9093a6f67b1S猫头猫                try {
9103a6f67b1S猫头猫                    rawLrc = await Mp3Util.getLyric(localPath);
9113a6f67b1S猫头猫                } catch (e) {
912a84a85c5S猫头猫                    console.log('读取内嵌歌词失败', e);
9137993f90eS猫头猫                }
9143a6f67b1S猫头猫                if (!rawLrc) {
9153a6f67b1S猫头猫                    // 读取配置歌词
9163a6f67b1S猫头猫                    const lastDot = localPath.lastIndexOf('.');
9173a6f67b1S猫头猫                    const lrcPath = localPath.slice(0, lastDot) + '.lrc';
9183a6f67b1S猫头猫
9193a6f67b1S猫头猫                    try {
9203a6f67b1S猫头猫                        if (await exists(lrcPath)) {
9213a6f67b1S猫头猫                            rawLrc = await readFile(lrcPath, 'utf8');
9223a6f67b1S猫头猫                        }
9233a6f67b1S猫头猫                    } catch {}
9243a6f67b1S猫头猫                }
9253a6f67b1S猫头猫            }
9263a6f67b1S猫头猫
9273a6f67b1S猫头猫            return rawLrc
9283a6f67b1S猫头猫                ? {
9293a6f67b1S猫头猫                      rawLrc,
9303a6f67b1S猫头猫                  }
9313a6f67b1S猫头猫                : null;
9327993f90eS猫头猫        },
933a84a85c5S猫头猫        async importMusicItem(urlLike) {
934a84a85c5S猫头猫            let meta: any = {};
935a84a85c5S猫头猫            try {
936a84a85c5S猫头猫                meta = await Mp3Util.getBasicMeta(urlLike);
937a84a85c5S猫头猫            } catch {}
938a84a85c5S猫头猫            const id = await FileSystem.hash(urlLike, 'MD5');
939a84a85c5S猫头猫            return {
940a84a85c5S猫头猫                id: id,
941a84a85c5S猫头猫                platform: '本地',
942a84a85c5S猫头猫                title: meta?.title ?? getFileName(urlLike),
943a84a85c5S猫头猫                artist: meta?.artist ?? '未知歌手',
944a84a85c5S猫头猫                duration: parseInt(meta?.duration ?? '0') / 1000,
945a84a85c5S猫头猫                album: meta?.album ?? '未知专辑',
946a84a85c5S猫头猫                artwork: '',
947a84a85c5S猫头猫                [internalSerializeKey]: {
948a84a85c5S猫头猫                    localPath: urlLike,
949a84a85c5S猫头猫                },
950a84a85c5S猫头猫            };
951a84a85c5S猫头猫        },
95211908939S猫头猫        async getMediaSource(musicItem, quality) {
95311908939S猫头猫            if (quality === 'standard') {
95411908939S猫头猫                return {
95511908939S猫头猫                    url: addFileScheme(musicItem.$?.localPath || musicItem.url),
95611908939S猫头猫                };
95711908939S猫头猫            }
95811908939S猫头猫            return null;
95911908939S猫头猫        },
96074d0cf81S猫头猫    };
96174d0cf81S猫头猫}, '');
9627993f90eS猫头猫localFilePlugin.hash = localPluginHash;
963927dbe93S猫头猫
964d5bfeb7eS猫头猫//#endregion
965d5bfeb7eS猫头猫
966927dbe93S猫头猫async function setup() {
967927dbe93S猫头猫    const _plugins: Array<Plugin> = [];
968927dbe93S猫头猫    try {
969927dbe93S猫头猫        // 加载插件
970927dbe93S猫头猫        const pluginsPaths = await readDir(pathConst.pluginPath);
971927dbe93S猫头猫        for (let i = 0; i < pluginsPaths.length; ++i) {
972927dbe93S猫头猫            const _pluginUrl = pluginsPaths[i];
9731e263108S猫头猫            trace('初始化插件', _pluginUrl);
9741e263108S猫头猫            if (
9751e263108S猫头猫                _pluginUrl.isFile() &&
9761e263108S猫头猫                (_pluginUrl.name?.endsWith?.('.js') ||
9771e263108S猫头猫                    _pluginUrl.path?.endsWith?.('.js'))
9781e263108S猫头猫            ) {
979927dbe93S猫头猫                const funcCode = await readFile(_pluginUrl.path, 'utf8');
980927dbe93S猫头猫                const plugin = new Plugin(funcCode, _pluginUrl.path);
9814060c00aS猫头猫                const _pluginIndex = _plugins.findIndex(
9824060c00aS猫头猫                    p => p.hash === plugin.hash,
9834060c00aS猫头猫                );
984927dbe93S猫头猫                if (_pluginIndex !== -1) {
985927dbe93S猫头猫                    // 重复插件,直接忽略
9860c266394S猫头猫                    continue;
987927dbe93S猫头猫                }
988927dbe93S猫头猫                plugin.hash !== '' && _plugins.push(plugin);
989927dbe93S猫头猫            }
990927dbe93S猫头猫        }
991927dbe93S猫头猫
992927dbe93S猫头猫        plugins = _plugins;
993e08d37a3S猫头猫        /** 初始化meta信息 */
994c2b3a262S猫头猫        await PluginMeta.setupMeta(plugins.map(_ => _.name));
995c2b3a262S猫头猫        /** 查看一下是否有禁用的标记 */
996c2b3a262S猫头猫        const allMeta = PluginMeta.getPluginMetaAll() ?? {};
997c2b3a262S猫头猫        for (let plugin of plugins) {
998c2b3a262S猫头猫            if (allMeta[plugin.name]?.enabled === false) {
999c2b3a262S猫头猫                plugin.state = 'disabled';
1000c2b3a262S猫头猫            }
1001c2b3a262S猫头猫        }
1002c2b3a262S猫头猫        pluginStateMapper.notify();
1003927dbe93S猫头猫    } catch (e: any) {
10044060c00aS猫头猫        ToastAndroid.show(
10054060c00aS猫头猫            `插件初始化失败:${e?.message ?? e}`,
10064060c00aS猫头猫            ToastAndroid.LONG,
10074060c00aS猫头猫        );
10081a5528a0S猫头猫        errorLog('插件初始化失败', e?.message);
1009927dbe93S猫头猫        throw e;
1010927dbe93S猫头猫    }
1011927dbe93S猫头猫}
1012927dbe93S猫头猫
1013e36e2599S猫头猫interface IInstallPluginConfig {
1014e36e2599S猫头猫    notCheckVersion?: boolean;
1015e36e2599S猫头猫}
1016e36e2599S猫头猫
1017927dbe93S猫头猫// 安装插件
1018e36e2599S猫头猫async function installPlugin(
1019e36e2599S猫头猫    pluginPath: string,
1020e36e2599S猫头猫    config?: IInstallPluginConfig,
1021e36e2599S猫头猫) {
102222c09412S猫头猫    // if (pluginPath.endsWith('.js')) {
1023927dbe93S猫头猫    const funcCode = await readFile(pluginPath, 'utf8');
1024e36e2599S猫头猫
1025e36e2599S猫头猫    if (funcCode) {
1026927dbe93S猫头猫        const plugin = new Plugin(funcCode, pluginPath);
1027927dbe93S猫头猫        const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
1028927dbe93S猫头猫        if (_pluginIndex !== -1) {
1029e36e2599S猫头猫            // 静默忽略
1030e36e2599S猫头猫            return plugin;
1031927dbe93S猫头猫        }
1032e36e2599S猫头猫        const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
1033e36e2599S猫头猫        if (oldVersionPlugin && !config?.notCheckVersion) {
1034e36e2599S猫头猫            if (
1035e36e2599S猫头猫                compare(
1036e36e2599S猫头猫                    oldVersionPlugin.instance.version ?? '',
1037e36e2599S猫头猫                    plugin.instance.version ?? '',
1038e36e2599S猫头猫                    '>',
1039e36e2599S猫头猫                )
1040e36e2599S猫头猫            ) {
1041e36e2599S猫头猫                throw new Error('已安装更新版本的插件');
1042e36e2599S猫头猫            }
1043e36e2599S猫头猫        }
1044e36e2599S猫头猫
1045927dbe93S猫头猫        if (plugin.hash !== '') {
1046927dbe93S猫头猫            const fn = nanoid();
1047e36e2599S猫头猫            if (oldVersionPlugin) {
1048e36e2599S猫头猫                plugins = plugins.filter(_ => _.hash !== oldVersionPlugin.hash);
1049e36e2599S猫头猫                try {
1050e36e2599S猫头猫                    await unlink(oldVersionPlugin.path);
1051e36e2599S猫头猫                } catch {}
1052e36e2599S猫头猫            }
1053927dbe93S猫头猫            const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
1054927dbe93S猫头猫            await copyFile(pluginPath, _pluginPath);
1055927dbe93S猫头猫            plugin.path = _pluginPath;
1056927dbe93S猫头猫            plugins = plugins.concat(plugin);
1057927dbe93S猫头猫            pluginStateMapper.notify();
1058a84a85c5S猫头猫            return plugin;
1059927dbe93S猫头猫        }
1060e36e2599S猫头猫        throw new Error('插件无法解析!');
1061927dbe93S猫头猫    }
1062e36e2599S猫头猫    throw new Error('插件无法识别!');
1063c2b3a262S猫头猫}
1064c2b3a262S猫头猫
1065ee781061S猫头猫const reqHeaders = {
1066ee781061S猫头猫    'Cache-Control': 'no-cache',
1067ee781061S猫头猫    Pragma: 'no-cache',
1068ee781061S猫头猫    Expires: '0',
1069ee781061S猫头猫};
1070ee781061S猫头猫
1071c2b3a262S猫头猫async function installPluginFromUrl(
1072c2b3a262S猫头猫    url: string,
1073c2b3a262S猫头猫    config?: IInstallPluginConfig,
1074c2b3a262S猫头猫) {
107558992c6bS猫头猫    try {
1076ee781061S猫头猫        const funcCode = (
1077ee781061S猫头猫            await axios.get(url, {
1078ee781061S猫头猫                headers: reqHeaders,
1079ee781061S猫头猫            })
1080ee781061S猫头猫        ).data;
108158992c6bS猫头猫        if (funcCode) {
108258992c6bS猫头猫            const plugin = new Plugin(funcCode, '');
108358992c6bS猫头猫            const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
108458992c6bS猫头猫            if (_pluginIndex !== -1) {
10858b7ddca8S猫头猫                // 静默忽略
10868b7ddca8S猫头猫                return;
108758992c6bS猫头猫            }
108825c1bd29S猫头猫            const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
1089c2b3a262S猫头猫            if (oldVersionPlugin && !config?.notCheckVersion) {
109025c1bd29S猫头猫                if (
109125c1bd29S猫头猫                    compare(
109225c1bd29S猫头猫                        oldVersionPlugin.instance.version ?? '',
109325c1bd29S猫头猫                        plugin.instance.version ?? '',
109425c1bd29S猫头猫                        '>',
109525c1bd29S猫头猫                    )
109625c1bd29S猫头猫                ) {
109725c1bd29S猫头猫                    throw new Error('已安装更新版本的插件');
109825c1bd29S猫头猫                }
109925c1bd29S猫头猫            }
110025c1bd29S猫头猫
110158992c6bS猫头猫            if (plugin.hash !== '') {
110258992c6bS猫头猫                const fn = nanoid();
110358992c6bS猫头猫                const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
110458992c6bS猫头猫                await writeFile(_pluginPath, funcCode, 'utf8');
110558992c6bS猫头猫                plugin.path = _pluginPath;
110658992c6bS猫头猫                plugins = plugins.concat(plugin);
110725c1bd29S猫头猫                if (oldVersionPlugin) {
110825c1bd29S猫头猫                    plugins = plugins.filter(
110925c1bd29S猫头猫                        _ => _.hash !== oldVersionPlugin.hash,
111025c1bd29S猫头猫                    );
111125c1bd29S猫头猫                    try {
111225c1bd29S猫头猫                        await unlink(oldVersionPlugin.path);
111325c1bd29S猫头猫                    } catch {}
111425c1bd29S猫头猫                }
111558992c6bS猫头猫                pluginStateMapper.notify();
111658992c6bS猫头猫                return;
111758992c6bS猫头猫            }
111874acbfc0S猫头猫            throw new Error('插件无法解析!');
111958992c6bS猫头猫        }
112025c1bd29S猫头猫    } catch (e: any) {
1121ea6d708fS猫头猫        devLog('error', 'URL安装插件失败', e, e?.message);
112258992c6bS猫头猫        errorLog('URL安装插件失败', e);
112325c1bd29S猫头猫        throw new Error(e?.message ?? '');
112458992c6bS猫头猫    }
112558992c6bS猫头猫}
112658992c6bS猫头猫
1127927dbe93S猫头猫/** 卸载插件 */
1128927dbe93S猫头猫async function uninstallPlugin(hash: string) {
1129927dbe93S猫头猫    const targetIndex = plugins.findIndex(_ => _.hash === hash);
1130927dbe93S猫头猫    if (targetIndex !== -1) {
1131927dbe93S猫头猫        try {
113224e5e74aS猫头猫            const pluginName = plugins[targetIndex].name;
1133927dbe93S猫头猫            await unlink(plugins[targetIndex].path);
1134927dbe93S猫头猫            plugins = plugins.filter(_ => _.hash !== hash);
1135927dbe93S猫头猫            pluginStateMapper.notify();
113643eb30bfS猫头猫            // 防止其他重名
113724e5e74aS猫头猫            if (plugins.every(_ => _.name !== pluginName)) {
1138*5589cdf3S猫头猫                MediaExtra.removeAll(pluginName);
113924e5e74aS猫头猫            }
1140927dbe93S猫头猫        } catch {}
1141927dbe93S猫头猫    }
1142927dbe93S猫头猫}
1143927dbe93S猫头猫
114408882a77S猫头猫async function uninstallAllPlugins() {
114508882a77S猫头猫    await Promise.all(
114608882a77S猫头猫        plugins.map(async plugin => {
114708882a77S猫头猫            try {
114808882a77S猫头猫                const pluginName = plugin.name;
114908882a77S猫头猫                await unlink(plugin.path);
1150*5589cdf3S猫头猫                MediaExtra.removeAll(pluginName);
115108882a77S猫头猫            } catch (e) {}
115208882a77S猫头猫        }),
115308882a77S猫头猫    );
115408882a77S猫头猫    plugins = [];
115508882a77S猫头猫    pluginStateMapper.notify();
1156e08d37a3S猫头猫
1157e08d37a3S猫头猫    /** 清除空余文件,异步做就可以了 */
1158e08d37a3S猫头猫    readDir(pathConst.pluginPath)
1159e08d37a3S猫头猫        .then(fns => {
1160e08d37a3S猫头猫            fns.forEach(fn => {
1161e08d37a3S猫头猫                unlink(fn.path).catch(emptyFunction);
1162e08d37a3S猫头猫            });
1163e08d37a3S猫头猫        })
1164e08d37a3S猫头猫        .catch(emptyFunction);
116508882a77S猫头猫}
116608882a77S猫头猫
116725c1bd29S猫头猫async function updatePlugin(plugin: Plugin) {
116825c1bd29S猫头猫    const updateUrl = plugin.instance.srcUrl;
116925c1bd29S猫头猫    if (!updateUrl) {
117025c1bd29S猫头猫        throw new Error('没有更新源');
117125c1bd29S猫头猫    }
117225c1bd29S猫头猫    try {
117325c1bd29S猫头猫        await installPluginFromUrl(updateUrl);
117425c1bd29S猫头猫    } catch (e: any) {
117525c1bd29S猫头猫        if (e.message === '插件已安装') {
117625c1bd29S猫头猫            throw new Error('当前已是最新版本');
117725c1bd29S猫头猫        } else {
117825c1bd29S猫头猫            throw e;
117925c1bd29S猫头猫        }
118025c1bd29S猫头猫    }
118125c1bd29S猫头猫}
118225c1bd29S猫头猫
1183927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) {
11842c595535S猫头猫    return getByName(mediaItem?.platform);
1185927dbe93S猫头猫}
1186927dbe93S猫头猫
1187927dbe93S猫头猫function getByHash(hash: string) {
11887993f90eS猫头猫    return hash === localPluginHash
11897993f90eS猫头猫        ? localFilePlugin
11907993f90eS猫头猫        : plugins.find(_ => _.hash === hash);
1191927dbe93S猫头猫}
1192927dbe93S猫头猫
1193927dbe93S猫头猫function getByName(name: string) {
11947993f90eS猫头猫    return name === localPluginPlatform
11950e4173cdS猫头猫        ? localFilePlugin
11960e4173cdS猫头猫        : plugins.find(_ => _.name === name);
1197927dbe93S猫头猫}
1198927dbe93S猫头猫
1199927dbe93S猫头猫function getValidPlugins() {
1200927dbe93S猫头猫    return plugins.filter(_ => _.state === 'enabled');
1201927dbe93S猫头猫}
1202927dbe93S猫头猫
12032b80a429S猫头猫function getSearchablePlugins(supportedSearchType?: ICommon.SupportMediaType) {
12042b80a429S猫头猫    return plugins.filter(
12052b80a429S猫头猫        _ =>
12062b80a429S猫头猫            _.state === 'enabled' &&
12072b80a429S猫头猫            _.instance.search &&
120839ac60f7S猫头猫            (supportedSearchType && _.instance.supportedSearchType
120939ac60f7S猫头猫                ? _.instance.supportedSearchType.includes(supportedSearchType)
12102b80a429S猫头猫                : true),
12112b80a429S猫头猫    );
1212efb9da24S猫头猫}
1213efb9da24S猫头猫
12142b80a429S猫头猫function getSortedSearchablePlugins(
12152b80a429S猫头猫    supportedSearchType?: ICommon.SupportMediaType,
12162b80a429S猫头猫) {
12172b80a429S猫头猫    return getSearchablePlugins(supportedSearchType).sort((a, b) =>
1218e08d37a3S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1219e08d37a3S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1220e08d37a3S猫头猫        0
1221e08d37a3S猫头猫            ? -1
1222e08d37a3S猫头猫            : 1,
1223e08d37a3S猫头猫    );
1224e08d37a3S猫头猫}
1225e08d37a3S猫头猫
122615feccc1S猫头猫function getTopListsablePlugins() {
122715feccc1S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists);
122815feccc1S猫头猫}
122915feccc1S猫头猫
123015feccc1S猫头猫function getSortedTopListsablePlugins() {
123115feccc1S猫头猫    return getTopListsablePlugins().sort((a, b) =>
123215feccc1S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
123315feccc1S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
123415feccc1S猫头猫        0
123515feccc1S猫头猫            ? -1
123615feccc1S猫头猫            : 1,
123715feccc1S猫头猫    );
123815feccc1S猫头猫}
123915feccc1S猫头猫
1240ceb900cdS猫头猫function getRecommendSheetablePlugins() {
1241ceb900cdS猫头猫    return plugins.filter(
1242ceb900cdS猫头猫        _ => _.state === 'enabled' && _.instance.getRecommendSheetsByTag,
1243ceb900cdS猫头猫    );
1244ceb900cdS猫头猫}
1245ceb900cdS猫头猫
1246ceb900cdS猫头猫function getSortedRecommendSheetablePlugins() {
1247ceb900cdS猫头猫    return getRecommendSheetablePlugins().sort((a, b) =>
1248ceb900cdS猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1249ceb900cdS猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1250ceb900cdS猫头猫        0
1251ceb900cdS猫头猫            ? -1
1252ceb900cdS猫头猫            : 1,
1253ceb900cdS猫头猫    );
1254ceb900cdS猫头猫}
1255ceb900cdS猫头猫
1256e08d37a3S猫头猫function useSortedPlugins() {
1257e08d37a3S猫头猫    const _plugins = pluginStateMapper.useMappedState();
1258e08d37a3S猫头猫    const _pluginMetaAll = PluginMeta.usePluginMetaAll();
1259e08d37a3S猫头猫
126034588741S猫头猫    const [sortedPlugins, setSortedPlugins] = useState(
126134588741S猫头猫        [..._plugins].sort((a, b) =>
1262e08d37a3S猫头猫            (_pluginMetaAll[a.name]?.order ?? Infinity) -
1263e08d37a3S猫头猫                (_pluginMetaAll[b.name]?.order ?? Infinity) <
1264e08d37a3S猫头猫            0
1265e08d37a3S猫头猫                ? -1
1266e08d37a3S猫头猫                : 1,
126734588741S猫头猫        ),
1268e08d37a3S猫头猫    );
126934588741S猫头猫
127034588741S猫头猫    useEffect(() => {
1271d4cd40d8S猫头猫        InteractionManager.runAfterInteractions(() => {
127234588741S猫头猫            setSortedPlugins(
127334588741S猫头猫                [..._plugins].sort((a, b) =>
127434588741S猫头猫                    (_pluginMetaAll[a.name]?.order ?? Infinity) -
127534588741S猫头猫                        (_pluginMetaAll[b.name]?.order ?? Infinity) <
127634588741S猫头猫                    0
127734588741S猫头猫                        ? -1
127834588741S猫头猫                        : 1,
127934588741S猫头猫                ),
128034588741S猫头猫            );
1281d4cd40d8S猫头猫        });
128234588741S猫头猫    }, [_plugins, _pluginMetaAll]);
128334588741S猫头猫
128434588741S猫头猫    return sortedPlugins;
1285e08d37a3S猫头猫}
1286e08d37a3S猫头猫
1287c2b3a262S猫头猫async function setPluginEnabled(plugin: Plugin, enabled?: boolean) {
1288c2b3a262S猫头猫    const target = plugins.find(it => it.hash === plugin.hash);
1289c2b3a262S猫头猫    if (target) {
1290c2b3a262S猫头猫        target.state = enabled ? 'enabled' : 'disabled';
1291c2b3a262S猫头猫        plugins = [...plugins];
1292c2b3a262S猫头猫        pluginStateMapper.notify();
1293c2b3a262S猫头猫        PluginMeta.setPluginMetaProp(plugin, 'enabled', enabled);
1294c2b3a262S猫头猫    }
1295c2b3a262S猫头猫}
1296c2b3a262S猫头猫
1297927dbe93S猫头猫const PluginManager = {
1298927dbe93S猫头猫    setup,
1299927dbe93S猫头猫    installPlugin,
130058992c6bS猫头猫    installPluginFromUrl,
130125c1bd29S猫头猫    updatePlugin,
1302927dbe93S猫头猫    uninstallPlugin,
1303927dbe93S猫头猫    getByMedia,
1304927dbe93S猫头猫    getByHash,
1305927dbe93S猫头猫    getByName,
1306927dbe93S猫头猫    getValidPlugins,
1307efb9da24S猫头猫    getSearchablePlugins,
1308e08d37a3S猫头猫    getSortedSearchablePlugins,
130915feccc1S猫头猫    getTopListsablePlugins,
1310ceb900cdS猫头猫    getSortedRecommendSheetablePlugins,
131115feccc1S猫头猫    getSortedTopListsablePlugins,
13125276aef9S猫头猫    usePlugins: pluginStateMapper.useMappedState,
1313e08d37a3S猫头猫    useSortedPlugins,
131408882a77S猫头猫    uninstallAllPlugins,
1315c2b3a262S猫头猫    setPluginEnabled,
13165276aef9S猫头猫};
1317927dbe93S猫头猫
1318927dbe93S猫头猫export default PluginManager;
1319