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'; 20927dbe93S猫头猫import MediaMeta from './mediaMeta'; 21927dbe93S猫头猫import {nanoid} from 'nanoid'; 22ea6d708fS猫头猫import {devLog, errorLog, trace} from '../utils/log'; 23927dbe93S猫头猫import Cache from './cache'; 24cfa0fc07S猫头猫import { 250e4173cdS猫头猫 getInternalData, 260e4173cdS猫头猫 InternalDataType, 270e4173cdS猫头猫 isSameMediaItem, 280e4173cdS猫头猫 resetMediaItem, 290e4173cdS猫头猫} from '@/utils/mediaItem'; 307993f90eS猫头猫import { 317993f90eS猫头猫 CacheControl, 32e08d37a3S猫头猫 emptyFunction, 337993f90eS猫头猫 internalSerializeKey, 347993f90eS猫头猫 localPluginHash, 357993f90eS猫头猫 localPluginPlatform, 367993f90eS猫头猫} from '@/constants/commonConst'; 37927dbe93S猫头猫import delay from '@/utils/delay'; 384d9d3c4cS猫头猫import * as cheerio from 'cheerio'; 397d7e864fS猫头猫import CookieManager from '@react-native-cookies/cookies'; 407d7e864fS猫头猫import he from 'he'; 41ef714860S猫头猫import Network from './network'; 420e4173cdS猫头猫import LocalMusicSheet from './localMusicSheet'; 430e4173cdS猫头猫import {FileSystem} from 'react-native-file-access'; 4474d0cf81S猫头猫import Mp3Util from '@/native/mp3Util'; 45e08d37a3S猫头猫import {PluginMeta} from './pluginMeta'; 4634588741S猫头猫import {useEffect, useState} from 'react'; 47a84a85c5S猫头猫import {getFileName} from '@/utils/fileUtils'; 48*a7b42a4cS猫头猫import {URL} from 'react-native-url-polyfill'; 49*a7b42a4cS猫头猫import Base64 from '@/utils/base64'; 50927dbe93S猫头猫 5161aca335S猫头猫axios.defaults.timeout = 2000; 52927dbe93S猫头猫 53927dbe93S猫头猫const sha256 = CryptoJs.SHA256; 54927dbe93S猫头猫 55cfa0fc07S猫头猫export enum PluginStateCode { 56927dbe93S猫头猫 /** 版本不匹配 */ 57927dbe93S猫头猫 VersionNotMatch = 'VERSION NOT MATCH', 58927dbe93S猫头猫 /** 无法解析 */ 59927dbe93S猫头猫 CannotParse = 'CANNOT PARSE', 60927dbe93S猫头猫} 61927dbe93S猫头猫 629c34d637S猫头猫const packages: Record<string, any> = { 639c34d637S猫头猫 cheerio, 649c34d637S猫头猫 'crypto-js': CryptoJs, 659c34d637S猫头猫 axios, 669c34d637S猫头猫 dayjs, 679c34d637S猫头猫 'big-integer': bigInt, 689c34d637S猫头猫 qs, 699c34d637S猫头猫 he, 703b3d6357S猫头猫 '@react-native-cookies/cookies': CookieManager, 7128ccceb7S猫头猫 webdav, 729c34d637S猫头猫}; 739c34d637S猫头猫 74b43683eaS猫头猫const _require = (packageName: string) => { 75b43683eaS猫头猫 let pkg = packages[packageName]; 76b43683eaS猫头猫 pkg.default = pkg; 77b43683eaS猫头猫 return pkg; 78b43683eaS猫头猫}; 799c34d637S猫头猫 8053f8cd8eS猫头猫const _consoleBind = function ( 8153f8cd8eS猫头猫 method: 'log' | 'error' | 'info' | 'warn', 8253f8cd8eS猫头猫 ...args: any 8353f8cd8eS猫头猫) { 8453f8cd8eS猫头猫 const fn = console[method]; 8553f8cd8eS猫头猫 if (fn) { 8653f8cd8eS猫头猫 fn(...args); 8753f8cd8eS猫头猫 devLog(method, ...args); 8853f8cd8eS猫头猫 } 8953f8cd8eS猫头猫}; 9053f8cd8eS猫头猫 9153f8cd8eS猫头猫const _console = { 9253f8cd8eS猫头猫 log: _consoleBind.bind(null, 'log'), 9353f8cd8eS猫头猫 warn: _consoleBind.bind(null, 'warn'), 9453f8cd8eS猫头猫 info: _consoleBind.bind(null, 'info'), 9553f8cd8eS猫头猫 error: _consoleBind.bind(null, 'error'), 9653f8cd8eS猫头猫}; 9753f8cd8eS猫头猫 98*a7b42a4cS猫头猫function formatAuthUrl(url: string) { 99*a7b42a4cS猫头猫 const urlObj = new URL(url); 100*a7b42a4cS猫头猫 101*a7b42a4cS猫头猫 try { 102*a7b42a4cS猫头猫 if (urlObj.username && urlObj.password) { 103*a7b42a4cS猫头猫 const auth = `Basic ${Base64.btoa( 104*a7b42a4cS猫头猫 `${decodeURIComponent(urlObj.username)}:${decodeURIComponent( 105*a7b42a4cS猫头猫 urlObj.password, 106*a7b42a4cS猫头猫 )}`, 107*a7b42a4cS猫头猫 )}`; 108*a7b42a4cS猫头猫 urlObj.username = ''; 109*a7b42a4cS猫头猫 urlObj.password = ''; 110*a7b42a4cS猫头猫 111*a7b42a4cS猫头猫 return { 112*a7b42a4cS猫头猫 url: urlObj.toString(), 113*a7b42a4cS猫头猫 auth, 114*a7b42a4cS猫头猫 }; 115*a7b42a4cS猫头猫 } 116*a7b42a4cS猫头猫 } catch (e) { 117*a7b42a4cS猫头猫 return { 118*a7b42a4cS猫头猫 url, 119*a7b42a4cS猫头猫 }; 120*a7b42a4cS猫头猫 } 121*a7b42a4cS猫头猫 return { 122*a7b42a4cS猫头猫 url, 123*a7b42a4cS猫头猫 }; 124*a7b42a4cS猫头猫} 125*a7b42a4cS猫头猫 126d5bfeb7eS猫头猫//#region 插件类 127927dbe93S猫头猫export class Plugin { 128927dbe93S猫头猫 /** 插件名 */ 129927dbe93S猫头猫 public name: string; 130927dbe93S猫头猫 /** 插件的hash,作为唯一id */ 131927dbe93S猫头猫 public hash: string; 132927dbe93S猫头猫 /** 插件状态:激活、关闭、错误 */ 133927dbe93S猫头猫 public state: 'enabled' | 'disabled' | 'error'; 134927dbe93S猫头猫 /** 插件状态信息 */ 135927dbe93S猫头猫 public stateCode?: PluginStateCode; 136927dbe93S猫头猫 /** 插件的实例 */ 137927dbe93S猫头猫 public instance: IPlugin.IPluginInstance; 138927dbe93S猫头猫 /** 插件路径 */ 139927dbe93S猫头猫 public path: string; 140927dbe93S猫头猫 /** 插件方法 */ 141927dbe93S猫头猫 public methods: PluginMethods; 142927dbe93S猫头猫 14374d0cf81S猫头猫 constructor( 14474d0cf81S猫头猫 funcCode: string | (() => IPlugin.IPluginInstance), 14574d0cf81S猫头猫 pluginPath: string, 14674d0cf81S猫头猫 ) { 147927dbe93S猫头猫 this.state = 'enabled'; 148927dbe93S猫头猫 let _instance: IPlugin.IPluginInstance; 1493b3d6357S猫头猫 const _module: any = {exports: {}}; 150927dbe93S猫头猫 try { 15174d0cf81S猫头猫 if (typeof funcCode === 'string') { 152e0caf342S猫头猫 // 插件的环境变量 153e0caf342S猫头猫 const env = { 154e0caf342S猫头猫 getUserVariables: () => { 155e0caf342S猫头猫 return ( 156e0caf342S猫头猫 PluginMeta.getPluginMeta(this)?.userVariables ?? {} 157e0caf342S猫头猫 ); 158e0caf342S猫头猫 }, 159e3fa9b3cS猫头猫 os: 'android', 160e0caf342S猫头猫 }; 161e0caf342S猫头猫 1624060c00aS猫头猫 // eslint-disable-next-line no-new-func 163927dbe93S猫头猫 _instance = Function(` 164927dbe93S猫头猫 'use strict'; 165e0caf342S猫头猫 return function(require, __musicfree_require, module, exports, console, env) { 1669c34d637S猫头猫 ${funcCode} 167927dbe93S猫头猫 } 168e0caf342S猫头猫 `)()( 169e0caf342S猫头猫 _require, 170e0caf342S猫头猫 _require, 171e0caf342S猫头猫 _module, 172e0caf342S猫头猫 _module.exports, 173e0caf342S猫头猫 _console, 174e0caf342S猫头猫 env, 175e0caf342S猫头猫 ); 1763b3d6357S猫头猫 if (_module.exports.default) { 1773b3d6357S猫头猫 _instance = _module.exports 1783b3d6357S猫头猫 .default as IPlugin.IPluginInstance; 1793b3d6357S猫头猫 } else { 1809c34d637S猫头猫 _instance = _module.exports as IPlugin.IPluginInstance; 1813b3d6357S猫头猫 } 18274d0cf81S猫头猫 } else { 18374d0cf81S猫头猫 _instance = funcCode(); 18474d0cf81S猫头猫 } 185c2b3a262S猫头猫 // 插件初始化后的一些操作 18695297592S猫头猫 if (Array.isArray(_instance.userVariables)) { 18795297592S猫头猫 _instance.userVariables = _instance.userVariables.filter( 18895297592S猫头猫 it => it?.key, 18995297592S猫头猫 ); 19095297592S猫头猫 } 191927dbe93S猫头猫 this.checkValid(_instance); 192927dbe93S猫头猫 } catch (e: any) { 193b43683eaS猫头猫 console.log(e); 194927dbe93S猫头猫 this.state = 'error'; 195927dbe93S猫头猫 this.stateCode = PluginStateCode.CannotParse; 196927dbe93S猫头猫 if (e?.stateCode) { 197927dbe93S猫头猫 this.stateCode = e.stateCode; 198927dbe93S猫头猫 } 199927dbe93S猫头猫 errorLog(`${pluginPath}插件无法解析 `, { 200927dbe93S猫头猫 stateCode: this.stateCode, 201927dbe93S猫头猫 message: e?.message, 202927dbe93S猫头猫 stack: e?.stack, 203927dbe93S猫头猫 }); 204927dbe93S猫头猫 _instance = e?.instance ?? { 205927dbe93S猫头猫 _path: '', 206927dbe93S猫头猫 platform: '', 207927dbe93S猫头猫 appVersion: '', 20820e6a092S猫头猫 async getMediaSource() { 209927dbe93S猫头猫 return null; 210927dbe93S猫头猫 }, 211927dbe93S猫头猫 async search() { 212927dbe93S猫头猫 return {}; 213927dbe93S猫头猫 }, 214927dbe93S猫头猫 async getAlbumInfo() { 215927dbe93S猫头猫 return null; 216927dbe93S猫头猫 }, 217927dbe93S猫头猫 }; 218927dbe93S猫头猫 } 219927dbe93S猫头猫 this.instance = _instance; 220927dbe93S猫头猫 this.path = pluginPath; 221927dbe93S猫头猫 this.name = _instance.platform; 222ab8941d9S猫头猫 if ( 223ab8941d9S猫头猫 this.instance.platform === '' || 224ab8941d9S猫头猫 this.instance.platform === undefined 225ab8941d9S猫头猫 ) { 226927dbe93S猫头猫 this.hash = ''; 227927dbe93S猫头猫 } else { 22874d0cf81S猫头猫 if (typeof funcCode === 'string') { 229927dbe93S猫头猫 this.hash = sha256(funcCode).toString(); 23074d0cf81S猫头猫 } else { 23174d0cf81S猫头猫 this.hash = sha256(funcCode.toString()).toString(); 23274d0cf81S猫头猫 } 233927dbe93S猫头猫 } 234927dbe93S猫头猫 235927dbe93S猫头猫 // 放在最后 236927dbe93S猫头猫 this.methods = new PluginMethods(this); 237927dbe93S猫头猫 } 238927dbe93S猫头猫 239927dbe93S猫头猫 private checkValid(_instance: IPlugin.IPluginInstance) { 240927dbe93S猫头猫 /** 版本号校验 */ 241927dbe93S猫头猫 if ( 242927dbe93S猫头猫 _instance.appVersion && 243927dbe93S猫头猫 !satisfies(DeviceInfo.getVersion(), _instance.appVersion) 244927dbe93S猫头猫 ) { 245927dbe93S猫头猫 throw { 246927dbe93S猫头猫 instance: _instance, 247927dbe93S猫头猫 stateCode: PluginStateCode.VersionNotMatch, 248927dbe93S猫头猫 }; 249927dbe93S猫头猫 } 250927dbe93S猫头猫 return true; 251927dbe93S猫头猫 } 252927dbe93S猫头猫} 253d5bfeb7eS猫头猫//#endregion 254927dbe93S猫头猫 255d5bfeb7eS猫头猫//#region 基于插件类封装的方法,供给APP侧直接调用 256927dbe93S猫头猫/** 有缓存等信息 */ 257927dbe93S猫头猫class PluginMethods implements IPlugin.IPluginInstanceMethods { 258927dbe93S猫头猫 private plugin; 259927dbe93S猫头猫 constructor(plugin: Plugin) { 260927dbe93S猫头猫 this.plugin = plugin; 261927dbe93S猫头猫 } 262927dbe93S猫头猫 /** 搜索 */ 263927dbe93S猫头猫 async search<T extends ICommon.SupportMediaType>( 264927dbe93S猫头猫 query: string, 265927dbe93S猫头猫 page: number, 266927dbe93S猫头猫 type: T, 267927dbe93S猫头猫 ): Promise<IPlugin.ISearchResult<T>> { 268927dbe93S猫头猫 if (!this.plugin.instance.search) { 269927dbe93S猫头猫 return { 270927dbe93S猫头猫 isEnd: true, 271927dbe93S猫头猫 data: [], 272927dbe93S猫头猫 }; 273927dbe93S猫头猫 } 274927dbe93S猫头猫 2754060c00aS猫头猫 const result = 2764060c00aS猫头猫 (await this.plugin.instance.search(query, page, type)) ?? {}; 277927dbe93S猫头猫 if (Array.isArray(result.data)) { 278927dbe93S猫头猫 result.data.forEach(_ => { 279927dbe93S猫头猫 resetMediaItem(_, this.plugin.name); 280927dbe93S猫头猫 }); 281927dbe93S猫头猫 return { 282927dbe93S猫头猫 isEnd: result.isEnd ?? true, 283927dbe93S猫头猫 data: result.data, 284927dbe93S猫头猫 }; 285927dbe93S猫头猫 } 286927dbe93S猫头猫 return { 287927dbe93S猫头猫 isEnd: true, 288927dbe93S猫头猫 data: [], 289927dbe93S猫头猫 }; 290927dbe93S猫头猫 } 291927dbe93S猫头猫 292927dbe93S猫头猫 /** 获取真实源 */ 29320e6a092S猫头猫 async getMediaSource( 294927dbe93S猫头猫 musicItem: IMusic.IMusicItemBase, 295abaede57S猫头猫 quality: IMusic.IQualityKey = 'standard', 296927dbe93S猫头猫 retryCount = 1, 297dc160d50S猫头猫 notUpdateCache = false, 298192ae2b0S猫头猫 ): Promise<IPlugin.IMediaSourceResult | null> { 299927dbe93S猫头猫 // 1. 本地搜索 其实直接读mediameta就好了 300927dbe93S猫头猫 const localPath = 3010e4173cdS猫头猫 getInternalData<string>(musicItem, InternalDataType.LOCALPATH) ?? 3020e4173cdS猫头猫 getInternalData<string>( 3030e4173cdS猫头猫 LocalMusicSheet.isLocalMusic(musicItem), 3040e4173cdS猫头猫 InternalDataType.LOCALPATH, 3050e4173cdS猫头猫 ); 306a84a85c5S猫头猫 if ( 307a84a85c5S猫头猫 localPath && 308a84a85c5S猫头猫 (localPath.startsWith('content://') || 309a84a85c5S猫头猫 (await FileSystem.exists(localPath))) 310a84a85c5S猫头猫 ) { 3110e4173cdS猫头猫 trace('本地播放', localPath); 312927dbe93S猫头猫 return { 313927dbe93S猫头猫 url: localPath, 314927dbe93S猫头猫 }; 315927dbe93S猫头猫 } 316a84a85c5S猫头猫 3177993f90eS猫头猫 if (musicItem.platform === localPluginPlatform) { 318f5935920S猫头猫 throw new Error('本地音乐不存在'); 319f5935920S猫头猫 } 320927dbe93S猫头猫 // 2. 缓存播放 321927dbe93S猫头猫 const mediaCache = Cache.get(musicItem); 322985f8e75S猫头猫 const pluginCacheControl = 323985f8e75S猫头猫 this.plugin.instance.cacheControl ?? 'no-cache'; 324cfa0fc07S猫头猫 if ( 325cfa0fc07S猫头猫 mediaCache && 326abaede57S猫头猫 mediaCache?.qualities?.[quality]?.url && 32748f4b873S猫头猫 (pluginCacheControl === CacheControl.Cache || 32848f4b873S猫头猫 (pluginCacheControl === CacheControl.NoCache && 329ef714860S猫头猫 Network.isOffline())) 330cfa0fc07S猫头猫 ) { 3315276aef9S猫头猫 trace('播放', '缓存播放'); 332abaede57S猫头猫 const qualityInfo = mediaCache.qualities[quality]; 333927dbe93S猫头猫 return { 334abaede57S猫头猫 url: qualityInfo.url, 335927dbe93S猫头猫 headers: mediaCache.headers, 3364060c00aS猫头猫 userAgent: 3374060c00aS猫头猫 mediaCache.userAgent ?? mediaCache.headers?.['user-agent'], 338927dbe93S猫头猫 }; 339927dbe93S猫头猫 } 340927dbe93S猫头猫 // 3. 插件解析 34120e6a092S猫头猫 if (!this.plugin.instance.getMediaSource) { 342*a7b42a4cS猫头猫 const {url, auth} = formatAuthUrl( 343*a7b42a4cS猫头猫 musicItem?.qualities?.[quality]?.url ?? musicItem.url, 344*a7b42a4cS猫头猫 ); 345*a7b42a4cS猫头猫 return { 346*a7b42a4cS猫头猫 url: url, 347*a7b42a4cS猫头猫 headers: auth 348*a7b42a4cS猫头猫 ? { 349*a7b42a4cS猫头猫 Authorization: auth, 350*a7b42a4cS猫头猫 } 351*a7b42a4cS猫头猫 : undefined, 352*a7b42a4cS猫头猫 }; 353927dbe93S猫头猫 } 354927dbe93S猫头猫 try { 355abaede57S猫头猫 const {url, headers} = (await this.plugin.instance.getMediaSource( 356abaede57S猫头猫 musicItem, 357abaede57S猫头猫 quality, 358abaede57S猫头猫 )) ?? {url: musicItem?.qualities?.[quality]?.url}; 359927dbe93S猫头猫 if (!url) { 360a28eac61S猫头猫 throw new Error('NOT RETRY'); 361927dbe93S猫头猫 } 3625276aef9S猫头猫 trace('播放', '插件播放'); 363927dbe93S猫头猫 const result = { 364927dbe93S猫头猫 url, 365927dbe93S猫头猫 headers, 366927dbe93S猫头猫 userAgent: headers?.['user-agent'], 367cfa0fc07S猫头猫 } as IPlugin.IMediaSourceResult; 368*a7b42a4cS猫头猫 const authFormattedResult = formatAuthUrl(result.url!); 369*a7b42a4cS猫头猫 if (authFormattedResult.auth) { 370*a7b42a4cS猫头猫 result.url = authFormattedResult.url; 371*a7b42a4cS猫头猫 result.headers = { 372*a7b42a4cS猫头猫 ...(result.headers ?? {}), 373*a7b42a4cS猫头猫 Authorization: authFormattedResult.auth, 374*a7b42a4cS猫头猫 }; 375*a7b42a4cS猫头猫 } 376927dbe93S猫头猫 377dc160d50S猫头猫 if ( 378dc160d50S猫头猫 pluginCacheControl !== CacheControl.NoStore && 379dc160d50S猫头猫 !notUpdateCache 380dc160d50S猫头猫 ) { 381abaede57S猫头猫 Cache.update(musicItem, [ 382abaede57S猫头猫 ['headers', result.headers], 383abaede57S猫头猫 ['userAgent', result.userAgent], 384abaede57S猫头猫 [`qualities.${quality}.url`, url], 385abaede57S猫头猫 ]); 386752ffc5aS猫头猫 } 387927dbe93S猫头猫 return result; 388927dbe93S猫头猫 } catch (e: any) { 389a28eac61S猫头猫 if (retryCount > 0 && e?.message !== 'NOT RETRY') { 390927dbe93S猫头猫 await delay(150); 391abaede57S猫头猫 return this.getMediaSource(musicItem, quality, --retryCount); 392927dbe93S猫头猫 } 393927dbe93S猫头猫 errorLog('获取真实源失败', e?.message); 394ea6d708fS猫头猫 devLog('error', '获取真实源失败', e, e?.message); 395192ae2b0S猫头猫 return null; 396927dbe93S猫头猫 } 397927dbe93S猫头猫 } 398927dbe93S猫头猫 399927dbe93S猫头猫 /** 获取音乐详情 */ 400927dbe93S猫头猫 async getMusicInfo( 401927dbe93S猫头猫 musicItem: ICommon.IMediaBase, 40274d0cf81S猫头猫 ): Promise<Partial<IMusic.IMusicItem> | null> { 403927dbe93S猫头猫 if (!this.plugin.instance.getMusicInfo) { 404d704daedS猫头猫 return null; 405927dbe93S猫头猫 } 40674d0cf81S猫头猫 try { 407927dbe93S猫头猫 return ( 408927dbe93S猫头猫 this.plugin.instance.getMusicInfo( 4097993f90eS猫头猫 resetMediaItem(musicItem, undefined, true), 410d704daedS猫头猫 ) ?? null 411927dbe93S猫头猫 ); 412ea6d708fS猫头猫 } catch (e: any) { 413ea6d708fS猫头猫 devLog('error', '获取音乐详情失败', e, e?.message); 414d704daedS猫头猫 return null; 41574d0cf81S猫头猫 } 416927dbe93S猫头猫 } 417927dbe93S猫头猫 418927dbe93S猫头猫 /** 获取歌词 */ 419927dbe93S猫头猫 async getLyric( 420927dbe93S猫头猫 musicItem: IMusic.IMusicItemBase, 421927dbe93S猫头猫 from?: IMusic.IMusicItemBase, 422927dbe93S猫头猫 ): Promise<ILyric.ILyricSource | null> { 423927dbe93S猫头猫 // 1.额外存储的meta信息 424927dbe93S猫头猫 const meta = MediaMeta.get(musicItem); 425927dbe93S猫头猫 if (meta && meta.associatedLrc) { 426927dbe93S猫头猫 // 有关联歌词 427927dbe93S猫头猫 if ( 428927dbe93S猫头猫 isSameMediaItem(musicItem, from) || 429927dbe93S猫头猫 isSameMediaItem(meta.associatedLrc, musicItem) 430927dbe93S猫头猫 ) { 431927dbe93S猫头猫 // 形成环路,断开当前的环 432927dbe93S猫头猫 await MediaMeta.update(musicItem, { 433927dbe93S猫头猫 associatedLrc: undefined, 434927dbe93S猫头猫 }); 435927dbe93S猫头猫 // 无歌词 436927dbe93S猫头猫 return null; 437927dbe93S猫头猫 } 438927dbe93S猫头猫 // 获取关联歌词 4397a91f04fS猫头猫 const associatedMeta = MediaMeta.get(meta.associatedLrc) ?? {}; 4404060c00aS猫头猫 const result = await this.getLyric( 4417a91f04fS猫头猫 {...meta.associatedLrc, ...associatedMeta}, 4424060c00aS猫头猫 from ?? musicItem, 4434060c00aS猫头猫 ); 444927dbe93S猫头猫 if (result) { 445927dbe93S猫头猫 // 如果有关联歌词,就返回关联歌词,深度优先 446927dbe93S猫头猫 return result; 447927dbe93S猫头猫 } 448927dbe93S猫头猫 } 449927dbe93S猫头猫 const cache = Cache.get(musicItem); 450927dbe93S猫头猫 let rawLrc = meta?.rawLrc || musicItem.rawLrc || cache?.rawLrc; 451927dbe93S猫头猫 let lrcUrl = meta?.lrc || musicItem.lrc || cache?.lrc; 452927dbe93S猫头猫 // 如果存在文本 453927dbe93S猫头猫 if (rawLrc) { 454927dbe93S猫头猫 return { 455927dbe93S猫头猫 rawLrc, 456927dbe93S猫头猫 lrc: lrcUrl, 457927dbe93S猫头猫 }; 458927dbe93S猫头猫 } 459927dbe93S猫头猫 // 2.本地缓存 460927dbe93S猫头猫 const localLrc = 4610e4173cdS猫头猫 meta?.[internalSerializeKey]?.local?.localLrc || 4620e4173cdS猫头猫 cache?.[internalSerializeKey]?.local?.localLrc; 463927dbe93S猫头猫 if (localLrc && (await exists(localLrc))) { 464927dbe93S猫头猫 rawLrc = await readFile(localLrc, 'utf8'); 465927dbe93S猫头猫 return { 466927dbe93S猫头猫 rawLrc, 467927dbe93S猫头猫 lrc: lrcUrl, 468927dbe93S猫头猫 }; 469927dbe93S猫头猫 } 470927dbe93S猫头猫 // 3.优先使用url 471927dbe93S猫头猫 if (lrcUrl) { 472927dbe93S猫头猫 try { 473927dbe93S猫头猫 // 需要超时时间 axios timeout 但是没生效 47461aca335S猫头猫 rawLrc = (await axios.get(lrcUrl, {timeout: 2000})).data; 475927dbe93S猫头猫 return { 476927dbe93S猫头猫 rawLrc, 477927dbe93S猫头猫 lrc: lrcUrl, 478927dbe93S猫头猫 }; 479927dbe93S猫头猫 } catch { 480927dbe93S猫头猫 lrcUrl = undefined; 481927dbe93S猫头猫 } 482927dbe93S猫头猫 } 483927dbe93S猫头猫 // 4. 如果地址失效 484927dbe93S猫头猫 if (!lrcUrl) { 485927dbe93S猫头猫 // 插件获得url 486927dbe93S猫头猫 try { 4877a91f04fS猫头猫 let lrcSource; 4887a91f04fS猫头猫 if (from) { 4897a91f04fS猫头猫 lrcSource = await PluginManager.getByMedia( 4907a91f04fS猫头猫 musicItem, 4917a91f04fS猫头猫 )?.instance?.getLyric?.( 492927dbe93S猫头猫 resetMediaItem(musicItem, undefined, true), 493927dbe93S猫头猫 ); 4947a91f04fS猫头猫 } else { 4957a91f04fS猫头猫 lrcSource = await this.plugin.instance?.getLyric?.( 4967a91f04fS猫头猫 resetMediaItem(musicItem, undefined, true), 4977a91f04fS猫头猫 ); 4987a91f04fS猫头猫 } 4997a91f04fS猫头猫 500927dbe93S猫头猫 rawLrc = lrcSource?.rawLrc; 501927dbe93S猫头猫 lrcUrl = lrcSource?.lrc; 502927dbe93S猫头猫 } catch (e: any) { 503927dbe93S猫头猫 trace('插件获取歌词失败', e?.message, 'error'); 504ea6d708fS猫头猫 devLog('error', '插件获取歌词失败', e, e?.message); 505927dbe93S猫头猫 } 506927dbe93S猫头猫 } 507927dbe93S猫头猫 // 5. 最后一次请求 508927dbe93S猫头猫 if (rawLrc || lrcUrl) { 509927dbe93S猫头猫 const filename = `${pathConst.lrcCachePath}${nanoid()}.lrc`; 510927dbe93S猫头猫 if (lrcUrl) { 511927dbe93S猫头猫 try { 51261aca335S猫头猫 rawLrc = (await axios.get(lrcUrl, {timeout: 2000})).data; 513927dbe93S猫头猫 } catch {} 514927dbe93S猫头猫 } 515927dbe93S猫头猫 if (rawLrc) { 516927dbe93S猫头猫 await writeFile(filename, rawLrc, 'utf8'); 517927dbe93S猫头猫 // 写入缓存 518927dbe93S猫头猫 Cache.update(musicItem, [ 5190e4173cdS猫头猫 [`${internalSerializeKey}.local.localLrc`, filename], 520927dbe93S猫头猫 ]); 521927dbe93S猫头猫 // 如果有meta 522927dbe93S猫头猫 if (meta) { 523927dbe93S猫头猫 MediaMeta.update(musicItem, [ 5240e4173cdS猫头猫 [`${internalSerializeKey}.local.localLrc`, filename], 525927dbe93S猫头猫 ]); 526927dbe93S猫头猫 } 527927dbe93S猫头猫 return { 528927dbe93S猫头猫 rawLrc, 529927dbe93S猫头猫 lrc: lrcUrl, 530927dbe93S猫头猫 }; 531927dbe93S猫头猫 } 532927dbe93S猫头猫 } 5333a6f67b1S猫头猫 // 6. 如果是本地文件 5343a6f67b1S猫头猫 const isDownloaded = LocalMusicSheet.isLocalMusic(musicItem); 5353a6f67b1S猫头猫 if (musicItem.platform !== localPluginPlatform && isDownloaded) { 5363a6f67b1S猫头猫 const res = await localFilePlugin.instance!.getLyric!(isDownloaded); 5373a6f67b1S猫头猫 if (res) { 5383a6f67b1S猫头猫 return res; 5393a6f67b1S猫头猫 } 5403a6f67b1S猫头猫 } 541ea6d708fS猫头猫 devLog('warn', '无歌词'); 542927dbe93S猫头猫 543927dbe93S猫头猫 return null; 544927dbe93S猫头猫 } 545927dbe93S猫头猫 546927dbe93S猫头猫 /** 获取歌词文本 */ 547927dbe93S猫头猫 async getLyricText( 548927dbe93S猫头猫 musicItem: IMusic.IMusicItem, 549927dbe93S猫头猫 ): Promise<string | undefined> { 550927dbe93S猫头猫 return (await this.getLyric(musicItem))?.rawLrc; 551927dbe93S猫头猫 } 552927dbe93S猫头猫 553927dbe93S猫头猫 /** 获取专辑信息 */ 554927dbe93S猫头猫 async getAlbumInfo( 555927dbe93S猫头猫 albumItem: IAlbum.IAlbumItemBase, 556f9afcc0dS猫头猫 page: number = 1, 557f9afcc0dS猫头猫 ): Promise<IPlugin.IAlbumInfoResult | null> { 558927dbe93S猫头猫 if (!this.plugin.instance.getAlbumInfo) { 559f9afcc0dS猫头猫 return { 560f9afcc0dS猫头猫 albumItem, 561f2a4767cS猫头猫 musicList: (albumItem?.musicList ?? []).map( 562f2a4767cS猫头猫 resetMediaItem, 563f2a4767cS猫头猫 this.plugin.name, 564f2a4767cS猫头猫 true, 565f2a4767cS猫头猫 ), 566f9afcc0dS猫头猫 isEnd: true, 567f9afcc0dS猫头猫 }; 568927dbe93S猫头猫 } 569927dbe93S猫头猫 try { 570927dbe93S猫头猫 const result = await this.plugin.instance.getAlbumInfo( 571927dbe93S猫头猫 resetMediaItem(albumItem, undefined, true), 572f9afcc0dS猫头猫 page, 573927dbe93S猫头猫 ); 5745276aef9S猫头猫 if (!result) { 5755276aef9S猫头猫 throw new Error(); 5765276aef9S猫头猫 } 577927dbe93S猫头猫 result?.musicList?.forEach(_ => { 578927dbe93S猫头猫 resetMediaItem(_, this.plugin.name); 57996744680S猫头猫 _.album = albumItem.title; 580927dbe93S猫头猫 }); 5815276aef9S猫头猫 582f9afcc0dS猫头猫 if (page <= 1) { 583f9afcc0dS猫头猫 // 合并信息 584f9afcc0dS猫头猫 return { 585f9afcc0dS猫头猫 albumItem: {...albumItem, ...(result?.albumItem ?? {})}, 586f9afcc0dS猫头猫 isEnd: result.isEnd === false ? false : true, 587f9afcc0dS猫头猫 musicList: result.musicList, 588f9afcc0dS猫头猫 }; 589f9afcc0dS猫头猫 } else { 590f9afcc0dS猫头猫 return { 591f9afcc0dS猫头猫 isEnd: result.isEnd === false ? false : true, 592f9afcc0dS猫头猫 musicList: result.musicList, 593f9afcc0dS猫头猫 }; 594f9afcc0dS猫头猫 } 5954394410dS猫头猫 } catch (e: any) { 5964394410dS猫头猫 trace('获取专辑信息失败', e?.message); 597ea6d708fS猫头猫 devLog('error', '获取专辑信息失败', e, e?.message); 598ea6d708fS猫头猫 599f9afcc0dS猫头猫 return null; 600927dbe93S猫头猫 } 601927dbe93S猫头猫 } 602927dbe93S猫头猫 6035830c002S猫头猫 /** 获取歌单信息 */ 6045830c002S猫头猫 async getMusicSheetInfo( 6055830c002S猫头猫 sheetItem: IMusic.IMusicSheetItem, 6065830c002S猫头猫 page: number = 1, 6075830c002S猫头猫 ): Promise<IPlugin.ISheetInfoResult | null> { 6085281926bS猫头猫 if (!this.plugin.instance.getMusicSheetInfo) { 6095830c002S猫头猫 return { 6105830c002S猫头猫 sheetItem, 6115830c002S猫头猫 musicList: sheetItem?.musicList ?? [], 6125830c002S猫头猫 isEnd: true, 6135830c002S猫头猫 }; 6145830c002S猫头猫 } 6155830c002S猫头猫 try { 6165830c002S猫头猫 const result = await this.plugin.instance?.getMusicSheetInfo?.( 6175830c002S猫头猫 resetMediaItem(sheetItem, undefined, true), 6185830c002S猫头猫 page, 6195830c002S猫头猫 ); 6205830c002S猫头猫 if (!result) { 6215830c002S猫头猫 throw new Error(); 6225830c002S猫头猫 } 6235830c002S猫头猫 result?.musicList?.forEach(_ => { 6245830c002S猫头猫 resetMediaItem(_, this.plugin.name); 6255830c002S猫头猫 }); 6265830c002S猫头猫 6275830c002S猫头猫 if (page <= 1) { 6285830c002S猫头猫 // 合并信息 6295830c002S猫头猫 return { 6305830c002S猫头猫 sheetItem: {...sheetItem, ...(result?.sheetItem ?? {})}, 6315830c002S猫头猫 isEnd: result.isEnd === false ? false : true, 6325830c002S猫头猫 musicList: result.musicList, 6335830c002S猫头猫 }; 6345830c002S猫头猫 } else { 6355830c002S猫头猫 return { 6365830c002S猫头猫 isEnd: result.isEnd === false ? false : true, 6375830c002S猫头猫 musicList: result.musicList, 6385830c002S猫头猫 }; 6395830c002S猫头猫 } 6405830c002S猫头猫 } catch (e: any) { 6415830c002S猫头猫 trace('获取歌单信息失败', e, e?.message); 6425830c002S猫头猫 devLog('error', '获取歌单信息失败', e, e?.message); 6435830c002S猫头猫 6445830c002S猫头猫 return null; 6455830c002S猫头猫 } 6465830c002S猫头猫 } 6475830c002S猫头猫 648927dbe93S猫头猫 /** 查询作者信息 */ 649efb9da24S猫头猫 async getArtistWorks<T extends IArtist.ArtistMediaType>( 650927dbe93S猫头猫 artistItem: IArtist.IArtistItem, 651927dbe93S猫头猫 page: number, 652927dbe93S猫头猫 type: T, 653927dbe93S猫头猫 ): Promise<IPlugin.ISearchResult<T>> { 654efb9da24S猫头猫 if (!this.plugin.instance.getArtistWorks) { 655927dbe93S猫头猫 return { 656927dbe93S猫头猫 isEnd: true, 657927dbe93S猫头猫 data: [], 658927dbe93S猫头猫 }; 659927dbe93S猫头猫 } 660927dbe93S猫头猫 try { 661efb9da24S猫头猫 const result = await this.plugin.instance.getArtistWorks( 662927dbe93S猫头猫 artistItem, 663927dbe93S猫头猫 page, 664927dbe93S猫头猫 type, 665927dbe93S猫头猫 ); 666927dbe93S猫头猫 if (!result.data) { 667927dbe93S猫头猫 return { 668927dbe93S猫头猫 isEnd: true, 669927dbe93S猫头猫 data: [], 670927dbe93S猫头猫 }; 671927dbe93S猫头猫 } 672927dbe93S猫头猫 result.data?.forEach(_ => resetMediaItem(_, this.plugin.name)); 673927dbe93S猫头猫 return { 674927dbe93S猫头猫 isEnd: result.isEnd ?? true, 675927dbe93S猫头猫 data: result.data, 676927dbe93S猫头猫 }; 6774394410dS猫头猫 } catch (e: any) { 6784394410dS猫头猫 trace('查询作者信息失败', e?.message); 679ea6d708fS猫头猫 devLog('error', '查询作者信息失败', e, e?.message); 680ea6d708fS猫头猫 681927dbe93S猫头猫 throw e; 682927dbe93S猫头猫 } 683927dbe93S猫头猫 } 68408380090S猫头猫 68508380090S猫头猫 /** 导入歌单 */ 68608380090S猫头猫 async importMusicSheet(urlLike: string): Promise<IMusic.IMusicItem[]> { 68708380090S猫头猫 try { 68808380090S猫头猫 const result = 68908380090S猫头猫 (await this.plugin.instance?.importMusicSheet?.(urlLike)) ?? []; 69008380090S猫头猫 result.forEach(_ => resetMediaItem(_, this.plugin.name)); 69108380090S猫头猫 return result; 692ea6d708fS猫头猫 } catch (e: any) { 6930e4173cdS猫头猫 console.log(e); 694ea6d708fS猫头猫 devLog('error', '导入歌单失败', e, e?.message); 695ea6d708fS猫头猫 69608380090S猫头猫 return []; 69708380090S猫头猫 } 69808380090S猫头猫 } 6994d9d3c4cS猫头猫 /** 导入单曲 */ 7004d9d3c4cS猫头猫 async importMusicItem(urlLike: string): Promise<IMusic.IMusicItem | null> { 7014d9d3c4cS猫头猫 try { 7024d9d3c4cS猫头猫 const result = await this.plugin.instance?.importMusicItem?.( 7034d9d3c4cS猫头猫 urlLike, 7044d9d3c4cS猫头猫 ); 7054d9d3c4cS猫头猫 if (!result) { 7064d9d3c4cS猫头猫 throw new Error(); 7074d9d3c4cS猫头猫 } 7084d9d3c4cS猫头猫 resetMediaItem(result, this.plugin.name); 7094d9d3c4cS猫头猫 return result; 710ea6d708fS猫头猫 } catch (e: any) { 711ea6d708fS猫头猫 devLog('error', '导入单曲失败', e, e?.message); 712ea6d708fS猫头猫 7134d9d3c4cS猫头猫 return null; 7144d9d3c4cS猫头猫 } 7154d9d3c4cS猫头猫 } 716d52aa40eS猫头猫 /** 获取榜单 */ 71792b6c95aS猫头猫 async getTopLists(): Promise<IMusic.IMusicSheetGroupItem[]> { 718d52aa40eS猫头猫 try { 719d52aa40eS猫头猫 const result = await this.plugin.instance?.getTopLists?.(); 720d52aa40eS猫头猫 if (!result) { 721d52aa40eS猫头猫 throw new Error(); 722d52aa40eS猫头猫 } 723d52aa40eS猫头猫 return result; 724d52aa40eS猫头猫 } catch (e: any) { 725d52aa40eS猫头猫 devLog('error', '获取榜单失败', e, e?.message); 726d52aa40eS猫头猫 return []; 727d52aa40eS猫头猫 } 728d52aa40eS猫头猫 } 729d52aa40eS猫头猫 /** 获取榜单详情 */ 730d52aa40eS猫头猫 async getTopListDetail( 73192b6c95aS猫头猫 topListItem: IMusic.IMusicSheetItemBase, 732956ee1b7S猫头猫 page: number, 733956ee1b7S猫头猫 ): Promise<IPlugin.ITopListInfoResult> { 734d52aa40eS猫头猫 try { 735d52aa40eS猫头猫 const result = await this.plugin.instance?.getTopListDetail?.( 736d52aa40eS猫头猫 topListItem, 737956ee1b7S猫头猫 page, 738d52aa40eS猫头猫 ); 739d52aa40eS猫头猫 if (!result) { 740d52aa40eS猫头猫 throw new Error(); 741d52aa40eS猫头猫 } 742d384662fS猫头猫 if (result.musicList) { 743d384662fS猫头猫 result.musicList.forEach(_ => 744d384662fS猫头猫 resetMediaItem(_, this.plugin.name), 745d384662fS猫头猫 ); 746d384662fS猫头猫 } 747956ee1b7S猫头猫 if (result.isEnd !== false) { 748956ee1b7S猫头猫 result.isEnd = true; 749956ee1b7S猫头猫 } 750d52aa40eS猫头猫 return result; 751d52aa40eS猫头猫 } catch (e: any) { 752d52aa40eS猫头猫 devLog('error', '获取榜单详情失败', e, e?.message); 753d52aa40eS猫头猫 return { 754956ee1b7S猫头猫 isEnd: true, 755956ee1b7S猫头猫 topListItem: topListItem as IMusic.IMusicSheetItem, 756d52aa40eS猫头猫 musicList: [], 757d52aa40eS猫头猫 }; 758d52aa40eS猫头猫 } 759d52aa40eS猫头猫 } 760ceb900cdS猫头猫 7615830c002S猫头猫 /** 获取推荐歌单的tag */ 762ceb900cdS猫头猫 async getRecommendSheetTags(): Promise<IPlugin.IGetRecommendSheetTagsResult> { 763ceb900cdS猫头猫 try { 764ceb900cdS猫头猫 const result = 765ceb900cdS猫头猫 await this.plugin.instance?.getRecommendSheetTags?.(); 766ceb900cdS猫头猫 if (!result) { 767ceb900cdS猫头猫 throw new Error(); 768ceb900cdS猫头猫 } 769ceb900cdS猫头猫 return result; 770ceb900cdS猫头猫 } catch (e: any) { 771ceb900cdS猫头猫 devLog('error', '获取推荐歌单失败', e, e?.message); 772ceb900cdS猫头猫 return { 773ceb900cdS猫头猫 data: [], 774ceb900cdS猫头猫 }; 775ceb900cdS猫头猫 } 776ceb900cdS猫头猫 } 7775830c002S猫头猫 /** 获取某个tag的推荐歌单 */ 778ceb900cdS猫头猫 async getRecommendSheetsByTag( 779ceb900cdS猫头猫 tagItem: ICommon.IUnique, 780ceb900cdS猫头猫 page?: number, 781ceb900cdS猫头猫 ): Promise<ICommon.PaginationResponse<IMusic.IMusicSheetItemBase>> { 782ceb900cdS猫头猫 try { 783ceb900cdS猫头猫 const result = 784ceb900cdS猫头猫 await this.plugin.instance?.getRecommendSheetsByTag?.( 785ceb900cdS猫头猫 tagItem, 786ceb900cdS猫头猫 page ?? 1, 787ceb900cdS猫头猫 ); 788ceb900cdS猫头猫 if (!result) { 789ceb900cdS猫头猫 throw new Error(); 790ceb900cdS猫头猫 } 791ceb900cdS猫头猫 if (result.isEnd !== false) { 792ceb900cdS猫头猫 result.isEnd = true; 793ceb900cdS猫头猫 } 794ceb900cdS猫头猫 if (!result.data) { 795ceb900cdS猫头猫 result.data = []; 796ceb900cdS猫头猫 } 797ceb900cdS猫头猫 result.data.forEach(item => resetMediaItem(item, this.plugin.name)); 798ceb900cdS猫头猫 799ceb900cdS猫头猫 return result; 800ceb900cdS猫头猫 } catch (e: any) { 801ceb900cdS猫头猫 devLog('error', '获取推荐歌单详情失败', e, e?.message); 802ceb900cdS猫头猫 return { 803ceb900cdS猫头猫 isEnd: true, 804ceb900cdS猫头猫 data: [], 805ceb900cdS猫头猫 }; 806ceb900cdS猫头猫 } 807ceb900cdS猫头猫 } 808927dbe93S猫头猫} 809d5bfeb7eS猫头猫//#endregion 8101a5528a0S猫头猫 811927dbe93S猫头猫let plugins: Array<Plugin> = []; 812927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins); 81374d0cf81S猫头猫 814d5bfeb7eS猫头猫//#region 本地音乐插件 81574d0cf81S猫头猫/** 本地插件 */ 81674d0cf81S猫头猫const localFilePlugin = new Plugin(function () { 8170e4173cdS猫头猫 return { 818d5bfeb7eS猫头猫 platform: localPluginPlatform, 81974d0cf81S猫头猫 _path: '', 82074d0cf81S猫头猫 async getMusicInfo(musicBase) { 82174d0cf81S猫头猫 const localPath = getInternalData<string>( 82274d0cf81S猫头猫 musicBase, 82374d0cf81S猫头猫 InternalDataType.LOCALPATH, 8240e4173cdS猫头猫 ); 82574d0cf81S猫头猫 if (localPath) { 82674d0cf81S猫头猫 const coverImg = await Mp3Util.getMediaCoverImg(localPath); 82774d0cf81S猫头猫 return { 82874d0cf81S猫头猫 artwork: coverImg, 82974d0cf81S猫头猫 }; 83074d0cf81S猫头猫 } 83174d0cf81S猫头猫 return null; 83274d0cf81S猫头猫 }, 8337993f90eS猫头猫 async getLyric(musicBase) { 8347993f90eS猫头猫 const localPath = getInternalData<string>( 8357993f90eS猫头猫 musicBase, 8367993f90eS猫头猫 InternalDataType.LOCALPATH, 8377993f90eS猫头猫 ); 8383a6f67b1S猫头猫 let rawLrc: string | null = null; 8397993f90eS猫头猫 if (localPath) { 8403a6f67b1S猫头猫 // 读取内嵌歌词 8413a6f67b1S猫头猫 try { 8423a6f67b1S猫头猫 rawLrc = await Mp3Util.getLyric(localPath); 8433a6f67b1S猫头猫 } catch (e) { 844a84a85c5S猫头猫 console.log('读取内嵌歌词失败', e); 8457993f90eS猫头猫 } 8463a6f67b1S猫头猫 if (!rawLrc) { 8473a6f67b1S猫头猫 // 读取配置歌词 8483a6f67b1S猫头猫 const lastDot = localPath.lastIndexOf('.'); 8493a6f67b1S猫头猫 const lrcPath = localPath.slice(0, lastDot) + '.lrc'; 8503a6f67b1S猫头猫 8513a6f67b1S猫头猫 try { 8523a6f67b1S猫头猫 if (await exists(lrcPath)) { 8533a6f67b1S猫头猫 rawLrc = await readFile(lrcPath, 'utf8'); 8543a6f67b1S猫头猫 } 8553a6f67b1S猫头猫 } catch {} 8563a6f67b1S猫头猫 } 8573a6f67b1S猫头猫 } 8583a6f67b1S猫头猫 8593a6f67b1S猫头猫 return rawLrc 8603a6f67b1S猫头猫 ? { 8613a6f67b1S猫头猫 rawLrc, 8623a6f67b1S猫头猫 } 8633a6f67b1S猫头猫 : null; 8647993f90eS猫头猫 }, 865a84a85c5S猫头猫 async importMusicItem(urlLike) { 866a84a85c5S猫头猫 let meta: any = {}; 867a84a85c5S猫头猫 try { 868a84a85c5S猫头猫 meta = await Mp3Util.getBasicMeta(urlLike); 869a84a85c5S猫头猫 } catch {} 870a84a85c5S猫头猫 const id = await FileSystem.hash(urlLike, 'MD5'); 871a84a85c5S猫头猫 return { 872a84a85c5S猫头猫 id: id, 873a84a85c5S猫头猫 platform: '本地', 874a84a85c5S猫头猫 title: meta?.title ?? getFileName(urlLike), 875a84a85c5S猫头猫 artist: meta?.artist ?? '未知歌手', 876a84a85c5S猫头猫 duration: parseInt(meta?.duration ?? '0') / 1000, 877a84a85c5S猫头猫 album: meta?.album ?? '未知专辑', 878a84a85c5S猫头猫 artwork: '', 879a84a85c5S猫头猫 [internalSerializeKey]: { 880a84a85c5S猫头猫 localPath: urlLike, 881a84a85c5S猫头猫 }, 882a84a85c5S猫头猫 }; 883a84a85c5S猫头猫 }, 88474d0cf81S猫头猫 }; 88574d0cf81S猫头猫}, ''); 8867993f90eS猫头猫localFilePlugin.hash = localPluginHash; 887927dbe93S猫头猫 888d5bfeb7eS猫头猫//#endregion 889d5bfeb7eS猫头猫 890927dbe93S猫头猫async function setup() { 891927dbe93S猫头猫 const _plugins: Array<Plugin> = []; 892927dbe93S猫头猫 try { 893927dbe93S猫头猫 // 加载插件 894927dbe93S猫头猫 const pluginsPaths = await readDir(pathConst.pluginPath); 895927dbe93S猫头猫 for (let i = 0; i < pluginsPaths.length; ++i) { 896927dbe93S猫头猫 const _pluginUrl = pluginsPaths[i]; 8971e263108S猫头猫 trace('初始化插件', _pluginUrl); 8981e263108S猫头猫 if ( 8991e263108S猫头猫 _pluginUrl.isFile() && 9001e263108S猫头猫 (_pluginUrl.name?.endsWith?.('.js') || 9011e263108S猫头猫 _pluginUrl.path?.endsWith?.('.js')) 9021e263108S猫头猫 ) { 903927dbe93S猫头猫 const funcCode = await readFile(_pluginUrl.path, 'utf8'); 904927dbe93S猫头猫 const plugin = new Plugin(funcCode, _pluginUrl.path); 9054060c00aS猫头猫 const _pluginIndex = _plugins.findIndex( 9064060c00aS猫头猫 p => p.hash === plugin.hash, 9074060c00aS猫头猫 ); 908927dbe93S猫头猫 if (_pluginIndex !== -1) { 909927dbe93S猫头猫 // 重复插件,直接忽略 9100c266394S猫头猫 continue; 911927dbe93S猫头猫 } 912927dbe93S猫头猫 plugin.hash !== '' && _plugins.push(plugin); 913927dbe93S猫头猫 } 914927dbe93S猫头猫 } 915927dbe93S猫头猫 916927dbe93S猫头猫 plugins = _plugins; 917e08d37a3S猫头猫 /** 初始化meta信息 */ 918c2b3a262S猫头猫 await PluginMeta.setupMeta(plugins.map(_ => _.name)); 919c2b3a262S猫头猫 /** 查看一下是否有禁用的标记 */ 920c2b3a262S猫头猫 const allMeta = PluginMeta.getPluginMetaAll() ?? {}; 921c2b3a262S猫头猫 for (let plugin of plugins) { 922c2b3a262S猫头猫 if (allMeta[plugin.name]?.enabled === false) { 923c2b3a262S猫头猫 plugin.state = 'disabled'; 924c2b3a262S猫头猫 } 925c2b3a262S猫头猫 } 926c2b3a262S猫头猫 pluginStateMapper.notify(); 927927dbe93S猫头猫 } catch (e: any) { 9284060c00aS猫头猫 ToastAndroid.show( 9294060c00aS猫头猫 `插件初始化失败:${e?.message ?? e}`, 9304060c00aS猫头猫 ToastAndroid.LONG, 9314060c00aS猫头猫 ); 9321a5528a0S猫头猫 errorLog('插件初始化失败', e?.message); 933927dbe93S猫头猫 throw e; 934927dbe93S猫头猫 } 935927dbe93S猫头猫} 936927dbe93S猫头猫 937e36e2599S猫头猫interface IInstallPluginConfig { 938e36e2599S猫头猫 notCheckVersion?: boolean; 939e36e2599S猫头猫} 940e36e2599S猫头猫 941927dbe93S猫头猫// 安装插件 942e36e2599S猫头猫async function installPlugin( 943e36e2599S猫头猫 pluginPath: string, 944e36e2599S猫头猫 config?: IInstallPluginConfig, 945e36e2599S猫头猫) { 94622c09412S猫头猫 // if (pluginPath.endsWith('.js')) { 947927dbe93S猫头猫 const funcCode = await readFile(pluginPath, 'utf8'); 948e36e2599S猫头猫 949e36e2599S猫头猫 if (funcCode) { 950927dbe93S猫头猫 const plugin = new Plugin(funcCode, pluginPath); 951927dbe93S猫头猫 const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash); 952927dbe93S猫头猫 if (_pluginIndex !== -1) { 953e36e2599S猫头猫 // 静默忽略 954e36e2599S猫头猫 return plugin; 955927dbe93S猫头猫 } 956e36e2599S猫头猫 const oldVersionPlugin = plugins.find(p => p.name === plugin.name); 957e36e2599S猫头猫 if (oldVersionPlugin && !config?.notCheckVersion) { 958e36e2599S猫头猫 if ( 959e36e2599S猫头猫 compare( 960e36e2599S猫头猫 oldVersionPlugin.instance.version ?? '', 961e36e2599S猫头猫 plugin.instance.version ?? '', 962e36e2599S猫头猫 '>', 963e36e2599S猫头猫 ) 964e36e2599S猫头猫 ) { 965e36e2599S猫头猫 throw new Error('已安装更新版本的插件'); 966e36e2599S猫头猫 } 967e36e2599S猫头猫 } 968e36e2599S猫头猫 969927dbe93S猫头猫 if (plugin.hash !== '') { 970927dbe93S猫头猫 const fn = nanoid(); 971e36e2599S猫头猫 if (oldVersionPlugin) { 972e36e2599S猫头猫 plugins = plugins.filter(_ => _.hash !== oldVersionPlugin.hash); 973e36e2599S猫头猫 try { 974e36e2599S猫头猫 await unlink(oldVersionPlugin.path); 975e36e2599S猫头猫 } catch {} 976e36e2599S猫头猫 } 977927dbe93S猫头猫 const _pluginPath = `${pathConst.pluginPath}${fn}.js`; 978927dbe93S猫头猫 await copyFile(pluginPath, _pluginPath); 979927dbe93S猫头猫 plugin.path = _pluginPath; 980927dbe93S猫头猫 plugins = plugins.concat(plugin); 981927dbe93S猫头猫 pluginStateMapper.notify(); 982a84a85c5S猫头猫 return plugin; 983927dbe93S猫头猫 } 984e36e2599S猫头猫 throw new Error('插件无法解析!'); 985927dbe93S猫头猫 } 986e36e2599S猫头猫 throw new Error('插件无法识别!'); 987c2b3a262S猫头猫} 988c2b3a262S猫头猫 989c2b3a262S猫头猫async function installPluginFromUrl( 990c2b3a262S猫头猫 url: string, 991c2b3a262S猫头猫 config?: IInstallPluginConfig, 992c2b3a262S猫头猫) { 99358992c6bS猫头猫 try { 99458992c6bS猫头猫 const funcCode = (await axios.get(url)).data; 99558992c6bS猫头猫 if (funcCode) { 99658992c6bS猫头猫 const plugin = new Plugin(funcCode, ''); 99758992c6bS猫头猫 const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash); 99858992c6bS猫头猫 if (_pluginIndex !== -1) { 9998b7ddca8S猫头猫 // 静默忽略 10008b7ddca8S猫头猫 return; 100158992c6bS猫头猫 } 100225c1bd29S猫头猫 const oldVersionPlugin = plugins.find(p => p.name === plugin.name); 1003c2b3a262S猫头猫 if (oldVersionPlugin && !config?.notCheckVersion) { 100425c1bd29S猫头猫 if ( 100525c1bd29S猫头猫 compare( 100625c1bd29S猫头猫 oldVersionPlugin.instance.version ?? '', 100725c1bd29S猫头猫 plugin.instance.version ?? '', 100825c1bd29S猫头猫 '>', 100925c1bd29S猫头猫 ) 101025c1bd29S猫头猫 ) { 101125c1bd29S猫头猫 throw new Error('已安装更新版本的插件'); 101225c1bd29S猫头猫 } 101325c1bd29S猫头猫 } 101425c1bd29S猫头猫 101558992c6bS猫头猫 if (plugin.hash !== '') { 101658992c6bS猫头猫 const fn = nanoid(); 101758992c6bS猫头猫 const _pluginPath = `${pathConst.pluginPath}${fn}.js`; 101858992c6bS猫头猫 await writeFile(_pluginPath, funcCode, 'utf8'); 101958992c6bS猫头猫 plugin.path = _pluginPath; 102058992c6bS猫头猫 plugins = plugins.concat(plugin); 102125c1bd29S猫头猫 if (oldVersionPlugin) { 102225c1bd29S猫头猫 plugins = plugins.filter( 102325c1bd29S猫头猫 _ => _.hash !== oldVersionPlugin.hash, 102425c1bd29S猫头猫 ); 102525c1bd29S猫头猫 try { 102625c1bd29S猫头猫 await unlink(oldVersionPlugin.path); 102725c1bd29S猫头猫 } catch {} 102825c1bd29S猫头猫 } 102958992c6bS猫头猫 pluginStateMapper.notify(); 103058992c6bS猫头猫 return; 103158992c6bS猫头猫 } 103274acbfc0S猫头猫 throw new Error('插件无法解析!'); 103358992c6bS猫头猫 } 103425c1bd29S猫头猫 } catch (e: any) { 1035ea6d708fS猫头猫 devLog('error', 'URL安装插件失败', e, e?.message); 103658992c6bS猫头猫 errorLog('URL安装插件失败', e); 103725c1bd29S猫头猫 throw new Error(e?.message ?? ''); 103858992c6bS猫头猫 } 103958992c6bS猫头猫} 104058992c6bS猫头猫 1041927dbe93S猫头猫/** 卸载插件 */ 1042927dbe93S猫头猫async function uninstallPlugin(hash: string) { 1043927dbe93S猫头猫 const targetIndex = plugins.findIndex(_ => _.hash === hash); 1044927dbe93S猫头猫 if (targetIndex !== -1) { 1045927dbe93S猫头猫 try { 104624e5e74aS猫头猫 const pluginName = plugins[targetIndex].name; 1047927dbe93S猫头猫 await unlink(plugins[targetIndex].path); 1048927dbe93S猫头猫 plugins = plugins.filter(_ => _.hash !== hash); 1049927dbe93S猫头猫 pluginStateMapper.notify(); 105024e5e74aS猫头猫 if (plugins.every(_ => _.name !== pluginName)) { 105124e5e74aS猫头猫 await MediaMeta.removePlugin(pluginName); 105224e5e74aS猫头猫 } 1053927dbe93S猫头猫 } catch {} 1054927dbe93S猫头猫 } 1055927dbe93S猫头猫} 1056927dbe93S猫头猫 105708882a77S猫头猫async function uninstallAllPlugins() { 105808882a77S猫头猫 await Promise.all( 105908882a77S猫头猫 plugins.map(async plugin => { 106008882a77S猫头猫 try { 106108882a77S猫头猫 const pluginName = plugin.name; 106208882a77S猫头猫 await unlink(plugin.path); 106308882a77S猫头猫 await MediaMeta.removePlugin(pluginName); 106408882a77S猫头猫 } catch (e) {} 106508882a77S猫头猫 }), 106608882a77S猫头猫 ); 106708882a77S猫头猫 plugins = []; 106808882a77S猫头猫 pluginStateMapper.notify(); 1069e08d37a3S猫头猫 1070e08d37a3S猫头猫 /** 清除空余文件,异步做就可以了 */ 1071e08d37a3S猫头猫 readDir(pathConst.pluginPath) 1072e08d37a3S猫头猫 .then(fns => { 1073e08d37a3S猫头猫 fns.forEach(fn => { 1074e08d37a3S猫头猫 unlink(fn.path).catch(emptyFunction); 1075e08d37a3S猫头猫 }); 1076e08d37a3S猫头猫 }) 1077e08d37a3S猫头猫 .catch(emptyFunction); 107808882a77S猫头猫} 107908882a77S猫头猫 108025c1bd29S猫头猫async function updatePlugin(plugin: Plugin) { 108125c1bd29S猫头猫 const updateUrl = plugin.instance.srcUrl; 108225c1bd29S猫头猫 if (!updateUrl) { 108325c1bd29S猫头猫 throw new Error('没有更新源'); 108425c1bd29S猫头猫 } 108525c1bd29S猫头猫 try { 108625c1bd29S猫头猫 await installPluginFromUrl(updateUrl); 108725c1bd29S猫头猫 } catch (e: any) { 108825c1bd29S猫头猫 if (e.message === '插件已安装') { 108925c1bd29S猫头猫 throw new Error('当前已是最新版本'); 109025c1bd29S猫头猫 } else { 109125c1bd29S猫头猫 throw e; 109225c1bd29S猫头猫 } 109325c1bd29S猫头猫 } 109425c1bd29S猫头猫} 109525c1bd29S猫头猫 1096927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) { 10972c595535S猫头猫 return getByName(mediaItem?.platform); 1098927dbe93S猫头猫} 1099927dbe93S猫头猫 1100927dbe93S猫头猫function getByHash(hash: string) { 11017993f90eS猫头猫 return hash === localPluginHash 11027993f90eS猫头猫 ? localFilePlugin 11037993f90eS猫头猫 : plugins.find(_ => _.hash === hash); 1104927dbe93S猫头猫} 1105927dbe93S猫头猫 1106927dbe93S猫头猫function getByName(name: string) { 11077993f90eS猫头猫 return name === localPluginPlatform 11080e4173cdS猫头猫 ? localFilePlugin 11090e4173cdS猫头猫 : plugins.find(_ => _.name === name); 1110927dbe93S猫头猫} 1111927dbe93S猫头猫 1112927dbe93S猫头猫function getValidPlugins() { 1113927dbe93S猫头猫 return plugins.filter(_ => _.state === 'enabled'); 1114927dbe93S猫头猫} 1115927dbe93S猫头猫 11162b80a429S猫头猫function getSearchablePlugins(supportedSearchType?: ICommon.SupportMediaType) { 11172b80a429S猫头猫 return plugins.filter( 11182b80a429S猫头猫 _ => 11192b80a429S猫头猫 _.state === 'enabled' && 11202b80a429S猫头猫 _.instance.search && 112139ac60f7S猫头猫 (supportedSearchType && _.instance.supportedSearchType 112239ac60f7S猫头猫 ? _.instance.supportedSearchType.includes(supportedSearchType) 11232b80a429S猫头猫 : true), 11242b80a429S猫头猫 ); 1125efb9da24S猫头猫} 1126efb9da24S猫头猫 11272b80a429S猫头猫function getSortedSearchablePlugins( 11282b80a429S猫头猫 supportedSearchType?: ICommon.SupportMediaType, 11292b80a429S猫头猫) { 11302b80a429S猫头猫 return getSearchablePlugins(supportedSearchType).sort((a, b) => 1131e08d37a3S猫头猫 (PluginMeta.getPluginMeta(a).order ?? Infinity) - 1132e08d37a3S猫头猫 (PluginMeta.getPluginMeta(b).order ?? Infinity) < 1133e08d37a3S猫头猫 0 1134e08d37a3S猫头猫 ? -1 1135e08d37a3S猫头猫 : 1, 1136e08d37a3S猫头猫 ); 1137e08d37a3S猫头猫} 1138e08d37a3S猫头猫 113915feccc1S猫头猫function getTopListsablePlugins() { 114015feccc1S猫头猫 return plugins.filter(_ => _.state === 'enabled' && _.instance.getTopLists); 114115feccc1S猫头猫} 114215feccc1S猫头猫 114315feccc1S猫头猫function getSortedTopListsablePlugins() { 114415feccc1S猫头猫 return getTopListsablePlugins().sort((a, b) => 114515feccc1S猫头猫 (PluginMeta.getPluginMeta(a).order ?? Infinity) - 114615feccc1S猫头猫 (PluginMeta.getPluginMeta(b).order ?? Infinity) < 114715feccc1S猫头猫 0 114815feccc1S猫头猫 ? -1 114915feccc1S猫头猫 : 1, 115015feccc1S猫头猫 ); 115115feccc1S猫头猫} 115215feccc1S猫头猫 1153ceb900cdS猫头猫function getRecommendSheetablePlugins() { 1154ceb900cdS猫头猫 return plugins.filter( 1155ceb900cdS猫头猫 _ => _.state === 'enabled' && _.instance.getRecommendSheetsByTag, 1156ceb900cdS猫头猫 ); 1157ceb900cdS猫头猫} 1158ceb900cdS猫头猫 1159ceb900cdS猫头猫function getSortedRecommendSheetablePlugins() { 1160ceb900cdS猫头猫 return getRecommendSheetablePlugins().sort((a, b) => 1161ceb900cdS猫头猫 (PluginMeta.getPluginMeta(a).order ?? Infinity) - 1162ceb900cdS猫头猫 (PluginMeta.getPluginMeta(b).order ?? Infinity) < 1163ceb900cdS猫头猫 0 1164ceb900cdS猫头猫 ? -1 1165ceb900cdS猫头猫 : 1, 1166ceb900cdS猫头猫 ); 1167ceb900cdS猫头猫} 1168ceb900cdS猫头猫 1169e08d37a3S猫头猫function useSortedPlugins() { 1170e08d37a3S猫头猫 const _plugins = pluginStateMapper.useMappedState(); 1171e08d37a3S猫头猫 const _pluginMetaAll = PluginMeta.usePluginMetaAll(); 1172e08d37a3S猫头猫 117334588741S猫头猫 const [sortedPlugins, setSortedPlugins] = useState( 117434588741S猫头猫 [..._plugins].sort((a, b) => 1175e08d37a3S猫头猫 (_pluginMetaAll[a.name]?.order ?? Infinity) - 1176e08d37a3S猫头猫 (_pluginMetaAll[b.name]?.order ?? Infinity) < 1177e08d37a3S猫头猫 0 1178e08d37a3S猫头猫 ? -1 1179e08d37a3S猫头猫 : 1, 118034588741S猫头猫 ), 1181e08d37a3S猫头猫 ); 118234588741S猫头猫 118334588741S猫头猫 useEffect(() => { 1184d4cd40d8S猫头猫 InteractionManager.runAfterInteractions(() => { 118534588741S猫头猫 setSortedPlugins( 118634588741S猫头猫 [..._plugins].sort((a, b) => 118734588741S猫头猫 (_pluginMetaAll[a.name]?.order ?? Infinity) - 118834588741S猫头猫 (_pluginMetaAll[b.name]?.order ?? Infinity) < 118934588741S猫头猫 0 119034588741S猫头猫 ? -1 119134588741S猫头猫 : 1, 119234588741S猫头猫 ), 119334588741S猫头猫 ); 1194d4cd40d8S猫头猫 }); 119534588741S猫头猫 }, [_plugins, _pluginMetaAll]); 119634588741S猫头猫 119734588741S猫头猫 return sortedPlugins; 1198e08d37a3S猫头猫} 1199e08d37a3S猫头猫 1200c2b3a262S猫头猫async function setPluginEnabled(plugin: Plugin, enabled?: boolean) { 1201c2b3a262S猫头猫 const target = plugins.find(it => it.hash === plugin.hash); 1202c2b3a262S猫头猫 if (target) { 1203c2b3a262S猫头猫 target.state = enabled ? 'enabled' : 'disabled'; 1204c2b3a262S猫头猫 plugins = [...plugins]; 1205c2b3a262S猫头猫 pluginStateMapper.notify(); 1206c2b3a262S猫头猫 PluginMeta.setPluginMetaProp(plugin, 'enabled', enabled); 1207c2b3a262S猫头猫 } 1208c2b3a262S猫头猫} 1209c2b3a262S猫头猫 1210927dbe93S猫头猫const PluginManager = { 1211927dbe93S猫头猫 setup, 1212927dbe93S猫头猫 installPlugin, 121358992c6bS猫头猫 installPluginFromUrl, 121425c1bd29S猫头猫 updatePlugin, 1215927dbe93S猫头猫 uninstallPlugin, 1216927dbe93S猫头猫 getByMedia, 1217927dbe93S猫头猫 getByHash, 1218927dbe93S猫头猫 getByName, 1219927dbe93S猫头猫 getValidPlugins, 1220efb9da24S猫头猫 getSearchablePlugins, 1221e08d37a3S猫头猫 getSortedSearchablePlugins, 122215feccc1S猫头猫 getTopListsablePlugins, 1223ceb900cdS猫头猫 getSortedRecommendSheetablePlugins, 122415feccc1S猫头猫 getSortedTopListsablePlugins, 12255276aef9S猫头猫 usePlugins: pluginStateMapper.useMappedState, 1226e08d37a3S猫头猫 useSortedPlugins, 122708882a77S猫头猫 uninstallAllPlugins, 1228c2b3a262S猫头猫 setPluginEnabled, 12295276aef9S猫头猫}; 1230927dbe93S猫头猫 1231927dbe93S猫头猫export default PluginManager; 1232