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 }