Compare commits
15 Commits
v1.9.0-bet
...
v1.9.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d3898161b9 | ||
![]() |
5a7a9e3add | ||
![]() |
652bb1fc32 | ||
![]() |
6737e8c11b | ||
![]() |
71862538b7 | ||
![]() |
4251b94b1f | ||
![]() |
8fdda048f6 | ||
![]() |
39c7294996 | ||
![]() |
48f879cb58 | ||
![]() |
f0875ad175 | ||
![]() |
02a146e069 | ||
![]() |
2e31853ffb | ||
![]() |
a7aaf246ae | ||
![]() |
4bc0a10c09 | ||
![]() |
3645dd7d01 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -3,6 +3,7 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- "**/*.js"
|
||||
- "**/*.ts"
|
||||
- "**/*.vue"
|
||||
- "public/**/*"
|
||||
- "package-lock.json"
|
||||
@@ -12,6 +13,7 @@ on:
|
||||
types: [ opened, synchronize, reopened ]
|
||||
paths:
|
||||
- "**/*.js"
|
||||
- "**/*.ts"
|
||||
- "**/*.vue"
|
||||
- "public/**/*"
|
||||
- "package-lock.json"
|
||||
|
4157
package-lock.json
generated
4157
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "unlock-music",
|
||||
"version": "v1.9.0-beta",
|
||||
"version": "v1.9.0",
|
||||
"updateInfo": "新增写入本地文件系统; 优化.kwm解锁; 支持.acc嗅探; 使用Typescript重构",
|
||||
"license": "MIT",
|
||||
"description": "Unlock encrypted music file in browser.",
|
||||
@@ -18,19 +18,19 @@
|
||||
"dependencies": {
|
||||
"base64-js": "^1.5.1",
|
||||
"browser-id3-writer": "^4.4.0",
|
||||
"core-js": "^3.12.1",
|
||||
"crypto-js": "^4.0.0",
|
||||
"element-ui": "^2.15.1",
|
||||
"core-js": "^3.16.0",
|
||||
"crypto-js": "^4.1.1",
|
||||
"element-ui": "^2.15.5",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"jimp": "^0.16.1",
|
||||
"metaflac-js": "^1.0.5",
|
||||
"music-metadata-browser": "^2.2.6",
|
||||
"music-metadata-browser": "^2.4.3",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"threads": "^1.6.4",
|
||||
"vue": "^2.6.12"
|
||||
"threads": "^1.6.5",
|
||||
"vue": "^2.6.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.0.1",
|
||||
"@types/crypto-js": "^4.0.2",
|
||||
"@vue/cli-plugin-babel": "^4.5.13",
|
||||
"@vue/cli-plugin-pwa": "^4.5.13",
|
||||
"@vue/cli-plugin-typescript": "^4.5.13",
|
||||
@@ -40,8 +40,8 @@
|
||||
"sass-loader": "^10.2.0",
|
||||
"semver": "^7.3.5",
|
||||
"threads-plugin": "^1.4.0",
|
||||
"typescript": "~4.1.5",
|
||||
"typescript": "~4.1.6",
|
||||
"vue-cli-plugin-element": "^1.0.1",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
}
|
||||
}
|
||||
|
@@ -63,7 +63,7 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (window.Worker && process.env.NODE_ENV === 'production') {
|
||||
if (window.Worker && window.location.protocol !== "file:" && process.env.NODE_ENV === 'production') {
|
||||
console.log("Using Worker Pool")
|
||||
this.queue = Pool(
|
||||
() => spawn(new Worker('@/utils/worker.ts')),
|
||||
|
@@ -1,37 +1,42 @@
|
||||
import {Decrypt as NcmDecrypt} from "@/decrypt/ncm";
|
||||
import {Decrypt as NcmCacheDecrypt} from "@/decrypt/ncmcache";
|
||||
import {Decrypt as XmDecrypt} from "@/decrypt/xm";
|
||||
import {Decrypt as QmcDecrypt} from "@/decrypt/qmc";
|
||||
import {Decrypt as QmcCacheDecrypt} from "@/decrypt/qmccache";
|
||||
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 {DecryptResult, FileInfo} from "@/decrypt/entity";
|
||||
import {SplitFilename} from "@/decrypt/utils";
|
||||
|
||||
|
||||
export async function CommonDecrypt(file: FileInfo): Promise<DecryptResult> {
|
||||
let raw_ext = file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length).toLowerCase();
|
||||
let raw_filename = file.name.substring(0, file.name.lastIndexOf("."));
|
||||
const raw = SplitFilename(file.name)
|
||||
let rt_data: DecryptResult;
|
||||
switch (raw_ext) {
|
||||
switch (raw.ext) {
|
||||
case "ncm":// Netease Mp3/Flac
|
||||
rt_data = await NcmDecrypt(file.raw, raw_filename, raw_ext);
|
||||
rt_data = await NcmDecrypt(file.raw, raw.name, raw.ext);
|
||||
break;
|
||||
case "uc":// Netease Cache
|
||||
rt_data = await NcmCacheDecrypt(file.raw, raw.name, raw.ext);
|
||||
break;
|
||||
case "kwm":// Kuwo Mp3/Flac
|
||||
rt_data = await KwmDecrypt(file.raw, raw_filename, raw_ext);
|
||||
rt_data = await KwmDecrypt(file.raw, raw.name, raw.ext);
|
||||
break
|
||||
case "xm": // Xiami Wav/M4a/Mp3/Flac
|
||||
case "wav":// Xiami/Raw Wav
|
||||
case "mp3":// Xiami/Raw Mp3
|
||||
case "flac":// Xiami/Raw Flac
|
||||
case "m4a":// Xiami/Raw M4a
|
||||
rt_data = await XmDecrypt(file.raw, raw_filename, raw_ext);
|
||||
rt_data = await XmDecrypt(file.raw, raw.name, raw.ext);
|
||||
break;
|
||||
case "ogg":// Raw Ogg
|
||||
rt_data = await RawDecrypt(file.raw, raw_filename, raw_ext);
|
||||
rt_data = await RawDecrypt(file.raw, raw.name, raw.ext);
|
||||
break;
|
||||
case "tm0":// QQ Music IOS Mp3
|
||||
case "tm3":// QQ Music IOS Mp3
|
||||
rt_data = await RawDecrypt(file.raw, raw_filename, "mp3");
|
||||
rt_data = await RawDecrypt(file.raw, raw.name, "mp3");
|
||||
break;
|
||||
case "qmc3"://QQ Music Android Mp3
|
||||
case "qmc2"://QQ Music Android Ogg
|
||||
@@ -48,23 +53,26 @@ export async function CommonDecrypt(file: FileInfo): Promise<DecryptResult> {
|
||||
case "6f6767"://QQ Music Weiyun Ogg
|
||||
case "6d3461"://QQ Music Weiyun M4a
|
||||
case "776176"://QQ Music Weiyun Wav
|
||||
rt_data = await QmcDecrypt(file.raw, raw_filename, raw_ext);
|
||||
rt_data = await QmcDecrypt(file.raw, raw.name, raw.ext);
|
||||
break;
|
||||
case "tm2":// QQ Music IOS M4a
|
||||
case "tm6":// QQ Music IOS M4a
|
||||
rt_data = await TmDecrypt(file.raw, raw_filename);
|
||||
rt_data = await TmDecrypt(file.raw, raw.name);
|
||||
break;
|
||||
case "cache"://QQ Music Cache
|
||||
rt_data = await QmcCacheDecrypt(file.raw, raw.name, raw.ext);
|
||||
break;
|
||||
case "vpr":
|
||||
case "kgm":
|
||||
case "kgma":
|
||||
rt_data = await KgmDecrypt(file.raw, raw_filename, raw_ext);
|
||||
rt_data = await KgmDecrypt(file.raw, raw.name, raw.ext);
|
||||
break
|
||||
default:
|
||||
throw "不支持此文件格式"
|
||||
}
|
||||
|
||||
if (!rt_data.rawExt) rt_data.rawExt = raw_ext;
|
||||
if (!rt_data.rawFilename) rt_data.rawFilename = raw_filename;
|
||||
if (!rt_data.rawExt) rt_data.rawExt = raw.ext;
|
||||
if (!rt_data.rawFilename) rt_data.rawFilename = raw.name;
|
||||
console.log(rt_data);
|
||||
return rt_data;
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import {
|
||||
} from "@/decrypt/utils.ts";
|
||||
import {parseBlob as metaParseBlob} from "music-metadata-browser";
|
||||
import {DecryptResult} from "@/decrypt/entity";
|
||||
import config from "@/../package.json"
|
||||
|
||||
const VprHeader = [
|
||||
0x05, 0x28, 0xBC, 0x96, 0xE9, 0xE4, 0x5A, 0x43,
|
||||
@@ -22,9 +23,6 @@ const VprMaskDiff = [
|
||||
|
||||
|
||||
export async function Decrypt(file: File, raw_filename: string, raw_ext: string): Promise<DecryptResult> {
|
||||
if (window?.location?.protocol === "file:") {
|
||||
throw Error("请使用 <a target='_blank' href='https://github.com/unlock-music/cli'>CLI版本</a> 进行解锁")
|
||||
}
|
||||
|
||||
const oriData = new Uint8Array(await GetArrayBuffer(file));
|
||||
if (raw_ext === "vpr") {
|
||||
@@ -84,8 +82,16 @@ function GetMask(pos: number) {
|
||||
let MaskV2: Uint8Array = new Uint8Array(0);
|
||||
|
||||
async function LoadMaskV2(): Promise<boolean> {
|
||||
let mask_url = `https://cdn.jsdelivr.net/gh/unlock-music/unlock-music@${config.version}/public/static/kgm.mask`
|
||||
if (["http:", "https:"].some(v => v == self.location.protocol)) {
|
||||
if (!!self.document) {// using Web Worker
|
||||
mask_url = "./static/kgm.mask"
|
||||
} else {// using Main thread
|
||||
mask_url = "../static/kgm.mask"
|
||||
}
|
||||
}
|
||||
try {
|
||||
const resp = await fetch("./static/kgm.mask", {method: "GET"})
|
||||
const resp = await fetch(mask_url, {method: "GET"})
|
||||
MaskV2 = new Uint8Array(await resp.arrayBuffer());
|
||||
return true
|
||||
} catch (e) {
|
||||
|
@@ -11,11 +11,18 @@ import {
|
||||
import {parseBlob as metaParseBlob} from "music-metadata-browser";
|
||||
import jimp from 'jimp';
|
||||
|
||||
import CryptoJS from "crypto-js";
|
||||
import AES from "crypto-js/aes";
|
||||
import PKCS7 from "crypto-js/pad-pkcs7";
|
||||
import ModeECB from "crypto-js/mode-ecb";
|
||||
import WordArray from "crypto-js/lib-typedarrays";
|
||||
import Base64 from "crypto-js/enc-base64";
|
||||
import EncUTF8 from "crypto-js/enc-utf8";
|
||||
import EncHex from "crypto-js/enc-hex";
|
||||
|
||||
import {DecryptResult} from "@/decrypt/entity";
|
||||
|
||||
const CORE_KEY = CryptoJS.enc.Hex.parse("687a4852416d736f356b496e62617857");
|
||||
const META_KEY = CryptoJS.enc.Hex.parse("2331346C6A6B5F215C5D2630553C2728");
|
||||
const CORE_KEY = EncHex.parse("687a4852416d736f356b496e62617857");
|
||||
const META_KEY = EncHex.parse("2331346C6A6B5F215C5D2630553C2728");
|
||||
const MagicHeader = [0x43, 0x54, 0x45, 0x4E, 0x46, 0x44, 0x41, 0x4D];
|
||||
|
||||
|
||||
@@ -67,11 +74,11 @@ class NcmDecrypt {
|
||||
.map(uint8 => uint8 ^ 0x64);
|
||||
this.offset += keyLen;
|
||||
|
||||
const plainText = CryptoJS.AES.decrypt(
|
||||
const plainText = AES.decrypt(
|
||||
// @ts-ignore
|
||||
{ciphertext: CryptoJS.lib.WordArray.create(cipherText)},
|
||||
{ciphertext: WordArray.create(cipherText)},
|
||||
CORE_KEY,
|
||||
{mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}
|
||||
{mode: ModeECB, padding: PKCS7}
|
||||
);
|
||||
|
||||
const result = new Uint8Array(plainText.sigBytes);
|
||||
@@ -115,17 +122,18 @@ class NcmDecrypt {
|
||||
.map(data => data ^ 0x63);
|
||||
this.offset += metaDataLen;
|
||||
|
||||
const plainText = CryptoJS.AES.decrypt(
|
||||
//@ts-ignore
|
||||
WordArray.create()
|
||||
const plainText = AES.decrypt(
|
||||
// @ts-ignore
|
||||
{
|
||||
ciphertext: CryptoJS.enc.Base64.parse(
|
||||
//@ts-ignore
|
||||
CryptoJS.lib.WordArray.create(cipherText.slice(22)).toString(CryptoJS.enc.Utf8)
|
||||
ciphertext: Base64.parse(
|
||||
// @ts-ignore
|
||||
WordArray.create(cipherText.slice(22)).toString(EncUTF8)
|
||||
)
|
||||
},
|
||||
META_KEY,
|
||||
{mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7}
|
||||
).toString(CryptoJS.enc.Utf8);
|
||||
{mode: ModeECB, padding: PKCS7}
|
||||
).toString(EncUTF8);
|
||||
|
||||
const labelIndex = plainText.indexOf(":");
|
||||
let result: NcmMusicMeta;
|
||||
|
29
src/decrypt/ncmcache.ts
Normal file
29
src/decrypt/ncmcache.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {AudioMimeType, GetArrayBuffer, GetCoverFromFile, GetMetaFromFile, SniffAudioExt} from "@/decrypt/utils.ts";
|
||||
|
||||
import {DecryptResult} from "@/decrypt/entity";
|
||||
|
||||
import {parseBlob as metaParseBlob} from "music-metadata-browser";
|
||||
|
||||
export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string)
|
||||
: Promise<DecryptResult> {
|
||||
const buffer = new Uint8Array(await GetArrayBuffer(file));
|
||||
let length = buffer.length
|
||||
for (let i = 0; i < length; i++) {
|
||||
buffer[i] ^= 163
|
||||
}
|
||||
const ext = SniffAudioExt(buffer, raw_ext);
|
||||
if (ext !== raw_ext) file = new Blob([buffer], {type: AudioMimeType[ext]})
|
||||
const tag = await metaParseBlob(file);
|
||||
const {title, artist} = GetMetaFromFile(raw_filename, tag.common.title, tag.common.artist)
|
||||
|
||||
return {
|
||||
title,
|
||||
artist,
|
||||
ext,
|
||||
album: tag.common.album,
|
||||
picture: GetCoverFromFile(tag),
|
||||
file: URL.createObjectURL(file),
|
||||
blob: file,
|
||||
mime: AudioMimeType[ext]
|
||||
}
|
||||
}
|
@@ -22,9 +22,11 @@ interface Handler {
|
||||
handler(data?: Uint8Array): QmcMask | undefined
|
||||
}
|
||||
|
||||
const HandlerMap: { [key: string]: Handler } = {
|
||||
export const HandlerMap: { [key: string]: Handler } = {
|
||||
"mgg": {handler: QmcMaskDetectMgg, ext: "ogg", detect: true},
|
||||
"mflac": {handler: QmcMaskDetectMflac, ext: "flac", detect: true},
|
||||
"mgg.cache": {handler: QmcMaskDetectMgg, ext: "ogg", detect: false},
|
||||
"mflac.cache": {handler: QmcMaskDetectMflac, ext: "flac", detect: false},
|
||||
"qmc0": {handler: QmcMaskGetDefault, ext: "mp3", detect: false},
|
||||
"qmc2": {handler: QmcMaskGetDefault, ext: "ogg", detect: false},
|
||||
"qmc3": {handler: QmcMaskGetDefault, ext: "mp3", detect: false},
|
||||
@@ -40,8 +42,8 @@ const HandlerMap: { [key: string]: Handler } = {
|
||||
"776176": {handler: QmcMaskGetDefault, ext: "wav", detect: false}
|
||||
};
|
||||
|
||||
export async function Decrypt(file: File, raw_filename: string, raw_ext: string): Promise<DecryptResult> {
|
||||
if (!(raw_ext in HandlerMap)) throw "File type is incorrect!";
|
||||
export async function Decrypt(file: Blob, raw_filename: string, raw_ext: string): Promise<DecryptResult> {
|
||||
if (!(raw_ext in HandlerMap)) throw `Qmc cannot handle type: ${raw_ext}`;
|
||||
const handler = HandlerMap[raw_ext];
|
||||
|
||||
const fileData = new Uint8Array(await GetArrayBuffer(file));
|
||||
@@ -57,6 +59,7 @@ export async function Decrypt(file: File, raw_filename: string, raw_ext: string)
|
||||
} else {
|
||||
audioData = fileData;
|
||||
seed = handler.handler(audioData) as QmcMask;
|
||||
if (!seed) throw raw_ext + "格式仅提供实验性支持";
|
||||
}
|
||||
let musicDecoded = seed.Decrypt(audioData);
|
||||
|
||||
|
51
src/decrypt/qmccache.ts
Normal file
51
src/decrypt/qmccache.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
AudioMimeType,
|
||||
GetArrayBuffer,
|
||||
GetCoverFromFile,
|
||||
GetMetaFromFile,
|
||||
SniffAudioExt,
|
||||
SplitFilename
|
||||
} from "@/decrypt/utils.ts";
|
||||
|
||||
import {Decrypt as QmcDecrypt, HandlerMap} from "@/decrypt/qmc";
|
||||
|
||||
import {DecryptResult} from "@/decrypt/entity";
|
||||
|
||||
import {parseBlob as metaParseBlob} from "music-metadata-browser";
|
||||
|
||||
export async function Decrypt(file: Blob, raw_filename: string, _: string)
|
||||
: Promise<DecryptResult> {
|
||||
const buffer = new Uint8Array(await GetArrayBuffer(file));
|
||||
let length = buffer.length
|
||||
for (let i = 0; i < length; i++) {
|
||||
buffer[i] ^= 0xf4
|
||||
if (buffer[i] <= 0x3f) buffer[i] = buffer[i] * 4;
|
||||
else if (buffer[i] <= 0x7f) buffer[i] = (buffer[i] - 0x40) * 4 + 1;
|
||||
else if (buffer[i] <= 0xbf) buffer[i] = (buffer[i] - 0x80) * 4 + 2;
|
||||
else buffer[i] = (buffer[i] - 0xc0) * 4 + 3;
|
||||
}
|
||||
let ext = SniffAudioExt(buffer, "");
|
||||
const newName = SplitFilename(raw_filename)
|
||||
let audioBlob: Blob
|
||||
if (ext !== "" || newName.ext === "mp3") {
|
||||
audioBlob = new Blob([buffer], {type: AudioMimeType[ext]})
|
||||
} else if (newName.ext in HandlerMap) {
|
||||
audioBlob = new Blob([buffer], {type: "application/octet-stream"})
|
||||
return QmcDecrypt(audioBlob, newName.name, newName.ext);
|
||||
} else {
|
||||
throw "不支持的QQ音乐缓存格式"
|
||||
}
|
||||
const tag = await metaParseBlob(audioBlob);
|
||||
const {title, artist} = GetMetaFromFile(raw_filename, tag.common.title, tag.common.artist)
|
||||
|
||||
return {
|
||||
title,
|
||||
artist,
|
||||
ext,
|
||||
album: tag.common.album,
|
||||
picture: GetCoverFromFile(tag),
|
||||
file: URL.createObjectURL(audioBlob),
|
||||
blob: audioBlob,
|
||||
mime: AudioMimeType[ext]
|
||||
}
|
||||
}
|
@@ -12,13 +12,16 @@ export const WMA_HEADER = [
|
||||
]
|
||||
export const WAV_HEADER = [0x52, 0x49, 0x46, 0x46]
|
||||
export const AAC_HEADER = [0xFF, 0xF1]
|
||||
export const DFF_HEADER = [0x46, 0x52, 0x4D, 0x38]
|
||||
|
||||
export const AudioMimeType: { [key: string]: string } = {
|
||||
mp3: "audio/mpeg",
|
||||
flac: "audio/flac",
|
||||
m4a: "audio/mp4",
|
||||
ogg: "audio/ogg",
|
||||
wma: "audio/x-ms-wma",
|
||||
wav: "audio/x-wav"
|
||||
wav: "audio/x-wav",
|
||||
dff: "audio/x-dff"
|
||||
};
|
||||
|
||||
|
||||
@@ -39,6 +42,7 @@ export function SniffAudioExt(data: Uint8Array, fallback_ext: string = "mp3"): s
|
||||
if (BytesHasPrefix(data, WAV_HEADER)) return "wav"
|
||||
if (BytesHasPrefix(data, WMA_HEADER)) return "wma"
|
||||
if (BytesHasPrefix(data, AAC_HEADER)) return "aac"
|
||||
if (BytesHasPrefix(data, DFF_HEADER)) return "dff"
|
||||
return fallback_ext;
|
||||
}
|
||||
|
||||
@@ -156,3 +160,11 @@ export function WriteMetaToFlac(audioData: Buffer, info: IMusicMeta, original: I
|
||||
}
|
||||
return writer.save()
|
||||
}
|
||||
|
||||
export function SplitFilename(n: string): { name: string; ext: string } {
|
||||
const pos = n.lastIndexOf(".")
|
||||
return {
|
||||
ext: n.substring(pos + 1).toLowerCase(),
|
||||
name: n.substring(0, pos)
|
||||
}
|
||||
}
|
||||
|
9
src/shims-fs.d.ts
vendored
9
src/shims-fs.d.ts
vendored
@@ -6,6 +6,10 @@ interface FileSystemCreateWritableOptions {
|
||||
keepExistingData?: boolean
|
||||
}
|
||||
|
||||
interface FileSystemRemoveOptions {
|
||||
recursive?: boolean
|
||||
}
|
||||
|
||||
interface FileSystemFileHandle {
|
||||
getFile(): Promise<File>;
|
||||
|
||||
@@ -37,13 +41,16 @@ interface FileSystemWritableFileStream extends WritableStream {
|
||||
close(): Promise<undefined> // should be implemented in WritableStream
|
||||
}
|
||||
|
||||
|
||||
export declare interface FileSystemDirectoryHandle {
|
||||
getFileHandle(name: string, options?: FileSystemGetFileOptions): Promise<FileSystemFileHandle>
|
||||
|
||||
removeEntry(name: string, options?: FileSystemRemoveOptions): Promise<undefined>
|
||||
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
FileSystemDirectoryHandle
|
||||
|
||||
showDirectoryPicker?(): Promise<FileSystemDirectoryHandle>
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import {fromByteArray as Base64Encode} from "base64-js";
|
||||
|
||||
export const IXAREA_API_ENDPOINT = "https://stats.ixarea.com/apis"
|
||||
export const IXAREA_API_ENDPOINT = "https://um-api.ixarea.com"
|
||||
|
||||
export interface UpdateInfo {
|
||||
Found: boolean
|
||||
|
@@ -144,9 +144,9 @@ export default {
|
||||
}
|
||||
try {
|
||||
this.dir = await window.showDirectoryPicker()
|
||||
window.dir = this.dir
|
||||
window.f = await this.dir.getFileHandle("write-test.txt", {create: true})
|
||||
|
||||
const test_filename = "__unlock_music_write_test.txt"
|
||||
await this.dir.getFileHandle(test_filename, {create: true})
|
||||
await this.dir.removeEntry(test_filename)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
@@ -26,7 +26,8 @@
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
],
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
|
Reference in New Issue
Block a user