committed by
							
								 MengYX
								MengYX
							
						
					
				
				 13 changed files with 207 additions and 16 deletions
			
			
		| @ -0,0 +1,53 @@ | |||||
|  | <template> | ||||
|  |   <el-dialog fullscreen @close="cancel()" title="解密设定" :visible="show" width="30%" center> | ||||
|  |     <el-form ref="form" :model="form" label-width="80px"> | ||||
|  |       <el-form-item label="Joox UUID"> | ||||
|  |         <el-input type="text" placeholder="UUID" v-model="form.jooxUUID" clearable maxlength="32" show-word-limit> | ||||
|  |         </el-input> | ||||
|  |       </el-form-item> | ||||
|  |     </el-form> | ||||
|  |     <span slot="footer" class="dialog-footer"> | ||||
|  |       <el-button type="primary" :loading="saving" @click="emitConfirm()">确 定</el-button> | ||||
|  |     </span> | ||||
|  |   </el-dialog> | ||||
|  | </template> | ||||
|  | 
 | ||||
|  | <script> | ||||
|  | import storage from '../utils/storage'; | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |   components: {}, | ||||
|  |   props: { | ||||
|  |     show: { type: Boolean, required: true }, | ||||
|  |   }, | ||||
|  |   data() { | ||||
|  |     return { | ||||
|  |       saving: false, | ||||
|  |       form: { | ||||
|  |         jooxUUID: '', | ||||
|  |       }, | ||||
|  |       centerDialogVisible: false, | ||||
|  |     }; | ||||
|  |   }, | ||||
|  |   async mounted() { | ||||
|  |     await this.resetForm(); | ||||
|  |   }, | ||||
|  |   methods: { | ||||
|  |     async resetForm() { | ||||
|  |       this.form.jooxUUID = await storage.loadJooxUUID(); | ||||
|  |     }, | ||||
|  | 
 | ||||
|  |     async cancel() { | ||||
|  |       await this.resetForm(); | ||||
|  |       this.$emit('done'); | ||||
|  |     }, | ||||
|  | 
 | ||||
|  |     async emitConfirm() { | ||||
|  |       this.saving = true; | ||||
|  |       await storage.saveJooxUUID(this.form.jooxUUID); | ||||
|  |       this.saving = false; | ||||
|  |       this.$emit('done'); | ||||
|  |     }, | ||||
|  |   }, | ||||
|  | }; | ||||
|  | </script> | ||||
| @ -0,0 +1,34 @@ | |||||
|  | import { DecryptResult } from './entity'; | ||||
|  | import { AudioMimeType, GetArrayBuffer, SniffAudioExt } from './utils'; | ||||
|  | 
 | ||||
|  | import jooxFactory from '@unlock-music-gh/joox-crypto'; | ||||
|  | import storage from '@/utils/storage'; | ||||
|  | import { MergeUint8Array } from '@/utils/MergeUint8Array'; | ||||
|  | 
 | ||||
