1*927dbe93S猫头猫import RNFS, { 2*927dbe93S猫头猫 copyFile, 3*927dbe93S猫头猫 exists, 4*927dbe93S猫头猫 moveFile, 5*927dbe93S猫头猫 readDir, 6*927dbe93S猫头猫 readFile, 7*927dbe93S猫头猫 unlink, 8*927dbe93S猫头猫 writeFile, 9*927dbe93S猫头猫} from 'react-native-fs'; 10*927dbe93S猫头猫import CryptoJs from 'crypto-js'; 11*927dbe93S猫头猫import dayjs from 'dayjs'; 12*927dbe93S猫头猫import axios from 'axios'; 13*927dbe93S猫头猫import {useEffect, useState} from 'react'; 14*927dbe93S猫头猫import {ToastAndroid} from 'react-native'; 15*927dbe93S猫头猫import pathConst from '@/constants/pathConst'; 16*927dbe93S猫头猫import {satisfies} from 'compare-versions'; 17*927dbe93S猫头猫import DeviceInfo from 'react-native-device-info'; 18*927dbe93S猫头猫import StateMapper from '@/utils/stateMapper'; 19*927dbe93S猫头猫import MediaMeta from './mediaMeta'; 20*927dbe93S猫头猫import {nanoid} from 'nanoid'; 21*927dbe93S猫头猫import {errorLog, trace} from '../utils/log'; 22*927dbe93S猫头猫import Cache from './cache'; 23*927dbe93S猫头猫import {isSameMediaItem, resetMediaItem} from '@/utils/mediaItem'; 24*927dbe93S猫头猫import {internalSerialzeKey, internalSymbolKey} from '@/constants/commonConst'; 25*927dbe93S猫头猫import Download from './download'; 26*927dbe93S猫头猫import delay from '@/utils/delay'; 27*927dbe93S猫头猫 28*927dbe93S猫头猫axios.defaults.timeout = 1500; 29*927dbe93S猫头猫 30*927dbe93S猫头猫const sha256 = CryptoJs.SHA256; 31*927dbe93S猫头猫 32*927dbe93S猫头猫enum PluginStateCode { 33*927dbe93S猫头猫 /** 版本不匹配 */ 34*927dbe93S猫头猫 VersionNotMatch = 'VERSION NOT MATCH', 35*927dbe93S猫头猫 /** 插件不完整 */ 36*927dbe93S猫头猫 NotComplete = 'NOT COMPLETE', 37*927dbe93S猫头猫 /** 无法解析 */ 38*927dbe93S猫头猫 CannotParse = 'CANNOT PARSE', 39*927dbe93S猫头猫} 40*927dbe93S猫头猫 41*927dbe93S猫头猫export class Plugin { 42*927dbe93S猫头猫 /** 插件名 */ 43*927dbe93S猫头猫 public name: string; 44*927dbe93S猫头猫 /** 插件的hash,作为唯一id */ 45*927dbe93S猫头猫 public hash: string; 46*927dbe93S猫头猫 /** 插件状态:激活、关闭、错误 */ 47*927dbe93S猫头猫 public state: 'enabled' | 'disabled' | 'error'; 48*927dbe93S猫头猫 /** 插件支持的搜索类型 */ 49*927dbe93S猫头猫 public supportedSearchType?: string; 50*927dbe93S猫头猫 /** 插件状态信息 */ 51*927dbe93S猫头猫 public stateCode?: PluginStateCode; 52*927dbe93S猫头猫 /** 插件的实例 */ 53*927dbe93S猫头猫 public instance: IPlugin.IPluginInstance; 54*927dbe93S猫头猫 /** 插件路径 */ 55*927dbe93S猫头猫 public path: string; 56*927dbe93S猫头猫 /** 插件方法 */ 57*927dbe93S猫头猫 public methods: PluginMethods; 58*927dbe93S猫头猫 59*927dbe93S猫头猫 constructor(funcCode: string, pluginPath: string) { 60*927dbe93S猫头猫 this.state = 'enabled'; 61*927dbe93S猫头猫 let _instance: IPlugin.IPluginInstance; 62*927dbe93S猫头猫 try { 63*927dbe93S猫头猫 _instance = Function(` 64*927dbe93S猫头猫 'use strict'; 65*927dbe93S猫头猫 try { 66*927dbe93S猫头猫 return ${funcCode}; 67*927dbe93S猫头猫 } catch(e) { 68*927dbe93S猫头猫 return null; 69*927dbe93S猫头猫 } 70*927dbe93S猫头猫 `)()({CryptoJs, axios, dayjs}); 71*927dbe93S猫头猫 72*927dbe93S猫头猫 this.checkValid(_instance); 73*927dbe93S猫头猫 } catch (e: any) { 74*927dbe93S猫头猫 this.state = 'error'; 75*927dbe93S猫头猫 this.stateCode = PluginStateCode.CannotParse; 76*927dbe93S猫头猫 if (e?.stateCode) { 77*927dbe93S猫头猫 this.stateCode = e.stateCode; 78*927dbe93S猫头猫 } 79*927dbe93S猫头猫 errorLog(`${pluginPath}插件无法解析 `, { 80*927dbe93S猫头猫 stateCode: this.stateCode, 81*927dbe93S猫头猫 message: e?.message, 82*927dbe93S猫头猫 stack: e?.stack, 83*927dbe93S猫头猫 }); 84*927dbe93S猫头猫 _instance = e?.instance ?? { 85*927dbe93S猫头猫 _path: '', 86*927dbe93S猫头猫 platform: '', 87*927dbe93S猫头猫 appVersion: '', 88*927dbe93S猫头猫 async getMusicTrack() { 89*927dbe93S猫头猫 return null; 90*927dbe93S猫头猫 }, 91*927dbe93S猫头猫 async search() { 92*927dbe93S猫头猫 return {}; 93*927dbe93S猫头猫 }, 94*927dbe93S猫头猫 async getAlbumInfo() { 95*927dbe93S猫头猫 return null; 96*927dbe93S猫头猫 }, 97*927dbe93S猫头猫 }; 98*927dbe93S猫头猫 } 99*927dbe93S猫头猫 this.instance = _instance; 100*927dbe93S猫头猫 this.path = pluginPath; 101*927dbe93S猫头猫 this.name = _instance.platform; 102*927dbe93S猫头猫 if (this.instance.platform === '') { 103*927dbe93S猫头猫 this.hash = ''; 104*927dbe93S猫头猫 } else { 105*927dbe93S猫头猫 this.hash = sha256(funcCode).toString(); 106*927dbe93S猫头猫 } 107*927dbe93S猫头猫 108*927dbe93S猫头猫 // 放在最后 109*927dbe93S猫头猫 this.methods = new PluginMethods(this); 110*927dbe93S猫头猫 } 111*927dbe93S猫头猫 112*927dbe93S猫头猫 private checkValid(_instance: IPlugin.IPluginInstance) { 113*927dbe93S猫头猫 // 总不会一个都没有吧 114*927dbe93S猫头猫 const keys: Array<keyof IPlugin.IPluginInstance> = [ 115*927dbe93S猫头猫 'getAlbumInfo', 116*927dbe93S猫头猫 'search', 117*927dbe93S猫头猫 'getMusicTrack', 118*927dbe93S猫头猫 ]; 119*927dbe93S猫头猫 if (keys.every(k => !_instance[k])) { 120*927dbe93S猫头猫 throw { 121*927dbe93S猫头猫 instance: _instance, 122*927dbe93S猫头猫 stateCode: PluginStateCode.NotComplete, 123*927dbe93S猫头猫 }; 124*927dbe93S猫头猫 } 125*927dbe93S猫头猫 /** 版本号校验 */ 126*927dbe93S猫头猫 if ( 127*927dbe93S猫头猫 _instance.appVersion && 128*927dbe93S猫头猫 !satisfies(DeviceInfo.getVersion(), _instance.appVersion) 129*927dbe93S猫头猫 ) { 130*927dbe93S猫头猫 throw { 131*927dbe93S猫头猫 instance: _instance, 132*927dbe93S猫头猫 stateCode: PluginStateCode.VersionNotMatch, 133*927dbe93S猫头猫 }; 134*927dbe93S猫头猫 } 135*927dbe93S猫头猫 return true; 136*927dbe93S猫头猫 } 137*927dbe93S猫头猫} 138*927dbe93S猫头猫 139*927dbe93S猫头猫/** 有缓存等信息 */ 140*927dbe93S猫头猫class PluginMethods implements IPlugin.IPluginInstanceMethods { 141*927dbe93S猫头猫 private plugin; 142*927dbe93S猫头猫 constructor(plugin: Plugin) { 143*927dbe93S猫头猫 this.plugin = plugin; 144*927dbe93S猫头猫 } 145*927dbe93S猫头猫 /** 搜索 */ 146*927dbe93S猫头猫 async search<T extends ICommon.SupportMediaType>( 147*927dbe93S猫头猫 query: string, 148*927dbe93S猫头猫 page: number, 149*927dbe93S猫头猫 type: T, 150*927dbe93S猫头猫 ): Promise<IPlugin.ISearchResult<T>> { 151*927dbe93S猫头猫 if (!this.plugin.instance.search) { 152*927dbe93S猫头猫 return { 153*927dbe93S猫头猫 isEnd: true, 154*927dbe93S猫头猫 data: [], 155*927dbe93S猫头猫 }; 156*927dbe93S猫头猫 } 157*927dbe93S猫头猫 158*927dbe93S猫头猫 const result = (await this.plugin.instance.search(query, page, type)) ?? {}; 159*927dbe93S猫头猫 if (Array.isArray(result.data)) { 160*927dbe93S猫头猫 result.data.forEach(_ => { 161*927dbe93S猫头猫 resetMediaItem(_, this.plugin.name); 162*927dbe93S猫头猫 }); 163*927dbe93S猫头猫 return { 164*927dbe93S猫头猫 isEnd: result.isEnd ?? true, 165*927dbe93S猫头猫 data: result.data, 166*927dbe93S猫头猫 }; 167*927dbe93S猫头猫 } 168*927dbe93S猫头猫 return { 169*927dbe93S猫头猫 isEnd: true, 170*927dbe93S猫头猫 data: [], 171*927dbe93S猫头猫 }; 172*927dbe93S猫头猫 } 173*927dbe93S猫头猫 174*927dbe93S猫头猫 /** 获取真实源 */ 175*927dbe93S猫头猫 async getMusicTrack( 176*927dbe93S猫头猫 musicItem: IMusic.IMusicItemBase, 177*927dbe93S猫头猫 retryCount = 1, 178*927dbe93S猫头猫 ): Promise<IPlugin.IMusicTrackResult> { 179*927dbe93S猫头猫 // 1. 本地搜索 其实直接读mediameta就好了 180*927dbe93S猫头猫 const localPath = 181*927dbe93S猫头猫 musicItem?.[internalSymbolKey]?.localPath ?? 182*927dbe93S猫头猫 Download.getDownloaded(musicItem)?.[internalSymbolKey]?.localPath; 183*927dbe93S猫头猫 if (localPath && (await exists(localPath))) { 184*927dbe93S猫头猫 return { 185*927dbe93S猫头猫 url: localPath, 186*927dbe93S猫头猫 }; 187*927dbe93S猫头猫 } 188*927dbe93S猫头猫 // 2. 缓存播放 189*927dbe93S猫头猫 const mediaCache = Cache.get(musicItem); 190*927dbe93S猫头猫 if (mediaCache && mediaCache?.url) { 191*927dbe93S猫头猫 return { 192*927dbe93S猫头猫 url: mediaCache.url, 193*927dbe93S猫头猫 headers: mediaCache.headers, 194*927dbe93S猫头猫 userAgent: mediaCache.userAgent ?? mediaCache.headers?.['user-agent'], 195*927dbe93S猫头猫 }; 196*927dbe93S猫头猫 } 197*927dbe93S猫头猫 // 3. 插件解析 198*927dbe93S猫头猫 if (!this.plugin.instance.getMusicTrack) { 199*927dbe93S猫头猫 return {url: musicItem.url}; 200*927dbe93S猫头猫 } 201*927dbe93S猫头猫 try { 202*927dbe93S猫头猫 const {url, headers} = 203*927dbe93S猫头猫 (await this.plugin.instance.getMusicTrack(musicItem)) ?? {}; 204*927dbe93S猫头猫 if (!url) { 205*927dbe93S猫头猫 throw new Error(); 206*927dbe93S猫头猫 } 207*927dbe93S猫头猫 const result = { 208*927dbe93S猫头猫 url, 209*927dbe93S猫头猫 headers, 210*927dbe93S猫头猫 userAgent: headers?.['user-agent'], 211*927dbe93S猫头猫 }; 212*927dbe93S猫头猫 213*927dbe93S猫头猫 Cache.update(musicItem, result); 214*927dbe93S猫头猫 return result; 215*927dbe93S猫头猫 } catch (e: any) { 216*927dbe93S猫头猫 if (retryCount > 0) { 217*927dbe93S猫头猫 await delay(150); 218*927dbe93S猫头猫 return this.getMusicTrack(musicItem, --retryCount); 219*927dbe93S猫头猫 } 220*927dbe93S猫头猫 errorLog('获取真实源失败', e?.message); 221*927dbe93S猫头猫 throw e; 222*927dbe93S猫头猫 } 223*927dbe93S猫头猫 } 224*927dbe93S猫头猫 225*927dbe93S猫头猫 /** 获取音乐详情 */ 226*927dbe93S猫头猫 async getMusicInfo( 227*927dbe93S猫头猫 musicItem: ICommon.IMediaBase, 228*927dbe93S猫头猫 ): Promise<IMusic.IMusicItem | null> { 229*927dbe93S猫头猫 if (!this.plugin.instance.getMusicInfo) { 230*927dbe93S猫头猫 return musicItem as IMusic.IMusicItem; 231*927dbe93S猫头猫 } 232*927dbe93S猫头猫 return ( 233*927dbe93S猫头猫 this.plugin.instance.getMusicInfo( 234*927dbe93S猫头猫 resetMediaItem(musicItem, undefined, true), 235*927dbe93S猫头猫 ) ?? musicItem 236*927dbe93S猫头猫 ); 237*927dbe93S猫头猫 } 238*927dbe93S猫头猫 239*927dbe93S猫头猫 /** 获取歌词 */ 240*927dbe93S猫头猫 async getLyric( 241*927dbe93S猫头猫 musicItem: IMusic.IMusicItemBase, 242*927dbe93S猫头猫 from?: IMusic.IMusicItemBase, 243*927dbe93S猫头猫 ): Promise<ILyric.ILyricSource | null> { 244*927dbe93S猫头猫 // 1.额外存储的meta信息 245*927dbe93S猫头猫 const meta = MediaMeta.get(musicItem); 246*927dbe93S猫头猫 if (meta && meta.associatedLrc) { 247*927dbe93S猫头猫 // 有关联歌词 248*927dbe93S猫头猫 if ( 249*927dbe93S猫头猫 isSameMediaItem(musicItem, from) || 250*927dbe93S猫头猫 isSameMediaItem(meta.associatedLrc, musicItem) 251*927dbe93S猫头猫 ) { 252*927dbe93S猫头猫 // 形成环路,断开当前的环 253*927dbe93S猫头猫 await MediaMeta.update(musicItem, { 254*927dbe93S猫头猫 associatedLrc: undefined, 255*927dbe93S猫头猫 }); 256*927dbe93S猫头猫 // 无歌词 257*927dbe93S猫头猫 return null; 258*927dbe93S猫头猫 } 259*927dbe93S猫头猫 // 获取关联歌词 260*927dbe93S猫头猫 const result = await this.getLyric(meta.associatedLrc, from ?? musicItem); 261*927dbe93S猫头猫 if (result) { 262*927dbe93S猫头猫 // 如果有关联歌词,就返回关联歌词,深度优先 263*927dbe93S猫头猫 return result; 264*927dbe93S猫头猫 } 265*927dbe93S猫头猫 } 266*927dbe93S猫头猫 const cache = Cache.get(musicItem); 267*927dbe93S猫头猫 let rawLrc = meta?.rawLrc || musicItem.rawLrc || cache?.rawLrc; 268*927dbe93S猫头猫 let lrcUrl = meta?.lrc || musicItem.lrc || cache?.lrc; 269*927dbe93S猫头猫 // 如果存在文本 270*927dbe93S猫头猫 if (rawLrc) { 271*927dbe93S猫头猫 return { 272*927dbe93S猫头猫 rawLrc, 273*927dbe93S猫头猫 lrc: lrcUrl, 274*927dbe93S猫头猫 }; 275*927dbe93S猫头猫 } 276*927dbe93S猫头猫 // 2.本地缓存 277*927dbe93S猫头猫 const localLrc = 278*927dbe93S猫头猫 meta?.[internalSerialzeKey]?.local?.localLrc || 279*927dbe93S猫头猫 cache?.[internalSerialzeKey]?.local?.localLrc; 280*927dbe93S猫头猫 if (localLrc && (await exists(localLrc))) { 281*927dbe93S猫头猫 rawLrc = await readFile(localLrc, 'utf8'); 282*927dbe93S猫头猫 return { 283*927dbe93S猫头猫 rawLrc, 284*927dbe93S猫头猫 lrc: lrcUrl, 285*927dbe93S猫头猫 }; 286*927dbe93S猫头猫 } 287*927dbe93S猫头猫 // 3.优先使用url 288*927dbe93S猫头猫 if (lrcUrl) { 289*927dbe93S猫头猫 try { 290*927dbe93S猫头猫 // 需要超时时间 axios timeout 但是没生效 291*927dbe93S猫头猫 rawLrc = (await axios.get(lrcUrl)).data; 292*927dbe93S猫头猫 return { 293*927dbe93S猫头猫 rawLrc, 294*927dbe93S猫头猫 lrc: lrcUrl, 295*927dbe93S猫头猫 }; 296*927dbe93S猫头猫 } catch { 297*927dbe93S猫头猫 lrcUrl = undefined; 298*927dbe93S猫头猫 } 299*927dbe93S猫头猫 } 300*927dbe93S猫头猫 // 4. 如果地址失效 301*927dbe93S猫头猫 if (!lrcUrl) { 302*927dbe93S猫头猫 // 插件获得url 303*927dbe93S猫头猫 try { 304*927dbe93S猫头猫 const lrcSource = await this.plugin.instance?.getLyric?.( 305*927dbe93S猫头猫 resetMediaItem(musicItem, undefined, true), 306*927dbe93S猫头猫 ); 307*927dbe93S猫头猫 rawLrc = lrcSource?.rawLrc; 308*927dbe93S猫头猫 lrcUrl = lrcSource?.lrc; 309*927dbe93S猫头猫 } catch (e: any) { 310*927dbe93S猫头猫 trace('插件获取歌词失败', e?.message, 'error'); 311*927dbe93S猫头猫 } 312*927dbe93S猫头猫 } 313*927dbe93S猫头猫 // 5. 最后一次请求 314*927dbe93S猫头猫 if (rawLrc || lrcUrl) { 315*927dbe93S猫头猫 const filename = `${pathConst.lrcCachePath}${nanoid()}.lrc`; 316*927dbe93S猫头猫 if (lrcUrl) { 317*927dbe93S猫头猫 try { 318*927dbe93S猫头猫 rawLrc = (await axios.get(lrcUrl)).data; 319*927dbe93S猫头猫 } catch {} 320*927dbe93S猫头猫 } 321*927dbe93S猫头猫 if (rawLrc) { 322*927dbe93S猫头猫 await writeFile(filename, rawLrc, 'utf8'); 323*927dbe93S猫头猫 // 写入缓存 324*927dbe93S猫头猫 Cache.update(musicItem, [ 325*927dbe93S猫头猫 [`${internalSerialzeKey}.local.localLrc`, filename], 326*927dbe93S猫头猫 ]); 327*927dbe93S猫头猫 // 如果有meta 328*927dbe93S猫头猫 if (meta) { 329*927dbe93S猫头猫 MediaMeta.update(musicItem, [ 330*927dbe93S猫头猫 [`${internalSerialzeKey}.local.localLrc`, filename], 331*927dbe93S猫头猫 ]); 332*927dbe93S猫头猫 } 333*927dbe93S猫头猫 return { 334*927dbe93S猫头猫 rawLrc, 335*927dbe93S猫头猫 lrc: lrcUrl, 336*927dbe93S猫头猫 }; 337*927dbe93S猫头猫 } 338*927dbe93S猫头猫 } 339*927dbe93S猫头猫 340*927dbe93S猫头猫 return null; 341*927dbe93S猫头猫 } 342*927dbe93S猫头猫 343*927dbe93S猫头猫 /** 获取歌词文本 */ 344*927dbe93S猫头猫 async getLyricText( 345*927dbe93S猫头猫 musicItem: IMusic.IMusicItem, 346*927dbe93S猫头猫 ): Promise<string | undefined> { 347*927dbe93S猫头猫 return (await this.getLyric(musicItem))?.rawLrc; 348*927dbe93S猫头猫 } 349*927dbe93S猫头猫 350*927dbe93S猫头猫 /** 获取专辑信息 */ 351*927dbe93S猫头猫 async getAlbumInfo( 352*927dbe93S猫头猫 albumItem: IAlbum.IAlbumItemBase, 353*927dbe93S猫头猫 ): Promise<IAlbum.IAlbumItem | null> { 354*927dbe93S猫头猫 if (!this.plugin.instance.getAlbumInfo) { 355*927dbe93S猫头猫 return {...albumItem, musicList: []}; 356*927dbe93S猫头猫 } 357*927dbe93S猫头猫 try { 358*927dbe93S猫头猫 const result = await this.plugin.instance.getAlbumInfo( 359*927dbe93S猫头猫 resetMediaItem(albumItem, undefined, true), 360*927dbe93S猫头猫 ); 361*927dbe93S猫头猫 result?.musicList?.forEach(_ => { 362*927dbe93S猫头猫 resetMediaItem(_, this.plugin.name); 363*927dbe93S猫头猫 }); 364*927dbe93S猫头猫 return result; 365*927dbe93S猫头猫 } catch { 366*927dbe93S猫头猫 return {...albumItem, musicList: []}; 367*927dbe93S猫头猫 } 368*927dbe93S猫头猫 } 369*927dbe93S猫头猫 370*927dbe93S猫头猫 /** 查询作者信息 */ 371*927dbe93S猫头猫 async queryArtistWorks<T extends IArtist.ArtistMediaType>( 372*927dbe93S猫头猫 artistItem: IArtist.IArtistItem, 373*927dbe93S猫头猫 page: number, 374*927dbe93S猫头猫 type: T, 375*927dbe93S猫头猫 ): Promise<IPlugin.ISearchResult<T>> { 376*927dbe93S猫头猫 if (!this.plugin.instance.queryArtistWorks) { 377*927dbe93S猫头猫 return { 378*927dbe93S猫头猫 isEnd: true, 379*927dbe93S猫头猫 data: [], 380*927dbe93S猫头猫 }; 381*927dbe93S猫头猫 } 382*927dbe93S猫头猫 try { 383*927dbe93S猫头猫 const result = await this.plugin.instance.queryArtistWorks( 384*927dbe93S猫头猫 artistItem, 385*927dbe93S猫头猫 page, 386*927dbe93S猫头猫 type, 387*927dbe93S猫头猫 ); 388*927dbe93S猫头猫 if (!result.data) { 389*927dbe93S猫头猫 return { 390*927dbe93S猫头猫 isEnd: true, 391*927dbe93S猫头猫 data: [], 392*927dbe93S猫头猫 }; 393*927dbe93S猫头猫 } 394*927dbe93S猫头猫 result.data?.forEach(_ => resetMediaItem(_, this.plugin.name)); 395*927dbe93S猫头猫 return { 396*927dbe93S猫头猫 isEnd: result.isEnd ?? true, 397*927dbe93S猫头猫 data: result.data, 398*927dbe93S猫头猫 }; 399*927dbe93S猫头猫 } catch (e) { 400*927dbe93S猫头猫 throw e; 401*927dbe93S猫头猫 } 402*927dbe93S猫头猫 } 403*927dbe93S猫头猫} 404*927dbe93S猫头猫let plugins: Array<Plugin> = []; 405*927dbe93S猫头猫const pluginStateMapper = new StateMapper(() => plugins); 406*927dbe93S猫头猫 407*927dbe93S猫头猫async function setup() { 408*927dbe93S猫头猫 const _plugins: Array<Plugin> = []; 409*927dbe93S猫头猫 try { 410*927dbe93S猫头猫 // 加载插件 411*927dbe93S猫头猫 const pluginsPaths = await readDir(pathConst.pluginPath); 412*927dbe93S猫头猫 for (let i = 0; i < pluginsPaths.length; ++i) { 413*927dbe93S猫头猫 const _pluginUrl = pluginsPaths[i]; 414*927dbe93S猫头猫 415*927dbe93S猫头猫 if (_pluginUrl.isFile() && _pluginUrl.name.endsWith('.js')) { 416*927dbe93S猫头猫 const funcCode = await readFile(_pluginUrl.path, 'utf8'); 417*927dbe93S猫头猫 const plugin = new Plugin(funcCode, _pluginUrl.path); 418*927dbe93S猫头猫 const _pluginIndex = _plugins.findIndex(p => p.hash === plugin.hash); 419*927dbe93S猫头猫 if (_pluginIndex !== -1) { 420*927dbe93S猫头猫 // 重复插件,直接忽略 421*927dbe93S猫头猫 return; 422*927dbe93S猫头猫 } 423*927dbe93S猫头猫 plugin.hash !== '' && _plugins.push(plugin); 424*927dbe93S猫头猫 } 425*927dbe93S猫头猫 } 426*927dbe93S猫头猫 427*927dbe93S猫头猫 plugins = _plugins; 428*927dbe93S猫头猫 pluginStateMapper.notify(); 429*927dbe93S猫头猫 } catch (e: any) { 430*927dbe93S猫头猫 ToastAndroid.show(`插件初始化失败:${e?.message ?? e}`, ToastAndroid.LONG); 431*927dbe93S猫头猫 throw e; 432*927dbe93S猫头猫 } 433*927dbe93S猫头猫} 434*927dbe93S猫头猫 435*927dbe93S猫头猫// 安装插件 436*927dbe93S猫头猫async function installPlugin(pluginPath: string) { 437*927dbe93S猫头猫 if (pluginPath.endsWith('.js') && (await exists(pluginPath))) { 438*927dbe93S猫头猫 const funcCode = await readFile(pluginPath, 'utf8'); 439*927dbe93S猫头猫 const plugin = new Plugin(funcCode, pluginPath); 440*927dbe93S猫头猫 const _pluginIndex = plugins.findIndex(p => p.hash === plugin.hash); 441*927dbe93S猫头猫 if (_pluginIndex !== -1) { 442*927dbe93S猫头猫 return; 443*927dbe93S猫头猫 } 444*927dbe93S猫头猫 if (plugin.hash !== '') { 445*927dbe93S猫头猫 const fn = nanoid(); 446*927dbe93S猫头猫 const _pluginPath = `${pathConst.pluginPath}${fn}.js`; 447*927dbe93S猫头猫 await copyFile(pluginPath, _pluginPath); 448*927dbe93S猫头猫 plugin.path = _pluginPath; 449*927dbe93S猫头猫 plugins = plugins.concat(plugin); 450*927dbe93S猫头猫 pluginStateMapper.notify(); 451*927dbe93S猫头猫 } 452*927dbe93S猫头猫 } 453*927dbe93S猫头猫} 454*927dbe93S猫头猫 455*927dbe93S猫头猫/** 卸载插件 */ 456*927dbe93S猫头猫async function uninstallPlugin(hash: string) { 457*927dbe93S猫头猫 const targetIndex = plugins.findIndex(_ => _.hash === hash); 458*927dbe93S猫头猫 if (targetIndex !== -1) { 459*927dbe93S猫头猫 try { 460*927dbe93S猫头猫 await unlink(plugins[targetIndex].path); 461*927dbe93S猫头猫 plugins = plugins.filter(_ => _.hash !== hash); 462*927dbe93S猫头猫 pluginStateMapper.notify(); 463*927dbe93S猫头猫 } catch {} 464*927dbe93S猫头猫 } 465*927dbe93S猫头猫} 466*927dbe93S猫头猫 467*927dbe93S猫头猫function getByMedia(mediaItem: ICommon.IMediaBase) { 468*927dbe93S猫头猫 return getByName(mediaItem.platform); 469*927dbe93S猫头猫} 470*927dbe93S猫头猫 471*927dbe93S猫头猫function getByHash(hash: string) { 472*927dbe93S猫头猫 return plugins.find(_ => _.hash === hash); 473*927dbe93S猫头猫} 474*927dbe93S猫头猫 475*927dbe93S猫头猫function getByName(name: string) { 476*927dbe93S猫头猫 return plugins.find(_ => _.name === name); 477*927dbe93S猫头猫} 478*927dbe93S猫头猫 479*927dbe93S猫头猫function getValidPlugins() { 480*927dbe93S猫头猫 return plugins.filter(_ => _.state === 'enabled'); 481*927dbe93S猫头猫} 482*927dbe93S猫头猫 483*927dbe93S猫头猫 484*927dbe93S猫头猫const PluginManager = { 485*927dbe93S猫头猫 setup, 486*927dbe93S猫头猫 installPlugin, 487*927dbe93S猫头猫 uninstallPlugin, 488*927dbe93S猫头猫 getByMedia, 489*927dbe93S猫头猫 getByHash, 490*927dbe93S猫头猫 getByName, 491*927dbe93S猫头猫 getValidPlugins, 492*927dbe93S猫头猫 usePlugins: pluginStateMapper.useMappedState 493*927dbe93S猫头猫} 494*927dbe93S猫头猫 495*927dbe93S猫头猫export default PluginManager; 496