Browse Source
			
			
			feat: add basic joox support
			
				(cherry picked from commit 699333ca06526d747a7eb4a188e896de81e9f014)
			
			
				20230320
			
			
		 
		
			
				
					
						 Jixun
					
					4 years ago
						Jixun
					
					4 years ago
					
						
							committed by
							
								 MengYX
								MengYX
							
						 
					
				 
				
			 
		 
		
			
				
					
					No known key found for this signature in database
					
						
							GPG Key ID: E63F9C7303E8F604
						
					
				
			
		
		
		
	
		
			
				 13 changed files with 
207 additions and 
16 deletions
			 
			
		 
		
			
				- 
					
					
					 
					package-lock.json
				
- 
					
					
					 
					package.json
				
- 
					
					
					 
					src/component/ConfigDialog.vue
				
- 
					
					
					 
					src/decrypt/common.ts
				
- 
					
					
					 
					src/decrypt/joox.ts
				
- 
					
					
					 
					src/decrypt/qmc_wasm.ts
				
- 
					
					
					 
					src/main.ts
				
- 
					
					
					 
					src/utils/MergeUint8Array.ts
				
- 
					
					
					 
					src/utils/storage.ts
				
- 
					
					
					 
					src/utils/storage/BaseStorage.ts
				
- 
					
					
					 
					src/utils/storage/BrowserNativeStorage.ts
				
- 
					
					
					 
					src/utils/storage/ChromeExtensionStorage.ts
				
