157 lines
5.3 KiB
JavaScript
157 lines
5.3 KiB
JavaScript
const Router = require('./router')
|
|
const aesjs = require('aes-js');
|
|
|
|
addEventListener('fetch', event => {
|
|
event.respondWith(handleRequest(event.request))
|
|
})
|
|
|
|
const AESKEY = [106, 111, 54, 97, 101, 121, 54, 104, 97, 105, 100, 50, 84, 101, 105, 104]
|
|
|
|
async function url_gen(md5_origin, format, id, media_version) {
|
|
const cipher = new aesjs.ModeOfOperation.ecb(AESKEY)
|
|
|
|
result = [md5_origin, format, id, media_version].join('\xa4')
|
|
|
|
buf = new ArrayBuffer(result.length);
|
|
result_hash = new Uint8Array(buf);
|
|
for (i = 0; i < result.length; i++) {
|
|
result_hash[i] = result.charCodeAt(i);
|
|
}
|
|
result_hash = await crypto.subtle.digest('MD5', result_hash)
|
|
result_hash = Array.from(new Uint8Array(result_hash))
|
|
result_hash = result_hash.reduce(function(previous, current) {
|
|
return previous + "0".concat(current.toString(16)).substr(-2, 2);
|
|
}, ''),
|
|
|
|
result = result_hash + '\xa4' + result + '\xa4'
|
|
|
|
// padding
|
|
result += '\x00'.repeat(result.length % 16 ? 16 - result.length % 16 : 0)
|
|
|
|
// converting to array for encryption / hex string
|
|
result = Array.from(result).map(function(item) {
|
|
return item.charCodeAt(0);
|
|
})
|
|
// encryption / hex string
|
|
result = (result = cipher.encrypt(result)).reduce(function(previous, current) {
|
|
return previous + "0".concat(current.toString(16)).substr(-2, 2);
|
|
}, '')
|
|
|
|
// cdn template with first character of md5 string + hash
|
|
return `https://e-cdns-proxy-${md5_origin[0]}.dzcdn.net/api/1/${result}`
|
|
}
|
|
|
|
const format_string_to_num = {
|
|
'64': '10',
|
|
'128': '1',
|
|
'320': '3',
|
|
'flac': '9',
|
|
'misc': '0',
|
|
}
|
|
|
|
async function handler(type, request) {
|
|
access_token = await KV.get("access_token")
|
|
if (access_token === null) {
|
|
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()
|
|
if (json.error !== undefined) {
|
|
return new Response("Couldn't get access token from Deezer", {status: 500, headers: {'content-type': "text/plain"}})
|
|
}
|
|
|
|
access_token = json.access_token
|
|
await KV.put("access_token", access_token, {expirationTtl: Number(json.expires)})
|
|
}
|
|
|
|
const url = new URL(request.url)
|
|
const id = url.pathname.split('/')[2]
|
|
|
|
if (id === "") {
|
|
return new Response("ID needs to be specified", {status: 400, headers: {'content-type': "text/plain"}})
|
|
}
|
|
|
|
format = url.searchParams.get('f')
|
|
if (format === null) {
|
|
if (!(id[0] === '-')) {
|
|
format = '320'
|
|
} else {
|
|
format = 'misc' // user-uploaded tracks
|
|
}
|
|
} else {
|
|
format = format.toLowerCase()
|
|
if (format_string_to_num[format] === undefined) {
|
|
index = Object.values(format_string_to_num).indexOf(format)
|
|
if (index === -1) {
|
|
return new Response("Invalid format", {status: 400, headers: {'content-type': "text/plain"}})
|
|
}
|
|
format = Object.keys(format_string_to_num)[index]
|
|
}
|
|
}
|
|
|
|
switch (type) {
|
|
case 'track':
|
|
return await track(id, format, access_token)
|
|
case 'album':
|
|
case 'playlist':
|
|
return await m3u(type, id, format, access_token)
|
|
}
|
|
}
|
|
|
|
async function track(id, format, access_token) {
|
|
const response = await fetch(`https://api.deezer.com/track/${id}?access_token=${access_token}`)
|
|
const json = await response.json()
|
|
if (json.error !== undefined) {
|
|
return new Response(JSON.stringify(json.error), {status: 403, headers: {'content-type': "application/json"}})
|
|
}
|
|
|
|
result = await track_url(json, format)
|
|
if (typeof result === 'object') {
|
|
return result
|
|
}
|
|
return new Response(null, {status: 302, headers: {'location': result}})
|
|
|
|
}
|
|
|
|
async function track_url(json, format) {
|
|
const id = json.id
|
|
const md5_origin = json.md5_origin
|
|
const media_version = json.media_version
|
|
|
|
if (json['filesize_' + format] === '0') {
|
|
return new Response("Format unavailable", {status: 403, headers: {'content-type': "text/plain"}})
|
|
}
|
|
|
|
format = format_string_to_num[format]
|
|
|
|
return await url_gen(md5_origin, format, id, media_version)
|
|
}
|
|
|
|
async function m3u(type, id, format, access_token) {
|
|
const response = await fetch(`https://api.deezer.com/${type}/${id}?access_token=${access_token}&limit=-1`)
|
|
const json = await response.json()
|
|
if (json.error !== undefined) {
|
|
return new Response(JSON.stringify(json.error), {status: 403, headers: {'content-type': "application/json"}})
|
|
}
|
|
|
|
var list = "#EXTM3U\n"
|
|
|
|
for (const track of json.tracks.data) {
|
|
const result = await track_url(track, format)
|
|
if (typeof result === 'object') {
|
|
return result
|
|
}
|
|
list += `#EXTINF:${track.duration},${track.title}\n${result}\n`
|
|
}
|
|
|
|
return new Response(list, {status: 200, headers: {'content-type': 'audio/mpegurl'}})
|
|
}
|
|
|
|
async function handleRequest(request) {
|
|
const r = new Router()
|
|
r.get('/track/.*', () => handler('track', request))
|
|
r.get('/album/.*', () => handler('album', request))
|
|
r.get('/playlist/.*', () => handler('playlist', request))
|
|
|
|
const resp = await r.route(request)
|
|
return resp
|
|
}
|