diff --git a/README.md b/README.md index 61a5f01..10efd7c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # dzunlock -enables deezer premium features lol +enables deezer hifi features lol # how 2 install diff --git a/dzunlock.user.js b/dzunlock.user.js index a03f1d7..b5b6b3c 100644 --- a/dzunlock.user.js +++ b/dzunlock.user.js @@ -1,9 +1,9 @@ // ==UserScript== // @name dzunlock // @namespace io.github.uhwot.dzunlock -// @description enables deezer premium features lol +// @description enables deezer hifi features lol // @author uh wot -// @version 1.2.6 +// @version 1.3 // @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 @@ -14,7 +14,6 @@ // @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 // ==/UserScript== @@ -85,11 +84,15 @@ fetchIntercept = { // main code starts here -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) +const quality_to_format = { + "standard": "MP3_128", + "high": "MP3_320", + "lossless": "FLAC" +} + function str2bin(str) { return Array.from(str).map(function (item) { return item.charCodeAt(0); @@ -118,7 +121,7 @@ function playerTokenPatch(playerToken) { playerToken = JSON.parse(decryptHex(playerToken)) // enables 320/flac quality selection - playerToken.audio_qualities.wifi_streaming = ['low', 'standard', 'high'] + playerToken.audio_qualities.wifi_streaming = ['low', 'standard', 'high', 'lossless'] // disables previews playerToken.streaming = true playerToken.limited = false @@ -128,74 +131,19 @@ function playerTokenPatch(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` - let resp = await fetch(url) - let json = await resp.json() - access_token = json['access_token'] - - // cache access token - GM_setValue('access_token', access_token) - GM_setValue('access_token_expiry', Math.floor(Date.now() / 1000) + Number(json['expires'])) - return access_token -} - window.addEventListener('DOMContentLoaded', (_) => { unsafeWindow.dzPlayer.setTrackList = (function (old) { - return async function (data, ...args) { - // don't get filesizes if account is hifi - if (unsafeWindow.dzPlayer.user_status.lossless) { - return old(data, ...args) - } - - let batchList = [] + return function (data, ...args) { + // needed for deezer's player to accept 320/flac responses for (let i = 0; i < data.data.length; i++) { - const id = Number(data.data[i].SNG_ID) - if (id >= 0) { // we don't need filesizes for user-upped tracks - batchList.push({ 'relative_url': `/track/${id}` }) + const id = parseInt(data.data[i].SNG_ID) + if (id >= 0) { // don't change filesizes on user-upped tracks + data.data[i].FILESIZE_MP3_320 = '1' + data.data[i].FILESIZE_FLAC = '1' } } - // return if all the tracks are user-upped - if (batchList.length === 0) { - return old(data, ...args) - } - - batchList = JSON.stringify(batchList) - - let access_token = GM_getValue('access_token', false) - const expiry = GM_getValue('access_token_expiry', 0) - if (!access_token || Math.floor(Date.now() / 1000) >= expiry) { - access_token = await renewAccessToken() - } - - url = `https://api.deezer.com/batch?methods=${batchList}&access_token=${access_token}` - let resp = await fetch(url) - let json = await resp.json() - - // renew access token if token is invalid - const error = json.batch_result[0].error - if (error && error.code === 300) { - access_token = await renewAccessToken() - url = `https://api.deezer.com/batch?methods=${batchList}&access_token=${access_token}` - resp = await fetch(url) - json = await resp.json() - } - - let userUppedSoFar = 0 - - for (let i = 0; i < data.data.length; i++) { - const id = Number(data.data[i].SNG_ID) - if (id < 0) { // user-uploaded track - userUppedSoFar++ - continue - } - data.data[i].FILESIZE_MP3_320 = json.batch_result[i - userUppedSoFar].filesize_320 - // for streaming while unlogged - data.data[i].MD5_ORIGIN = json.batch_result[i - userUppedSoFar].md5_origin - } - log(data) return old(data, ...args) @@ -206,6 +154,43 @@ window.addEventListener('DOMContentLoaded', (_) => { fetchIntercept.register({ request: function (url, config) { // Modify the url or config here + if (url === 'https://media.deezer.com/v1/get_url') { + const quality = unsafeWindow.dzPlayer.control.getAudioQuality() + const track = unsafeWindow.dzPlayer.getCurrentSong() + const id = parseInt(track.SNG_ID) + + let user_status_quality = quality + if (user_status_quality === 'high') { + user_status_quality = 'hq' + } + + let is_subbed = !unsafeWindow.dzPlayer.user_status.can_subscribe + let is_quality_available = unsafeWindow.dzPlayer.user_status[user_status_quality] + // STREAM_ADS_AVAILABLE is used to check if track is restricted to premium/hifi + if (quality === 'standard' && (track.RIGHTS.STREAM_ADS_AVAILABLE === true || is_subbed)) { + is_quality_available = true + } + + if (id >= 0 && !is_quality_available) { + const media_server = GM_getValue('media_server', 'https://dzmedia.herokuapp.com') + url = `${media_server}/get_url` + + const body = { + formats: ['FLAC', 'MP3_320', 'MP3_128', 'MP3_64', 'MP3_MISC'], + ids: [id] + } + + for (let i = 0; i < body.formats.length; i++) { + if (body.formats[0] !== quality_to_format[quality]) { + body.formats.shift() + } else { + break + } + } + + config.body = JSON.stringify(body) + } + } return [url, config]; }, @@ -229,8 +214,6 @@ fetchIntercept.register({ // disables ads json.results.USER.OPTIONS.ads_display = false json.results.USER.OPTIONS.ads_audio = false - // 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)