diff --git a/dzunlock.user.js b/dzunlock.user.js index 4408ad0..a03f1d7 100644 --- a/dzunlock.user.js +++ b/dzunlock.user.js @@ -3,7 +3,7 @@ // @namespace io.github.uhwot.dzunlock // @description enables deezer premium features lol // @author uh wot -// @version 1.2.5 +// @version 1.2.6 // @license GPL-3.0-only // @homepageURL https://git.freezer.life/uhwot/dzunlock // @downloadURL https://git.freezer.life/uhwot/dzunlock/raw/branch/master/dzunlock.user.js @@ -12,6 +12,7 @@ // @match https://www.deezer.com/search/* // @match https://www.deezer.com/account/* // @match https://www.deezer.com/smarttracklist/* +// @require https://cdnjs.cloudflare.com/ajax/libs/aes-js/3.1.2/index.min.js // @grant GM_getValue // @grant GM_setValue // @run-at document-start @@ -86,6 +87,46 @@ fetchIntercept = { const clientId = '119915' const clientSecret = '2f5b4c9785ddc367975b83d90dc46f5c' +const playerTokenKey = [102, 228, 95, 242, 215, 50, 122, 26, 57, 216, 206, 38, 164, 237, 200, 85] +const cipher = new aesjs.ModeOfOperation.ecb(playerTokenKey) + +function str2bin(str) { + return Array.from(str).map(function (item) { + return item.charCodeAt(0); + }) +} + +function bin2str(bin) { + return String.fromCharCode.apply(String, bin); +} + +function decryptHex(hex) { + hex = aesjs.utils.hex.toBytes(hex) + return bin2str(cipher.decrypt(hex)).replace(/\0+$/, '') // removes zero-padding +} + +function encryptHex(str) { + // zero-padding + if (str.length % 16) { + str += '\x00'.repeat(16 - str.length % 16) + } + + return aesjs.utils.hex.fromBytes(cipher.encrypt(str2bin(str))) +} + +function playerTokenPatch(playerToken) { + playerToken = JSON.parse(decryptHex(playerToken)) + + // enables 320/flac quality selection + playerToken.audio_qualities.wifi_streaming = ['low', 'standard', 'high'] + // disables previews + playerToken.streaming = true + playerToken.limited = false + + log(playerToken) + + return encryptHex(JSON.stringify(playerToken)) +} async function renewAccessToken() { let url = `https://connect.deezer.com/oauth/access_token.php?grant_type=client_credentials&client_id=${clientId}&client_secret=${clientSecret}&output=json` @@ -191,11 +232,23 @@ fetchIntercept.register({ // disables call to get_url endpoint and enables track url gen json.results.__DZR_GATEKEEPS__.use_media_service = false + json.results.PLAYER_TOKEN = playerTokenPatch(json.results.PLAYER_TOKEN) + log(json) return new Response(JSON.stringify(json)) } + if (response.url.startsWith('https://www.deezer.com/ajax/gw-light.php?method=log.listen')) { + const json = await response.json() + + if (typeof json.results === 'string') { + json.results = playerTokenPatch(json.results) + } + + return new Response(JSON.stringify(json)) + } + return response; }, @@ -204,55 +257,4 @@ fetchIntercept.register({ return Promise.reject(error); } -}); - -worker_input = function (e) { - let json = e.data - if (typeof json !== 'object') { - json = JSON.parse(json) - } - //console.log("input", json) - input = json - worker.postMessage(e.data) -} - -worker_output = function (e) { - let json = e.data - if (typeof json !== 'object') { - json = JSON.parse(json) - } - - if (input) { - if (input.message.token && !input.message.id) { - // player token decryption - - // enables 320/flac quality selection - json.message.result.audio_qualities.wifi_streaming = ['low', 'standard', 'high'] - // disables previews - json.message.result.streaming = true - json.message.result.limited = false - - //console.log("player token output", json) - postMessage(JSON.stringify(json)) - return - } - } - - //console.log("output", json) - postMessage(e.data) -} - -unsafeWindow.URL.createObjectURL = (function (old) { - return function (object) { - if (object instanceof Blob) { - let worker = ` - let worker = new Worker('${old(object)}') - let input - worker.onmessage = ${String(worker_output)} - onmessage = ${String(worker_input)} - ` - return old(new Blob([worker])) - } - return old(object); - }; -})(unsafeWindow.URL.createObjectURL); \ No newline at end of file +}); \ No newline at end of file