added decryption bullshit, also flac ded

This commit is contained in:
uh wot 2021-07-30 19:34:39 +02:00
parent 705da64066
commit 026edf3808
Signed by: uhwot
GPG Key ID: CB2454984587B781
4 changed files with 79 additions and 47 deletions

1
blowfish.js Normal file

File diff suppressed because one or more lines are too long

6
dist/worker.js vendored

File diff suppressed because one or more lines are too long

117
index.js
View File

@ -1,12 +1,14 @@
const Router = require('./router') const Router = require('./router')
const aesjs = require('aes-js'); const aesjs = require('aes-js');
const ID3Writer = require('browser-id3-writer'); const ID3Writer = require('browser-id3-writer');
const Blowfish = require('./blowfish');
addEventListener('fetch', event => { addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request)) event.respondWith(handleRequest(event.request))
}) })
const AESKEY = [106, 111, 54, 97, 101, 121, 54, 104, 97, 105, 100, 50, 84, 101, 105, 104] const trackCDNKey = [106, 111, 54, 97, 101, 121, 54, 104, 97, 105, 100, 50, 84, 101, 105, 104]
const bfSecret = [103, 52, 101, 108, 53, 56, 119, 99, 48, 122, 118, 102, 57, 110, 97, 49]
function str2bin(str) { function str2bin(str) {
return Array.from(str).map(function (item) { return Array.from(str).map(function (item) {
@ -14,8 +16,23 @@ function str2bin(str) {
}) })
} }
function bin2str(bin) {
return String.fromCharCode.apply(String, bin);
}
async function bf_key(id) {
let id_md5 = await crypto.subtle.digest('MD5', new Uint8Array(str2bin(id)))
id_md5 = aesjs.utils.hex.fromBytes(new Uint8Array(id_md5))
key = []
for (let i = 0; i < 16; i++) {
key.push(id_md5[i].charCodeAt(0) ^ id_md5[i + 16].charCodeAt(0) ^ bfSecret[i])
}
return bin2str(key)
}
async function url_gen(md5_origin, format, id, media_version) { async function url_gen(md5_origin, format, id, media_version) {
const cipher = new aesjs.ModeOfOperation.ecb(AESKEY) const cipher = new aesjs.ModeOfOperation.ecb(trackCDNKey)
let result = [md5_origin, format, id, media_version].join('\xa4') let result = [md5_origin, format, id, media_version].join('\xa4')
@ -33,14 +50,13 @@ async function url_gen(md5_origin, format, id, media_version) {
result = aesjs.utils.hex.fromBytes(cipher.encrypt(str2bin(result))) result = aesjs.utils.hex.fromBytes(cipher.encrypt(str2bin(result)))
// cdn template with first character of md5 string + hash // cdn template with first character of md5 string + hash
return `https://cdns-proxy-${md5_origin[0]}.dzcdn.net/api/1/${result}` return `https://cdns-proxy-${md5_origin[0]}.dzcdn.net/mobile/1/${result}`
} }
const format_string_to_num = { const format_string_to_num = {
'64': '10', '64': '10',
'128': '1', '128': '1',
'320': '3', '320': '3',
'flac': '9',
'misc': '0', 'misc': '0',
} }
@ -50,18 +66,18 @@ async function handler(type, request) {
const response = await fetch('https://connect.deezer.com/oauth/access_token.php?grant_type=client_credentials&client_id=447462&client_secret=a83bf7f38ad2f137e444727cfc3775cf&output=json') const response = await fetch('https://connect.deezer.com/oauth/access_token.php?grant_type=client_credentials&client_id=447462&client_secret=a83bf7f38ad2f137e444727cfc3775cf&output=json')
const json = await response.json() const json = await response.json()
if (json.error !== undefined) { if (json.error !== undefined) {
return new Response("Couldn't get access token from Deezer", {status: 500, headers: {'content-type': 'text/plain'}}) return new Response("Couldn't get access token from Deezer", { status: 500, headers: { 'content-type': 'text/plain' } })
} }
access_token = json.access_token access_token = json.access_token
await KV.put('access_token', access_token, {expirationTtl: Number(json.expires)}) await KV.put('access_token', access_token, { expirationTtl: Number(json.expires) })
} }
const url = new URL(request.url) const url = new URL(request.url)
const id = url.pathname.split('/')[2] const id = url.pathname.split('/')[2]
if (id === "") { if (id === "") {
return new Response('ID needs to be specified', {status: 400, headers: {'content-type': 'text/plain'}}) return new Response('ID needs to be specified', { status: 400, headers: { 'content-type': 'text/plain' } })
} }
format = url.searchParams.get('f') format = url.searchParams.get('f')
@ -72,14 +88,14 @@ async function handler(type, request) {
if (format_string_to_num[format] === undefined) { if (format_string_to_num[format] === undefined) {
index = Object.values(format_string_to_num).indexOf(format) index = Object.values(format_string_to_num).indexOf(format)
if (index === -1) { if (index === -1) {
return new Response('Invalid format', {status: 400, headers: {'content-type': 'text/plain'}}) return new Response('Invalid format', { status: 400, headers: { 'content-type': 'text/plain' } })
} }
format = Object.keys(format_string_to_num)[index] format = Object.keys(format_string_to_num)[index]
} }
} }
var tagging = url.searchParams.get('t') var tagging = url.searchParams.get('t')
tagging = (tagging === 'true' || tagging === '1') && format !== 'flac' tagging = tagging === 'true' || tagging === '1'
switch (type) { switch (type) {
case 'track': case 'track':
@ -94,7 +110,7 @@ async function track(id, format, access_token, tagging) {
const response = await fetch(`https://api.deezer.com/track/${id}?access_token=${access_token}`) const response = await fetch(`https://api.deezer.com/track/${id}?access_token=${access_token}`)
const json = await response.json() const json = await response.json()
if (json.error !== undefined) { if (json.error !== undefined) {
return new Response(JSON.stringify(json.error), {status: 403, headers: {'content-type': "application/json"}}) return new Response(JSON.stringify(json.error), { status: 403, headers: { 'content-type': "application/json" } })
} }
result = await track_url(json, format) result = await track_url(json, format)
@ -102,15 +118,14 @@ async function track(id, format, access_token, tagging) {
return result return result
} }
if (!tagging) { const track = await fetch(result)
return new Response(null, {status: 302, headers: {'location': result}}) if (track.status !== 200) {
} else { return new Response("Couldn't get track stream", { status: 403, headers: { 'content-type': 'text/plain' } })
const track = await fetch(result) }
if (track.status !== 200) {
return new Response("Couldn't get track stream", {status: 403, headers: {'content-type': 'text/plain'}})
}
const id3 = new ID3Writer(Buffer.alloc(0)); let id3
if (tagging) {
id3 = new ID3Writer(Buffer.alloc(0));
id3.padding = 0 id3.padding = 0
id3.setFrame('TIT2', json.title) id3.setFrame('TIT2', json.title)
.setFrame('TALB', json.album.title) .setFrame('TALB', json.album.title)
@ -144,7 +159,7 @@ async function track(id, format, access_token, tagging) {
if (json.release_date !== undefined) { if (json.release_date !== undefined) {
const split = json.release_date.split('-') const split = json.release_date.split('-')
id3.setFrame('TYER', split[0]) id3.setFrame('TYER', split[0])
id3.setFrame('TDAT', split[2]+split[1]) id3.setFrame('TDAT', split[2] + split[1])
} }
if (json.md5_image !== "") { if (json.md5_image !== "") {
@ -160,16 +175,40 @@ async function track(id, format, access_token, tagging) {
} }
id3.addTag(); id3.addTag();
let { readable, writable } = new TransformStream()
const writer = writable.getWriter()
writer.write(id3.arrayBuffer)
writer.releaseLock()
track.body.pipeTo(writable)
return new Response(readable, {status: 200, headers: {'content-type': 'audio/mpeg'}})
} }
let { readable, writable } = new TransformStream()
const writer = writable.getWriter()
if (tagging) {
writer.write(id3.arrayBuffer)
}
const bfKey = await bf_key(id)
console.log(bfKey)
const cipher = new Blowfish(bfKey, Blowfish.MODE.CBC, Blowfish.PADDING.NULL)
cipher.setIv('\x00\x01\x02\x03\x04\x05\x06\x07')
const buffer = await track.arrayBuffer()
const length = buffer.byteLength
let byteCount = 0
let end = false
while (!end) {
let chunkEnd = byteCount + 2048
if (chunkEnd > length) {
chunkEnd = length
end = true
}
let chunk = buffer.slice(byteCount, byteCount+2048)
if (byteCount % 6144 === 0 && !end) {
// encrypted chunk
chunk = cipher.decode(chunk, Blowfish.TYPE.UINT8_ARRAY)
}
writer.write(chunk)
byteCount += 2048
}
return new Response(readable, { status: 200, headers: { 'content-type': 'audio/mpeg' } })
} }
async function track_url(json, format) { async function track_url(json, format) {
@ -187,7 +226,7 @@ async function track_url(json, format) {
} }
if (json['filesize_' + format] === '0') { if (json['filesize_' + format] === '0') {
return new Response('Format unavailable', {status: 403, headers: {'content-type': 'text/plain'}}) return new Response('Format unavailable', { status: 403, headers: { 'content-type': 'text/plain' } })
} }
format = format_string_to_num[format] format = format_string_to_num[format]
@ -199,28 +238,20 @@ async function m3u8(type, id, format, access_token, tagging) {
const response = await fetch(`https://api.deezer.com/${type}/${id}?access_token=${access_token}&limit=-1`) const response = await fetch(`https://api.deezer.com/${type}/${id}?access_token=${access_token}&limit=-1`)
const json = await response.json() const json = await response.json()
if (json.error !== undefined) { if (json.error !== undefined) {
return new Response(JSON.stringify(json.error), {status: 403, headers: {'content-type': "application/json"}}) return new Response(JSON.stringify(json.error), { status: 403, headers: { 'content-type': "application/json" } })
} }
var list = '#EXTM3U\n' var list = '#EXTM3U\n'
for (const track of json.tracks.data) { for (const track of json.tracks.data) {
let result if (track.id < 0) { // user-uploaded track
if (!tagging) { format = 'misc'
result = await track_url(track, format)
if (typeof result === 'object') {
return result
}
} else {
if (track.id < 0) { // user-uploaded track
format = 'misc'
}
result = `https://dz.uhwot.workers.dev/track/${track.id}?f=${format}&t=1`
} }
let result = `https://dz.uhwot.workers.dev/track/${track.id}?f=${format}&t=${Number(tagging)}`
list += `#EXTINF:${track.duration},${track.title}\n${result}\n` list += `#EXTINF:${track.duration},${track.title}\n${result}\n`
} }
return new Response(list, {status: 200, headers: {'content-type': 'audio/mpegurl'}}) return new Response(list, { status: 200, headers: { 'content-type': 'audio/mpegurl' } })
} }
async function handleRequest(request) { async function handleRequest(request) {

View File

@ -5,5 +5,5 @@ workers_dev = true
route = "" route = ""
zone_id = "" zone_id = ""
kv_namespaces = [ kv_namespaces = [
{ binding = "KV", id = "08543eae8cb7494f8f17addaf70f4143", preview_id = "66868b338ff34776a7d8a5d1877c8181" } { binding = "KV", id = "974c0967a84e415daa054bbbcc7f80c6", preview_id = "cfcc6491f3484cbca664913836635113" }
] ]