Emmm Monster
4 years ago
9 changed files with 230 additions and 164 deletions
@ -0,0 +1,56 @@ |
|||
import {fromByteArray as Base64Encode} from "base64-js"; |
|||
|
|||
export const IXAREA_API_ENDPOINT = "https://stats.ixarea.com/apis" |
|||
|
|||
export interface UpdateInfo { |
|||
Found: boolean |
|||
HttpsFound: boolean |
|||
Version: string |
|||
URL: string |
|||
Detail: string |
|||
} |
|||
|
|||
export async function checkUpdate(version: string): Promise<UpdateInfo> { |
|||
const resp = await fetch(IXAREA_API_ENDPOINT + "/music/app-version", { |
|||
method: "POST", |
|||
headers: {"Content-Type": "application/json"}, |
|||
body: JSON.stringify({"Version": version}) |
|||
}); |
|||
return await resp.json(); |
|||
} |
|||
|
|||
export function reportKeyUsage(keyData: Uint8Array, maskData: number[], filename: string, format: string, title: string, artist?: string, album?: string) { |
|||
return fetch(IXAREA_API_ENDPOINT + "/qmcmask/usage", { |
|||
method: "POST", |
|||
headers: {"Content-Type": "application/json"}, |
|||
body: JSON.stringify({ |
|||
Mask: Base64Encode(new Uint8Array(maskData)), Key: Base64Encode(keyData), |
|||
Artist: artist, Title: title, Album: album, Filename: filename, Format: format |
|||
}), |
|||
}) |
|||
} |
|||
|
|||
interface KeyInfo { |
|||
Matrix44: string |
|||
} |
|||
|
|||
export async function queryKeyInfo(keyData: Uint8Array, filename: string, format: string): Promise<KeyInfo> { |
|||
const resp = await fetch(IXAREA_API_ENDPOINT + "/qmcmask/query", { |
|||
method: "POST", |
|||
headers: {"Content-Type": "application/json"}, |
|||
body: JSON.stringify({Format: format, Key: Base64Encode(keyData), Filename: filename, Type: 44}), |
|||
}); |
|||
return await resp.json(); |
|||
} |
|||
|
|||
export interface CoverInfo { |
|||
Id: string |
|||
Type: number |
|||
} |
|||
|
|||
export async function queryAlbumCover(title: string, artist?: string, album?: string): Promise<CoverInfo> { |
|||
const endpoint = IXAREA_API_ENDPOINT + "/music/qq-cover" |
|||
const params = new URLSearchParams([["Title", title], ["Artist", artist ?? ""], ["Album", album ?? ""]]) |
|||
const resp = await fetch(`${endpoint}?${params.toString()}`) |
|||
return await resp.json() |
|||
} |
@ -1,20 +1,35 @@ |
|||
import {DecryptResult} from "@/decrypt/entity"; |
|||
|
|||
export function DownloadBlobMusic(data: DecryptResult, format: string) {//todo: use enum
|
|||
export enum FilenamePolicy { |
|||
ArtistAndTitle, |
|||
TitleOnly, |
|||
TitleAndArtist, |
|||
SameAsOriginal, |
|||
} |
|||
|
|||
export const FilenamePolicies: { key: FilenamePolicy, text: string }[] = [ |
|||
{key: FilenamePolicy.ArtistAndTitle, text: "歌手-歌曲名"}, |
|||
{key: FilenamePolicy.TitleOnly, text: "歌曲名"}, |
|||
{key: FilenamePolicy.TitleAndArtist, text: "歌曲名-歌手"}, |
|||
{key: FilenamePolicy.SameAsOriginal, text: "同源文件名"}, |
|||
] |
|||
|
|||
|
|||
export function DownloadBlobMusic(data: DecryptResult, policy: FilenamePolicy) { |
|||
const a = document.createElement('a'); |
|||
a.href = data.file; |
|||
switch (format) { |
|||
switch (policy) { |
|||
default: |
|||
case "1": |
|||
case FilenamePolicy.ArtistAndTitle: |
|||
a.download = data.artist + " - " + data.title + "." + data.ext; |
|||
break; |
|||
case "2": |
|||
case FilenamePolicy.TitleOnly: |
|||
a.download = data.title + "." + data.ext; |
|||
break; |
|||
case "3": |
|||
case FilenamePolicy.TitleAndArtist: |
|||
a.download = data.title + " - " + data.artist + "." + data.ext; |
|||
break; |
|||
case "4": |
|||
case FilenamePolicy.SameAsOriginal: |
|||
a.download = data.rawFilename + "." + data.ext; |
|||
break; |
|||
} |
@ -0,0 +1,111 @@ |
|||
<template> |
|||
<div> |
|||
<file-selector @error="showFail" @success="showSuccess"/> |
|||
|
|||
<div id="app-control"> |
|||
<el-row class="mb-3"> |
|||
<span>歌曲命名格式:</span> |
|||
<el-radio v-for="k in FilenamePolicies" :key="k.key" |
|||
v-model="filename_policy" :label="k.key"> |
|||
{{ k.text }} |
|||
</el-radio> |
|||
</el-row> |
|||
<el-row> |
|||
<el-button icon="el-icon-download" plain @click="handleDownloadAll">下载全部</el-button> |
|||
<el-button icon="el-icon-delete" plain type="danger" @click="handleDeleteAll">清除全部</el-button> |
|||
|
|||
<el-tooltip class="item" effect="dark" placement="top-start"> |
|||
<div slot="content"> |
|||
当您使用此工具进行大量文件解锁的时候,建议开启此选项。<br/> |
|||
开启后,解锁结果将不会存留于浏览器中,防止内存不足。 |
|||
</div> |
|||
<el-checkbox v-model="instant_download" border class="ml-2">立即保存</el-checkbox> |
|||
</el-tooltip> |
|||
</el-row> |
|||
</div> |
|||
|
|||
<audio :autoplay="playing_auto" :src="playing_url" controls/> |
|||
|
|||
<PreviewTable :policy="filename_policy" :table-data="tableData" @play="changePlaying"/> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
|
|||
import FileSelector from "../component/FileSelector" |
|||
import PreviewTable from "../component/PreviewTable" |
|||
import {DownloadBlobMusic, FilenamePolicy, FilenamePolicies, RemoveBlobMusic} from "@/utils/utils" |
|||
|
|||
export default { |
|||
name: 'Home', |
|||
components: { |
|||
FileSelector, |
|||
PreviewTable |
|||
}, |
|||
data() { |
|||
return { |
|||
activeIndex: '1', |
|||
tableData: [], |
|||
playing_url: "", |
|||
playing_auto: false, |
|||
filename_policy: FilenamePolicy.ArtistAndTitle, |
|||
instant_download: false, |
|||
FilenamePolicies |
|||
} |
|||
}, |
|||
methods: { |
|||
showSuccess(data) { |
|||
if (this.instant_download) { |
|||
DownloadBlobMusic(data, this.filename_policy); |
|||
RemoveBlobMusic(data); |
|||
} else { |
|||
this.tableData.push(data); |
|||
this.$notify.success({ |
|||
title: '解锁成功', |
|||
message: '成功解锁 ' + data.title, |
|||
duration: 3000 |
|||
}); |
|||
} |
|||
if (process.env.NODE_ENV === 'production') { |
|||
let _rp_data = [data.title, data.artist, data.album]; |
|||
window._paq.push(["trackEvent", "Unlock", data.rawExt + "," + data.mime, JSON.stringify(_rp_data)]); |
|||
} |
|||
}, |
|||
showFail(errInfo, filename) { |
|||
console.error(errInfo, filename) |
|||
this.$notify.error({ |
|||
title: '出现问题', |
|||
message: errInfo + "," + filename + |
|||
',参考<a target="_blank" href="https://github.com/ix64/unlock-music/wiki/使用提示">使用提示</a>', |
|||
dangerouslyUseHTMLString: true, |
|||
duration: 6000 |
|||
}); |
|||
if (process.env.NODE_ENV === 'production') { |
|||
window._paq.push(["trackEvent", "Error", errInfo, filename]); |
|||
} |
|||
}, |
|||
changePlaying(url) { |
|||
this.playing_url = url; |
|||
this.playing_auto = true; |
|||
}, |
|||
handleDeleteAll() { |
|||
this.tableData.forEach(value => { |
|||
RemoveBlobMusic(value); |
|||
}); |
|||
this.tableData = []; |
|||
}, |
|||
handleDownloadAll() { |
|||
let index = 0; |
|||
let c = setInterval(() => { |
|||
if (index < this.tableData.length) { |
|||
DownloadBlobMusic(this.tableData[index], this.filename_policy); |
|||
index++; |
|||
} else { |
|||
clearInterval(c); |
|||
} |
|||
}, 300); |
|||
} |
|||
}, |
|||
} |
|||
</script> |
|||
|
Loading…
Reference in new issue