updated to use url gen on deezer's player
This commit is contained in:
parent
ce3c31514f
commit
e486befd18
168
dzunlock.user.js
168
dzunlock.user.js
|
@ -3,7 +3,7 @@
|
|||
// @namespace io.github.uhwot.dzunlock
|
||||
// @description enables deezer hifi features lol
|
||||
// @author uh wot
|
||||
// @version 1.0.3
|
||||
// @version 1.1
|
||||
// @homepageURL https://git.freezer.life/uhwot/dzunlock
|
||||
// @downloadURL https://git.freezer.life/uhwot/dzunlock/raw/branch/master/dzunlock.user.js
|
||||
// @icon https://cdns-files.dzcdn.net/cache/images/common/favicon/favicon-96x96.852baf648e79894b668670e115e4a375.png
|
||||
|
@ -11,7 +11,6 @@
|
|||
// @match https://www.deezer.com/search/*
|
||||
// @match https://www.deezer.com/account/*
|
||||
// @require https://cdnjs.cloudflare.com/ajax/libs/aes-js/3.1.2/index.min.js
|
||||
// @require https://cdnjs.cloudflare.com/ajax/libs/spark-md5/3.0.0/spark-md5.min.js
|
||||
// @grant GM_getValue
|
||||
// @grant GM_setValue
|
||||
// ==/UserScript==
|
||||
|
@ -86,45 +85,11 @@ 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 trackCDNKey = [106, 111, 54, 97, 101, 121, 54, 104, 97, 105, 100, 50, 84, 101, 105, 104]
|
||||
|
||||
const qualityToFormat = {
|
||||
'standard': '128',
|
||||
'high': '320',
|
||||
'lossless': 'flac'
|
||||
}
|
||||
|
||||
const formatToNum = {
|
||||
'128': 1,
|
||||
'320': 3,
|
||||
'flac': 9
|
||||
}
|
||||
|
||||
let dataTemplate = {
|
||||
data: [
|
||||
{
|
||||
media: [
|
||||
{
|
||||
cipher: { type: "BF_CBC_STRIPE" },
|
||||
exp: 694208008135,
|
||||
format: "",
|
||||
media_type: "FULL",
|
||||
nbf: 694208008135,
|
||||
sources: [
|
||||
{
|
||||
provider: "ak",
|
||||
url: "",
|
||||
},
|
||||
{
|
||||
provider: "ec",
|
||||
url: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const formats = [
|
||||
{api: '320', gw: 'MP3_320'},
|
||||
{api: 'flac', gw: 'FLAC'}
|
||||
]
|
||||
|
||||
function str2bin(str) {
|
||||
return Array.from(str).map(function (item) {
|
||||
|
@ -161,15 +126,6 @@ function playerTokenPatch(playerToken) {
|
|||
return encryptHex(JSON.stringify(playerToken), playerTokenKey)
|
||||
}
|
||||
|
||||
function trackCDNGen(md5_origin, format, id, media_version) {
|
||||
result = [md5_origin, format, id, media_version].join('\xa4')
|
||||
log(result)
|
||||
|
||||
result = SparkMD5.hashBinary(result) + '\xa4' + result + '\xa4'
|
||||
log(result)
|
||||
return encryptHex(result, trackCDNKey)
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -183,10 +139,58 @@ async function renewAccessToken() {
|
|||
}
|
||||
|
||||
unsafeWindow.dzPlayer.setTrackList = (function (old) {
|
||||
return function (...args) {
|
||||
// needed for player to accept flac url responses
|
||||
return async function (...args) {
|
||||
// don't get filesizes if account is hifi
|
||||
if (unsafeWindow.dzPlayer.user_status.audio_qualities.wifi_download.includes('lossless')) {
|
||||
return old(...args)
|
||||
}
|
||||
|
||||
let batchList = []
|
||||
|
||||
for (let i = 0; i < args[0].data.length; i++) {
|
||||
args[0].data[i].FILESIZE_FLAC = "1"
|
||||
const id = Number(args[0].data[i].SNG_ID)
|
||||
if (id >= 0) { // we don't need filesizes for user-upped tracks
|
||||
batchList.push({ 'relative_url': `/track/${id}` })
|
||||
}
|
||||
}
|
||||
|
||||
// return if all the tracks are user-upped
|
||||
if (batchList.length === 0) {
|
||||
return old(...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 < args[0].data.length; i++) {
|
||||
const id = Number(args[0].data[i].SNG_ID)
|
||||
if (id < 0) { // user-uploaded track
|
||||
userUppedSoFar++
|
||||
continue
|
||||
}
|
||||
for (let j = 0; j < formats.length; j++) {
|
||||
args[0].data[i]['FILESIZE_' + formats[j].gw] = json.batch_result[i - userUppedSoFar]['filesize_' + formats[j].api]
|
||||
}
|
||||
}
|
||||
|
||||
log(args)
|
||||
|
@ -221,6 +225,8 @@ 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)
|
||||
|
||||
|
@ -239,64 +245,6 @@ fetchIntercept.register({
|
|||
return new Response(JSON.stringify(json))
|
||||
}
|
||||
|
||||
if (response.url === 'https://media.deezer.com/v1/get_url') {
|
||||
const id = Number(unsafeWindow.dzPlayer.getSongId())
|
||||
const quality = unsafeWindow.dzPlayer.control.getAudioQuality()
|
||||
log(id, quality)
|
||||
|
||||
// check if account is allowed to get quality, if so use the server's response instead
|
||||
const orig = await response.json()
|
||||
if (id < 0 /* user-upped track */ || (unsafeWindow.dzPlayer.user_status.audio_qualities.wifi_download.includes(quality) && orig.data && orig.data[0].media && orig.data[0].media[0])) {
|
||||
return new Response(JSON.stringify(orig))
|
||||
}
|
||||
|
||||
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/track/${id}?access_token=${access_token}`
|
||||
resp = await fetch(url)
|
||||
let json = await resp.json()
|
||||
|
||||
// renew access token if token is invalid
|
||||
if (json.error && json.error.code === 300) {
|
||||
access_token = await renewAccessToken()
|
||||
url = `https://api.deezer.com/track/${id}?access_token=${access_token}`
|
||||
resp = await fetch(url)
|
||||
json = await resp.json()
|
||||
}
|
||||
|
||||
const md5_origin = json['md5_origin']
|
||||
const media_version = json['media_version']
|
||||
|
||||
const formats = Object.values(qualityToFormat)
|
||||
let format = qualityToFormat[quality]
|
||||
while (format !== '128') {
|
||||
if (json['filesize_' + format] !== "0") {
|
||||
break
|
||||
}
|
||||
format = formats[formats.indexOf(format) - 1]
|
||||
}
|
||||
format_num = formatToNum[format]
|
||||
|
||||
const trackCDNPath = trackCDNGen(md5_origin, format_num, id, media_version)
|
||||
|
||||
if (format === 'flac') {
|
||||
dataTemplate.data[0].media[0].format = 'FLAC'
|
||||
} else {
|
||||
dataTemplate.data[0].media[0].format = 'MP3_128' // doesn't matter whether it's 128 or 320
|
||||
}
|
||||
|
||||
dataTemplate.data[0].media[0].sources[0].url = `https://cdns-proxy-${md5_origin[0]}.dzcdn.net/mobile/1/${trackCDNPath}`
|
||||
dataTemplate.data[0].media[0].sources[1].url = `https://e-cdns-proxy-${md5_origin[0]}.dzcdn.net/mobile/1/${trackCDNPath}`
|
||||
|
||||
log(dataTemplate)
|
||||
|
||||
return new Response(JSON.stringify(dataTemplate))
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
|
|
Loading…
Reference in New Issue