feat: add basic joox support
(cherry picked from commit 699333ca06526d747a7eb4a188e896de81e9f014)remotes/origin/HEAD
parent
9add76c060
commit
1e7116a3a9
@ -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