xref: /MusicFree/src/core/pluginManager.ts (revision 91eb8fa80130bb664f4db054805cd9f6337563c1)
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';
2043eb30bfS猫头猫import MediaMeta from './mediaExtra';
21927dbe93S猫头猫import {nanoid} from 'nanoid';
22ea6d708fS猫头猫import {devLog, errorLog, trace} from '../utils/log';
23cfa0fc07S猫头猫import {
240e4173cdS猫头猫    getInternalData,
250e4173cdS猫头猫    InternalDataType,
260e4173cdS猫头猫    isSameMediaItem,
270e4173cdS猫头猫    resetMediaItem,
280e4173cdS猫头猫} from '@/utils/mediaItem';
297993f90eS猫头猫import {
307993f90eS猫头猫    CacheControl,
31e08d37a3S猫头猫    emptyFunction,
327993f90eS猫头猫    internalSerializeKey,
337993f90eS猫头猫    localPluginHash,
347993f90eS猫头猫    localPluginPlatform,
357993f90eS猫头猫} from '@/constants/commonConst';
36927dbe93S猫头猫import delay from '@/utils/delay';
374d9d3c4cS猫头猫import * as cheerio from 'cheerio';
387d7e864fS猫头猫import CookieManager from '@react-native-cookies/cookies';
397d7e864fS猫头猫import he from 'he';
40ef714860S猫头猫import Network from './network';
410e4173cdS猫头猫import LocalMusicSheet from './localMusicSheet';
420e4173cdS猫头猫import {FileSystem} from 'react-native-file-access';
4374d0cf81S猫头猫import Mp3Util from '@/native/mp3Util';
44e08d37a3S猫头猫import {PluginMeta} from './pluginMeta';
4534588741S猫头猫import {useEffect, useState} from 'react';
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';
5043eb30bfS猫头猫import produce from 'immer';
5143eb30bfS猫头猫import MediaExtra from './mediaExtra';
5243eb30bfS猫头猫import objectPath from 'object-path';
53927dbe93S猫头猫
5461aca335S猫头猫axios.defaults.timeout = 2000;
55927dbe93S猫头猫
56927dbe93S猫头猫const sha256 = CryptoJs.SHA256;
57927dbe93S猫头猫
58cfa0fc07S猫头猫export enum PluginStateCode {
59927dbe93S猫头猫    /** 版本不匹配 */
60927dbe93S猫头猫    VersionNotMatch = 'VERSION NOT MATCH',
61927dbe93S猫头猫    /** 无法解析 */
62927dbe93S猫头猫    CannotParse = 'CANNOT PARSE',
63927dbe93S猫头猫}
64927dbe93S猫头猫
659c34d637S猫头猫const packages: Record<string, any> = {
669c34d637S猫头猫    cheerio,
679c34d637S猫头猫    'crypto-js': CryptoJs,
689c34d637S猫头猫    axios,
699c34d637S猫头猫    dayjs,
709c34d637S猫头猫    'big-integer': bigInt,
719c34d637S猫头猫    qs,
729c34d637S猫头猫    he,
733b3d6357S猫头猫    '@react-native-cookies/cookies': CookieManager,
7428ccceb7S猫头猫    webdav,
759c34d637S猫头猫};
769c34d637S猫头猫
77b43683eaS猫头猫const _require = (packageName: string) => {
78b43683eaS猫头猫    let pkg = packages[packageName];
79b43683eaS猫头猫    pkg.default = pkg;
80b43683eaS猫头猫    return pkg;
81b43683eaS猫头猫};
829c34d637S猫头猫
8353f8cd8eS猫头猫const _consoleBind = function (
8453f8cd8eS猫头猫    method: 'log' | 'error' | 'info' | 'warn',
8553f8cd8eS猫头猫    ...args: any
8653f8cd8eS猫头猫) {
8753f8cd8eS猫头猫    const fn = console[method];
8853f8cd8eS猫头猫    if (fn) {
8953f8cd8eS猫头猫        fn(...args);
9053f8cd8eS猫头猫        devLog(method, ...args);
9153f8cd8eS猫头猫    }
9253f8cd8eS猫头猫};
9353f8cd8eS猫头猫
9453f8cd8eS猫头猫const _console = {
9553f8cd8eS猫头猫    log: _consoleBind.bind(null, 'log'),
9653f8cd8eS猫头猫    warn: _consoleBind.bind(null, 'warn'),
9753f8cd8eS猫头猫    info: _consoleBind.bind(null, 'info'),
9853f8cd8eS猫头猫    error: _consoleBind.bind(null, 'error'),
9953f8cd8eS猫头猫};
10053f8cd8eS猫头猫
101a7b42a4cS猫头猫function formatAuthUrl(url: string) {
102a7b42a4cS猫头猫    const urlObj = new URL(url);
103a7b42a4cS猫头猫
104a7b42a4cS猫头猫    try {
105a7b42a4cS猫头猫        if (urlObj.username && urlObj.password) {
106a7b42a4cS猫头猫            const auth = `Basic ${Base64.btoa(
107a7b42a4cS猫头猫                `${decodeURIComponent(urlObj.username)}:${decodeURIComponent(
108a7b42a4cS猫头猫                    urlObj.password,
109a7b42a4cS猫头猫                )}`,
110a7b42a4cS猫头猫            )}`;
111a7b42a4cS猫头猫            urlObj.username = '';
112a7b42a4cS猫头猫            urlObj.password = '';
113a7b42a4cS猫头猫
114a7b42a4cS猫头猫            return {
115a7b42a4cS猫头猫                url: urlObj.toString(),
116a7b42a4cS猫头猫                auth,
117a7b42a4cS猫头猫            };
118a7b42a4cS猫头猫        }
119a7b42a4cS猫头猫    } catch (e) {
120a7b42a4cS猫头猫        return {
121a7b42a4cS猫头猫            url,
122a7b42a4cS猫头猫        };
123a7b42a4cS猫头猫    }
124a7b42a4cS猫头猫    return {
125a7b42a4cS猫头猫        url,
126a7b42a4cS猫头猫    };
127a7b42a4cS猫头猫}
128a7b42a4cS猫头猫
129d5bfeb7eS猫头猫//#region 插件类
130927dbe93S猫头猫export class Plugin {
131927dbe93S猫头猫    /** 插件名 */
132927dbe93S猫头猫    public name: string;
133927dbe93S猫头猫    /** 插件的hash,作为唯一id */
134927dbe93S猫头猫    public hash: string;
135927dbe93S猫头猫    /** 插件状态:激活、关闭、错误 */
136927dbe93S猫头猫    public state: 'enabled' | 'disabled' | 'error';
137927dbe93S猫头猫    /** 插件状态信息 */
138927dbe93S猫头猫    public stateCode?: PluginStateCode;
139927dbe93S猫头猫    /** 插件的实例 */
140927dbe93S猫头猫    public instance: IPlugin.IPluginInstance;
141927dbe93S猫头猫    /** 插件路径 */
142927dbe93S猫头猫    public path: string;
143927dbe93S猫头猫    /** 插件方法 */
144927dbe93S猫头猫    public methods: PluginMethods;
145927dbe93S猫头猫
14674d0cf81S猫头猫    constructor(
14774d0cf81S猫头猫        funcCode: string | (() => IPlugin.IPluginInstance),
14874d0cf81S猫头猫        pluginPath: string,
14974d0cf81S猫头猫    ) {
150927dbe93S猫头猫        this.state = 'enabled';
151927dbe93S猫头猫        let _instance: IPlugin.IPluginInstance;
1523b3d6357S猫头猫        const _module: any = {exports: {}};
153927dbe93S猫头猫        try {
15474d0cf81S猫头猫            if (typeof funcCode === 'string') {
155e0caf342S猫头猫                // 插件的环境变量
156e0caf342S猫头猫                const env = {
157e0caf342S猫头猫                    getUserVariables: () => {
158e0caf342S猫头猫                        return (
159e0caf342S猫头猫                            PluginMeta.getPluginMeta(this)?.userVariables ?? {}
160e0caf342S猫头猫                        );
161e0caf342S猫头猫                    },
162e3fa9b3cS猫头猫                    os: 'android',
163e0caf342S猫头猫                };
164e0caf342S猫头猫
1654060c00aS猫头猫                // eslint-disable-next-line no-new-func
166927dbe93S猫头猫                _instance = Function(`
167927dbe93S猫头猫                    'use strict';
168*91eb8fa8S猫头猫                    return function(require, __musicfree_require, module, exports, console, env, URL) {
1699c34d637S猫头猫                        ${funcCode}
170927dbe93S猫头猫                    }
171e0caf342S猫头猫                `)()(
172e0caf342S猫头猫                    _require,
173e0caf342S猫头猫                    _require,
174e0caf342S猫头猫                    _module,
175e0caf342S猫头猫                    _module.exports,
176e0caf342S猫头猫                    _console,
177e0caf342S猫头猫                    env,
178*91eb8fa8S猫头猫                    URL,
179e0caf342S猫头猫                );
1803b3d6357S猫头猫                if (_module.exports.default) {
1813b3d6357S猫头猫                    _instance = _module.exports
1823b3d6357S猫头猫                        .default as IPlugin.IPluginInstance;
1833b3d6357S猫头猫                } else {
1849c34d637S猫头猫                    _instance = _module.exports as IPlugin.IPluginInstance;
1853b3d6357S猫头猫                }
18674d0cf81S猫头猫            } else {
18774d0cf81S猫头猫                _instance = funcCode();
18874d0cf81S猫头猫            }
189c2b3a262S猫头猫            // 插件初始化后的一些操作
19095297592S猫头猫            if (Array.isArray(_instance.userVariables)) {
19195297592S猫头猫                _instance.userVariables = _instance.userVariables.filter(
19295297592S猫头猫                    it => it?.key,
19395297592S猫头猫                );
19495297592S猫头猫            }
195927dbe93S猫头猫            this.checkValid(_instance);
196927dbe93S猫头猫        } catch (e: any) {
197b43683eaS猫头猫            console.log(e);
198927dbe93S猫头猫            this.state = 'error';
199927dbe93S猫头猫            this.stateCode = PluginStateCode.CannotParse;
200927dbe93S猫头猫            if (e?.stateCode) {
201927dbe93S猫头猫                this.stateCode = e.stateCode;
202927dbe93S猫头猫            }
203927dbe93S猫头猫            errorLog(`${pluginPath}插件无法解析 `, {
204927dbe93S猫头猫                stateCode: this.stateCode,
205927dbe93S猫头猫                message: e?.message,
206927dbe93S猫头猫                stack: e?.stack,
207927dbe93S猫头猫            });
208927dbe93S猫头猫            _instance = e?.instance ?? {
209927dbe93S猫头猫                _path: '',
210927dbe93S猫头猫                platform: '',
211927dbe93S猫头猫                appVersion: '',
21220e6a092S猫头猫                async getMediaSource() {
213927dbe93S猫头猫                    return null;
214927dbe93S猫头猫                },
215927dbe93S猫头猫                async search() {
216927dbe93S猫头猫                    return {};
217927dbe93S猫头猫                },
218927dbe93S猫头猫                async getAlbumInfo() {
219927dbe93S猫头猫                    return null;
220927dbe93S猫头猫                },
221927dbe93S猫头猫            };
222927dbe93S猫头猫        }
223927dbe93S猫头猫        this.instance = _instance;
224927dbe93S猫头猫        this.path = pluginPath;
225927dbe93S猫头猫        this.name = _instance.platform;
226ab8941d9S猫头猫        if (
227ab8941d9S猫头猫            this.instance.platform === '' ||
228ab8941d9S猫头猫            this.instance.platform === undefined
229ab8941d9S猫头猫        ) {
230927dbe93S猫头猫            this.hash = '';
231927dbe93S猫头猫        } else {
23274d0cf81S猫头猫            if (typeof funcCode === 'string') {
233927dbe93S猫头猫                this.hash = sha256(funcCode).toString();
23474d0cf81S猫头猫            } else {
23574d0cf81S猫头猫                this.hash = sha256(funcCode.toString()).toString();
23674d0cf81S猫头猫            }
237927dbe93S猫头猫        }
238927dbe93S猫头猫
239927dbe93S猫头猫        // 放在最后
240927dbe93S猫头猫        this.methods = new PluginMethods(this);
241927dbe93S猫头猫    }
242927dbe93S猫头猫
243927dbe93S猫头猫    private checkValid(_instance: IPlugin.IPluginInstance) {
244927dbe93S猫头猫        /** 版本号校验 */
245927dbe93S猫头猫        if (
246927dbe93S猫头猫            _instance.appVersion &&
247927dbe93S猫头猫            !satisfies(DeviceInfo.getVersion(), _instance.appVersion)
248927dbe93S猫头猫        ) {
249927dbe93S猫头猫            throw {
250927dbe93S猫头猫                instance: _instance,
251927dbe93S猫头猫                stateCode: PluginStateCode.VersionNotMatch,
252927dbe93S猫头猫            };
253927dbe93S猫头猫        }
254927dbe93S猫头猫        return true;
255927dbe93S猫头猫    }
256927dbe93S猫头猫}
257d5bfeb7eS猫头猫//#endregion
258927dbe93S猫头猫
259d5bfeb7eS猫头猫//#region 基于插件类封装的方法,供给APP侧直接调用
260927dbe93S猫头猫/** 有缓存等信息 */
261927dbe93S猫头猫class PluginMethods implements IPlugin.IPluginInstanceMethods {
262927dbe93S猫头猫    private plugin;
263927dbe93S猫头猫    constructor(plugin: Plugin) {
264927dbe93S猫头猫        this.plugin = plugin;
265927dbe93S猫头猫    }
266927dbe93S猫头猫    /** 搜索 */
267927dbe93S猫头猫    async search<T extends ICommon.SupportMediaType>(
268927dbe93S猫头猫        query: string,
269927dbe93S猫头猫        page: number,
270927dbe93S猫头猫        type: T,
271927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
272927dbe93S猫头猫        if (!this.plugin.instance.search) {
273927dbe93S猫头猫            return {
274927dbe93S猫头猫                isEnd: true,
275927dbe93S猫头猫                data: [],
276927dbe93S猫头猫            };
277927dbe93S猫头猫        }
278927dbe93S猫头猫
2794060c00aS猫头猫        const result =
2804060c00aS猫头猫            (await this.plugin.instance.search(query, page, type)) ?? {};
281927dbe93S猫头猫        if (Array.isArray(result.data)) {
282927dbe93S猫头猫            result.data.forEach(_ => {
283927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
284927dbe93S猫头猫            });
285927dbe93S猫头猫            return {
286927dbe93S猫头猫                isEnd: result.isEnd ?? true,
287927dbe93S猫头猫                data: result.data,
288927dbe93S猫头猫            };
289927dbe93S猫头猫        }
290927dbe93S猫头猫        return {
291927dbe93S猫头猫            isEnd: true,
292927dbe93S猫头猫            data: [],
293927dbe93S猫头猫        };
294927dbe93S猫头猫    }
295927dbe93S猫头猫
296927dbe93S猫头猫    /** 获取真实源 */
29720e6a092S猫头猫    async getMediaSource(
298927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
299abaede57S猫头猫        quality: IMusic.IQualityKey = 'standard',
300927dbe93S猫头猫        retryCount = 1,
301dc160d50S猫头猫        notUpdateCache = false,
302192ae2b0S猫头猫    ): Promise<IPlugin.IMediaSourceResult | null> {
303927dbe93S猫头猫        // 1. 本地搜索 其实直接读mediameta就好了
30443eb30bfS猫头猫        const mediaExtra = MediaExtra.get(musicItem);
305927dbe93S猫头猫        const localPath =
30643eb30bfS猫头猫            mediaExtra?.localPath ||
30743eb30bfS猫头猫            getInternalData<string>(musicItem, InternalDataType.LOCALPATH) ||
3080e4173cdS猫头猫            getInternalData<string>(
3090e4173cdS猫头猫                LocalMusicSheet.isLocalMusic(musicItem),
3100e4173cdS猫头猫                InternalDataType.LOCALPATH,
3110e4173cdS猫头猫            );
312a84a85c5S猫头猫        if (
313a84a85c5S猫头猫            localPath &&
314a84a85c5S猫头猫            (localPath.startsWith('content://') ||
315a84a85c5S猫头猫                (await FileSystem.exists(localPath)))
316a84a85c5S猫头猫        ) {
3170e4173cdS猫头猫            trace('本地播放', localPath);
31843eb30bfS猫头猫            if (mediaExtra && mediaExtra.localPath !== localPath) {
31943eb30bfS猫头猫                // 修正一下本地数据
32043eb30bfS猫头猫                MediaExtra.update(musicItem, {
32143eb30bfS猫头猫                    localPath,
32243eb30bfS猫头猫                });
32343eb30bfS猫头猫            }
324927dbe93S猫头猫            return {
32511908939S猫头猫                url: addFileScheme(localPath),
326927dbe93S猫头猫            };
32743eb30bfS猫头猫        } else if (mediaExtra?.localPath) {
32843eb30bfS猫头猫            MediaExtra.update(musicItem, {
32943eb30bfS猫头猫                localPath: undefined,
33043eb30bfS猫头猫            });
331927dbe93S猫头猫        }
332a84a85c5S猫头猫
3337993f90eS猫头猫        if (musicItem.platform === localPluginPlatform) {
334f5935920S猫头猫            throw new Error('本地音乐不存在');
335f5935920S猫头猫        }
336927dbe93S猫头猫        // 2. 缓存播放
33743eb30bfS猫头猫        const mediaCache = MediaCache.getMediaCache(
33843eb30bfS猫头猫            musicItem,
33943eb30bfS猫头猫        ) as IMusic.IMusicItem | null;
340985f8e75S猫头猫        const pluginCacheControl =
341985f8e75S猫头猫            this.plugin.instance.cacheControl ?? 'no-cache';
342cfa0fc07S猫头猫        if (
343cfa0fc07S猫头猫            mediaCache &&
34443eb30bfS猫头猫            mediaCache?.source?.[quality]?.url &&
34548f4b873S猫头猫            (pluginCacheControl === CacheControl.Cache ||
34648f4b873S猫头猫                (pluginCacheControl === CacheControl.NoCache &&
347ef714860S猫头猫                    Network.isOffline()))
348cfa0fc07S猫头猫        ) {
3495276aef9S猫头猫            trace('播放', '缓存播放');
35043eb30bfS猫头猫            const qualityInfo = mediaCache.source[quality];
351927dbe93S猫头猫            return {
35243eb30bfS猫头猫                url: qualityInfo!.url,
353927dbe93S猫头猫                headers: mediaCache.headers,
3544060c00aS猫头猫                userAgent:
3554060c00aS猫头猫                    mediaCache.userAgent ?? mediaCache.headers?.['user-agent'],
356927dbe93S猫头猫            };
357927dbe93S猫头猫        }
358927dbe93S猫头猫        // 3. 插件解析
35920e6a092S猫头猫        if (!this.plugin.instance.getMediaSource) {
360a7b42a4cS猫头猫            const {url, auth} = formatAuthUrl(
361a7b42a4cS猫头猫                musicItem?.qualities?.[quality]?.url ?? musicItem.url,
362a7b42a4cS猫头猫            );
363a7b42a4cS猫头猫            return {
364a7b42a4cS猫头猫                url: url,
365a7b42a4cS猫头猫                headers: auth
366a7b42a4cS猫头猫                    ? {
367a7b42a4cS猫头猫                          Authorization: auth,
368a7b42a4cS猫头猫                      }
369a7b42a4cS猫头猫                    : undefined,
370a7b42a4cS猫头猫            };
371927dbe93S猫头猫        }
372927dbe93S猫头猫        try {
373abaede57S猫头猫            const {url, headers} = (await this.plugin.instance.getMediaSource(
374abaede57S猫头猫                musicItem,
375abaede57S猫头猫                quality,
376abaede57S猫头猫            )) ?? {url: musicItem?.qualities?.[quality]?.url};
377927dbe93S猫头猫            if (!url) {
378a28eac61S猫头猫                throw new Error('NOT RETRY');
379927dbe93S猫头猫            }
3805276aef9S猫头猫            trace('播放', '插件播放');
381927dbe93S猫头猫            const result = {
382927dbe93S猫头猫                url,
383927dbe93S猫头猫                headers,
384927dbe93S猫头猫                userAgent: headers?.['user-agent'],
385cfa0fc07S猫头猫            } as IPlugin.IMediaSourceResult;
386a7b42a4cS猫头猫            const authFormattedResult = formatAuthUrl(result.url!);
387a7b42a4cS猫头猫            if (authFormattedResult.auth) {
388a7b42a4cS猫头猫                result.url = authFormattedResult.url;
389a7b42a4cS猫头猫                result.headers = {
390a7b42a4cS猫头猫                    ...(result.headers ?? {}),
391a7b42a4cS猫头猫                    Authorization: authFormattedResult.auth,
392a7b42a4cS猫头猫                };
393a7b42a4cS猫头猫            }
394927dbe93S猫头猫
395dc160d50S猫头猫            if (
396dc160d50S猫头猫                pluginCacheControl !== CacheControl.NoStore &&
397dc160d50S猫头猫                !notUpdateCache
398dc160d50S猫头猫            ) {
39943eb30bfS猫头猫                // 更新缓存
40043eb30bfS猫头猫                const cacheSource = {
40143eb30bfS猫头猫                    headers: result.headers,
40243eb30bfS猫头猫                    userAgent: result.userAgent,
40343eb30bfS猫头猫                    url,
40443eb30bfS猫头猫                };
40543eb30bfS猫头猫                let realMusicItem = {
40643eb30bfS猫头猫                    ...musicItem,
40743eb30bfS猫头猫                    ...(mediaCache || {}),
40843eb30bfS猫头猫                };
40943eb30bfS猫头猫                realMusicItem.source = {
41043eb30bfS猫头猫                    ...(realMusicItem.source || {}),
41143eb30bfS猫头猫                    [quality]: cacheSource,
41243eb30bfS猫头猫                };
41343eb30bfS猫头猫
41443eb30bfS猫头猫                MediaCache.setMediaCache(realMusicItem);
415752ffc5aS猫头猫            }
416927dbe93S猫头猫            return result;
417927dbe93S猫头猫        } catch (e: any) {
418a28eac61S猫头猫            if (retryCount > 0 && e?.message !== 'NOT RETRY') {
419927dbe93S猫头猫                await delay(150);
420abaede57S猫头猫                return this.getMediaSource(musicItem, quality, --retryCount);
421927dbe93S猫头猫            }
422927dbe93S猫头猫            errorLog('获取真实源失败', e?.message);
423ea6d708fS猫头猫            devLog('error', '获取真实源失败', e, e?.message);
424192ae2b0S猫头猫            return null;
425927dbe93S猫头猫        }
426927dbe93S猫头猫    }
427927dbe93S猫头猫
428927dbe93S猫头猫    /** 获取音乐详情 */
429927dbe93S猫头猫    async getMusicInfo(
430927dbe93S猫头猫        musicItem: ICommon.IMediaBase,
43174d0cf81S猫头猫    ): Promise<Partial<IMusic.IMusicItem> | null> {
432927dbe93S猫头猫        if (!this.plugin.instance.getMusicInfo) {
433d704daedS猫头猫            return null;
434927dbe93S猫头猫        }
43574d0cf81S猫头猫        try {
436927dbe93S猫头猫            return (
437927dbe93S猫头猫                this.plugin.instance.getMusicInfo(
4387993f90eS猫头猫                    resetMediaItem(musicItem, undefined, true),
439d704daedS猫头猫                ) ?? null
440927dbe93S猫头猫            );
441ea6d708fS猫头猫        } catch (e: any) {
442ea6d708fS猫头猫            devLog('error', '获取音乐详情失败', e, e?.message);
443d704daedS猫头猫            return null;
44474d0cf81S猫头猫        }
445927dbe93S猫头猫    }
446927dbe93S猫头猫
4477e883dbbS猫头猫    /**
4487e883dbbS猫头猫     *
4497e883dbbS猫头猫     * getLyric(musicItem) => {
4507e883dbbS猫头猫     *      lyric: string;
4517e883dbbS猫头猫     *      trans: string;
4527e883dbbS猫头猫     * }
4537e883dbbS猫头猫     *
4547e883dbbS猫头猫     */
455927dbe93S猫头猫    /** 获取歌词 */
456927dbe93S猫头猫    async getLyric(
45743eb30bfS猫头猫        originalMusicItem: IMusic.IMusicItemBase,
458927dbe93S猫头猫    ): Promise<ILyric.ILyricSource | null> {
45943eb30bfS猫头猫        // 1.额外存储的meta信息(关联歌词)
46043eb30bfS猫头猫        const meta = MediaMeta.get(originalMusicItem);
46143eb30bfS猫头猫        let musicItem: IMusic.IMusicItem;
462927dbe93S猫头猫        if (meta && meta.associatedLrc) {
46343eb30bfS猫头猫            musicItem = meta.associatedLrc as IMusic.IMusicItem;
46443eb30bfS猫头猫        } else {
46543eb30bfS猫头猫            musicItem = originalMusicItem as IMusic.IMusicItem;
466927dbe93S猫头猫        }
46743eb30bfS猫头猫
46843eb30bfS猫头猫        const musicItemCache = MediaCache.getMediaCache(
4697a91f04fS猫头猫            musicItem,
47043eb30bfS猫头猫        ) as IMusic.IMusicItemCache | null;
47143eb30bfS猫头猫
47243eb30bfS猫头猫        /** 原始歌词文本 */
4737e883dbbS猫头猫        let rawLrc: string | null = musicItem.rawLrc || null;
4747e883dbbS猫头猫        let translation: string | null = null;
47543eb30bfS猫头猫
47643eb30bfS猫头猫        // 2. 缓存歌词 / 对象上本身的歌词
47743eb30bfS猫头猫        if (musicItemCache?.lyric) {
47843eb30bfS猫头猫            // 缓存的远程结果
47943eb30bfS猫头猫            let cacheLyric: ILyric.ILyricSource | null =
48043eb30bfS猫头猫                musicItemCache.lyric || null;
48143eb30bfS猫头猫            // 缓存的本地结果
48243eb30bfS猫头猫            let localLyric: ILyric.ILyricSource | null =
48343eb30bfS猫头猫                musicItemCache.$localLyric || null;
48443eb30bfS猫头猫
4857e883dbbS猫头猫            // 优先用缓存的结果
4867e883dbbS猫头猫            if (cacheLyric.rawLrc || cacheLyric.translation) {
48743eb30bfS猫头猫                return {
4887e883dbbS猫头猫                    rawLrc: cacheLyric.rawLrc,
4897e883dbbS猫头猫                    translation: cacheLyric.translation,
49043eb30bfS猫头猫                };
49143eb30bfS猫头猫            }
49243eb30bfS猫头猫
4937e883dbbS猫头猫            // 本地其实是缓存的路径
4947e883dbbS猫头猫            if (localLyric) {
4957e883dbbS猫头猫                let needRefetch = false;
4967e883dbbS猫头猫                if (localLyric.rawLrc && (await exists(localLyric.rawLrc))) {
4977e883dbbS猫头猫                    rawLrc = await readFile(localLyric.rawLrc, 'utf8');
4987e883dbbS猫头猫                } else if (localLyric.rawLrc) {
4997e883dbbS猫头猫                    needRefetch = true;
5007e883dbbS猫头猫                }
5017e883dbbS猫头猫                if (
5027e883dbbS猫头猫                    localLyric.translation &&
5037e883dbbS猫头猫                    (await exists(localLyric.translation))
5047e883dbbS猫头猫                ) {
5057e883dbbS猫头猫                    translation = await readFile(
5067e883dbbS猫头猫                        localLyric.translation,
5077e883dbbS猫头猫                        'utf8',
5087e883dbbS猫头猫                    );
5097e883dbbS猫头猫                } else if (localLyric.translation) {
5107e883dbbS猫头猫                    needRefetch = true;
51143eb30bfS猫头猫                }
51243eb30bfS猫头猫
5137e883dbbS猫头猫                if (!needRefetch && (rawLrc || translation)) {
51443eb30bfS猫头猫                    return {
5157e883dbbS猫头猫                        rawLrc: rawLrc || undefined,
5167e883dbbS猫头猫                        translation: translation || undefined,
51743eb30bfS猫头猫                    };
51843eb30bfS猫头猫                }
51943eb30bfS猫头猫            }
52043eb30bfS猫头猫        }
52143eb30bfS猫头猫
52243eb30bfS猫头猫        // 3. 无缓存歌词/无自带歌词/无本地歌词
52343eb30bfS猫头猫        let lrcSource: ILyric.ILyricSource | null;
52443eb30bfS猫头猫        if (isSameMediaItem(originalMusicItem, musicItem)) {
52543eb30bfS猫头猫            lrcSource =
52643eb30bfS猫头猫                (await this.plugin.instance
5277e883dbbS猫头猫                    ?.getLyric?.(resetMediaItem(musicItem, undefined, true))
52843eb30bfS猫头猫                    ?.catch(() => null)) || null;
52943eb30bfS猫头猫        } else {
53043eb30bfS猫头猫            lrcSource =
53143eb30bfS猫头猫                (await PluginManager.getByMedia(musicItem)
53243eb30bfS猫头猫                    ?.instance?.getLyric?.(
53343eb30bfS猫头猫                        resetMediaItem(musicItem, undefined, true),
53443eb30bfS猫头猫                    )
53543eb30bfS猫头猫                    ?.catch(() => null)) || null;
53643eb30bfS猫头猫        }
53743eb30bfS猫头猫
53843eb30bfS猫头猫        if (lrcSource) {
5397e883dbbS猫头猫            rawLrc = lrcSource?.rawLrc || rawLrc;
5407e883dbbS猫头猫            translation = lrcSource?.translation || null;
54143eb30bfS猫头猫
5427e883dbbS猫头猫            const deprecatedLrcUrl = lrcSource?.lrc || musicItem.lrc;
54343eb30bfS猫头猫
5447e883dbbS猫头猫            // 本地的文件名
5457e883dbbS猫头猫            let filename: string | undefined = `${
5467e883dbbS猫头猫                pathConst.lrcCachePath
5477e883dbbS猫头猫            }${nanoid()}.lrc`;
5487e883dbbS猫头猫            let filenameTrans: string | undefined = `${
5497e883dbbS猫头猫                pathConst.lrcCachePath
5507e883dbbS猫头猫            }${nanoid()}.lrc`;
55143eb30bfS猫头猫
5527e883dbbS猫头猫            // 旧版本兼容
5537e883dbbS猫头猫            if (!(rawLrc || translation)) {
5547e883dbbS猫头猫                if (deprecatedLrcUrl) {
55543eb30bfS猫头猫                    rawLrc = (
5567e883dbbS猫头猫                        await axios
5577e883dbbS猫头猫                            .get(deprecatedLrcUrl, {timeout: 3000})
5587e883dbbS猫头猫                            .catch(() => null)
55943eb30bfS猫头猫                    )?.data;
5607e883dbbS猫头猫                } else if (musicItem.rawLrc) {
5617e883dbbS猫头猫                    rawLrc = musicItem.rawLrc;
5627e883dbbS猫头猫                }
56343eb30bfS猫头猫            }
56443eb30bfS猫头猫
56543eb30bfS猫头猫            if (rawLrc) {
56643eb30bfS猫头猫                await writeFile(filename, rawLrc, 'utf8');
56743eb30bfS猫头猫            } else {
5687e883dbbS猫头猫                filename = undefined;
5697e883dbbS猫头猫            }
5707e883dbbS猫头猫            if (translation) {
5717e883dbbS猫头猫                await writeFile(filenameTrans, translation, 'utf8');
5727e883dbbS猫头猫            } else {
5737e883dbbS猫头猫                filenameTrans = undefined;
5747a91f04fS猫头猫            }
5757a91f04fS猫头猫
5767e883dbbS猫头猫            if (rawLrc || translation) {
5777e883dbbS猫头猫                MediaCache.setMediaCache(
5787e883dbbS猫头猫                    produce(musicItemCache || musicItem, draft => {
5797e883dbbS猫头猫                        musicItemCache?.$localLyric?.rawLrc;
5807e883dbbS猫头猫                        objectPath.set(draft, '$localLyric.rawLrc', filename);
58143eb30bfS猫头猫                        objectPath.set(
58243eb30bfS猫头猫                            draft,
5837e883dbbS猫头猫                            '$localLyric.translation',
5847e883dbbS猫头猫                            filenameTrans,
58543eb30bfS猫头猫                        );
58643eb30bfS猫头猫                        return draft;
58743eb30bfS猫头猫                    }),
58843eb30bfS猫头猫                );
589927dbe93S猫头猫                return {
5907e883dbbS猫头猫                    rawLrc: rawLrc || undefined,
5917e883dbbS猫头猫                    translation: translation || undefined,
592927dbe93S猫头猫                };
593927dbe93S猫头猫            }
594927dbe93S猫头猫        }
59543eb30bfS猫头猫
5963a6f67b1S猫头猫        // 6. 如果是本地文件
59743eb30bfS猫头猫        const isDownloaded = LocalMusicSheet.isLocalMusic(originalMusicItem);
59843eb30bfS猫头猫        if (
59943eb30bfS猫头猫            originalMusicItem.platform !== localPluginPlatform &&
60043eb30bfS猫头猫            isDownloaded
60143eb30bfS猫头猫        ) {
6027e883dbbS猫头猫            const res = await localFilePlugin.instance!.getLyric!(isDownloaded);
60343eb30bfS猫头猫
60443eb30bfS猫头猫            console.log('本地文件歌词');
60543eb30bfS猫头猫
6063a6f67b1S猫头猫            if (res) {
6073a6f67b1S猫头猫                return res;
6083a6f67b1S猫头猫            }
6093a6f67b1S猫头猫        }
610ea6d708fS猫头猫        devLog('warn', '无歌词');
611927dbe93S猫头猫
612927dbe93S猫头猫        return null;
613927dbe93S猫头猫    }
614927dbe93S猫头猫
615927dbe93S猫头猫    /** 获取歌词文本 */
616927dbe93S猫头猫    async getLyricText(
617927dbe93S猫头猫        musicItem: IMusic.IMusicItem,
618927dbe93S猫头猫    ): Promise<string | undefined> {
6197e883dbbS猫头猫        return (await this.getLyric(musicItem))?.rawLrc;
620927dbe93S猫头猫    }
621927dbe93S猫头猫
622927dbe93S猫头猫    /** 获取专辑信息 */
623927dbe93S猫头猫    async getAlbumInfo(
624927dbe93S猫头猫        albumItem: IAlbum.IAlbumItemBase,
625f9afcc0dS猫头猫        page: number = 1,
626f9afcc0dS猫头猫    ): Promise<IPlugin.IAlbumInfoResult | null> {
627927dbe93S猫头猫        if (!this.plugin.instance.getAlbumInfo) {
628f9afcc0dS猫头猫            return {
629f9afcc0dS猫头猫                albumItem,
630f2a4767cS猫头猫                musicList: (albumItem?.musicList ?? []).map(
631f2a4767cS猫头猫                    resetMediaItem,
632f2a4767cS猫头猫                    this.plugin.name,
633f2a4767cS猫头猫                    true,
634f2a4767cS猫头猫                ),
635f9afcc0dS猫头猫                isEnd: true,
636f9afcc0dS猫头猫            };
637927dbe93S猫头猫        }
638927dbe93S猫头猫        try {
639927dbe93S猫头猫            const result = await this.plugin.instance.getAlbumInfo(
640927dbe93S猫头猫                resetMediaItem(albumItem, undefined, true),
641f9afcc0dS猫头猫                page,
642927dbe93S猫头猫            );
6435276aef9S猫头猫            if (!result) {
6445276aef9S猫头猫                throw new Error();
6455276aef9S猫头猫            }
646927dbe93S猫头猫            result?.musicList?.forEach(_ => {
647927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
64896744680S猫头猫                _.album = albumItem.title;
649927dbe93S猫头猫            });
6505276aef9S猫头猫
651f9afcc0dS猫头猫            if (page <= 1) {
652f9afcc0dS猫头猫                // 合并信息
653f9afcc0dS猫头猫                return {
654f9afcc0dS猫头猫                    albumItem: {...albumItem, ...(result?.albumItem ?? {})},
655f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
656f9afcc0dS猫头猫                    musicList: result.musicList,
657f9afcc0dS猫头猫                };
658f9afcc0dS猫头猫            } else {
659f9afcc0dS猫头猫                return {
660f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
661f9afcc0dS猫头猫                    musicList: result.musicList,
662f9afcc0dS猫头猫                };
663f9afcc0dS猫头猫            }
6644394410dS猫头猫        } catch (e: any) {
6654394410dS猫头猫            trace('获取专辑信息失败', e?.message);
666ea6d708fS猫头猫            devLog('error', '获取专辑信息失败', e, e?.message);
667ea6d708fS猫头猫
668f9afcc0dS猫头猫            return null;
669927dbe93S猫头猫        }
670927dbe93S猫头猫    }
671927dbe93S猫头猫
6725830c002S猫头猫    /** 获取歌单信息 */
6735830c002S猫头猫    async getMusicSheetInfo(
6745830c002S猫头猫        sheetItem: IMusic.IMusicSheetItem,
6755830c002S猫头猫        page: number = 1,
6765830c002S猫头猫    ): Promise<IPlugin.ISheetInfoResult | null> {
6775281926bS猫头猫        if (!this.plugin.instance.getMusicSheetInfo) {
6785830c002S猫头猫            return {
6795830c002S猫头猫                sheetItem,
6805830c002S猫头猫                musicList: sheetItem?.musicList ?? [],
6815830c002S猫头猫                isEnd: true,
6825830c002S猫头猫            };
6835830c002S猫头猫        }
6845830c002S猫头猫        try {
6855830c002S猫头猫            const result = await this.plugin.instance?.getMusicSheetInfo?.(
6865830c002S猫头猫                resetMediaItem(sheetItem, undefined, true),
6875830c002S猫头猫                page,
6885830c002S猫头猫            );
6895830c002S猫头猫            if (!result) {
6905830c002S猫头猫                throw new Error();
6915830c002S猫头猫            }
6925830c002S猫头猫            result?.musicList?.forEach(_ => {
6935830c002S猫头猫                resetMediaItem(_, this.plugin.name);
6945830c002S猫头猫            });
6955830c002S猫头猫
6965830c002S猫头猫            if (page <= 1) {
6975830c002S猫头猫                // 合并信息
6985830c002S猫头猫                return {
6995830c002S猫头猫                    sheetItem: {...sheetItem, ...(result?.sheetItem ?? {})},
7005830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
7015830c002S猫头猫                    musicList: result.musicList,
7025830c002S猫头猫                };
7035830c002S猫头猫            } else {
7045830c002S猫头猫                return {
7055830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
7065830c002S猫头猫                    musicList: result.musicList,
7075830c002S猫头猫                };
7085830c002S猫头猫            }
7095830c002S猫头猫        } catch (e: any) {
7105830c002S猫头猫            trace('获取歌单信息失败', e, e?.message);
7115830c002S猫头猫            devLog('error', '获取歌单信息失败', e, e?.message);
7125830c002S猫头猫
7135830c002S猫头猫            return null;
7145830c002S猫头猫        }
7155830c002S猫头猫    }
7165830c002S猫头猫
717927dbe93S猫头猫    /** 查询作者信息 */
718efb9da24S猫头猫    async getArtistWorks<T extends IArtist.ArtistMediaType>(
719927dbe93S猫头猫        artistItem: IArtist.IArtistItem,
720927dbe93S猫头猫        page: number,
721927dbe93S猫头猫        type: T,
722927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
723efb9da24S猫头猫        if (!this.plugin.instance.getArtistWorks) {
724927dbe93S猫头猫            return {
725927dbe93S猫头猫                isEnd: true,
726927dbe93S猫头猫                data: [],
727927dbe93S猫头猫            };
728927dbe93S猫头猫        }
729927dbe93S猫头猫        try {
730efb9da24S猫头猫            const result = await this.plugin.instance.getArtistWorks(
731927dbe93S猫头猫                artistItem,
732927dbe93S猫头猫                page,
733927dbe93S猫头猫                type,
734927dbe93S猫头猫            );
735927dbe93S猫头猫            if (!result.data) {
736927dbe93S猫头猫                return {
737927dbe93S猫头猫                    isEnd: true,
738927dbe93S猫头猫                    data: [],
739927dbe93S猫头猫                };
740927dbe93S猫头猫            }
741927dbe93S猫头猫            result.data?.forEach(_ => resetMediaItem(_, this.plugin.name));
742927dbe93S猫头猫            return {
743927dbe93S猫头猫                isEnd: result.isEnd ?? true,
744927dbe93S猫头猫                data: result.data,
745927dbe93S猫头猫            };
7464394410dS猫头猫        } catch (e: any) {
7474394410dS猫头猫            trace('查询作者信息失败', e?.message);
748ea6d708fS猫头猫            devLog('error', '查询作者信息失败', e, e?.message);
749ea6d708fS猫头猫
750927dbe93S猫头猫            throw e;
751927dbe93S猫头猫        }
752927dbe93S猫头猫    }
75308380090S猫头猫
75408380090S猫头猫    /** 导入歌单 */
75508380090S猫头猫    async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> {
75608380090S猫头猫        try {
75708380090S猫头猫            const result =
75808380090S猫头猫                (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? [];
75908380090S猫头猫            result.forEach(_ => resetMediaItem(_, this.plugin.name));
76008380090S猫头猫            return result;
761ea6d708fS猫头猫        } catch (e: any) {
7620e4173cdS猫头猫            console.log(e);
763ea6d708fS猫头猫            devLog('error', '导入歌单失败', e, e?.message);
764ea6d708fS猫头猫
76508380090S猫头猫            return [];
76608380090S猫头猫        }
76708380090S猫头猫    }
7684d9d3c4cS猫头猫    /** 导入单曲 */
7694d9d3c4cS猫头猫    async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> {
7704d9d3c4cS猫头猫        try {
7714d9d3c4cS猫头猫            const result = await this.plugin.instance?.importMusicItem?.(
7724d9d3c4cS猫头猫                urlLike,
7734d9d3c4cS猫头猫            );
7744d9d3c4cS猫头猫            if (!result) {
7754d9d3c4cS猫头猫                throw new Error();
7764d9d3c4cS猫头猫            }
7774d9d3c4cS猫头猫            resetMediaItem(result, this.plugin.name);
7784d9d3c4cS猫头猫            return result;
779ea6d708fS猫头猫        } catch (e: any) {
780ea6d708fS猫头猫            devLog('error', '导入单曲失败', e, e?.message);
781ea6d708fS猫头猫
7824d9d3c4cS猫头猫            return null;
7834d9d3c4cS猫头猫        }
7844d9d3c4cS猫头猫    }
785d52aa40eS猫头猫    /** 获取榜单 */
78692b6c95aS猫头猫    async getTopLists(): Promise<IMusic.IMusicSheetGroupItem[]> {
787d52aa40eS猫头猫        try {
788d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopLists?.();
789d52aa40eS猫头猫            if (!result) {
790d52aa40eS猫头猫                throw new Error();
791d52aa40eS猫头猫            }
792d52aa40eS猫头猫            return result;
793d52aa40eS猫头猫        } catch (e: any) {
794d52aa40eS猫头猫            devLog('error', '获取榜单失败', e, e?.message);
795d52aa40eS猫头猫            return [];
796d52aa40eS猫头猫        }
797d52aa40eS猫头猫    }
798d52aa40eS猫头猫    /** 获取榜单详情 */
799d52aa40eS猫头猫    async getTopListDetail(
80092b6c95aS猫头猫        topListItem: IMusic.IMusicSheetItemBase,
801956ee1b7S猫头猫        page: number,
802956ee1b7S猫头猫    ): Promise<IPlugin.ITopListInfoResult> {
803d52aa40eS猫头猫        try {
804d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopListDetail?.(
805d52aa40eS猫头猫                topListItem,
806956ee1b7S猫头猫                page,
807d52aa40eS猫头猫            );
808d52aa40eS猫头猫            if (!result) {
809d52aa40eS猫头猫                throw new Error();
810d52aa40eS猫头猫            }
811d384662fS猫头猫            if (result.musicList) {
812d384662fS猫头猫                result.musicList.forEach(_ =>
813d384662fS猫头猫                    resetMediaItem(_, this.plugin.name),
814d384662fS猫头猫                );
815d384662fS猫头猫            }
816956ee1b7S猫头猫            if (result.isEnd !== false) {
817956ee1b7S猫头猫                result.isEnd = true;
818956ee1b7S猫头猫            }
819d52aa40eS猫头猫            return result;
820d52aa40eS猫头猫        } catch (e: any) {
821d52aa40eS猫头猫            devLog('error', '获取榜单详情失败', e, e?.message);
822d52aa40eS猫头猫            return {
823956ee1b7S猫头猫                isEnd: true,
824956ee1b7S猫头猫                topListItem: topListItem as IMusic.IMusicSheetItem,
825d52aa40eS猫头猫                musicList: [],
826d52aa40eS猫头猫            };
827d52aa40eS猫头猫        }
828d52aa40eS猫头猫    }
829ceb900cdS猫头猫
8305830c002S猫头猫    /** 获取推荐歌单的tag */
831ceb900cdS猫头猫    async getRecommendSheetTags(): Promise<IPlugin.IGetRecommendSheetTagsResult> {
832ceb900cdS猫头猫        try {
833ceb900cdS猫头猫            const result =
834ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetTags?.();
835ceb900cdS猫头猫            if (!result) {
836ceb900cdS猫头猫                throw new Error();
837ceb900cdS猫头猫            }
838ceb900cdS猫头猫            return result;
839ceb900cdS猫头猫        } catch (e: any) {
840ceb900cdS猫头猫            devLog('error', '获取推荐歌单失败', e, e?.message);
841ceb900cdS猫头猫            return {
842ceb900cdS猫头猫                data: [],
843ceb900cdS猫头猫            };
844ceb900cdS猫头猫        }
845ceb900cdS猫头猫    }
8465830c002S猫头猫    /** 获取某个tag的推荐歌单 */
847ceb900cdS猫头猫    async getRecommendSheetsByTag(
848ceb900cdS猫头猫        tagItem: ICommon.IUnique,
849ceb900cdS猫头猫        page?: number,
850ceb900cdS猫头猫    ): Promise<ICommon.PaginationResponse<IMusic.IMusicSheetItemBase>> {
851ceb900cdS猫头猫        try {
852ceb900cdS猫头猫            const result =
853ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetsByTag?.(
854ceb900cdS猫头猫                    tagItem,
855ceb900cdS猫头猫                    page ?? 1,
856ceb900cdS猫头猫                );
857ceb900cdS猫头猫            if (!result) {
858ceb900cdS猫头猫                throw new Error();
859ceb900cdS猫头猫            }
860ceb900cdS猫头猫            if (result.isEnd !== false) {
861ceb900cdS猫头猫                result.isEnd = true;
862ceb900cdS猫头猫            }
863ceb900cdS猫头猫            if (!result.data) {
864ceb900cdS猫头猫                result.data = [];
865ceb900cdS猫头猫            }
866ceb900cdS猫头猫            result.data.forEach(item => resetMediaItem(item, this.plugin.name));
867ceb900cdS猫头猫
868ceb900cdS猫头猫            return result;
869ceb900cdS猫头猫        } catch (e: any) {
870ceb900cdS猫头猫            devLog('error', '获取推荐歌单详情失败', e, e?.message);
871ceb900cdS猫头猫            return {
872ceb900cdS猫头猫                isEnd: true,
873ceb900cdS猫头猫                data: [],
874ceb900cdS猫头猫            };
875ceb900cdS猫头猫        }
876ceb900cdS猫头猫    }
877927dbe93S猫头猫}
878d5bfeb7eS猫头猫//#endregion
8791a5528a0S猫头猫
880927dbe93S猫头猫let plugins: Array<Plugin> = [];
881927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins);
88274d0cf81S猫头猫
883d5bfeb7eS猫头猫//#region 本地音乐插件
88474d0cf81S猫头猫/** 本地插件 */
88574d0cf81S猫头猫const localFilePlugin = new Plugin(function () {
8860e4173cdS猫头猫    return {
887d5bfeb7eS猫头猫        platform: localPluginPlatform,
88874d0cf81S猫头猫        _path: '',
88974d0cf81S猫头猫        async getMusicInfo(musicBase) {
89074d0cf81S猫头猫            const localPath = getInternalData<string>(
89174d0cf81S猫头猫                musicBase,
89274d0cf81S猫头猫                InternalDataType.LOCALPATH,
8930e4173cdS猫头猫            );
89474d0cf81S猫头猫            if (localPath) {
89574d0cf81S猫头猫                const coverImg = await Mp3Util.getMediaCoverImg(localPath);
89674d0cf81S猫头猫                return {
89774d0cf81S猫头猫                    artwork: coverImg,
89874d0cf81S猫头猫                };
89974d0cf81S猫头猫            }
90074d0cf81S猫头猫            return null;
90174d0cf81S猫头猫        },
9027993f90eS猫头猫        async getLyric(musicBase) {
9037993f90eS猫头猫            const localPath = getInternalData<string>(
9047993f90eS猫头猫                musicBase,
9057993f90eS猫头猫                InternalDataType.LOCALPATH,
9067993f90eS猫头猫            );
9073a6f67b1S猫头猫            let rawLrc: string | null = null;
9087993f90eS猫头猫            if (localPath) {
9093a6f67b1S猫头猫                // 读取内嵌歌词
9103a6f67b1S猫头猫                try {
9113a6f67b1S猫头猫                    rawLrc = await Mp3Util.getLyric(localPath);
9123a6f67b1S猫头猫                } catch (e) {
913a84a85c5S猫头猫                    console.log('读取内嵌歌词失败', e);
9147993f90eS猫头猫                }
9153a6f67b1S猫头猫                if (!rawLrc) {
9163a6f67b1S猫头猫                    // 读取配置歌词
9173a6f67b1S猫头猫                    const lastDot = localPath.lastIndexOf('.');
9183a6f67b1S猫头猫                    const lrcPath = localPath.slice(0, lastDot) + '.lrc';
9193a6f67b1S猫头猫
9203a6f67b1S猫头猫                    try {
9213a6f67b1S猫头猫                        if (await exists(lrcPath)) {
9223a6f67b1S猫头猫                            rawLrc = await readFile(lrcPath, 'utf8');
9233a6f67b1S猫头猫                        }
9243a6f67b1S猫头猫                    } catch {}
9253a6f67b1S猫头猫                }
9263a6f67b1S猫头猫            }
9273a6f67b1S猫头猫
9283a6f67b1S猫头猫            return rawLrc
9293a6f67b1S猫头猫                ? {
9303a6f67b1S猫头猫                      rawLrc,
9313a6f67b1S猫头猫                  }
9323a6f67b1S猫头猫                : null;
9337993f90eS猫头猫        },
934a84a85c5S猫头猫        async importMusicItem(urlLike) {
935a84a85c5S猫头猫            let meta: any = {};
936a84a85c5S猫头猫            try {
937a84a85c5S猫头猫                meta = await Mp3Util.getBasicMeta(urlLike);
938a84a85c5S猫头猫            } catch {}
939a84a85c5S猫头猫            const id = await FileSystem.hash(urlLike, 'MD5');
940a84a85c5S猫头猫            return {
941a84a85c5S猫头猫                id: id,
942a84a85c5S猫头猫                platform: '本地',
943a84a85c5S猫头猫                title: meta?.title ?? getFileName(urlLike),
944a84a85c5S猫头猫                artist: meta?.artist ?? '未知歌手',
945a84a85c5S猫头猫                duration: parseInt(meta?.duration ?? '0') / 1000,
946a84a85c5S猫头猫                album: meta?.album ?? '未知专辑',
947a84a85c5S猫头猫                artwork: '',
948a84a85c5S猫头猫                [internalSerializeKey]: {
949a84a85c5S猫头猫                    localPath: urlLike,
950a84a85c5S猫头猫                },
951a84a85c5S猫头猫            };
952a84a85c5S猫头猫        },
95311908939S猫头猫        async getMediaSource(musicItem, quality) {
95411908939S猫头猫            if (quality === 'standard') {
95511908939S猫头猫                return {
95611908939S猫头猫                    url: addFileScheme(musicItem.$?.localPath || musicItem.url),
95711908939S猫头猫                };
95811908939S猫头猫            }
95911908939S猫头猫            return null;
96011908939S猫头猫        },
96174d0cf81S猫头猫    };
96274d0cf81S猫头猫}, '');
9637993f90eS猫头猫localFilePlugin.hash = localPluginHash;
964927dbe93S猫头猫
965d5bfeb7eS猫头猫//#endregion
966d5bfeb7eS猫头猫
967927dbe93S猫头猫async function setup() {
968927dbe93S猫头猫    const _plugins: Array<Plugin> = [];
969927dbe93S猫头猫    try {
970927dbe93S猫头猫        // 加载插件
971927dbe93S猫头猫        const pluginsPaths = await readDir(pathConst.pluginPath);
972927dbe93S猫头猫        for (let i = 0; i < pluginsPaths.length; ++i) {
973927dbe93S猫头猫            const _pluginUrl = pluginsPaths[i];
9741e263108S猫头猫            trace('初始化插件', _pluginUrl);
9751e263108S猫头猫            if (
9761e263108S猫头猫                _pluginUrl.isFile() &&
9771e263108S猫头猫                (_pluginUrl.name?.endsWith?.('.js') ||
9781e263108S猫头猫                    _pluginUrl.path?.endsWith?.('.js'))
9791e263108S猫头猫            ) {
980927dbe93S猫头猫                const funcCode = await readFile(_pluginUrl.path, 'utf8');
981927dbe93S猫头猫                const plugin = new Plugin(funcCode, _pluginUrl.path);
9824060c00aS猫头猫                const _pluginIndex = _plugins.findIndex(
9834060c00aS猫头猫                    p => p.hash === plugin.hash,
9844060c00aS猫头猫                );
985927dbe93S猫头猫                if (_pluginIndex !== -1) {
986927dbe93S猫头猫                    // 重复插件,直接忽略
9870c266394S猫头猫                    continue;
988927dbe93S猫头猫                }
989927dbe93S猫头猫                plugin.hash !== '' && _plugins.push(plugin);
990927dbe93S猫头猫            }
991927dbe93S猫头猫        }
992927dbe93S猫头猫
993927dbe93S猫头猫        plugins = _plugins;
994e08d37a3S猫头猫        /** 初始化meta信息 */
995c2b3a262S猫头猫        await PluginMeta.setupMeta(plugins.map(_ => _.name));
996c2b3a262S猫头猫        /** 查看一下是否有禁用的标记 */
997c2b3a262S猫头猫        const allMeta = PluginMeta.getPluginMetaAll() ?? {};
998c2b3a262S猫头猫        for (let plugin of plugins) {
999c2b3a262S猫头猫            if (allMeta[plugin.name]?.enabled === false) {
1000c2b3a262S猫头猫                plugin.state = 'disabled';
1001c2b3a262S猫头猫            }
1002c2b3a262S猫头猫        }
1003c2b3a262S猫头猫        pluginStateMapper.notify();
1004927dbe93S猫头猫    } catch (e: any) {
10054060c00aS猫头猫        ToastAndroid.show(
10064060c00aS猫头猫            `插件初始化失败:${e?.message ?? e}`,
10074060c00aS猫头猫            ToastAndroid.LONG,
10084060c00aS猫头猫        );
10091a5528a0S猫头猫        errorLog('插件初始化失败', e?.message);
1010927dbe93S猫头猫        throw e;
1011927dbe93S猫头猫    }
1012927dbe93S猫头猫}
1013927dbe93S猫头猫
1014e36e2599S猫头猫interface IInstallPluginConfig {
1015e36e2599S猫头猫    notCheckVersion?: boolean;
1016e36e2599S猫头猫}
1017e36e2599S猫头猫
1018927dbe93S猫头猫// 安装插件
1019e36e2599S猫头猫async function installPlugin(
1020e36e2599S猫头猫    pluginPath: string,
1021e36e2599S猫头猫    config?: IInstallPluginConfig,
1022e36e2599S猫头猫) {
102322c09412S猫头猫    // if (pluginPath.endsWith('.js')) {
1024927dbe93S猫头猫    const funcCode = await readFile(pluginPath, 'utf8');
1025e36e2599S猫头猫
1026e36e2599S猫头猫    if (funcCode) {
1027927dbe93S猫头猫        const plugin = new Plugin(funcCode, pluginPath);
1028927dbe93S猫头猫        const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
1029927dbe93S猫头猫        if (_pluginIndex !== -1) {
1030e36e2599S猫头猫            // 静默忽略
1031e36e2599S猫头猫            return plugin;
1032927dbe93S猫头猫        }
1033e36e2599S猫头猫        const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
1034e36e2599S猫头猫        if (oldVersionPlugin && !config?.notCheckVersion) {
1035e36e2599S猫头猫            if (
1036e36e2599S猫头猫                compare(
1037e36e2599S猫头猫                    oldVersionPlugin.instance.version ?? '',
1038e36e2599S猫头猫                    plugin.instance.version ?? '',
1039e36e2599S猫头猫                    '>',
1040e36e2599S猫头猫                )
1041e36e2599S猫头猫            ) {
1042e36e2599S猫头猫                throw new Error('已安装更新版本的插件');
1043e36e2599S猫头猫            }
1044e36e2599S猫头猫        }
1045e36e2599S猫头猫
1046927dbe93S猫头猫        if (plugin.hash !== '') {
1047927dbe93S猫头猫            const fn = nanoid();
1048e36e2599S猫头猫            if (oldVersionPlugin) {
1049e36e2599S猫头猫                plugins = plugins.filter(_ => _.hash !== oldVersionPlugin.hash);
1050e36e2599S猫头猫                try {
1051e36e2599S猫头猫                    await unlink(oldVersionPlugin.path);
1052e36e2599S猫头猫                } catch {}
1053e36e2599S猫头猫            }
1054927dbe93S猫头猫            const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
1055927dbe93S猫头猫            await copyFile(pluginPath, _pluginPath);
1056927dbe93S猫头猫            plugin.path = _pluginPath;
1057927dbe93S猫头猫            plugins = plugins.concat(plugin);
1058927dbe93S猫头猫            pluginStateMapper.notify();
1059a84a85c5S猫头猫            return plugin;
1060927dbe93S猫头猫        }
1061e36e2599S猫头猫        throw new Error('插件无法解析!');
1062927dbe93S猫头猫    }
1063e36e2599S猫头猫    throw new Error('插件无法识别!');
1064c2b3a262S猫头猫}
1065c2b3a262S猫头猫
1066ee781061S猫头猫const reqHeaders = {
1067ee781061S猫头猫    'Cache-Control': 'no-cache',
1068ee781061S猫头猫    Pragma: 'no-cache',
1069ee781061S猫头猫    Expires: '0',
1070ee781061S猫头猫};
1071ee781061S猫头猫
1072c2b3a262S猫头猫async function installPluginFromUrl(
1073c2b3a262S猫头猫    url: string,
1074c2b3a262S猫头猫    config?: IInstallPluginConfig,
1075c2b3a262S猫头猫) {
107658992c6bS猫头猫    try {
1077ee781061S猫头猫        const funcCode = (
1078ee781061S猫头猫            await axios.get(url, {
1079ee781061S猫头猫                headers: reqHeaders,
1080ee781061S猫头猫            })
1081ee781061S猫头猫        ).data;
108258992c6bS猫头猫        if (funcCode) {
108358992c6bS猫头猫            const plugin = new Plugin(funcCode, '');
108458992c6bS猫头猫            const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
108558992c6bS猫头猫            if (_pluginIndex !== -1) {
10868b7ddca8S猫头猫                // 静默忽略
10878b7ddca8S猫头猫                return;
108858992c6bS猫头猫            }
108925c1bd29S猫头猫            const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
1090c2b3a262S猫头猫            if (oldVersionPlugin && !config?.notCheckVersion) {
109125c1bd29S猫头猫                if (
109225c1bd29S猫头猫                    compare(
109325c1bd29S猫头猫                        oldVersionPlugin.instance.version ?? '',
109425c1bd29S猫头猫                        plugin.instance.version ?? '',
109525c1bd29S猫头猫                        '>',
109625c1bd29S猫头猫                    )
109725c1bd29S猫头猫                ) {
109825c1bd29S猫头猫                    throw new Error('已安装更新版本的插件');
109925c1bd29S猫头猫                }
110025c1bd29S猫头猫            }
110125c1bd29S猫头猫
110258992c6bS猫头猫            if (plugin.hash !== '') {
110358992c6bS猫头猫                const fn = nanoid();
110458992c6bS猫头猫                const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
110558992c6bS猫头猫                await writeFile(_pluginPath, funcCode, 'utf8');
110658992c6bS猫头猫                plugin.path = _pluginPath;
110758992c6bS猫头猫                plugins = plugins.concat(plugin);
110825c1bd29S猫头猫                if (oldVersionPlugin) {
110925c1bd29S猫头猫                    plugins = plugins.filter(
111025c1bd29S猫头猫                        _ => _.hash !== oldVersionPlugin.hash,
111125c1bd29S猫头猫                    );
111225c1bd29S猫头猫                    try {
111325c1bd29S猫头猫                        await unlink(oldVersionPlugin.path);
111425c1bd29S猫头猫                    } catch {}
111525c1bd29S猫头猫                }
111658992c6bS猫头猫                pluginStateMapper.notify();
111758992c6bS猫头猫                return;
111858992c6bS猫头猫            }
111974acbfc0S猫头猫            throw new Error('插件无法解析!');
112058992c6bS猫头猫        }
112125c1bd29S猫头猫    } catch (e: any) {
1122ea6d708fS猫头猫        devLog('error', 'URL安装插件失败', e, e?.message);
112358992c6bS猫头猫        errorLog('URL安装插件失败', e);
112425c1bd29S猫头猫        throw new Error(e?.message ?? '');
112558992c6bS猫头猫    }
112658992c6bS猫头猫}
112758992c6bS猫头猫
1128927dbe93S猫头猫/** 卸载插件 */
1129927dbe93S猫头猫async function uninstallPlugin(hash: string) {
1130927dbe93S猫头猫    const targetIndex = plugins.findIndex(_ => _.hash === hash);
1131927dbe93S猫头猫    if (targetIndex !== -1) {
1132927dbe93S猫头猫        try {
113324e5e74aS猫头猫            const pluginName = plugins[targetIndex].name;
1134927dbe93S猫头猫            await unlink(plugins[targetIndex].path);
1135927dbe93S猫头猫            plugins = plugins.filter(_ => _.hash !== hash);
1136927dbe93S猫头猫            pluginStateMapper.notify();
113743eb30bfS猫头猫            // 防止其他重名
113824e5e74aS猫头猫            if (plugins.every(_ => _.name !== pluginName)) {
113943eb30bfS猫头猫                MediaMeta.removeAll(pluginName);
114024e5e74aS猫头猫            }
1141927dbe93S猫头猫        } catch {}
1142927dbe93S猫头猫    }
1143927dbe93S猫头猫}
1144927dbe93S猫头猫
114508882a77S猫头猫async function uninstallAllPlugins() {
114608882a77S猫头猫    await Promise.all(
114708882a77S猫头猫        plugins.map(async plugin => {
114808882a77S猫头猫            try {
114908882a77S猫头猫                const pluginName = plugin.name;
115008882a77S猫头猫                await unlink(plugin.path);
115143eb30bfS猫头猫                MediaMeta.removeAll(pluginName);
115208882a77S猫头猫            } catch (e) {}
115308882a77S猫头猫        }),
115408882a77S猫头猫    );
115508882a77S猫头猫    plugins = [];
115608882a77S猫头猫    pluginStateMapper.notify();
1157e08d37a3S猫头猫
1158e08d37a3S猫头猫    /** 清除空余文件,异步做就可以了 */
1159e08d37a3S猫头猫    readDir(pathConst.pluginPath)
1160e08d37a3S猫头猫        .then(fns => {
1161e08d37a3S猫头猫            fns.forEach(fn => {
1162e08d37a3S猫头猫                unlink(fn.path).catch(emptyFunction);
1163e08d37a3S猫头猫            });
1164e08d37a3S猫头猫        })
1165e08d37a3S猫头猫        .catch(emptyFunction);
116608882a77S猫头猫}
116708882a77S猫头猫
116825c1bd29S猫头猫async function updatePlugin(plugin: Plugin) {
116925c1bd29S猫头猫    const updateUrl = plugin.instance.srcUrl;
117025c1bd29S猫头猫    if (!updateUrl) {
117125c1bd29S猫头猫        throw new Error('没有更新源');
117225c1bd29S猫头猫    }
117325c1bd29S猫头猫    try {
117425c1bd29S猫头猫        await installPluginFromUrl(updateUrl);
117525c1bd29S猫头猫    } catch (e: any) {
117625c1bd29S猫头猫        if (e.message === '插件已安装') {
117725c1bd29S猫头猫            throw new Error('当前已是最新版本');
117825c1bd29S猫头猫        } else {
117925c1bd29S猫头猫            throw e;
118025c1bd29S猫头猫        }
118125c1bd29S猫头猫    }
118225c1bd29S猫头猫}
118325c1bd29S猫头猫
1184927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) {
11852c595535S猫头猫    return getByName(mediaItem?.platform);
1186927dbe93S猫头猫}
1187927dbe93S猫头猫
1188927dbe93S猫头猫function getByHash(hash: string) {
11897993f90eS猫头猫    return hash === localPluginHash
11907993f90eS猫头猫        ? localFilePlugin
11917993f90eS猫头猫        : plugins.find(_ => _.hash === hash);
1192927dbe93S猫头猫}
1193927dbe93S猫头猫
1194927dbe93S猫头猫function getByName(name: string) {
11957993f90eS猫头猫    return name === localPluginPlatform
11960e4173cdS猫头猫        ? localFilePlugin
11970e4173cdS猫头猫        : plugins.find(_ => _.name === name);
1198927dbe93S猫头猫}
1199927dbe93S猫头猫
1200927dbe93S猫头猫function getValidPlugins() {
1201927dbe93S猫头猫    return plugins.filter(_ => _.state === 'enabled');
1202927dbe93S猫头猫}
1203927dbe93S猫头猫
12042b80a429S猫头猫function getSearchablePlugins(supportedSearchType?: ICommon.SupportMediaType) {
12052b80a429S猫头猫    return plugins.filter(
12062b80a429S猫头猫        _ =>
12072b80a429S猫头猫            _.state === 'enabled' &&
12082b80a429S猫头猫            _.instance.search &&
120939ac60f7S猫头猫            (supportedSearchType && _.instance.supportedSearchType
121039ac60f7S猫头猫                ? _.instance.supportedSearchType.includes(supportedSearchType)
12112b80a429S猫头猫                : true),
12122b80a429S猫头猫    );
1213efb9da24S猫头猫}
1214efb9da24S猫头猫
12152b80a429S猫头猫function getSortedSearchablePlugins(
12162b80a429S猫头猫    supportedSearchType?: ICommon.SupportMediaType,
12172b80a429S猫头猫) {
12182b80a429S猫头猫    return getSearchablePlugins(supportedSearchType).sort((a, b) =>
1219e08d37a3S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1220e08d37a3S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1221e08d37a3S猫头猫        0
1222e08d37a3S猫头猫            ? -1
1223e08d37a3S猫头猫            : 1,
1224e08d37a3S猫头猫    );
1225e08d37a3S猫头猫}
1226e08d37a3S猫头猫
122715feccc1S猫头猫function getTopListsablePlugins() {
122815feccc1S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists);
122915feccc1S猫头猫}
123015feccc1S猫头猫
123115feccc1S猫头猫function getSortedTopListsablePlugins() {
123215feccc1S猫头猫    return getTopListsablePlugins().sort((a, b) =>
123315feccc1S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
123415feccc1S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
123515feccc1S猫头猫        0
123615feccc1S猫头猫            ? -1
123715feccc1S猫头猫            : 1,
123815feccc1S猫头猫    );
123915feccc1S猫头猫}
124015feccc1S猫头猫
1241ceb900cdS猫头猫function getRecommendSheetablePlugins() {
1242ceb900cdS猫头猫    return plugins.filter(
1243ceb900cdS猫头猫        _ => _.state === 'enabled' && _.instance.getRecommendSheetsByTag,
1244ceb900cdS猫头猫    );
1245ceb900cdS猫头猫}
1246ceb900cdS猫头猫
1247ceb900cdS猫头猫function getSortedRecommendSheetablePlugins() {
1248ceb900cdS猫头猫    return getRecommendSheetablePlugins().sort((a, b) =>
1249ceb900cdS猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1250ceb900cdS猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1251ceb900cdS猫头猫        0
1252ceb900cdS猫头猫            ? -1
1253ceb900cdS猫头猫            : 1,
1254ceb900cdS猫头猫    );
1255ceb900cdS猫头猫}
1256ceb900cdS猫头猫
1257e08d37a3S猫头猫function useSortedPlugins() {
1258e08d37a3S猫头猫    const _plugins = pluginStateMapper.useMappedState();
1259e08d37a3S猫头猫    const _pluginMetaAll = PluginMeta.usePluginMetaAll();
1260e08d37a3S猫头猫
126134588741S猫头猫    const [sortedPlugins, setSortedPlugins] = useState(
126234588741S猫头猫        [..._plugins].sort((a, b) =>
1263e08d37a3S猫头猫            (_pluginMetaAll[a.name]?.order ?? Infinity) -
1264e08d37a3S猫头猫                (_pluginMetaAll[b.name]?.order ?? Infinity) <
1265e08d37a3S猫头猫            0
1266e08d37a3S猫头猫                ? -1
1267e08d37a3S猫头猫                : 1,
126834588741S猫头猫        ),
1269e08d37a3S猫头猫    );
127034588741S猫头猫
127134588741S猫头猫    useEffect(() => {
1272d4cd40d8S猫头猫        InteractionManager.runAfterInteractions(() => {
127334588741S猫头猫            setSortedPlugins(
127434588741S猫头猫                [..._plugins].sort((a, b) =>
127534588741S猫头猫                    (_pluginMetaAll[a.name]?.order ?? Infinity) -
127634588741S猫头猫                        (_pluginMetaAll[b.name]?.order ?? Infinity) <
127734588741S猫头猫                    0
127834588741S猫头猫                        ? -1
127934588741S猫头猫                        : 1,
128034588741S猫头猫                ),
128134588741S猫头猫            );
1282d4cd40d8S猫头猫        });
128334588741S猫头猫    }, [_plugins, _pluginMetaAll]);
128434588741S猫头猫
128534588741S猫头猫    return sortedPlugins;
1286e08d37a3S猫头猫}
1287e08d37a3S猫头猫
1288c2b3a262S猫头猫async function setPluginEnabled(plugin: Plugin, enabled?: boolean) {
1289c2b3a262S猫头猫    const target = plugins.find(it => it.hash === plugin.hash);
1290c2b3a262S猫头猫    if (target) {
1291c2b3a262S猫头猫        target.state = enabled ? 'enabled' : 'disabled';
1292c2b3a262S猫头猫        plugins = [...plugins];
1293c2b3a262S猫头猫        pluginStateMapper.notify();
1294c2b3a262S猫头猫        PluginMeta.setPluginMetaProp(plugin, 'enabled', enabled);
1295c2b3a262S猫头猫    }
1296c2b3a262S猫头猫}
1297c2b3a262S猫头猫
1298927dbe93S猫头猫const PluginManager = {
1299927dbe93S猫头猫    setup,
1300927dbe93S猫头猫    installPlugin,
130158992c6bS猫头猫    installPluginFromUrl,
130225c1bd29S猫头猫    updatePlugin,
1303927dbe93S猫头猫    uninstallPlugin,
1304927dbe93S猫头猫    getByMedia,
1305927dbe93S猫头猫    getByHash,
1306927dbe93S猫头猫    getByName,
1307927dbe93S猫头猫    getValidPlugins,
1308efb9da24S猫头猫    getSearchablePlugins,
1309e08d37a3S猫头猫    getSortedSearchablePlugins,
131015feccc1S猫头猫    getTopListsablePlugins,
1311ceb900cdS猫头猫    getSortedRecommendSheetablePlugins,
131215feccc1S猫头猫    getSortedTopListsablePlugins,
13135276aef9S猫头猫    usePlugins: pluginStateMapper.useMappedState,
1314e08d37a3S猫头猫    useSortedPlugins,
131508882a77S猫头猫    uninstallAllPlugins,
1316c2b3a262S猫头猫    setPluginEnabled,
13175276aef9S猫头猫};
1318927dbe93S猫头猫
1319927dbe93S猫头猫export default PluginManager;
1320