xref: /MusicFree/src/core/pluginManager.ts (revision 34bfbe58c7be2b25145bbf29046f8a418be73f02)
150245d53Smaotoumaoimport RNFS, { copyFile, exists, readDir, readFile, stat, unlink, writeFile } from "react-native-fs";
250245d53Smaotoumaoimport CryptoJs from "crypto-js";
350245d53Smaotoumaoimport dayjs from "dayjs";
450245d53Smaotoumaoimport axios from "axios";
550245d53Smaotoumaoimport bigInt from "big-integer";
650245d53Smaotoumaoimport qs from "qs";
750245d53Smaotoumaoimport * as webdav from "webdav";
850245d53Smaotoumaoimport { InteractionManager, ToastAndroid } from "react-native";
950245d53Smaotoumaoimport pathConst from "@/constants/pathConst";
1050245d53Smaotoumaoimport { compare, satisfies } from "compare-versions";
1150245d53Smaotoumaoimport DeviceInfo from "react-native-device-info";
1250245d53Smaotoumaoimport deviceInfoModule from "react-native-device-info";
1350245d53Smaotoumaoimport StateMapper from "@/utils/stateMapper";
1450245d53Smaotoumaoimport MediaExtra from "./mediaExtra";
1550245d53Smaotoumaoimport { nanoid } from "nanoid";
1650245d53Smaotoumaoimport { devLog, errorLog, trace } from "../utils/log";
1750245d53Smaotoumaoimport { getInternalData, InternalDataType, isSameMediaItem, resetMediaItem } from "@/utils/mediaItem";
187993f90eS猫头猫import {
197993f90eS猫头猫    CacheControl,
20e08d37a3S猫头猫    emptyFunction,
217993f90eS猫头猫    internalSerializeKey,
227993f90eS猫头猫    localPluginHash,
2350245d53Smaotoumao    localPluginPlatform
2450245d53Smaotoumao} from "@/constants/commonConst";
2550245d53Smaotoumaoimport delay from "@/utils/delay";
2650245d53Smaotoumaoimport * as cheerio from "cheerio";
2750245d53Smaotoumaoimport he from "he";
2850245d53Smaotoumaoimport Network from "./network";
2950245d53Smaotoumaoimport LocalMusicSheet from "./localMusicSheet";
3050245d53Smaotoumaoimport Mp3Util from "@/native/mp3Util";
3150245d53Smaotoumaoimport { PluginMeta } from "./pluginMeta";
3250245d53Smaotoumaoimport { useEffect, useState } from "react";
3350245d53Smaotoumaoimport { addFileScheme, getFileName } from "@/utils/fileUtils";
3450245d53Smaotoumaoimport { URL } from "react-native-url-polyfill";
3550245d53Smaotoumaoimport Base64 from "@/utils/base64";
3650245d53Smaotoumaoimport MediaCache from "./mediaCache";
3750245d53Smaotoumaoimport { produce } from "immer";
3850245d53Smaotoumaoimport objectPath from "object-path";
3950245d53Smaotoumaoimport notImplementedFunction from "@/utils/notImplementedFunction.ts";
4050245d53Smaotoumaoimport { readAsStringAsync } from "expo-file-system";
41927dbe93S猫头猫
4261aca335S猫头猫axios.defaults.timeout = 2000;
43927dbe93S猫头猫
44927dbe93S猫头猫const sha256 = CryptoJs.SHA256;
45927dbe93S猫头猫
46cfa0fc07S猫头猫export enum PluginStateCode {
47927dbe93S猫头猫    /** 版本不匹配 */
48927dbe93S猫头猫    VersionNotMatch = 'VERSION NOT MATCH',
49927dbe93S猫头猫    /** 无法解析 */
50927dbe93S猫头猫    CannotParse = 'CANNOT PARSE',
51927dbe93S猫头猫}
52927dbe93S猫头猫
5333d52955Smaotoumaoconst deprecatedCookieManager = {
5433d52955Smaotoumao    get: notImplementedFunction,
5533d52955Smaotoumao    set: notImplementedFunction,
5633d52955Smaotoumao    flush: notImplementedFunction,
5733d52955Smaotoumao};
5833d52955Smaotoumao
599c34d637S猫头猫const packages: Record<string, any> = {
609c34d637S猫头猫    cheerio,
619c34d637S猫头猫    'crypto-js': CryptoJs,
629c34d637S猫头猫    axios,
639c34d637S猫头猫    dayjs,
649c34d637S猫头猫    'big-integer': bigInt,
659c34d637S猫头猫    qs,
669c34d637S猫头猫    he,
6733d52955Smaotoumao    '@react-native-cookies/cookies': deprecatedCookieManager,
6828ccceb7S猫头猫    webdav,
699c34d637S猫头猫};
709c34d637S猫头猫
71b43683eaS猫头猫const _require = (packageName: string) => {
72b43683eaS猫头猫    let pkg = packages[packageName];
73b43683eaS猫头猫    pkg.default = pkg;
74b43683eaS猫头猫    return pkg;
75b43683eaS猫头猫};
769c34d637S猫头猫
7753f8cd8eS猫头猫const _consoleBind = function (
7853f8cd8eS猫头猫    method: 'log' | 'error' | 'info' | 'warn',
7953f8cd8eS猫头猫    ...args: any
8053f8cd8eS猫头猫) {
8153f8cd8eS猫头猫    const fn = console[method];
8253f8cd8eS猫头猫    if (fn) {
8353f8cd8eS猫头猫        fn(...args);
8453f8cd8eS猫头猫        devLog(method, ...args);
8553f8cd8eS猫头猫    }
8653f8cd8eS猫头猫};
8753f8cd8eS猫头猫
8853f8cd8eS猫头猫const _console = {
8953f8cd8eS猫头猫    log: _consoleBind.bind(null, 'log'),
9053f8cd8eS猫头猫    warn: _consoleBind.bind(null, 'warn'),
9153f8cd8eS猫头猫    info: _consoleBind.bind(null, 'info'),
9253f8cd8eS猫头猫    error: _consoleBind.bind(null, 'error'),
9353f8cd8eS猫头猫};
9453f8cd8eS猫头猫
95ab012f7fSmaotoumaoconst appVersion = deviceInfoModule.getVersion();
96ab012f7fSmaotoumao
97a7b42a4cS猫头猫function formatAuthUrl(url: string) {
98a7b42a4cS猫头猫    const urlObj = new URL(url);
99a7b42a4cS猫头猫
100a7b42a4cS猫头猫    try {
101a7b42a4cS猫头猫        if (urlObj.username && urlObj.password) {
102a7b42a4cS猫头猫            const auth = `Basic ${Base64.btoa(
103a7b42a4cS猫头猫                `${decodeURIComponent(urlObj.username)}:${decodeURIComponent(
104a7b42a4cS猫头猫                    urlObj.password,
105a7b42a4cS猫头猫                )}`,
106a7b42a4cS猫头猫            )}`;
107a7b42a4cS猫头猫            urlObj.username = '';
108a7b42a4cS猫头猫            urlObj.password = '';
109a7b42a4cS猫头猫
110a7b42a4cS猫头猫            return {
111a7b42a4cS猫头猫                url: urlObj.toString(),
112a7b42a4cS猫头猫                auth,
113a7b42a4cS猫头猫            };
114a7b42a4cS猫头猫        }
115a7b42a4cS猫头猫    } catch (e) {
116a7b42a4cS猫头猫        return {
117a7b42a4cS猫头猫            url,
118a7b42a4cS猫头猫        };
119a7b42a4cS猫头猫    }
120a7b42a4cS猫头猫    return {
121a7b42a4cS猫头猫        url,
122a7b42a4cS猫头猫    };
123a7b42a4cS猫头猫}
124a7b42a4cS猫头猫
125d5bfeb7eS猫头猫//#region 插件类
126927dbe93S猫头猫export class Plugin {
127927dbe93S猫头猫    /** 插件名 */
128927dbe93S猫头猫    public name: string;
129927dbe93S猫头猫    /** 插件的hash,作为唯一id */
130927dbe93S猫头猫    public hash: string;
131927dbe93S猫头猫    /** 插件状态:激活、关闭、错误 */
132927dbe93S猫头猫    public state: 'enabled' | 'disabled' | 'error';
133927dbe93S猫头猫    /** 插件状态信息 */
134927dbe93S猫头猫    public stateCode?: PluginStateCode;
135927dbe93S猫头猫    /** 插件的实例 */
136927dbe93S猫头猫    public instance: IPlugin.IPluginInstance;
137927dbe93S猫头猫    /** 插件路径 */
138927dbe93S猫头猫    public path: string;
139927dbe93S猫头猫    /** 插件方法 */
140927dbe93S猫头猫    public methods: PluginMethods;
141927dbe93S猫头猫
14274d0cf81S猫头猫    constructor(
14374d0cf81S猫头猫        funcCode: string | (() => IPlugin.IPluginInstance),
14474d0cf81S猫头猫        pluginPath: string,
14574d0cf81S猫头猫    ) {
146927dbe93S猫头猫        this.state = 'enabled';
147927dbe93S猫头猫        let _instance: IPlugin.IPluginInstance;
1483b3d6357S猫头猫        const _module: any = {exports: {}};
149927dbe93S猫头猫        try {
15074d0cf81S猫头猫            if (typeof funcCode === 'string') {
151e0caf342S猫头猫                // 插件的环境变量
152e0caf342S猫头猫                const env = {
153e0caf342S猫头猫                    getUserVariables: () => {
154e0caf342S猫头猫                        return (
155e0caf342S猫头猫                            PluginMeta.getPluginMeta(this)?.userVariables ?? {}
156e0caf342S猫头猫                        );
157e0caf342S猫头猫                    },
158ab012f7fSmaotoumao                    appVersion,
159e3fa9b3cS猫头猫                    os: 'android',
160ab012f7fSmaotoumao                    lang: 'zh-CN'
161e0caf342S猫头猫                };
162ab012f7fSmaotoumao                const _process = {
163ab012f7fSmaotoumao                    platform: 'android',
164ab012f7fSmaotoumao                    version: appVersion,
165ab012f7fSmaotoumao                    env,
166ab012f7fSmaotoumao                }
167e0caf342S猫头猫
1684060c00aS猫头猫                // eslint-disable-next-line no-new-func
169927dbe93S猫头猫                _instance = Function(`
170927dbe93S猫头猫                    'use strict';
171ab012f7fSmaotoumao                    return function(require, __musicfree_require, module, exports, console, env, URL, process) {
1729c34d637S猫头猫                        ${funcCode}
173927dbe93S猫头猫                    }
174e0caf342S猫头猫                `)()(
175e0caf342S猫头猫                    _require,
176e0caf342S猫头猫                    _require,
177e0caf342S猫头猫                    _module,
178e0caf342S猫头猫                    _module.exports,
179e0caf342S猫头猫                    _console,
180e0caf342S猫头猫                    env,
18191eb8fa8S猫头猫                    URL,
182ab012f7fSmaotoumao                    _process
183e0caf342S猫头猫                );
1843b3d6357S猫头猫                if (_module.exports.default) {
1853b3d6357S猫头猫                    _instance = _module.exports
1863b3d6357S猫头猫                        .default as IPlugin.IPluginInstance;
1873b3d6357S猫头猫                } else {
1889c34d637S猫头猫                    _instance = _module.exports as IPlugin.IPluginInstance;
1893b3d6357S猫头猫                }
19074d0cf81S猫头猫            } else {
19174d0cf81S猫头猫                _instance = funcCode();
19274d0cf81S猫头猫            }
193c2b3a262S猫头猫            // 插件初始化后的一些操作
19495297592S猫头猫            if (Array.isArray(_instance.userVariables)) {
19595297592S猫头猫                _instance.userVariables = _instance.userVariables.filter(
19695297592S猫头猫                    it => it?.key,
19795297592S猫头猫                );
19895297592S猫头猫            }
199927dbe93S猫头猫            this.checkValid(_instance);
200927dbe93S猫头猫        } catch (e: any) {
201b43683eaS猫头猫            console.log(e);
202927dbe93S猫头猫            this.state = 'error';
203927dbe93S猫头猫            this.stateCode = PluginStateCode.CannotParse;
204927dbe93S猫头猫            if (e?.stateCode) {
205927dbe93S猫头猫                this.stateCode = e.stateCode;
206927dbe93S猫头猫            }
207927dbe93S猫头猫            errorLog(`${pluginPath}插件无法解析 `, {
208927dbe93S猫头猫                stateCode: this.stateCode,
209927dbe93S猫头猫                message: e?.message,
210927dbe93S猫头猫                stack: e?.stack,
211927dbe93S猫头猫            });
212927dbe93S猫头猫            _instance = e?.instance ?? {
213927dbe93S猫头猫                _path: '',
214927dbe93S猫头猫                platform: '',
215927dbe93S猫头猫                appVersion: '',
21620e6a092S猫头猫                async getMediaSource() {
217927dbe93S猫头猫                    return null;
218927dbe93S猫头猫                },
219927dbe93S猫头猫                async search() {
220927dbe93S猫头猫                    return {};
221927dbe93S猫头猫                },
222927dbe93S猫头猫                async getAlbumInfo() {
223927dbe93S猫头猫                    return null;
224927dbe93S猫头猫                },
225927dbe93S猫头猫            };
226927dbe93S猫头猫        }
227927dbe93S猫头猫        this.instance = _instance;
228927dbe93S猫头猫        this.path = pluginPath;
229927dbe93S猫头猫        this.name = _instance.platform;
230ab8941d9S猫头猫        if (
231ab8941d9S猫头猫            this.instance.platform === '' ||
232ab8941d9S猫头猫            this.instance.platform === undefined
233ab8941d9S猫头猫        ) {
234927dbe93S猫头猫            this.hash = '';
235927dbe93S猫头猫        } else {
23674d0cf81S猫头猫            if (typeof funcCode === 'string') {
237927dbe93S猫头猫                this.hash = sha256(funcCode).toString();
23874d0cf81S猫头猫            } else {
23974d0cf81S猫头猫                this.hash = sha256(funcCode.toString()).toString();
24074d0cf81S猫头猫            }
241927dbe93S猫头猫        }
242927dbe93S猫头猫
243927dbe93S猫头猫        // 放在最后
244927dbe93S猫头猫        this.methods = new PluginMethods(this);
245927dbe93S猫头猫    }
246927dbe93S猫头猫
247927dbe93S猫头猫    private checkValid(_instance: IPlugin.IPluginInstance) {
248927dbe93S猫头猫        /** 版本号校验 */
249927dbe93S猫头猫        if (
250927dbe93S猫头猫            _instance.appVersion &&
251927dbe93S猫头猫            !satisfies(DeviceInfo.getVersion(), _instance.appVersion)
252927dbe93S猫头猫        ) {
253927dbe93S猫头猫            throw {
254927dbe93S猫头猫                instance: _instance,
255927dbe93S猫头猫                stateCode: PluginStateCode.VersionNotMatch,
256927dbe93S猫头猫            };
257927dbe93S猫头猫        }
258927dbe93S猫头猫        return true;
259927dbe93S猫头猫    }
260927dbe93S猫头猫}
26142a9f3e6Smaotoumao
262d5bfeb7eS猫头猫//#endregion
263927dbe93S猫头猫
264d5bfeb7eS猫头猫//#region 基于插件类封装的方法,供给APP侧直接调用
265927dbe93S猫头猫/** 有缓存等信息 */
266927dbe93S猫头猫class PluginMethods implements IPlugin.IPluginInstanceMethods {
267927dbe93S猫头猫    private plugin;
26842a9f3e6Smaotoumao
269927dbe93S猫头猫    constructor(plugin: Plugin) {
270927dbe93S猫头猫        this.plugin = plugin;
271927dbe93S猫头猫    }
27242a9f3e6Smaotoumao
273927dbe93S猫头猫    /** 搜索 */
274927dbe93S猫头猫    async search<T extends ICommon.SupportMediaType>(
275927dbe93S猫头猫        query: string,
276927dbe93S猫头猫        page: number,
277927dbe93S猫头猫        type: T,
278927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
279927dbe93S猫头猫        if (!this.plugin.instance.search) {
280927dbe93S猫头猫            return {
281927dbe93S猫头猫                isEnd: true,
282927dbe93S猫头猫                data: [],
283927dbe93S猫头猫            };
284927dbe93S猫头猫        }
285927dbe93S猫头猫
2864060c00aS猫头猫        const result =
2874060c00aS猫头猫            (await this.plugin.instance.search(query, page, type)) ?? {};
288927dbe93S猫头猫        if (Array.isArray(result.data)) {
289927dbe93S猫头猫            result.data.forEach(_ => {
290927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
291927dbe93S猫头猫            });
292927dbe93S猫头猫            return {
293927dbe93S猫头猫                isEnd: result.isEnd ?? true,
294927dbe93S猫头猫                data: result.data,
295927dbe93S猫头猫            };
296927dbe93S猫头猫        }
297927dbe93S猫头猫        return {
298927dbe93S猫头猫            isEnd: true,
299927dbe93S猫头猫            data: [],
300927dbe93S猫头猫        };
301927dbe93S猫头猫    }
302927dbe93S猫头猫
303927dbe93S猫头猫    /** 获取真实源 */
30420e6a092S猫头猫    async getMediaSource(
305927dbe93S猫头猫        musicItem: IMusic.IMusicItemBase,
306abaede57S猫头猫        quality: IMusic.IQualityKey = 'standard',
307927dbe93S猫头猫        retryCount = 1,
308dc160d50S猫头猫        notUpdateCache = false,
309192ae2b0S猫头猫    ): Promise<IPlugin.IMediaSourceResult | null> {
310927dbe93S猫头猫        // 1. 本地搜索 其实直接读mediameta就好了
31143eb30bfS猫头猫        const mediaExtra = MediaExtra.get(musicItem);
312927dbe93S猫头猫        const localPath =
31343eb30bfS猫头猫            mediaExtra?.localPath ||
31443eb30bfS猫头猫            getInternalData<string>(musicItem, InternalDataType.LOCALPATH) ||
3150e4173cdS猫头猫            getInternalData<string>(
3160e4173cdS猫头猫                LocalMusicSheet.isLocalMusic(musicItem),
3170e4173cdS猫头猫                InternalDataType.LOCALPATH,
3180e4173cdS猫头猫            );
31942a9f3e6Smaotoumao        if (localPath && (await exists(localPath))) {
3200e4173cdS猫头猫            trace('本地播放', localPath);
32143eb30bfS猫头猫            if (mediaExtra && mediaExtra.localPath !== localPath) {
32243eb30bfS猫头猫                // 修正一下本地数据
32343eb30bfS猫头猫                MediaExtra.update(musicItem, {
32443eb30bfS猫头猫                    localPath,
32543eb30bfS猫头猫                });
32643eb30bfS猫头猫            }
327927dbe93S猫头猫            return {
32811908939S猫头猫                url: addFileScheme(localPath),
329927dbe93S猫头猫            };
33043eb30bfS猫头猫        } else if (mediaExtra?.localPath) {
33143eb30bfS猫头猫            MediaExtra.update(musicItem, {
33243eb30bfS猫头猫                localPath: undefined,
33343eb30bfS猫头猫            });
334927dbe93S猫头猫        }
335a84a85c5S猫头猫
3367993f90eS猫头猫        if (musicItem.platform === localPluginPlatform) {
337f5935920S猫头猫            throw new Error('本地音乐不存在');
338f5935920S猫头猫        }
339927dbe93S猫头猫        // 2. 缓存播放
34043eb30bfS猫头猫        const mediaCache = MediaCache.getMediaCache(
34143eb30bfS猫头猫            musicItem,
34243eb30bfS猫头猫        ) as IMusic.IMusicItem | null;
343985f8e75S猫头猫        const pluginCacheControl =
344985f8e75S猫头猫            this.plugin.instance.cacheControl ?? 'no-cache';
345cfa0fc07S猫头猫        if (
346cfa0fc07S猫头猫            mediaCache &&
34743eb30bfS猫头猫            mediaCache?.source?.[quality]?.url &&
34848f4b873S猫头猫            (pluginCacheControl === CacheControl.Cache ||
34948f4b873S猫头猫                (pluginCacheControl === CacheControl.NoCache &&
350ef714860S猫头猫                    Network.isOffline()))
351cfa0fc07S猫头猫        ) {
3525276aef9S猫头猫            trace('播放', '缓存播放');
35343eb30bfS猫头猫            const qualityInfo = mediaCache.source[quality];
354927dbe93S猫头猫            return {
35543eb30bfS猫头猫                url: qualityInfo!.url,
356927dbe93S猫头猫                headers: mediaCache.headers,
3574060c00aS猫头猫                userAgent:
3584060c00aS猫头猫                    mediaCache.userAgent ?? mediaCache.headers?.['user-agent'],
359927dbe93S猫头猫            };
360927dbe93S猫头猫        }
361927dbe93S猫头猫        // 3. 插件解析
36220e6a092S猫头猫        if (!this.plugin.instance.getMediaSource) {
363a7b42a4cS猫头猫            const {url, auth} = formatAuthUrl(
364a7b42a4cS猫头猫                musicItem?.qualities?.[quality]?.url ?? musicItem.url,
365a7b42a4cS猫头猫            );
366a7b42a4cS猫头猫            return {
367a7b42a4cS猫头猫                url: url,
368a7b42a4cS猫头猫                headers: auth
369a7b42a4cS猫头猫                    ? {
370a7b42a4cS猫头猫                          Authorization: auth,
371a7b42a4cS猫头猫                      }
372a7b42a4cS猫头猫                    : undefined,
373a7b42a4cS猫头猫            };
374927dbe93S猫头猫        }
375927dbe93S猫头猫        try {
376abaede57S猫头猫            const {url, headers} = (await this.plugin.instance.getMediaSource(
377abaede57S猫头猫                musicItem,
378abaede57S猫头猫                quality,
379abaede57S猫头猫            )) ?? {url: musicItem?.qualities?.[quality]?.url};
380927dbe93S猫头猫            if (!url) {
381a28eac61S猫头猫                throw new Error('NOT RETRY');
382927dbe93S猫头猫            }
3835276aef9S猫头猫            trace('播放', '插件播放');
384927dbe93S猫头猫            const result = {
385927dbe93S猫头猫                url,
386927dbe93S猫头猫                headers,
387927dbe93S猫头猫                userAgent: headers?.['user-agent'],
388cfa0fc07S猫头猫            } as IPlugin.IMediaSourceResult;
389a7b42a4cS猫头猫            const authFormattedResult = formatAuthUrl(result.url!);
390a7b42a4cS猫头猫            if (authFormattedResult.auth) {
391a7b42a4cS猫头猫                result.url = authFormattedResult.url;
392a7b42a4cS猫头猫                result.headers = {
393a7b42a4cS猫头猫                    ...(result.headers ?? {}),
394a7b42a4cS猫头猫                    Authorization: authFormattedResult.auth,
395a7b42a4cS猫头猫                };
396a7b42a4cS猫头猫            }
397927dbe93S猫头猫
398dc160d50S猫头猫            if (
399dc160d50S猫头猫                pluginCacheControl !== CacheControl.NoStore &&
400dc160d50S猫头猫                !notUpdateCache
401dc160d50S猫头猫            ) {
40243eb30bfS猫头猫                // 更新缓存
40343eb30bfS猫头猫                const cacheSource = {
40443eb30bfS猫头猫                    headers: result.headers,
40543eb30bfS猫头猫                    userAgent: result.userAgent,
40643eb30bfS猫头猫                    url,
40743eb30bfS猫头猫                };
40843eb30bfS猫头猫                let realMusicItem = {
40943eb30bfS猫头猫                    ...musicItem,
41043eb30bfS猫头猫                    ...(mediaCache || {}),
41143eb30bfS猫头猫                };
41243eb30bfS猫头猫                realMusicItem.source = {
41343eb30bfS猫头猫                    ...(realMusicItem.source || {}),
41443eb30bfS猫头猫                    [quality]: cacheSource,
41543eb30bfS猫头猫                };
41643eb30bfS猫头猫
41743eb30bfS猫头猫                MediaCache.setMediaCache(realMusicItem);
418752ffc5aS猫头猫            }
419927dbe93S猫头猫            return result;
420927dbe93S猫头猫        } catch (e: any) {
421a28eac61S猫头猫            if (retryCount > 0 && e?.message !== 'NOT RETRY') {
422927dbe93S猫头猫                await delay(150);
423abaede57S猫头猫                return this.getMediaSource(musicItem, quality, --retryCount);
424927dbe93S猫头猫            }
425927dbe93S猫头猫            errorLog('获取真实源失败', e?.message);
426ea6d708fS猫头猫            devLog('error', '获取真实源失败', e, e?.message);
427192ae2b0S猫头猫            return null;
428927dbe93S猫头猫        }
429927dbe93S猫头猫    }
430927dbe93S猫头猫
431927dbe93S猫头猫    /** 获取音乐详情 */
432927dbe93S猫头猫    async getMusicInfo(
433927dbe93S猫头猫        musicItem: ICommon.IMediaBase,
43474d0cf81S猫头猫    ): Promise<Partial<IMusic.IMusicItem> | null> {
435927dbe93S猫头猫        if (!this.plugin.instance.getMusicInfo) {
436d704daedS猫头猫            return null;
437927dbe93S猫头猫        }
43874d0cf81S猫头猫        try {
439927dbe93S猫头猫            return (
440927dbe93S猫头猫                this.plugin.instance.getMusicInfo(
4417993f90eS猫头猫                    resetMediaItem(musicItem, undefined, true),
442d704daedS猫头猫                ) ?? null
443927dbe93S猫头猫            );
444ea6d708fS猫头猫        } catch (e: any) {
445ea6d708fS猫头猫            devLog('error', '获取音乐详情失败', e, e?.message);
446d704daedS猫头猫            return null;
44774d0cf81S猫头猫        }
448927dbe93S猫头猫    }
449927dbe93S猫头猫
4507e883dbbS猫头猫    /**
4517e883dbbS猫头猫     *
4527e883dbbS猫头猫     * getLyric(musicItem) => {
4537e883dbbS猫头猫     *      lyric: string;
4547e883dbbS猫头猫     *      trans: string;
4557e883dbbS猫头猫     * }
4567e883dbbS猫头猫     *
4577e883dbbS猫头猫     */
458927dbe93S猫头猫    /** 获取歌词 */
459927dbe93S猫头猫    async getLyric(
46043eb30bfS猫头猫        originalMusicItem: IMusic.IMusicItemBase,
461927dbe93S猫头猫    ): Promise<ILyric.ILyricSource | null> {
46243eb30bfS猫头猫        // 1.额外存储的meta信息(关联歌词)
4635589cdf3S猫头猫        const meta = MediaExtra.get(originalMusicItem);
46443eb30bfS猫头猫        let musicItem: IMusic.IMusicItem;
465927dbe93S猫头猫        if (meta && meta.associatedLrc) {
46643eb30bfS猫头猫            musicItem = meta.associatedLrc as IMusic.IMusicItem;
46743eb30bfS猫头猫        } else {
46843eb30bfS猫头猫            musicItem = originalMusicItem as IMusic.IMusicItem;
469927dbe93S猫头猫        }
47043eb30bfS猫头猫
47143eb30bfS猫头猫        const musicItemCache = MediaCache.getMediaCache(
4727a91f04fS猫头猫            musicItem,
47343eb30bfS猫头猫        ) as IMusic.IMusicItemCache | null;
47443eb30bfS猫头猫
47543eb30bfS猫头猫        /** 原始歌词文本 */
4767e883dbbS猫头猫        let rawLrc: string | null = musicItem.rawLrc || null;
4777e883dbbS猫头猫        let translation: string | null = null;
47843eb30bfS猫头猫
47926f32636S猫头猫        // 2. 本地手动设置的歌词
48026f32636S猫头猫        const platformHash = CryptoJs.MD5(musicItem.platform).toString(
48126f32636S猫头猫            CryptoJs.enc.Hex,
48226f32636S猫头猫        );
48326f32636S猫头猫        const idHash = CryptoJs.MD5(musicItem.id).toString(CryptoJs.enc.Hex);
48426f32636S猫头猫        if (
48526f32636S猫头猫            await RNFS.exists(
48626f32636S猫头猫                pathConst.localLrcPath + platformHash + '/' + idHash + '.lrc',
48726f32636S猫头猫            )
48826f32636S猫头猫        ) {
48926f32636S猫头猫            rawLrc = await RNFS.readFile(
49026f32636S猫头猫                pathConst.localLrcPath + platformHash + '/' + idHash + '.lrc',
49126f32636S猫头猫                'utf8',
49226f32636S猫头猫            );
49326f32636S猫头猫
49426f32636S猫头猫            if (
49526f32636S猫头猫                await RNFS.exists(
49626f32636S猫头猫                    pathConst.localLrcPath +
49726f32636S猫头猫                        platformHash +
49826f32636S猫头猫                        '/' +
49926f32636S猫头猫                        idHash +
50026f32636S猫头猫                        '.tran.lrc',
50126f32636S猫头猫                )
50226f32636S猫头猫            ) {
50326f32636S猫头猫                translation =
50426f32636S猫头猫                    (await RNFS.readFile(
50526f32636S猫头猫                        pathConst.localLrcPath +
50626f32636S猫头猫                            platformHash +
50726f32636S猫头猫                            '/' +
50826f32636S猫头猫                            idHash +
50926f32636S猫头猫                            '.tran.lrc',
51026f32636S猫头猫                        'utf8',
51126f32636S猫头猫                    )) || null;
51226f32636S猫头猫            }
51326f32636S猫头猫
51426f32636S猫头猫            return {
51526f32636S猫头猫                rawLrc,
51626f32636S猫头猫                translation: translation || undefined, // TODO: 这里写的不好
51726f32636S猫头猫            };
51826f32636S猫头猫        }
51926f32636S猫头猫
52043eb30bfS猫头猫        // 2. 缓存歌词 / 对象上本身的歌词
52143eb30bfS猫头猫        if (musicItemCache?.lyric) {
52243eb30bfS猫头猫            // 缓存的远程结果
52343eb30bfS猫头猫            let cacheLyric: ILyric.ILyricSource | null =
52443eb30bfS猫头猫                musicItemCache.lyric || null;
52543eb30bfS猫头猫            // 缓存的本地结果
52643eb30bfS猫头猫            let localLyric: ILyric.ILyricSource | null =
52743eb30bfS猫头猫                musicItemCache.$localLyric || null;
52843eb30bfS猫头猫
5297e883dbbS猫头猫            // 优先用缓存的结果
5307e883dbbS猫头猫            if (cacheLyric.rawLrc || cacheLyric.translation) {
53143eb30bfS猫头猫                return {
5327e883dbbS猫头猫                    rawLrc: cacheLyric.rawLrc,
5337e883dbbS猫头猫                    translation: cacheLyric.translation,
53443eb30bfS猫头猫                };
53543eb30bfS猫头猫            }
53643eb30bfS猫头猫
5377e883dbbS猫头猫            // 本地其实是缓存的路径
5387e883dbbS猫头猫            if (localLyric) {
5397e883dbbS猫头猫                let needRefetch = false;
5407e883dbbS猫头猫                if (localLyric.rawLrc && (await exists(localLyric.rawLrc))) {
5417e883dbbS猫头猫                    rawLrc = await readFile(localLyric.rawLrc, 'utf8');
5427e883dbbS猫头猫                } else if (localLyric.rawLrc) {
5437e883dbbS猫头猫                    needRefetch = true;
5447e883dbbS猫头猫                }
5457e883dbbS猫头猫                if (
5467e883dbbS猫头猫                    localLyric.translation &&
5477e883dbbS猫头猫                    (await exists(localLyric.translation))
5487e883dbbS猫头猫                ) {
5497e883dbbS猫头猫                    translation = await readFile(
5507e883dbbS猫头猫                        localLyric.translation,
5517e883dbbS猫头猫                        'utf8',
5527e883dbbS猫头猫                    );
5537e883dbbS猫头猫                } else if (localLyric.translation) {
5547e883dbbS猫头猫                    needRefetch = true;
55543eb30bfS猫头猫                }
55643eb30bfS猫头猫
5577e883dbbS猫头猫                if (!needRefetch && (rawLrc || translation)) {
55843eb30bfS猫头猫                    return {
5597e883dbbS猫头猫                        rawLrc: rawLrc || undefined,
5607e883dbbS猫头猫                        translation: translation || undefined,
56143eb30bfS猫头猫                    };
56243eb30bfS猫头猫                }
56343eb30bfS猫头猫            }
56443eb30bfS猫头猫        }
56543eb30bfS猫头猫
56643eb30bfS猫头猫        // 3. 无缓存歌词/无自带歌词/无本地歌词
56743eb30bfS猫头猫        let lrcSource: ILyric.ILyricSource | null;
56843eb30bfS猫头猫        if (isSameMediaItem(originalMusicItem, musicItem)) {
56943eb30bfS猫头猫            lrcSource =
57043eb30bfS猫头猫                (await this.plugin.instance
5717e883dbbS猫头猫                    ?.getLyric?.(resetMediaItem(musicItem, undefined, true))
57243eb30bfS猫头猫                    ?.catch(() => null)) || null;
57343eb30bfS猫头猫        } else {
57443eb30bfS猫头猫            lrcSource =
57543eb30bfS猫头猫                (await PluginManager.getByMedia(musicItem)
57643eb30bfS猫头猫                    ?.instance?.getLyric?.(
57743eb30bfS猫头猫                        resetMediaItem(musicItem, undefined, true),
57843eb30bfS猫头猫                    )
57943eb30bfS猫头猫                    ?.catch(() => null)) || null;
58043eb30bfS猫头猫        }
58143eb30bfS猫头猫
58243eb30bfS猫头猫        if (lrcSource) {
5837e883dbbS猫头猫            rawLrc = lrcSource?.rawLrc || rawLrc;
5847e883dbbS猫头猫            translation = lrcSource?.translation || null;
58543eb30bfS猫头猫
5867e883dbbS猫头猫            const deprecatedLrcUrl = lrcSource?.lrc || musicItem.lrc;
58743eb30bfS猫头猫
5887e883dbbS猫头猫            // 本地的文件名
5897e883dbbS猫头猫            let filename: string | undefined = `${
5907e883dbbS猫头猫                pathConst.lrcCachePath
5917e883dbbS猫头猫            }${nanoid()}.lrc`;
5927e883dbbS猫头猫            let filenameTrans: string | undefined = `${
5937e883dbbS猫头猫                pathConst.lrcCachePath
5947e883dbbS猫头猫            }${nanoid()}.lrc`;
59543eb30bfS猫头猫
5967e883dbbS猫头猫            // 旧版本兼容
5977e883dbbS猫头猫            if (!(rawLrc || translation)) {
5987e883dbbS猫头猫                if (deprecatedLrcUrl) {
59943eb30bfS猫头猫                    rawLrc = (
6007e883dbbS猫头猫                        await axios
6017e883dbbS猫头猫                            .get(deprecatedLrcUrl, {timeout: 3000})
6027e883dbbS猫头猫                            .catch(() => null)
60343eb30bfS猫头猫                    )?.data;
6047e883dbbS猫头猫                } else if (musicItem.rawLrc) {
6057e883dbbS猫头猫                    rawLrc = musicItem.rawLrc;
6067e883dbbS猫头猫                }
60743eb30bfS猫头猫            }
60843eb30bfS猫头猫
60943eb30bfS猫头猫            if (rawLrc) {
61043eb30bfS猫头猫                await writeFile(filename, rawLrc, 'utf8');
61143eb30bfS猫头猫            } else {
6127e883dbbS猫头猫                filename = undefined;
6137e883dbbS猫头猫            }
6147e883dbbS猫头猫            if (translation) {
6157e883dbbS猫头猫                await writeFile(filenameTrans, translation, 'utf8');
6167e883dbbS猫头猫            } else {
6177e883dbbS猫头猫                filenameTrans = undefined;
6187a91f04fS猫头猫            }
6197a91f04fS猫头猫
6207e883dbbS猫头猫            if (rawLrc || translation) {
6217e883dbbS猫头猫                MediaCache.setMediaCache(
6227e883dbbS猫头猫                    produce(musicItemCache || musicItem, draft => {
6237e883dbbS猫头猫                        musicItemCache?.$localLyric?.rawLrc;
6247e883dbbS猫头猫                        objectPath.set(draft, '$localLyric.rawLrc', filename);
62543eb30bfS猫头猫                        objectPath.set(
62643eb30bfS猫头猫                            draft,
6277e883dbbS猫头猫                            '$localLyric.translation',
6287e883dbbS猫头猫                            filenameTrans,
62943eb30bfS猫头猫                        );
63043eb30bfS猫头猫                        return draft;
63143eb30bfS猫头猫                    }),
63243eb30bfS猫头猫                );
633927dbe93S猫头猫                return {
6347e883dbbS猫头猫                    rawLrc: rawLrc || undefined,
6357e883dbbS猫头猫                    translation: translation || undefined,
636927dbe93S猫头猫                };
637927dbe93S猫头猫            }
638927dbe93S猫头猫        }
63943eb30bfS猫头猫
6403a6f67b1S猫头猫        // 6. 如果是本地文件
64143eb30bfS猫头猫        const isDownloaded = LocalMusicSheet.isLocalMusic(originalMusicItem);
64243eb30bfS猫头猫        if (
64343eb30bfS猫头猫            originalMusicItem.platform !== localPluginPlatform &&
64443eb30bfS猫头猫            isDownloaded
64543eb30bfS猫头猫        ) {
6467e883dbbS猫头猫            const res = await localFilePlugin.instance!.getLyric!(isDownloaded);
64743eb30bfS猫头猫
64843eb30bfS猫头猫            console.log('本地文件歌词');
64943eb30bfS猫头猫
6503a6f67b1S猫头猫            if (res) {
6513a6f67b1S猫头猫                return res;
6523a6f67b1S猫头猫            }
6533a6f67b1S猫头猫        }
654ea6d708fS猫头猫        devLog('warn', '无歌词');
655927dbe93S猫头猫
656927dbe93S猫头猫        return null;
657927dbe93S猫头猫    }
658927dbe93S猫头猫
659927dbe93S猫头猫    /** 获取歌词文本 */
660927dbe93S猫头猫    async getLyricText(
661927dbe93S猫头猫        musicItem: IMusic.IMusicItem,
662927dbe93S猫头猫    ): Promise<string | undefined> {
6637e883dbbS猫头猫        return (await this.getLyric(musicItem))?.rawLrc;
664927dbe93S猫头猫    }
665927dbe93S猫头猫
666927dbe93S猫头猫    /** 获取专辑信息 */
667927dbe93S猫头猫    async getAlbumInfo(
668927dbe93S猫头猫        albumItem: IAlbum.IAlbumItemBase,
669f9afcc0dS猫头猫        page: number = 1,
670f9afcc0dS猫头猫    ): Promise<IPlugin.IAlbumInfoResult | null> {
671927dbe93S猫头猫        if (!this.plugin.instance.getAlbumInfo) {
672f9afcc0dS猫头猫            return {
673f9afcc0dS猫头猫                albumItem,
674f2a4767cS猫头猫                musicList: (albumItem?.musicList ?? []).map(
675f2a4767cS猫头猫                    resetMediaItem,
676f2a4767cS猫头猫                    this.plugin.name,
677f2a4767cS猫头猫                    true,
678f2a4767cS猫头猫                ),
679f9afcc0dS猫头猫                isEnd: true,
680f9afcc0dS猫头猫            };
681927dbe93S猫头猫        }
682927dbe93S猫头猫        try {
683927dbe93S猫头猫            const result = await this.plugin.instance.getAlbumInfo(
684927dbe93S猫头猫                resetMediaItem(albumItem, undefined, true),
685f9afcc0dS猫头猫                page,
686927dbe93S猫头猫            );
6875276aef9S猫头猫            if (!result) {
6885276aef9S猫头猫                throw new Error();
6895276aef9S猫头猫            }
690927dbe93S猫头猫            result?.musicList?.forEach(_ => {
691927dbe93S猫头猫                resetMediaItem(_, this.plugin.name);
69296744680S猫头猫                _.album = albumItem.title;
693927dbe93S猫头猫            });
6945276aef9S猫头猫
695f9afcc0dS猫头猫            if (page <= 1) {
696f9afcc0dS猫头猫                // 合并信息
697f9afcc0dS猫头猫                return {
698f9afcc0dS猫头猫                    albumItem: {...albumItem, ...(result?.albumItem ?? {})},
699f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
700f9afcc0dS猫头猫                    musicList: result.musicList,
701f9afcc0dS猫头猫                };
702f9afcc0dS猫头猫            } else {
703f9afcc0dS猫头猫                return {
704f9afcc0dS猫头猫                    isEnd: result.isEnd === false ? false : true,
705f9afcc0dS猫头猫                    musicList: result.musicList,
706f9afcc0dS猫头猫                };
707f9afcc0dS猫头猫            }
7084394410dS猫头猫        } catch (e: any) {
7094394410dS猫头猫            trace('获取专辑信息失败', e?.message);
710ea6d708fS猫头猫            devLog('error', '获取专辑信息失败', e, e?.message);
711ea6d708fS猫头猫
712f9afcc0dS猫头猫            return null;
713927dbe93S猫头猫        }
714927dbe93S猫头猫    }
715927dbe93S猫头猫
7165830c002S猫头猫    /** 获取歌单信息 */
7175830c002S猫头猫    async getMusicSheetInfo(
7185830c002S猫头猫        sheetItem: IMusic.IMusicSheetItem,
7195830c002S猫头猫        page: number = 1,
7205830c002S猫头猫    ): Promise<IPlugin.ISheetInfoResult | null> {
7215281926bS猫头猫        if (!this.plugin.instance.getMusicSheetInfo) {
7225830c002S猫头猫            return {
7235830c002S猫头猫                sheetItem,
7245830c002S猫头猫                musicList: sheetItem?.musicList ?? [],
7255830c002S猫头猫                isEnd: true,
7265830c002S猫头猫            };
7275830c002S猫头猫        }
7285830c002S猫头猫        try {
7295830c002S猫头猫            const result = await this.plugin.instance?.getMusicSheetInfo?.(
7305830c002S猫头猫                resetMediaItem(sheetItem, undefined, true),
7315830c002S猫头猫                page,
7325830c002S猫头猫            );
7335830c002S猫头猫            if (!result) {
7345830c002S猫头猫                throw new Error();
7355830c002S猫头猫            }
7365830c002S猫头猫            result?.musicList?.forEach(_ => {
7375830c002S猫头猫                resetMediaItem(_, this.plugin.name);
7385830c002S猫头猫            });
7395830c002S猫头猫
7405830c002S猫头猫            if (page <= 1) {
7415830c002S猫头猫                // 合并信息
7425830c002S猫头猫                return {
7435830c002S猫头猫                    sheetItem: {...sheetItem, ...(result?.sheetItem ?? {})},
7445830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
7455830c002S猫头猫                    musicList: result.musicList,
7465830c002S猫头猫                };
7475830c002S猫头猫            } else {
7485830c002S猫头猫                return {
7495830c002S猫头猫                    isEnd: result.isEnd === false ? false : true,
7505830c002S猫头猫                    musicList: result.musicList,
7515830c002S猫头猫                };
7525830c002S猫头猫            }
7535830c002S猫头猫        } catch (e: any) {
7545830c002S猫头猫            trace('获取歌单信息失败', e, e?.message);
7555830c002S猫头猫            devLog('error', '获取歌单信息失败', e, e?.message);
7565830c002S猫头猫
7575830c002S猫头猫            return null;
7585830c002S猫头猫        }
7595830c002S猫头猫    }
7605830c002S猫头猫
761927dbe93S猫头猫    /** 查询作者信息 */
762efb9da24S猫头猫    async getArtistWorks<T extends IArtist.ArtistMediaType>(
763927dbe93S猫头猫        artistItem: IArtist.IArtistItem,
764927dbe93S猫头猫        page: number,
765927dbe93S猫头猫        type: T,
766927dbe93S猫头猫    ): Promise<IPlugin.ISearchResult<T>> {
767efb9da24S猫头猫        if (!this.plugin.instance.getArtistWorks) {
768927dbe93S猫头猫            return {
769927dbe93S猫头猫                isEnd: true,
770927dbe93S猫头猫                data: [],
771927dbe93S猫头猫            };
772927dbe93S猫头猫        }
773927dbe93S猫头猫        try {
774efb9da24S猫头猫            const result = await this.plugin.instance.getArtistWorks(
775927dbe93S猫头猫                artistItem,
776927dbe93S猫头猫                page,
777927dbe93S猫头猫                type,
778927dbe93S猫头猫            );
779927dbe93S猫头猫            if (!result.data) {
780927dbe93S猫头猫                return {
781927dbe93S猫头猫                    isEnd: true,
782927dbe93S猫头猫                    data: [],
783927dbe93S猫头猫                };
784927dbe93S猫头猫            }
785927dbe93S猫头猫            result.data?.forEach(_ => resetMediaItem(_, this.plugin.name));
786927dbe93S猫头猫            return {
787927dbe93S猫头猫                isEnd: result.isEnd ?? true,
788927dbe93S猫头猫                data: result.data,
789927dbe93S猫头猫            };
7904394410dS猫头猫        } catch (e: any) {
7914394410dS猫头猫            trace('查询作者信息失败', e?.message);
792ea6d708fS猫头猫            devLog('error', '查询作者信息失败', e, e?.message);
793ea6d708fS猫头猫
794927dbe93S猫头猫            throw e;
795927dbe93S猫头猫        }
796927dbe93S猫头猫    }
79708380090S猫头猫
79808380090S猫头猫    /** 导入歌单 */
79908380090S猫头猫    async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> {
80008380090S猫头猫        try {
80108380090S猫头猫            const result =
80208380090S猫头猫                (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? [];
80308380090S猫头猫            result.forEach(_ => resetMediaItem(_, this.plugin.name));
80408380090S猫头猫            return result;
805ea6d708fS猫头猫        } catch (e: any) {
8060e4173cdS猫头猫            console.log(e);
807ea6d708fS猫头猫            devLog('error', '导入歌单失败', e, e?.message);
808ea6d708fS猫头猫
80908380090S猫头猫            return [];
81008380090S猫头猫        }
81108380090S猫头猫    }
81242a9f3e6Smaotoumao
8134d9d3c4cS猫头猫    /** 导入单曲 */
8144d9d3c4cS猫头猫    async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> {
8154d9d3c4cS猫头猫        try {
8164d9d3c4cS猫头猫            const result = await this.plugin.instance?.importMusicItem?.(
8174d9d3c4cS猫头猫                urlLike,
8184d9d3c4cS猫头猫            );
8194d9d3c4cS猫头猫            if (!result) {
8204d9d3c4cS猫头猫                throw new Error();
8214d9d3c4cS猫头猫            }
8224d9d3c4cS猫头猫            resetMediaItem(result, this.plugin.name);
8234d9d3c4cS猫头猫            return result;
824ea6d708fS猫头猫        } catch (e: any) {
825ea6d708fS猫头猫            devLog('error', '导入单曲失败', e, e?.message);
826ea6d708fS猫头猫
8274d9d3c4cS猫头猫            return null;
8284d9d3c4cS猫头猫        }
8294d9d3c4cS猫头猫    }
83042a9f3e6Smaotoumao
831d52aa40eS猫头猫    /** 获取榜单 */
83292b6c95aS猫头猫    async getTopLists(): Promise<IMusic.IMusicSheetGroupItem[]> {
833d52aa40eS猫头猫        try {
834d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopLists?.();
835d52aa40eS猫头猫            if (!result) {
836d52aa40eS猫头猫                throw new Error();
837d52aa40eS猫头猫            }
838d52aa40eS猫头猫            return result;
839d52aa40eS猫头猫        } catch (e: any) {
840d52aa40eS猫头猫            devLog('error', '获取榜单失败', e, e?.message);
841d52aa40eS猫头猫            return [];
842d52aa40eS猫头猫        }
843d52aa40eS猫头猫    }
84442a9f3e6Smaotoumao
845d52aa40eS猫头猫    /** 获取榜单详情 */
846d52aa40eS猫头猫    async getTopListDetail(
84792b6c95aS猫头猫        topListItem: IMusic.IMusicSheetItemBase,
848956ee1b7S猫头猫        page: number,
849956ee1b7S猫头猫    ): Promise<IPlugin.ITopListInfoResult> {
850d52aa40eS猫头猫        try {
851d52aa40eS猫头猫            const result = await this.plugin.instance?.getTopListDetail?.(
852d52aa40eS猫头猫                topListItem,
853956ee1b7S猫头猫                page,
854d52aa40eS猫头猫            );
855d52aa40eS猫头猫            if (!result) {
856d52aa40eS猫头猫                throw new Error();
857d52aa40eS猫头猫            }
858d384662fS猫头猫            if (result.musicList) {
859d384662fS猫头猫                result.musicList.forEach(_ =>
860d384662fS猫头猫                    resetMediaItem(_, this.plugin.name),
861d384662fS猫头猫                );
862d384662fS猫头猫            }
863956ee1b7S猫头猫            if (result.isEnd !== false) {
864956ee1b7S猫头猫                result.isEnd = true;
865956ee1b7S猫头猫            }
866d52aa40eS猫头猫            return result;
867d52aa40eS猫头猫        } catch (e: any) {
868d52aa40eS猫头猫            devLog('error', '获取榜单详情失败', e, e?.message);
869d52aa40eS猫头猫            return {
870956ee1b7S猫头猫                isEnd: true,
871956ee1b7S猫头猫                topListItem: topListItem as IMusic.IMusicSheetItem,
872d52aa40eS猫头猫                musicList: [],
873d52aa40eS猫头猫            };
874d52aa40eS猫头猫        }
875d52aa40eS猫头猫    }
876ceb900cdS猫头猫
8775830c002S猫头猫    /** 获取推荐歌单的tag */
878ceb900cdS猫头猫    async getRecommendSheetTags(): Promise<IPlugin.IGetRecommendSheetTagsResult> {
879ceb900cdS猫头猫        try {
880ceb900cdS猫头猫            const result =
881ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetTags?.();
882ceb900cdS猫头猫            if (!result) {
883ceb900cdS猫头猫                throw new Error();
884ceb900cdS猫头猫            }
885ceb900cdS猫头猫            return result;
886ceb900cdS猫头猫        } catch (e: any) {
887ceb900cdS猫头猫            devLog('error', '获取推荐歌单失败', e, e?.message);
888ceb900cdS猫头猫            return {
889ceb900cdS猫头猫                data: [],
890ceb900cdS猫头猫            };
891ceb900cdS猫头猫        }
892ceb900cdS猫头猫    }
89342a9f3e6Smaotoumao
8945830c002S猫头猫    /** 获取某个tag的推荐歌单 */
895ceb900cdS猫头猫    async getRecommendSheetsByTag(
896ceb900cdS猫头猫        tagItem: ICommon.IUnique,
897ceb900cdS猫头猫        page?: number,
898ceb900cdS猫头猫    ): Promise<ICommon.PaginationResponse<IMusic.IMusicSheetItemBase>> {
899ceb900cdS猫头猫        try {
900ceb900cdS猫头猫            const result =
901ceb900cdS猫头猫                await this.plugin.instance?.getRecommendSheetsByTag?.(
902ceb900cdS猫头猫                    tagItem,
903ceb900cdS猫头猫                    page ?? 1,
904ceb900cdS猫头猫                );
905ceb900cdS猫头猫            if (!result) {
906ceb900cdS猫头猫                throw new Error();
907ceb900cdS猫头猫            }
908ceb900cdS猫头猫            if (result.isEnd !== false) {
909ceb900cdS猫头猫                result.isEnd = true;
910ceb900cdS猫头猫            }
911ceb900cdS猫头猫            if (!result.data) {
912ceb900cdS猫头猫                result.data = [];
913ceb900cdS猫头猫            }
914ceb900cdS猫头猫            result.data.forEach(item => resetMediaItem(item, this.plugin.name));
915ceb900cdS猫头猫
916ceb900cdS猫头猫            return result;
917ceb900cdS猫头猫        } catch (e: any) {
918ceb900cdS猫头猫            devLog('error', '获取推荐歌单详情失败', e, e?.message);
919ceb900cdS猫头猫            return {
920ceb900cdS猫头猫                isEnd: true,
921ceb900cdS猫头猫                data: [],
922ceb900cdS猫头猫            };
923ceb900cdS猫头猫        }
924ceb900cdS猫头猫    }
925da2a2959S猫头猫
926da2a2959S猫头猫    async getMusicComments(
927da2a2959S猫头猫        musicItem: IMusic.IMusicItem,
928da2a2959S猫头猫    ): Promise<ICommon.PaginationResponse<IMedia.IComment>> {
929da2a2959S猫头猫        const result = await this.plugin.instance?.getMusicComments?.(
930da2a2959S猫头猫            musicItem,
931da2a2959S猫头猫        );
932da2a2959S猫头猫        if (!result) {
933da2a2959S猫头猫            throw new Error();
934da2a2959S猫头猫        }
935da2a2959S猫头猫        if (result.isEnd !== false) {
936da2a2959S猫头猫            result.isEnd = true;
937da2a2959S猫头猫        }
938da2a2959S猫头猫        if (!result.data) {
939da2a2959S猫头猫            result.data = [];
940da2a2959S猫头猫        }
941da2a2959S猫头猫
942da2a2959S猫头猫        return result;
943da2a2959S猫头猫    }
9447fb90110S猫头猫
9457fb90110S猫头猫    async migrateFromOtherPlugin(
9467fb90110S猫头猫        mediaItem: ICommon.IMediaBase,
9477fb90110S猫头猫        fromPlatform: string,
9487fb90110S猫头猫    ): Promise<{isOk: boolean; data?: ICommon.IMediaBase}> {
9497fb90110S猫头猫        try {
9507fb90110S猫头猫            const result = await this.plugin.instance?.migrateFromOtherPlugin(
9517fb90110S猫头猫                mediaItem,
9527fb90110S猫头猫                fromPlatform,
9537fb90110S猫头猫            );
9547fb90110S猫头猫
9557fb90110S猫头猫            if (
9567fb90110S猫头猫                result.isOk &&
9577fb90110S猫头猫                result.data?.id &&
9587fb90110S猫头猫                result.data?.platform === this.plugin.platform
9597fb90110S猫头猫            ) {
9607fb90110S猫头猫                return {
9617fb90110S猫头猫                    isOk: result.isOk,
9627fb90110S猫头猫                    data: result.data,
9637fb90110S猫头猫                };
9647fb90110S猫头猫            }
9657fb90110S猫头猫            return {
9667fb90110S猫头猫                isOk: false,
9677fb90110S猫头猫            };
9687fb90110S猫头猫        } catch {
9697fb90110S猫头猫            return {
9707fb90110S猫头猫                isOk: false,
9717fb90110S猫头猫            };
9727fb90110S猫头猫        }
9737fb90110S猫头猫    }
974927dbe93S猫头猫}
97533d52955Smaotoumao
976d5bfeb7eS猫头猫//#endregion
9771a5528a0S猫头猫
978927dbe93S猫头猫let plugins: Array<Plugin> = [];
979927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins);
98074d0cf81S猫头猫
981d5bfeb7eS猫头猫//#region 本地音乐插件
98274d0cf81S猫头猫/** 本地插件 */
98374d0cf81S猫头猫const localFilePlugin = new Plugin(function () {
9840e4173cdS猫头猫    return {
985d5bfeb7eS猫头猫        platform: localPluginPlatform,
98674d0cf81S猫头猫        _path: '',
98774d0cf81S猫头猫        async getMusicInfo(musicBase) {
98874d0cf81S猫头猫            const localPath = getInternalData<string>(
98974d0cf81S猫头猫                musicBase,
99074d0cf81S猫头猫                InternalDataType.LOCALPATH,
9910e4173cdS猫头猫            );
99274d0cf81S猫头猫            if (localPath) {
99374d0cf81S猫头猫                const coverImg = await Mp3Util.getMediaCoverImg(localPath);
99474d0cf81S猫头猫                return {
99574d0cf81S猫头猫                    artwork: coverImg,
99674d0cf81S猫头猫                };
99774d0cf81S猫头猫            }
99874d0cf81S猫头猫            return null;
99974d0cf81S猫头猫        },
10007993f90eS猫头猫        async getLyric(musicBase) {
10017993f90eS猫头猫            const localPath = getInternalData<string>(
10027993f90eS猫头猫                musicBase,
10037993f90eS猫头猫                InternalDataType.LOCALPATH,
10047993f90eS猫头猫            );
10053a6f67b1S猫头猫            let rawLrc: string | null = null;
10067993f90eS猫头猫            if (localPath) {
10073a6f67b1S猫头猫                // 读取内嵌歌词
10083a6f67b1S猫头猫                try {
10093a6f67b1S猫头猫                    rawLrc = await Mp3Util.getLyric(localPath);
10103a6f67b1S猫头猫                } catch (e) {
1011a84a85c5S猫头猫                    console.log('读取内嵌歌词失败', e);
10127993f90eS猫头猫                }
10133a6f67b1S猫头猫                if (!rawLrc) {
10143a6f67b1S猫头猫                    // 读取配置歌词
10153a6f67b1S猫头猫                    const lastDot = localPath.lastIndexOf('.');
10163a6f67b1S猫头猫                    const lrcPath = localPath.slice(0, lastDot) + '.lrc';
10173a6f67b1S猫头猫
10183a6f67b1S猫头猫                    try {
10193a6f67b1S猫头猫                        if (await exists(lrcPath)) {
10203a6f67b1S猫头猫                            rawLrc = await readFile(lrcPath, 'utf8');
10213a6f67b1S猫头猫                        }
10223a6f67b1S猫头猫                    } catch {}
10233a6f67b1S猫头猫                }
10243a6f67b1S猫头猫            }
10253a6f67b1S猫头猫
10263a6f67b1S猫头猫            return rawLrc
10273a6f67b1S猫头猫                ? {
10283a6f67b1S猫头猫                      rawLrc,
10293a6f67b1S猫头猫                  }
10303a6f67b1S猫头猫                : null;
10317993f90eS猫头猫        },
1032a84a85c5S猫头猫        async importMusicItem(urlLike) {
1033a84a85c5S猫头猫            let meta: any = {};
103442a9f3e6Smaotoumao            let id: string;
103542a9f3e6Smaotoumao
1036a84a85c5S猫头猫            try {
1037a84a85c5S猫头猫                meta = await Mp3Util.getBasicMeta(urlLike);
103842a9f3e6Smaotoumao                const fileStat = await stat(urlLike);
103942a9f3e6Smaotoumao                id =
104042a9f3e6Smaotoumao                    CryptoJs.MD5(fileStat.originalFilepath).toString(
104142a9f3e6Smaotoumao                        CryptoJs.enc.Hex,
104242a9f3e6Smaotoumao                    ) || nanoid();
104342a9f3e6Smaotoumao            } catch {
10445353b473S猫头猫                id = nanoid();
10455353b473S猫头猫            }
104642a9f3e6Smaotoumao
1047a84a85c5S猫头猫            return {
1048a84a85c5S猫头猫                id: id,
1049a84a85c5S猫头猫                platform: '本地',
1050a84a85c5S猫头猫                title: meta?.title ?? getFileName(urlLike),
1051a84a85c5S猫头猫                artist: meta?.artist ?? '未知歌手',
10525353b473S猫头猫                duration: parseInt(meta?.duration ?? '0', 10) / 1000,
1053a84a85c5S猫头猫                album: meta?.album ?? '未知专辑',
1054a84a85c5S猫头猫                artwork: '',
1055a84a85c5S猫头猫                [internalSerializeKey]: {
1056a84a85c5S猫头猫                    localPath: urlLike,
1057a84a85c5S猫头猫                },
1058a84a85c5S猫头猫            };
1059a84a85c5S猫头猫        },
106011908939S猫头猫        async getMediaSource(musicItem, quality) {
106111908939S猫头猫            if (quality === 'standard') {
106211908939S猫头猫                return {
106311908939S猫头猫                    url: addFileScheme(musicItem.$?.localPath || musicItem.url),
106411908939S猫头猫                };
106511908939S猫头猫            }
106611908939S猫头猫            return null;
106711908939S猫头猫        },
106874d0cf81S猫头猫    };
106974d0cf81S猫头猫}, '');
10707993f90eS猫头猫localFilePlugin.hash = localPluginHash;
1071927dbe93S猫头猫
1072d5bfeb7eS猫头猫//#endregion
1073d5bfeb7eS猫头猫
1074927dbe93S猫头猫async function setup() {
1075927dbe93S猫头猫    const _plugins: Array<Plugin> = [];
1076927dbe93S猫头猫    try {
1077927dbe93S猫头猫        // 加载插件
1078927dbe93S猫头猫        const pluginsPaths = await readDir(pathConst.pluginPath);
1079927dbe93S猫头猫        for (let i = 0; i < pluginsPaths.length; ++i) {
1080927dbe93S猫头猫            const _pluginUrl = pluginsPaths[i];
10811e263108S猫头猫            trace('初始化插件', _pluginUrl);
10821e263108S猫头猫            if (
10831e263108S猫头猫                _pluginUrl.isFile() &&
10841e263108S猫头猫                (_pluginUrl.name?.endsWith?.('.js') ||
10851e263108S猫头猫                    _pluginUrl.path?.endsWith?.('.js'))
10861e263108S猫头猫            ) {
1087927dbe93S猫头猫                const funcCode = await readFile(_pluginUrl.path, 'utf8');
1088927dbe93S猫头猫                const plugin = new Plugin(funcCode, _pluginUrl.path);
10894060c00aS猫头猫                const _pluginIndex = _plugins.findIndex(
10904060c00aS猫头猫                    p => p.hash === plugin.hash,
10914060c00aS猫头猫                );
1092927dbe93S猫头猫                if (_pluginIndex !== -1) {
1093927dbe93S猫头猫                    // 重复插件,直接忽略
10940c266394S猫头猫                    continue;
1095927dbe93S猫头猫                }
1096927dbe93S猫头猫                plugin.hash !== '' && _plugins.push(plugin);
1097927dbe93S猫头猫            }
1098927dbe93S猫头猫        }
1099927dbe93S猫头猫
1100927dbe93S猫头猫        plugins = _plugins;
1101e08d37a3S猫头猫        /** 初始化meta信息 */
1102c2b3a262S猫头猫        await PluginMeta.setupMeta(plugins.map(_ => _.name));
1103c2b3a262S猫头猫        /** 查看一下是否有禁用的标记 */
1104c2b3a262S猫头猫        const allMeta = PluginMeta.getPluginMetaAll() ?? {};
1105c2b3a262S猫头猫        for (let plugin of plugins) {
1106c2b3a262S猫头猫            if (allMeta[plugin.name]?.enabled === false) {
1107c2b3a262S猫头猫                plugin.state = 'disabled';
1108c2b3a262S猫头猫            }
1109c2b3a262S猫头猫        }
1110c2b3a262S猫头猫        pluginStateMapper.notify();
1111927dbe93S猫头猫    } catch (e: any) {
11124060c00aS猫头猫        ToastAndroid.show(
11134060c00aS猫头猫            `插件初始化失败:${e?.message ?? e}`,
11144060c00aS猫头猫            ToastAndroid.LONG,
11154060c00aS猫头猫        );
11161a5528a0S猫头猫        errorLog('插件初始化失败', e?.message);
1117927dbe93S猫头猫        throw e;
1118927dbe93S猫头猫    }
1119927dbe93S猫头猫}
1120927dbe93S猫头猫
1121e36e2599S猫头猫interface IInstallPluginConfig {
1122e36e2599S猫头猫    notCheckVersion?: boolean;
1123e36e2599S猫头猫}
1124e36e2599S猫头猫
1125*34bfbe58Smaotoumaoexport interface IInstallPluginResult {
1126*34bfbe58Smaotoumao    success: boolean;
1127*34bfbe58Smaotoumao    message?: string;
1128*34bfbe58Smaotoumao    pluginName?: string;
1129*34bfbe58Smaotoumao    pluginHash?: string;
1130*34bfbe58Smaotoumao    pluginUrl?: string;
1131*34bfbe58Smaotoumao}
1132*34bfbe58Smaotoumao
113350245d53Smaotoumao// 从本地文件安装插件
113450245d53Smaotoumaoasync function installPluginFromLocalFile(
1135e36e2599S猫头猫    pluginPath: string,
113650245d53Smaotoumao    config?: IInstallPluginConfig & {
113750245d53Smaotoumao        useExpoFs?: boolean
113850245d53Smaotoumao    },
1139*34bfbe58Smaotoumao): Promise<IInstallPluginResult> {
114050245d53Smaotoumao    let funcCode: string;
114150245d53Smaotoumao    if (config?.useExpoFs) {
114250245d53Smaotoumao        funcCode = await readAsStringAsync(pluginPath);
114350245d53Smaotoumao    } else {
114450245d53Smaotoumao        funcCode = await readFile(pluginPath, 'utf8');
114550245d53Smaotoumao    }
1146e36e2599S猫头猫
1147e36e2599S猫头猫    if (funcCode) {
1148927dbe93S猫头猫        const plugin = new Plugin(funcCode, pluginPath);
1149927dbe93S猫头猫        const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
1150927dbe93S猫头猫        if (_pluginIndex !== -1) {
1151e36e2599S猫头猫            // 静默忽略
1152*34bfbe58Smaotoumao            return {
1153*34bfbe58Smaotoumao                success: true,
1154*34bfbe58Smaotoumao                message: '插件已安装',
1155*34bfbe58Smaotoumao                pluginName: plugin.name,
1156*34bfbe58Smaotoumao                pluginHash: plugin.hash,
1157*34bfbe58Smaotoumao            };
1158927dbe93S猫头猫        }
1159e36e2599S猫头猫        const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
1160e36e2599S猫头猫        if (oldVersionPlugin && !config?.notCheckVersion) {
1161e36e2599S猫头猫            if (
1162e36e2599S猫头猫                compare(
1163e36e2599S猫头猫                    oldVersionPlugin.instance.version ?? '',
1164e36e2599S猫头猫                    plugin.instance.version ?? '',
1165e36e2599S猫头猫                    '>',
1166e36e2599S猫头猫                )
1167e36e2599S猫头猫            ) {
1168*34bfbe58Smaotoumao                return {
1169*34bfbe58Smaotoumao                    success: false,
1170*34bfbe58Smaotoumao                    message: '已安装更新版本的插件',
1171*34bfbe58Smaotoumao                    pluginName: plugin.name,
1172*34bfbe58Smaotoumao                    pluginHash: plugin.hash,
1173*34bfbe58Smaotoumao                };
1174e36e2599S猫头猫            }
1175e36e2599S猫头猫        }
1176e36e2599S猫头猫
1177927dbe93S猫头猫        if (plugin.hash !== '') {
1178927dbe93S猫头猫            const fn = nanoid();
1179e36e2599S猫头猫            if (oldVersionPlugin) {
1180e36e2599S猫头猫                plugins = plugins.filter(_ => _.hash !== oldVersionPlugin.hash);
1181e36e2599S猫头猫                try {
1182e36e2599S猫头猫                    await unlink(oldVersionPlugin.path);
1183e36e2599S猫头猫                } catch {}
1184e36e2599S猫头猫            }
1185927dbe93S猫头猫            const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
1186927dbe93S猫头猫            await copyFile(pluginPath, _pluginPath);
1187927dbe93S猫头猫            plugin.path = _pluginPath;
1188927dbe93S猫头猫            plugins = plugins.concat(plugin);
1189927dbe93S猫头猫            pluginStateMapper.notify();
1190*34bfbe58Smaotoumao            return {
1191*34bfbe58Smaotoumao                success: true,
1192*34bfbe58Smaotoumao                pluginName: plugin.name,
1193*34bfbe58Smaotoumao                pluginHash: plugin.hash,
1194*34bfbe58Smaotoumao            };
1195927dbe93S猫头猫        }
1196*34bfbe58Smaotoumao        return {
1197*34bfbe58Smaotoumao            success: false,
1198*34bfbe58Smaotoumao            message: '插件无法解析',
1199927dbe93S猫头猫        }
1200*34bfbe58Smaotoumao    }
1201*34bfbe58Smaotoumao    return {
1202*34bfbe58Smaotoumao        success: false,
1203*34bfbe58Smaotoumao        message: '插件无法识别',
1204*34bfbe58Smaotoumao    };
1205c2b3a262S猫头猫}
1206c2b3a262S猫头猫
1207ee781061S猫头猫
1208c2b3a262S猫头猫async function installPluginFromUrl(
1209c2b3a262S猫头猫    url: string,
1210c2b3a262S猫头猫    config?: IInstallPluginConfig,
1211*34bfbe58Smaotoumao) : Promise<IInstallPluginResult> {
121258992c6bS猫头猫    try {
1213ee781061S猫头猫        const funcCode = (
1214ee781061S猫头猫            await axios.get(url, {
1215*34bfbe58Smaotoumao                headers: {
1216*34bfbe58Smaotoumao                    'Cache-Control': 'no-cache',
1217*34bfbe58Smaotoumao                    Pragma: 'no-cache',
1218*34bfbe58Smaotoumao                    Expires: '0',
1219*34bfbe58Smaotoumao                },
1220ee781061S猫头猫            })
1221ee781061S猫头猫        ).data;
122258992c6bS猫头猫        if (funcCode) {
122358992c6bS猫头猫            const plugin = new Plugin(funcCode, '');
122458992c6bS猫头猫            const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash);
122558992c6bS猫头猫            if (_pluginIndex !== -1) {
12268b7ddca8S猫头猫                // 静默忽略
1227*34bfbe58Smaotoumao                return {
1228*34bfbe58Smaotoumao                    success: true,
1229*34bfbe58Smaotoumao                    message: '插件已安装',
1230*34bfbe58Smaotoumao                    pluginName: plugin.name,
1231*34bfbe58Smaotoumao                    pluginHash: plugin.hash,
1232*34bfbe58Smaotoumao                    pluginUrl: url,
1233*34bfbe58Smaotoumao                };
123458992c6bS猫头猫            }
123525c1bd29S猫头猫            const oldVersionPlugin = plugins.find(p => p.name === plugin.name);
1236c2b3a262S猫头猫            if (oldVersionPlugin && !config?.notCheckVersion) {
123725c1bd29S猫头猫                if (
123825c1bd29S猫头猫                    compare(
123925c1bd29S猫头猫                        oldVersionPlugin.instance.version ?? '',
124025c1bd29S猫头猫                        plugin.instance.version ?? '',
124125c1bd29S猫头猫                        '>',
124225c1bd29S猫头猫                    )
124325c1bd29S猫头猫                ) {
1244*34bfbe58Smaotoumao                    return {
1245*34bfbe58Smaotoumao                        success: false,
1246*34bfbe58Smaotoumao                        message: '已安装更新版本的插件',
1247*34bfbe58Smaotoumao                        pluginName: plugin.name,
1248*34bfbe58Smaotoumao                        pluginHash: plugin.hash,
1249*34bfbe58Smaotoumao                        pluginUrl: url,
1250*34bfbe58Smaotoumao                    };
125125c1bd29S猫头猫                }
125225c1bd29S猫头猫            }
125325c1bd29S猫头猫
125458992c6bS猫头猫            if (plugin.hash !== '') {
125558992c6bS猫头猫                const fn = nanoid();
125658992c6bS猫头猫                const _pluginPath = `${pathConst.pluginPath}${fn}.js`;
125758992c6bS猫头猫                await writeFile(_pluginPath, funcCode, 'utf8');
125858992c6bS猫头猫                plugin.path = _pluginPath;
125958992c6bS猫头猫                plugins = plugins.concat(plugin);
126025c1bd29S猫头猫                if (oldVersionPlugin) {
126125c1bd29S猫头猫                    plugins = plugins.filter(
126225c1bd29S猫头猫                        _ => _.hash !== oldVersionPlugin.hash,
126325c1bd29S猫头猫                    );
126425c1bd29S猫头猫                    try {
126525c1bd29S猫头猫                        await unlink(oldVersionPlugin.path);
126625c1bd29S猫头猫                    } catch {}
126725c1bd29S猫头猫                }
126858992c6bS猫头猫                pluginStateMapper.notify();
1269*34bfbe58Smaotoumao                return {
1270*34bfbe58Smaotoumao                    success: true,
1271*34bfbe58Smaotoumao                    pluginName: plugin.name,
1272*34bfbe58Smaotoumao                    pluginHash: plugin.hash,
1273*34bfbe58Smaotoumao                    pluginUrl: url,
127458992c6bS猫头猫                }
1275*34bfbe58Smaotoumao            }
1276*34bfbe58Smaotoumao            return {
1277*34bfbe58Smaotoumao                success: false,
1278*34bfbe58Smaotoumao                message: '插件无法解析',
1279*34bfbe58Smaotoumao                pluginUrl: url,
1280*34bfbe58Smaotoumao            }
1281*34bfbe58Smaotoumao        } else {
1282*34bfbe58Smaotoumao            return {
1283*34bfbe58Smaotoumao                success: false,
1284*34bfbe58Smaotoumao                message: '插件无法识别',
1285*34bfbe58Smaotoumao                pluginUrl: url,
1286*34bfbe58Smaotoumao            }
128758992c6bS猫头猫        }
128825c1bd29S猫头猫    } catch (e: any) {
1289ea6d708fS猫头猫        devLog('error', 'URL安装插件失败', e, e?.message);
129058992c6bS猫头猫        errorLog('URL安装插件失败', e);
1291*34bfbe58Smaotoumao
1292*34bfbe58Smaotoumao        if (e?.response?.statusCode === 404) {
1293*34bfbe58Smaotoumao            return {
1294*34bfbe58Smaotoumao                success: false,
1295*34bfbe58Smaotoumao                message: '插件不存在,请联系插件作者',
1296*34bfbe58Smaotoumao                pluginUrl: url,
1297*34bfbe58Smaotoumao            }
1298*34bfbe58Smaotoumao        } else {
1299*34bfbe58Smaotoumao            return {
1300*34bfbe58Smaotoumao                success: false,
1301*34bfbe58Smaotoumao                message: e?.message ?? '',
1302*34bfbe58Smaotoumao                pluginUrl: url,
1303*34bfbe58Smaotoumao            }
1304*34bfbe58Smaotoumao        }
130558992c6bS猫头猫    }
130658992c6bS猫头猫}
130758992c6bS猫头猫
1308927dbe93S猫头猫/** 卸载插件 */
1309927dbe93S猫头猫async function uninstallPlugin(hash: string) {
1310927dbe93S猫头猫    const targetIndex = plugins.findIndex(_ => _.hash === hash);
1311927dbe93S猫头猫    if (targetIndex !== -1) {
1312927dbe93S猫头猫        try {
131324e5e74aS猫头猫            const pluginName = plugins[targetIndex].name;
1314927dbe93S猫头猫            await unlink(plugins[targetIndex].path);
1315927dbe93S猫头猫            plugins = plugins.filter(_ => _.hash !== hash);
1316927dbe93S猫头猫            pluginStateMapper.notify();
131743eb30bfS猫头猫            // 防止其他重名
131824e5e74aS猫头猫            if (plugins.every(_ => _.name !== pluginName)) {
13195589cdf3S猫头猫                MediaExtra.removeAll(pluginName);
132024e5e74aS猫头猫            }
1321927dbe93S猫头猫        } catch {}
1322927dbe93S猫头猫    }
1323927dbe93S猫头猫}
1324927dbe93S猫头猫
132508882a77S猫头猫async function uninstallAllPlugins() {
132608882a77S猫头猫    await Promise.all(
132708882a77S猫头猫        plugins.map(async plugin => {
132808882a77S猫头猫            try {
132908882a77S猫头猫                const pluginName = plugin.name;
133008882a77S猫头猫                await unlink(plugin.path);
13315589cdf3S猫头猫                MediaExtra.removeAll(pluginName);
133208882a77S猫头猫            } catch (e) {}
133308882a77S猫头猫        }),
133408882a77S猫头猫    );
133508882a77S猫头猫    plugins = [];
133608882a77S猫头猫    pluginStateMapper.notify();
1337e08d37a3S猫头猫
1338e08d37a3S猫头猫    /** 清除空余文件,异步做就可以了 */
1339e08d37a3S猫头猫    readDir(pathConst.pluginPath)
1340e08d37a3S猫头猫        .then(fns => {
1341e08d37a3S猫头猫            fns.forEach(fn => {
1342e08d37a3S猫头猫                unlink(fn.path).catch(emptyFunction);
1343e08d37a3S猫头猫            });
1344e08d37a3S猫头猫        })
1345e08d37a3S猫头猫        .catch(emptyFunction);
134608882a77S猫头猫}
134708882a77S猫头猫
134825c1bd29S猫头猫async function updatePlugin(plugin: Plugin) {
134925c1bd29S猫头猫    const updateUrl = plugin.instance.srcUrl;
135025c1bd29S猫头猫    if (!updateUrl) {
135125c1bd29S猫头猫        throw new Error('没有更新源');
135225c1bd29S猫头猫    }
135325c1bd29S猫头猫    try {
135425c1bd29S猫头猫        await installPluginFromUrl(updateUrl);
135525c1bd29S猫头猫    } catch (e: any) {
135625c1bd29S猫头猫        if (e.message === '插件已安装') {
135725c1bd29S猫头猫            throw new Error('当前已是最新版本');
135825c1bd29S猫头猫        } else {
135925c1bd29S猫头猫            throw e;
136025c1bd29S猫头猫        }
136125c1bd29S猫头猫    }
136225c1bd29S猫头猫}
136325c1bd29S猫头猫
1364927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) {
13652c595535S猫头猫    return getByName(mediaItem?.platform);
1366927dbe93S猫头猫}
1367927dbe93S猫头猫
1368927dbe93S猫头猫function getByHash(hash: string) {
13697993f90eS猫头猫    return hash === localPluginHash
13707993f90eS猫头猫        ? localFilePlugin
13717993f90eS猫头猫        : plugins.find(_ => _.hash === hash);
1372927dbe93S猫头猫}
1373927dbe93S猫头猫
1374927dbe93S猫头猫function getByName(name: string) {
13757993f90eS猫头猫    return name === localPluginPlatform
13760e4173cdS猫头猫        ? localFilePlugin
13770e4173cdS猫头猫        : plugins.find(_ => _.name === name);
1378927dbe93S猫头猫}
1379927dbe93S猫头猫
1380927dbe93S猫头猫function getValidPlugins() {
1381927dbe93S猫头猫    return plugins.filter(_ => _.state === 'enabled');
1382927dbe93S猫头猫}
1383927dbe93S猫头猫
13842b80a429S猫头猫function getSearchablePlugins(supportedSearchType?: ICommon.SupportMediaType) {
13852b80a429S猫头猫    return plugins.filter(
13862b80a429S猫头猫        _ =>
13872b80a429S猫头猫            _.state === 'enabled' &&
13882b80a429S猫头猫            _.instance.search &&
138939ac60f7S猫头猫            (supportedSearchType && _.instance.supportedSearchType
139039ac60f7S猫头猫                ? _.instance.supportedSearchType.includes(supportedSearchType)
13912b80a429S猫头猫                : true),
13922b80a429S猫头猫    );
1393efb9da24S猫头猫}
1394efb9da24S猫头猫
13952b80a429S猫头猫function getSortedSearchablePlugins(
13962b80a429S猫头猫    supportedSearchType?: ICommon.SupportMediaType,
13972b80a429S猫头猫) {
13982b80a429S猫头猫    return getSearchablePlugins(supportedSearchType).sort((a, b) =>
1399e08d37a3S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1400e08d37a3S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1401e08d37a3S猫头猫        0
1402e08d37a3S猫头猫            ? -1
1403e08d37a3S猫头猫            : 1,
1404e08d37a3S猫头猫    );
1405e08d37a3S猫头猫}
1406e08d37a3S猫头猫
140715feccc1S猫头猫function getTopListsablePlugins() {
140815feccc1S猫头猫    return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists);
140915feccc1S猫头猫}
141015feccc1S猫头猫
141115feccc1S猫头猫function getSortedTopListsablePlugins() {
141215feccc1S猫头猫    return getTopListsablePlugins().sort((a, b) =>
141315feccc1S猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
141415feccc1S猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
141515feccc1S猫头猫        0
141615feccc1S猫头猫            ? -1
141715feccc1S猫头猫            : 1,
141815feccc1S猫头猫    );
141915feccc1S猫头猫}
142015feccc1S猫头猫
1421ceb900cdS猫头猫function getRecommendSheetablePlugins() {
1422ceb900cdS猫头猫    return plugins.filter(
1423ceb900cdS猫头猫        _ => _.state === 'enabled' && _.instance.getRecommendSheetsByTag,
1424ceb900cdS猫头猫    );
1425ceb900cdS猫头猫}
1426ceb900cdS猫头猫
1427ceb900cdS猫头猫function getSortedRecommendSheetablePlugins() {
1428ceb900cdS猫头猫    return getRecommendSheetablePlugins().sort((a, b) =>
1429ceb900cdS猫头猫        (PluginMeta.getPluginMeta(a).order ?? Infinity) -
1430ceb900cdS猫头猫            (PluginMeta.getPluginMeta(b).order ?? Infinity) <
1431ceb900cdS猫头猫        0
1432ceb900cdS猫头猫            ? -1
1433ceb900cdS猫头猫            : 1,
1434ceb900cdS猫头猫    );
1435ceb900cdS猫头猫}
1436ceb900cdS猫头猫
1437e08d37a3S猫头猫function useSortedPlugins() {
1438e08d37a3S猫头猫    const _plugins = pluginStateMapper.useMappedState();
1439e08d37a3S猫头猫    const _pluginMetaAll = PluginMeta.usePluginMetaAll();
1440e08d37a3S猫头猫
144134588741S猫头猫    const [sortedPlugins, setSortedPlugins] = useState(
144234588741S猫头猫        [..._plugins].sort((a, b) =>
1443e08d37a3S猫头猫            (_pluginMetaAll[a.name]?.order ?? Infinity) -
1444e08d37a3S猫头猫                (_pluginMetaAll[b.name]?.order ?? Infinity) <
1445e08d37a3S猫头猫            0
1446e08d37a3S猫头猫                ? -1
1447e08d37a3S猫头猫                : 1,
144834588741S猫头猫        ),
1449e08d37a3S猫头猫    );
145034588741S猫头猫
145134588741S猫头猫    useEffect(() => {
1452d4cd40d8S猫头猫        InteractionManager.runAfterInteractions(() => {
145334588741S猫头猫            setSortedPlugins(
145434588741S猫头猫                [..._plugins].sort((a, b) =>
145534588741S猫头猫                    (_pluginMetaAll[a.name]?.order ?? Infinity) -
145634588741S猫头猫                        (_pluginMetaAll[b.name]?.order ?? Infinity) <
145734588741S猫头猫                    0
145834588741S猫头猫                        ? -1
145934588741S猫头猫                        : 1,
146034588741S猫头猫                ),
146134588741S猫头猫            );
1462d4cd40d8S猫头猫        });
146334588741S猫头猫    }, [_plugins, _pluginMetaAll]);
146434588741S猫头猫
146534588741S猫头猫    return sortedPlugins;
1466e08d37a3S猫头猫}
1467e08d37a3S猫头猫
1468c2b3a262S猫头猫async function setPluginEnabled(plugin: Plugin, enabled?: boolean) {
1469c2b3a262S猫头猫    const target = plugins.find(it => it.hash === plugin.hash);
1470c2b3a262S猫头猫    if (target) {
1471c2b3a262S猫头猫        target.state = enabled ? 'enabled' : 'disabled';
1472c2b3a262S猫头猫        plugins = [...plugins];
1473c2b3a262S猫头猫        pluginStateMapper.notify();
1474c2b3a262S猫头猫        PluginMeta.setPluginMetaProp(plugin, 'enabled', enabled);
1475c2b3a262S猫头猫    }
1476c2b3a262S猫头猫}
1477c2b3a262S猫头猫
1478927dbe93S猫头猫const PluginManager = {
1479927dbe93S猫头猫    setup,
148050245d53Smaotoumao    installPluginFromLocalFile,
148158992c6bS猫头猫    installPluginFromUrl,
148225c1bd29S猫头猫    updatePlugin,
1483927dbe93S猫头猫    uninstallPlugin,
1484927dbe93S猫头猫    getByMedia,
1485927dbe93S猫头猫    getByHash,
1486927dbe93S猫头猫    getByName,
1487927dbe93S猫头猫    getValidPlugins,
1488efb9da24S猫头猫    getSearchablePlugins,
1489e08d37a3S猫头猫    getSortedSearchablePlugins,
149015feccc1S猫头猫    getTopListsablePlugins,
1491ceb900cdS猫头猫    getSortedRecommendSheetablePlugins,
149215feccc1S猫头猫    getSortedTopListsablePlugins,
14935276aef9S猫头猫    usePlugins: pluginStateMapper.useMappedState,
1494e08d37a3S猫头猫    useSortedPlugins,
149508882a77S猫头猫    uninstallAllPlugins,
1496c2b3a262S猫头猫    setPluginEnabled,
14975276aef9S猫头猫};
1498927dbe93S猫头猫
1499927dbe93S猫头猫export default PluginManager;
1500