- 
					
					
					 
					src/view/Home.vue
				
				
				
					
						
							
								
									
	
		
			
				
					|  |  | @ -12,6 +12,7 @@ | 
			
		
	
		
			
				
					|  |  |  |       "dependencies": { | 
			
		
	
		
			
				
					|  |  |  |         "@babel/preset-typescript": "^7.16.5", | 
			
		
	
		
			
				
					|  |  |  |         "@jixun/qmc2-crypto": "^0.0.5-R4", | 
			
		
	
		
			
				
					|  |  |  |         "@unlock-music-gh/joox-crypto": "^0.0.1-R2", | 
			
		
	
		
			
				
					|  |  |  |         "base64-js": "^1.5.1", | 
			
		
	
		
			
				
					|  |  |  |         "browser-id3-writer": "^4.4.0", | 
			
		
	
		
			
				
					|  |  |  |         "core-js": "^3.16.0", | 
			
		
	
	
		
			
				
					|  |  | @ -3485,6 +3486,17 @@ | 
			
		
	
		
			
				
					|  |  |  |       "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", | 
			
		
	
		
			
				
					|  |  |  |       "dev": true | 
			
		
	
		
			
				
					|  |  |  |     }, | 
			
		
	
		
			
				
					|  |  |  |     "node_modules/@unlock-music-gh/joox-crypto": { | 
			
		
	
		
			
				
					|  |  |  |       "version": "0.0.1-R3", | 
			
		
	
		
			
				
					|  |  |  |       "resolved": "https://registry.npmjs.org/@unlock-music-gh/joox-crypto/-/joox-crypto-0.0.1-R3.tgz", | 
			
		
	
		
			
				
					|  |  |  |       "integrity": "sha512-zZRiDXKI5SxuBIcW/rsGL8jNvyWxtA5cNRfg69WcsZK2DqztY8M2q1kMe96MP1AyM+cKpNQ50jAKh77VdFv9rA==", | 
			
		
	
		
			
				
					|  |  |  |       "dependencies": { | 
			
		
	
		
			
				
					|  |  |  |         "crypto-js": "^4.1.1" | 
			
		
	
		
			
				
					|  |  |  |       }, | 
			
		
	
		
			
				
					|  |  |  |       "bin": { | 
			
		
	
		
			
				
					|  |  |  |         "joox-decrypt": "joox-decrypt" | 
			
		
	
		
			
				
					|  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |     }, | 
			
		
	
		
			
				
					|  |  |  |     "node_modules/@vue/babel-helper-vue-jsx-merge-props": { | 
			
		
	
		
			
				
					|  |  |  |       "version": "1.2.1", | 
			
		
	
		
			
				
					|  |  |  |       "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz", | 
			
		
	
	
		
			
				
					|  |  | @ -23622,6 +23634,14 @@ | 
			
		
	
		
			
				
					|  |  |  |       "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", | 
			
		
	
		
			
				
					|  |  |  |       "dev": true | 
			
		
	
		
			
				
					|  |  |  |     }, | 
			
		
	
		
			
				
					|  |  |  |     "@unlock-music-gh/joox-crypto": { | 
			
		
	
		
			
				
					|  |  |  |       "version": "0.0.1-R3", | 
			
		
	
		
			
				
					|  |  |  |       "resolved": "https://registry.npmjs.org/@unlock-music-gh/joox-crypto/-/joox-crypto-0.0.1-R3.tgz", | 
			
		
	
		
			
				
					|  |  |  |       "integrity": "sha512-zZRiDXKI5SxuBIcW/rsGL8jNvyWxtA5cNRfg69WcsZK2DqztY8M2q1kMe96MP1AyM+cKpNQ50jAKh77VdFv9rA==", | 
			
		
	
		
			
				
					|  |  |  |       "requires": { | 
			
		
	
		
			
				
					|  |  |  |         "crypto-js": "^4.1.1" | 
			
		
	
		
			
				
					|  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |     }, | 
			
		
	
		
			
				
					|  |  |  |     "@vue/babel-helper-vue-jsx-merge-props": { | 
			
		
	
		
			
				
					|  |  |  |       "version": "1.2.1", | 
			
		
	
		
			
				
					|  |  |  |       "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz", | 
			
		
	
	
		
			
				
					|  |  | 
 | 
			
		
	
								
							
						
					 
					
				 
			 
		
			
			
			
			
			
			
				
				
					
						
							
								
									
	
		
			
				
					|  |  | @ -22,6 +22,7 @@ | 
			
		
	
		
			
				
					|  |  |  |   "dependencies": { | 
			
		
	
		
			
				
					|  |  |  |     "@babel/preset-typescript": "^7.16.5", | 
			
		
	
		
			
				
					|  |  |  |     "@jixun/qmc2-crypto": "^0.0.5-R4", | 
			
		
	
		
			
				
					|  |  |  |     "@unlock-music-gh/joox-crypto": "^0.0.1-R2", | 
			
		
	
		
			
				
					|  |  |  |     "base64-js": "^1.5.1", | 
			
		
	
		
			
				
					|  |  |  |     "browser-id3-writer": "^4.4.0", | 
			
		
	
		
			
				
					|  |  |  |     "core-js": "^3.16.0", | 
			
		
	
	
		
			
				
					|  |  | 
 | 
			
		
	
								
							
						
					 
					
				 
			 
		
			
			
			
			
			
			
				
				
					
						
							
								
									
	
		
			
				
					|  |  | @ -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> | 
			
		
	
								
							
						
					 
					
				 
			 
		
			
			
			
			
			
			
				
				
					
						
							
								
									
	
		
			
				
					|  |  | @ -7,6 +7,7 @@ import { Decrypt as KgmDecrypt } from '@/decrypt/kgm'; | 
			
		
	
		
			
				
					|  |  |  | import { Decrypt as KwmDecrypt } from '@/decrypt/kwm'; | 
			
		
	
		
			
				
					|  |  |  | import { Decrypt as RawDecrypt } from '@/decrypt/raw'; | 
			
		
	
		
			
				
					|  |  |  | import { Decrypt as TmDecrypt } from '@/decrypt/tm'; | 
			
		
	
		
			
				
					|  |  |  | import { Decrypt as JooxDecrypt } from '@/decrypt/joox'; | 
			
		
	
		
			
				
					|  |  |  | import { DecryptResult, FileInfo } from '@/decrypt/entity'; | 
			
		
	
		
			
				
					|  |  |  | import { SplitFilename } from '@/decrypt/utils'; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  | @ -68,6 +69,9 @@ export async function CommonDecrypt(file: FileInfo): Promise<DecryptResult> { | 
			
		
	
		
			
				
					|  |  |  |     case 'kgma': | 
			
		
	
		
			
				
					|  |  |  |       rt_data = await KgmDecrypt(file.raw, raw.name, raw.ext); | 
			
		
	
		
			
				
					|  |  |  |       break; | 
			
		
	
		
			
				
					|  |  |  |     case 'ofl_en': | 
			
		
	
		
			
				
					|  |  |  |       rt_data = await JooxDecrypt(file.raw, raw.name, raw.ext); | 
			
		
	
		
			
				
					|  |  |  |       break; | 
			
		
	
		
			
				
					|  |  |  |     default: | 
			
		
	
		
			
				
					|  |  |  |       throw '不支持此文件格式'; | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
	
		
			
				
					|  |  | 
 | 
			
		
	
								
							
						
					 
					
				 
			 
		
			
			
			
			
			
			
				
				
					
						
							
								
									
	
		
			
				
					|  |  | @ -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, | 
			
		
	
		
			
				
					|  |  |  |   }; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
								
							
						
					 
					
				 
			 
		
			
			
			
			
			
			
				
				
					
						
							
								
									
	
		
			
				
					|  |  | @ -1,4 +1,5 @@ | 
			
		
	
		
			
				
					|  |  |  | import QMCCryptoModule from '@jixun/qmc2-crypto/QMC2-wasm-bundle'; | 
			
		
	
		
			
				
					|  |  |  | import { MergeUint8Array } from '@/utils/MergeUint8Array'; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | // 检测文件末端使用的缓冲区大小
 | 
			
		
	
		
			
				
					|  |  |  | const DETECTION_SIZE = 40; | 
			
		
	
	
		
			
				
					|  |  | @ -6,22 +7,6 @@ const DETECTION_SIZE = 40; | 
			
		
	
		
			
				
					|  |  |  | // 每次处理 2M 的数据
 | 
			
		
	
		
			
				
					|  |  |  | const DECRYPTION_BUF_SIZE = 2 * 1024 * 1024; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | 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; | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  |  * 解密一个 QMC2 加密的文件。 | 
			
		
	
		
			
				
					|  |  |  |  * | 
			
		
	
	
		
			
				
					|  |  | 
 | 
			
		
	
								
							
						
					 
					
				 
			 
		
			
			
			
			
			
			
				
				
					
						
							
								
									
	
		
			
				
					|  |  | @ -6,9 +6,13 @@ import { | 
			
		
	
		
			
				
					|  |  |  |   Checkbox, | 
			
		
	
		
			
				
					|  |  |  |   Col, | 
			
		
	
		
			
				
					|  |  |  |   Container, | 
			
		
	
		
			
				
					|  |  |  |   Dialog, | 
			
		
	
		
			
				
					|  |  |  |   Form, | 
			
		
	
		
			
				
					|  |  |  |   FormItem, | 
			
		
	
		
			
				
					|  |  |  |   Footer, | 
			
		
	
		
			
				
					|  |  |  |   Icon, | 
			
		
	
		
			
				
					|  |  |  |   Image, | 
			
		
	
		
			
				
					|  |  |  |   Input, | 
			
		
	
		
			
				
					|  |  |  |   Link, | 
			
		
	
		
			
				
					|  |  |  |   Main, | 
			
		
	
		
			
				
					|  |  |  |   Notification, | 
			
		
	
	
		
			
				
					|  |  | @ -26,6 +30,10 @@ import 'element-ui/lib/theme-chalk/base.css'; | 
			
		
	
		
			
				
					|  |  |  | Vue.use(Link); | 
			
		
	
		
			
				
					|  |  |  | Vue.use(Image); | 
			
		
	
		
			
				
					|  |  |  | Vue.use(Button); | 
			
		
	
		
			
				
					|  |  |  | Vue.use(Dialog); | 
			
		
	
		
			
				
					|  |  |  | Vue.use(Form); | 
			
		
	
		
			
				
					|  |  |  | Vue.use(FormItem); | 
			
		
	
		
			
				
					|  |  |  | Vue.use(Input); | 
			
		
	
		
			
				
					|  |  |  | Vue.use(Table); | 
			
		
	
		
			
				
					|  |  |  | Vue.use(TableColumn); | 
			
		
	
		
			
				
					|  |  |  | Vue.use(Main); | 
			
		
	
	
		
			
				
					|  |  | 
 | 
			
		
	
								
							
						
					 
					
				 
			 
		
			
			
			
			
			
			
				
				
					
						
							
								
									
	
		
			
				
					|  |  | @ -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 }); | 
			
		
	
		
			
				
					|  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  | } | 
			
		
	
								
							
						
					 
					
				 
			 
		
			
			
			
			
			
			
				
				
					
						
							
								
									
	
		
			
				
					|  |  | @ -10,6 +10,13 @@ | 
			
		
	
		
			
				
					|  |  |  |         </el-radio> | 
			
		
	
		
			
				
					|  |  |  |       </el-row> | 
			
		
	
		
			
				
					|  |  |  |       <el-row> | 
			
		
	
		
			
				
					|  |  |  |         <config-dialog :show="showConfigDialog" @done="showConfigDialog = false"></config-dialog> | 
			
		
	
		
			
				
					|  |  |  |         <el-tooltip class="item" effect="dark" placement="top"> | 
			
		
	
		
			
				
					|  |  |  |           <div slot="content"> | 
			
		
	
		
			
				
					|  |  |  |             <span> 部分解密方案需要设定解密参数。 </span> | 
			
		
	
		
			
				
					|  |  |  |           </div> | 
			
		
	
		
			
				
					|  |  |  |           <el-button icon="el-icon-s-tools" plain @click="showConfigDialog = true">解密设定</el-button> | 
			
		
	
		
			
				
					|  |  |  |         </el-tooltip> | 
			
		
	
		
			
				
					|  |  |  |         <el-button icon="el-icon-download" plain @click="handleDownloadAll">下载全部</el-button> | 
			
		
	
		
			
				
					|  |  |  |         <el-button icon="el-icon-delete" plain type="danger" @click="handleDeleteAll">清除全部</el-button> | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  | @ -35,6 +42,8 @@ | 
			
		
	
		
			
				
					|  |  |  | <script> | 
			
		
	
		
			
				
					|  |  |  | import FileSelector from '@/component/FileSelector'; | 
			
		
	
		
			
				
					|  |  |  | import PreviewTable from '@/component/PreviewTable'; | 
			
		
	
		
			
				
					|  |  |  | import ConfigDialog from '@/component/ConfigDialog'; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | import { DownloadBlobMusic, FilenamePolicy, FilenamePolicies, RemoveBlobMusic, DirectlyWriteFile } from '@/utils/utils'; | 
			
		
	
		
			
				
					|  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  | export default { | 
			
		
	
	
		
			
				
					|  |  | @ -42,9 +51,11 @@ export default { | 
			
		
	
		
			
				
					|  |  |  |   components: { | 
			
		
	
		
			
				
					|  |  |  |     FileSelector, | 
			
		
	
		
			
				
					|  |  |  |     PreviewTable, | 
			
		
	
		
			
				
					|  |  |  |     ConfigDialog, | 
			
		
	
		
			
				
					|  |  |  |   }, | 
			
		
	
		
			
				
					|  |  |  |   data() { | 
			
		
	
		
			
				
					|  |  |  |     return { | 
			
		
	
		
			
				
					|  |  |  |       showConfigDialog: false, | 
			
		
	
		
			
				
					|  |  |  |       tableData: [], | 
			
		
	
		
			
				
					|  |  |  |       playing_url: '', | 
			
		
	
		
			
				
					|  |  |  |       playing_auto: false, | 
			
		
	
	
		
			
				
					|  |  | @ -103,6 +114,9 @@ export default { | 
			
		
	
		
			
				
					|  |  |  |       }); | 
			
		
	
		
			
				
					|  |  |  |       this.tableData = []; | 
			
		
	
		
			
				
					|  |  |  |     }, | 
			
		
	
		
			
				
					|  |  |  |     handleDecryptionConfig() { | 
			
		
	
		
			
				
					|  |  |  |       this.showConfigDialog = true; | 
			
		
	
		
			
				
					|  |  |  |     }, | 
			
		
	
		
			
				
					|  |  |  |     handleDownloadAll() { | 
			
		
	
		
			
				
					|  |  |  |       let index = 0; | 
			
		
	
		
			
				
					|  |  |  |       let c = setInterval(() => { | 
			
		
	
	
		
			
				
					|  |  | 
 |