|  | export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string): Promise<DecryptResult> { | ||||
|  |   const uuid = await storage.loadJooxUUID(''); | ||||
|  |   if (!uuid || uuid.length !== 32) { | ||||
|  |     throw new Error('请在“解密设定”填写应用 Joox 应用的 UUID。'); | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   const fileBuffer = new Uint8Array(await GetArrayBuffer(file)); | ||||
|  |   const decryptor = jooxFactory(fileBuffer, uuid); | ||||
|  |   if (!decryptor) { | ||||
|  |     throw new Error('不支持的 joox 加密格式'); | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   const musicDecoded = MergeUint8Array(decryptor.decryptFile(fileBuffer)); | ||||
|  |   const ext = SniffAudioExt(musicDecoded); | ||||
|  |   const mime = AudioMimeType[ext]; | ||||
|  |   const musicBlob = new Blob([musicDecoded], { type: mime }); | ||||
|  | 
 | ||||
|  |   return { | ||||
|  |     title: raw_filename.replace(/\.[^\.]+$/, ''), | ||||
|  |     artist: '未知', | ||||
|  |     album: '未知', | ||||
|  |     file: URL.createObjectURL(musicBlob), | ||||
|  |     blob: musicBlob, | ||||
|  |     mime: mime, | ||||
|  |     ext: ext, | ||||
|  |   }; | ||||
|  | } | ||||
| @ -0,0 +1,15 @@ | |||||
|  | export function MergeUint8Array(array: Uint8Array[]): Uint8Array { | ||||
|  |   let length = 0; | ||||
|  |   array.forEach((item) => { | ||||
|  |     length += item.length; | ||||
|  |   }); | ||||
|  | 
 | ||||
|  |   let mergedArray = new Uint8Array(length); | ||||
|  |   let offset = 0; | ||||
|  |   array.forEach((item) => { | ||||
|  |     mergedArray.set(item, offset); | ||||
|  |     offset += item.length; | ||||
|  |   }); | ||||
|  | 
 | ||||
|  |   return mergedArray; | ||||
|  | } | ||||
| @ -0,0 +1,7 @@ | |||||
|  | import BaseStorage from './storage/BaseStorage'; | ||||
|  | import BrowserNativeStorage from './storage/BrowserNativeStorage'; | ||||
|  | import ChromeExtensionStorage from './storage/ChromeExtensionStorage'; | ||||
|  | 
 | ||||
|  | const storage: BaseStorage = ChromeExtensionStorage.works ? new ChromeExtensionStorage() : new BrowserNativeStorage(); | ||||
|  | 
 | ||||
|  | export default storage; | ||||
| @ -0,0 +1,14 @@ | |||||
|  | const KEY_JOOX_UUID = 'joox.uuid'; | ||||
|  | 
 | ||||
|  | export default abstract class BaseStorage { | ||||
|  |   protected abstract save<T>(name: string, value: T): Promise<void>; | ||||
|  |   protected abstract load<T>(name: string, defaultValue: T): Promise<T>; | ||||
|  | 
 | ||||
|  |   public saveJooxUUID(uuid: string): Promise<void> { | ||||
|  |     return this.save(KEY_JOOX_UUID, uuid); | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   public loadJooxUUID(defaultValue: string = ''): Promise<string> { | ||||
|  |     return this.load(KEY_JOOX_UUID, defaultValue); | ||||
|  |   } | ||||
|  | } | ||||
| @ -0,0 +1,15 @@ | |||||
|  | import BaseStorage from './BaseStorage'; | ||||
|  | 
 | ||||
|  | export default class BrowserNativeStorage extends BaseStorage { | ||||
|  |   protected async load<T>(name: string, defaultValue: T): Promise<T> { | ||||
|  |     const result = localStorage.getItem(name); | ||||
|  |     if (result === null) { | ||||
|  |       return defaultValue; | ||||
|  |     } | ||||
|  |     return JSON.parse(result); | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   protected async save<T>(name: string, value: T): Promise<void> { | ||||
|  |     localStorage.setItem(name, JSON.stringify(value)); | ||||
|  |   } | ||||
|  | } | ||||
| @ -0,0 +1,21 @@ | |||||
|  | import BaseStorage from './BaseStorage'; | ||||
|  | 
 | ||||
|  | declare var chrome: any; | ||||
|  | 
 | ||||
|  | export default class ChromeExtensionStorage extends BaseStorage { | ||||
|  |   static get works(): boolean { | ||||
|  |     return Boolean(chrome?.storage?.local?.set); | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   protected async load<T>(name: string, defaultValue: T): Promise<T> { | ||||
|  |     const result = await chrome.storage.local.get({ [name]: defaultValue }); | ||||
|  |     if (Object.prototype.hasOwnProperty.call(result, name)) { | ||||
|  |       return result[name]; | ||||
|  |     } | ||||
|  |     return defaultValue; | ||||
|  |   } | ||||
|  | 
 | ||||
|  |   protected async save<T>(name: string, value: T): Promise<void> { | ||||
|  |     return chrome.storage.local.set({ [name]: value }); | ||||
|  |   } | ||||
|  | } | ||||
					Loading…
					
					
				
		Reference in new issue