Last active
February 20, 2022 10:59
-
-
Save cw2k/151b299ae31c3cd4c063cb79daf39bd8 to your computer and use it in GitHub Desktop.
Deezer:Downloader update
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// How to use I n s t a l l this? | |
// 1. https://tampermonkey.net | |
// ... and get the tampermonkey extension. | |
// With tampermonkey running you are able to install this userscript. | |
// 2. Open this in RAW | |
// tampermonkey will ask you to install this | |
// (... as it'll do with any file you open in the browser that aus the file name *.user.js) | |
// 3. Open deezer.com to see if it's working | |
// You should see a '1' or 2 a small red number on the Tampermonkey icon | |
// that show that a userscript (or more) for the current site is/are in use. | |
// | |
// Nicer url (aka short url) to this site: | |
// http://bit.do/DeezerUserScript | |
// | |
// Recommended Downloaders for Deezer in case this script does work | |
// * https://deemix.app/gui | |
// * Search in Telegramm for @DeezerMusicBot // join that channel and send the url of the playlist, album or track | |
// | |
/* JSHINT PARAMS - Useful for debugging the script, do not delete | |
* < see http://jshint.com/docs/options/ > for more details. */ | |
/* jslint bitwise: true */ | |
/* jslint esnext: true */ | |
/* jslint moz: true */ | |
/* jshint expr: true */ | |
/* jshint sub:true*/ | |
/* jshint multistr: true */ | |
/* globals postMessage, self, aesjs, hex_md5, unsafeWindow, GM_info, opr, safari*/ | |
/* jslint ignore: start */ | |
const W = window; | |
const D = document; | |
const uW = unsafeWindow; | |
/* jslint ignore: end */ | |
// START OF METADATAS ------------------------------------------------------------------------------------------------------------------------ | |
// ==UserScript== | |
// | |
// @name Deezer: Download | |
// @description Download music from Deezer in any chosen format. Supports HQ even without Premium subscription. Also recommend 'Deezer Premium Enabler' to have a better experience. | |
// @version 2.0.3 | |
// @author Deki Haker & Kawashi666 & Djamana | |
// @namespace Deki Haker & Kawashi666 | |
// @id 13fbc8ca-c92d-4032-aa17-276e77cd8552-Deezer-Download-Revived | |
// @updateUrl https://gist.github.com/cw2k/151b299ae31c3cd4c063cb79daf39bd8/raw/145ba6a80ae6df5411bfff9c580e4fd1040c627f/Deezer:Download.user.js | |
// @downloadUrl https://gist.githubusercontent.com/K-mikaZ/6876925fbe3c9e10973c44b9a220916c/raw/b8214efdb8a29c4460c9fc81715e4dcb9ff9a8fd/Deezer:Download.user.meta.js | |
// | |
// @domain deezer.com | |
// @match http*://*.deezer.com/* | |
// @run-at document-end | |
// @delay 1000 | |
// @priority 2 | |
// | |
// @developer Jonathan Tavares [ Revisions, fixes, proofreading, compatibility ] , | |
// K-mik@Z [ Fixes, proofreading (code), style, translation, compatibility and 2 or 3 tricks ] < cool2larime@yahoo.fr > , | |
// [...] | |
// | |
// @licence BEER-WARE Licence | |
/* < https://stupidedia.org/stupi/Beerware > | |
*/ | |
// @icon https://e-cdn-files.dzcdn.net/cache/images/common/favicon/favicon-96x96.852baf648e79894b668670e115e4a375.png | |
// | |
// @screenshot https://kindpng.com/picc/b/281-2818779_deezer-png.png | |
// | |
// @contributor Jonathan Tavares, K-mik@Z, | |
// you [...] - `Try To Take Over The World` | |
// | |
// @name:en Deezer:Download [Revived] - Download your music easily. | |
// @description:en Download the currently playing song (or any song from the current tracklist) in any chosen format. Supports HQ even without Premium subscription. | |
// | |
// @name:fr Deezer:Download [Revived] - Télécharger vos musiques facilement. | |
// @description:fr-FR Téléchargez la chanson en cours de lecture (ou n'importe quelle chanson de la liste en cours) dans n'importe quel format. Supporte le HQ même sans abonnement Premium. | |
// | |
// @name:pt Deezer:Download [Revived] - Baixe sua música facilmente. | |
// @description:pt-BR Faz o download da música atual (ou qualquer musica na playlist atual) em qualquer formato escolhido. Suporta HQ mesmo sem ter assinatura Premium. | |
// | |
/* Compatibility & Security | |
* ------------------------- < see https://www.chromium.org/developers/design-documents/user-scripts > */ | |
// | |
// @include http*://*.deezer.com/* | |
// @connect self | |
// @connect githubusercontent.com | |
// | |
// @grant unsafeWindow | |
// @noframes | |
// @require https://gist.githubusercontent.com/arantius/3123124/raw/grant-none-shim.js | |
// | |
// @require https://cdnjs.cloudflare.com/ajax/libs/aes-js/2.1.1/index.js | |
// @require https://greasyfork.org/scripts/32982-pajhome-md5/code/PajHome%20MD5.js | |
// | |
// @compatible chrom[e|ium] TamperMonkey || ViolentMonkey | |
// @compatible vivaldi TamperMonkey || ViolentMonkey | |
// @compatible opera TamperMonkey || ViolentMonkey | |
// @compatible firefox TamperMonkey || ViolentMonkey || GreaseMonkey (+ FIXME) | |
// | |
// ==/UserScript== | |
// END METADATAS ------------------------------------------------------------------------------------------------------------------------------ | |
// VERSIONS ----------------------------------------------------------------------------------------------------------------------------------- | |
// 2.2.1 : Released Sat Feb 19 2022 23:39:45 GMT+0100 (CET) by Djamana | |
// Updated Urls | |
// 2.0.1 : Released Tue Mar 20 2018 13:39:45 GMT+0100 (CET) > 20/03/2018 | |
// | |
// | |
// | |
// MODIFICATIONS made in this version --------------------------------------------------------------------------------------------------------- | |
/* | |
* FIXME: - GreaseMonkey ( Works, but you needs to reclick a second time on the link after the conversion is done. ) | |
* | |
* | |
* PERFORMED: - Style : | |
* (*) Resizing the min/max-width window and some other CSS tricks (spacing) for more readability. | |
* (*) Put the name of the artist in capital letters and place it before the title. | |
* (*) Changing Logo of Deezer. | |
* - Accessibility : | |
* (*) Reduction of the choice in the name proposition (redundancy). | |
* (*) Reduction of the choice in the download links (redundancy). | |
* (*) Adding an escape mode to quit the app. | |
* (*) Adding a message on the GUI, for GreaseMonkey's users (only), explaining the FIXME. | |
* - Codage : | |
* (*) Integration `convert size script` to render the file size more `human readable` (in Mo). | |
* (*) Reorder some lines in `Blowfish.prototype` and some others, `here and then`. | |
* (*) Puting in the script the search for term in regex from the url. | |
* (*) Replacement, where possible, 'single quote' instead of "double quote" in the code. | |
* (*) Replace as soon as possible semicolons `;` with commas `,` without breaking the code. | |
* (*) Create `two debug mode (GM_Debug & WORKER_Debug)` with two possible values, 0 or 1 (for having log only on `prod mode`). | |
* (*) Added console.warn () and console.error () in addition to console.log () (better to debug). | |
* (*) Creation of a function by stylesheet with its own Id, to facilitate integration. | |
* (*) Internationalization: Create a translate fn with dictionary (dict) `_getI18N` to translate more easily ( Using translate(`wordToTranslate`) ). | |
* And assume `en` default when no supported lng are found. | |
* (*) Reduce the content page loading by adding globals vars outside the code and a `;` at the beginning of the main function. | |
* - Compatibility & Security : | |
* (*) Use of the file grant-none-shim.js. This script is intended to be used with @require, for Greasemonkey scripts using `@grant none`. | |
* It emulates the GM_ APIs as closely as possible, using modern browser features like DOM storage. | |
* For explanation and license, see in the JS file itself. | |
* (*) Emulate `unsafeWindow` with gm_win for browsers that don’t support it. | |
* (*) Using `unsafeWindow.console.log()`, or rather `gm_win.console.log()` instead of `console.log()`. GreaseMonkey trick. | |
* (*) Adding metadata delay and priority to avoid conflicts with a third-party script. | |
* (*) Using `noframes` to support `non-frames` capable browsers | |
* (*) Adding a function to determinate browser prefs lng and to propose GUI translated in case. | |
* (*) Tested in different browsers (firefox, opera, vivaldi and chrom[e|ium]) with different script Managers (TamperMonkey, ViolentMonkey and GreaseMonkey). | |
* (*) Adding `patch` to manually trigger deferred DOMContentLoaded. | |
* | |
* ABORT: - Adding MD5, SHA1 & SHA-256 sums in externs links of metadatas. | |
* (If the content of the external resource doesn't match the selected hash, then the resource is not delivered to the userscript). | |
* ( It's not supported by chrome only). | |
* | |
* TODO: - Getting the TrackList when the page change. | |
* - Making new test with checksums in view of chrome. | |
* | |
* IDEAS: - Have a return of test with ( edge || TamperMonkey and perhaps safari || GreaseKit ) - Maybe with user feedback. | |
* - Improve my English ;) | |
* | |
* SPECIAL THANKS: - Stack Overflow < https://stackoverflow.com/ > for the wealth of information and tips. | |
* | |
*/ | |
// GLOBALS VARS ( /!\ WARNING ) ---------------------------------------------------------------------------------------------------------------------------------- | |
const GM_Debug = 0; //Possible values ( 0 =N || 1 =Y ). | |
//Write: if(GM_Debug) { gm_win.console.log(`yourMsg` + `whatYouWant`); }; where gm_win === unsafeWindow. | |
// Now all you have to change one character to enable/disable logging when debugging. | |
// For debugging set var(s) GM_Debug and/or WORKER_Debug value(s) to 1. (searching with CTRL-F, only 2 vars). | |
// Don't forget to return to 0 after. Logs are only useful in prod and slow down the operation. | |
const AcceptLngs = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pt-BR']; | |
// LANGUAGES MAP If you add new languages in the `LANGUAGES MAP`, add them in the var `AcceptLngs` too (see above). | |
// Try to respect alphabetical order. | |
const _getI18N = { | |
'Click to open the app' : { | |
'de' : `Klicken Sie, um die Anwendung zu öffnen`, | |
'en' : `Click to open the app`, | |
'es' : `Haga clic para abrir la aplicación`, | |
'fr' : `Cliquer pour ouvrir l'application`, | |
'it' : `Clicca per aprire l'applicazione`, | |
'pt' : `Clique para abrir o aplicativo`, | |
'pt-BR' : `Clique para abrir o aplicativo` | |
}, | |
'Current track' : { | |
'de' : `Aktueller Titel`, | |
'en' : `Current track`, | |
'es' : `Pista actual`, | |
'fr' : `Piste actuelle`, | |
'it' : `Traccia corrente`, | |
'pt' : `Faixa atual`, | |
'pt-BR' : `Faixa atual` | |
}, | |
'Track list' : { | |
'de' : `Liste der Titel`, | |
'en' : `Track list`, | |
'es' : `Lista de canciones`, | |
'fr' : `Liste des pistes`, | |
'it' : `Elenco dei brani`, | |
'pt' : `Lista de trilhas`, | |
'pt-BR' : `Lista de trilhas` | |
}, | |
'refresh track list' : { | |
'de' : `aktualisiere die Liste der Tracks`, | |
'en' : `refresh track list`, | |
'es' : `actualizar la lista de pistas`, | |
'fr' : `actualiser la liste des pistes`, | |
'it' : `aggiorna la lista delle tracce`, | |
'pt' : `atualize a lista de faixas`, | |
'pt-BR' : `atualize a lista de faixas` | |
}, | |
'Choose' : { | |
'de' : `Wählen`, | |
'en' : `Choose`, | |
'es' : `Escoger`, | |
'fr' : `Choisir`, | |
'it' : `Scegliere`, | |
'pt' : `Escolher`, | |
'pt-BR' : `Escolher` | |
}, | |
'Choose the file name' : { | |
'de' : `Wählen Sie den Dateinamen`, | |
'en' : `Choose the file name`, | |
'es' : `Elige el nombre del archivo`, | |
'fr' : `Choisir le nom du fichier`, | |
'it' : `Scegli il nome del file`, | |
'pt' : `Escolha o nome do arquivo`, | |
'pt-BR' : `Escolha o nome do arquivo` | |
}, | |
'Title' : { | |
'de' : `Titel`, | |
'en' : `Title`, | |
'es' : `Título`, | |
'fr' : `Titre`, | |
'it' : `Titolo`, | |
'pt' : `Título`, | |
'pt-BR' : `Título` | |
}, | |
'Artist' : { | |
'de' : `Künstler`, | |
'en' : `Artist`, | |
'es' : `Artista`, | |
'fr' : `Artiste`, | |
'it' : `Artista`, | |
'pt' : `Artista`, | |
'pt-BR' : `Artista` | |
}, | |
'standard' : { | |
'de' : `standard`, | |
'en' : `standard`, | |
'es' : `estándar`, | |
'fr' : `standard`, | |
'it' : `standard`, | |
'pt' : `padrão`, | |
'pt-BR' : `padrão` | |
} | |
}; | |
// Emulate `unsafeWindow` in browsers that don’t support it. ( http://mths.be/unsafewindow ) | |
const gm_win = (function gmWin(W) { | |
var a, | |
e = D.createElement('p'), | |
onclick = e.getAttribute('onclick'); // get old onclick attribute | |
try { | |
a = uW === W ? false : uW; | |
} finally { | |
return a || (function() { | |
// if onclick is not a function, it's not IE7, so use setAttribute | |
if(typeof(onclick) != 'function') { | |
// for FF,IE8,Chrome | |
e.setAttribute('onclick','return W;' + onclick); | |
// if onclick is a function, use the IE7 method and call onclick() in the anonymous function | |
} else { | |
// for IE7 | |
e.onclick = function() { | |
onclick(); | |
return W; | |
}; | |
} | |
}()); | |
} | |
})(); | |
// You can now use `unsafeWindow`, ehm, safely. | |
//if(GM_Debug) { gm_win.console.log(gm_win); } | |
// If the current document uses a JavaScript library, you can use it in your user script like this: | |
//if(GM_Debug) { gm_win.console.log(gm_win.jQuery); } | |
// DETERMINING THE LANGUAGE OF THE GUI. (Thx to Paul S. - Stack Overflow) | |
const nav = W.navigator; | |
const clientLngs = [ | |
nav.language + '' || | |
nav.browserLanguage + '' || // Adding `+ ''` for suppress `TypeError: lang.split is not a function` | |
nav.userLanguage + '', | |
nav.languages + '', | |
'en'+ '' | |
].filter(Boolean); | |
const getRoot = lng => lng.split('-')[0]; | |
const lngRootIncludes = lng => { | |
let root = getRoot(lng); | |
return AcceptLngs.includes(root); | |
}; | |
const candidateLng = clientLngs.find( | |
lng => lngRootIncludes(lng) | |
); | |
const candidateLngRoot = getRoot(candidateLng); | |
const translate = content => { | |
let dict = _getI18N[content]; | |
if (!dict) return content; | |
return dict[candidateLng] || // e.g. first try ru-MD | |
dict[candidateLngRoot] || // then fall back to ru | |
content; // then fall back to input (if nothing found, default app lng `en`). | |
}; | |
// END GLOBALS VARS ---------------------------------------------------------------------------------------------------------------------------------- | |
// STARTING THE CODE NOW ----------------------------------------------------------------------------------------------------------------------------- | |
;(function _main() { // Please, don't touch the `;` at the beginning. | |
'use strict'; | |
// The following manual DOMContentLoaded triggering technique may be of interest for a fix in regards to DOMContentLoaded event being triggered too early. | |
if( D.createEvent ) { // < https://stackoverflow.com/questions/942921/lazy-loading-the-addthis-script-or-lazy-loading-external-js-content-dependent#answer-943315 > | |
const evt = document.createEvent('MutationEvents'); | |
evt.initMutationEvent('DOMContentLoaded', true, true, document, '', '', '', 0); | |
D.dispatchEvent(evt); | |
} | |
if( GM_Debug ) { | |
const arr = []; | |
const ua = navigator.userAgent; | |
arr.push( `---------------------------------------------------------------------------` ); | |
arr.push( `_Report date [ ` + (new Date()) + ` > ` + (new Date().toLocaleDateString(candidateLngRoot)) + ` ]` ); | |
// Determining Browser type // https://stackoverflow.com/questions/42015724/best-practice-for-browser-details-detection-should-i-do-it-in-client-side-usi | |
const _wichBrwsr_ = (function _getBrwsr(){ | |
let isOpera = ( !!W.opr && !!opr.addons ) || !!W.opera || ua.indexOf(' OPR/') >= 0, // Opera 8.0+ | |
isFirefox = typeof InstallTrigger !== 'undefined', // Firefox 1.0+ | |
isSafari = | |
/constructor/i.test( W.HTMLElement ) || | |
(function (p) { | |
return p.toString() === '[object SafariRemoteNotification]'; | |
})( !W['safari'] || (typeof safari !== 'undefined' && safari.pushNotification) ), // Safari 3.0+ "[object HTMLElementConstructor]" | |
isIE = /*@cc_on!@*/false || !!D.documentMode, // Internet Explorer 6-11 | |
isEdge = !isIE && !!W.StyleMedia, // Edge 20+ | |
isChrome = !!W.chrome && !!W.chrome.webstore; // Chrome 1+ | |
if ( isOpera ) { return 'Opera'; } | |
if ( isFirefox ) { return 'Firefox'; } | |
if ( isSafari ) { return 'Safari'; } | |
if ( isIE ) { return 'IE'; } | |
if ( isEdge ) { return 'Edge'; } | |
if ( isChrome ) { return 'Chrome'; } | |
return 'Unknown'; | |
}()), | |
_whichMajVer_ = (function getBrowserMajorVersion() { | |
let tem, | |
M = ua.match( /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i ) || []; | |
if ( /trident/i.test(M[1]) ) { | |
tem = /\brv[ :]+(\d+)/g.exec(ua) || []; | |
return ( tem[1] || '' ); | |
} | |
if ( M[1] === 'Chrome' ) { | |
tem = ua.match( /\b(OPR|Edge)\/(\d+)/ ); | |
if ( tem !== null ) return tem[2]; | |
} | |
M = M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?']; | |
if ( ( tem = ua.match( /version\/(\d+)/i ) ) !== null ) M.splice(1, 1, tem[1]); | |
return M[1]; | |
}()); | |
arr.push( `_Browser [ ` + navigator.appCodeName + ` ` + _wichBrwsr_ + ` ` + _whichMajVer_ + ` (` + candidateLng + `) ]` ); | |
// What's your script manager? | |
arr.push( `_Script Manager [ ` + GM_info.scriptHandler + ` v.` + GM_info.version + ` ]` ); | |
for (var j = 0, count = arr.length; j < count; j++) { | |
gm_win.console.log( arr[j] ); | |
} | |
// Is script updated? | |
let isUpdate = GM_info.scriptWillUpdate, | |
msgScriptInfos = `_Infos script [ ` + GM_info.script.name + ` v.` + GM_info.script.version; | |
if ( GM_info.scriptWillUpdate == 1 ) { | |
gm_win.console.log( | |
msgScriptInfos += ` ( is updated: ` + isUpdate + `, last update: ` + GM_info.script.lastUpdated + ` ) ]` | |
); | |
} | |
else { gm_win.console.log( msgScriptInfos += ` ( is updated: ` + isUpdate + ` ) ]` ); } | |
gm_win.console.log( `---------------------------------------------------------------------------` ); gm_win.console.log( ); // Line break | |
} | |
// For metadatas of the script: | |
//if(GM_Debug) { gm_win.console.info(`UserScript Metadatas: ` + `%s`, GM_info.scriptMetaStr); } //unsafeWindow is not defined here, use gm_win instead. | |
//if(GM_Debug) { gm_win.console.info(`UserScript Metadatas: ` + `%s`, JSON.stringify(GM_info.script)); } //unsafeWindow is not defined here, use gm_win instead. | |
/* | |
// DETERMINING THE LANGUAGE OF THE GUI. | |
nav = window.navigator, | |
navLng = nav.language || nav.browserLanguage || nav.userLanguage, | |
prefs = nav.languages, | |
navPrefLng = prefs[0], | |
defaultLng = 'en', | |
lng = navLng, //It must be set for translation | |
// CHECK THE LANGUAGE OF THE NAVIGATOR. | |
checkLng = function checkLng() { | |
// isL10nAvailable: | |
if (AcceptLngs.indexOf(lng) < -1) { | |
lng = lng; | |
return lng; | |
} | |
}, | |
// SEARCH IF LANGUAGE OF THE APP IS AVAILABLE. | |
isLngDispo = function isLngDispo() { | |
// isSubstrL10nAvailable: | |
checkLng(); | |
if (AcceptLngs.indexOf(lng) === -1) { | |
lng = lng.substring(0,2); | |
return lng; | |
} | |
}, | |
// TO DO IF LANGUAGE SUPPORTED ISN'T FIND. | |
setLng = function setLng() { | |
// isL10nNoAvailable: | |
isLngDispo(); | |
if (AcceptLngs.indexOf(lng) === -1) { | |
lng = defaultLng; | |
return lng; | |
} | |
}; | |
// APPLYING LANGUAGE FOR THE APP. | |
setLng(); | |
*/ | |
/* | |
function convertDate(date) { | |
var matches, | |
convertedDate, | |
fraction; | |
// we have a date, so just return it. | |
if (typeof(date) == 'object') { | |
return date; | |
}; | |
matches = date.toString().match( /(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})([\.,]\d{1,3})?)?(Z|\+00:?00)?/ ); | |
if (matches) { | |
for (var i = 1; i <= 6; i++) { | |
matches[i] = parseInt( matches[i], 10 ) || 0; | |
} | |
// month starts on 0 | |
matches[2] -= 1; | |
fraction = matches[7] ? 1000 * ('0' + matches[7]) : null; | |
if (matches[8]) { | |
convertedDate = new Date( Date.UTC(matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction) ); | |
} else { | |
convertedDate = new Date( matches[1], matches[2], matches[3], matches[4], matches[5], matches[6], fraction ); | |
} | |
} else if (typeof(date) == 'number') { | |
// UNIX timestamp | |
convertedDate = new Date(); | |
convertedDate.setTime(date); | |
} else if (date.match( /([A-Z][a-z]{2}) ([A-Z][a-z]{2}) (\d+) (\d+:\d+:\d+) ([+-]\d+) (\d+)/ )) { | |
// This format `Wed Jul 20 13:03:39 +0000 2011` is parsed by | |
// webkit/firefox, but not by IE, so we must parse it manually. | |
convertedDate = new Date(); | |
convertedDate.setTime(Date.parse([ | |
RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$6, RegExp.$4, RegExp.$5 | |
].join(' '))); | |
} else if (date.match(/\d+ \d+:\d+:\d+ [+-]\d+ \d+/)) { | |
// a valid javascript format with timezone info | |
convertedDate = new Date(); | |
convertedDate.setTime(Date.parse(date)); | |
} else { | |
// an arbitrary javascript string | |
convertedDate = new Date(); | |
convertedDate.setTime(Date.parse(date)); | |
} | |
return convertedDate; | |
} | |
*/ | |
/* https://stackoverflow.com/questions/4784745/how-can-i-measure-the-execution-time-of-a-script | |
function time_my_script(script) { | |
const start = performance.now(); | |
script(); | |
return performance.now() - start; | |
} | |
*/ | |
//TESTING URLS WITH REGEX | |
if ( D.domain == 'deezer.com' ) { | |
if ( !!location.href.match( /(login|register|signout|blabla)/ ) ) { | |
if( GM_Debug ) { gm_win.console.info( `This url contains an unwanted word, don\'t apply Downloader App, applying Deezer:Download Logo only` ); } | |
_dzLogoCss_(); if( GM_Debug ) { gm_win.console.info( `Applying Deezer:Download Logo.` ); } | |
} else { // This url not contains unwanted things | |
if( GM_Debug ) { gm_win.console.info( `Nothing prohibited here... Yeah, we loose ourselves ! 😊` ); } | |
_dzLogoCss_(); if( GM_Debug ) { gm_win.console.log( `Applying Deezer:Download Logo. ✓` ); }; | |
_dzApp_(uW, D); if( GM_Debug ) { gm_win.console.log( `Create the app. ✓` ); } | |
// FIXME: Having an info for greasemonkey's users into the GUI | |
if ( GM_info.scriptHandler === 'Greasemonkey' ) { // User is running Greasemonkey, do this. | |
_gmAlertCss_(); if(GM_Debug) { gm_win.console.info( `Applying FIXME for `, GM_info.scriptHandler ); } | |
} | |
} | |
if( GM_Debug ) { | |
var i, | |
output = '', | |
// Remember when we started | |
start = performance.now(); | |
for ( i = 1; i <= 1e6; i++ ) | |
output += i; | |
// Remember when we finished | |
var end = performance.now(); | |
// Now calculate and output the difference | |
gm_win.console.info( `All done ! 😊 The page Loading in: ` + (end - start) + ` ms ( for more precision, https://developers.google.com/web/updates/2012/08/When-milliseconds-are-not-enough-performance-now )` ); | |
gm_win.console.log( ); // Line break | |
} | |
} | |
function _dzApp_(W, D) { | |
// WORKER | |
function createWorker(code) { | |
const blobURL = URL.createObjectURL(new Blob(['(', code.toString(), ')()'], { | |
type: 'application/javascript' | |
})), | |
worker = new Worker(blobURL); | |
URL.revokeObjectURL(blobURL); | |
return worker; | |
} | |
const mainWk = createWorker(function() { // gm_win && _getI18N are undefined here < FIND a soluce | |
const WORKER_Debug = 0; //Possible values ( 0 =N || 1 =Y ). | |
//`Debug mode` ONLY for the var mainWk. For all the rest, we use GM_Debug. | |
//Write: if(WORKER_Debug) { gm_win.console.log(``yourMsg` + `whatYouWant``); } where gm_win = unsafeWindow. | |
// BLOWFISH LIBRARY, adapted from https://github.com/agorlov/javascript-blowfish (MIT-licensed) | |
// Modified to work with byte arrays, and also supports encryption / decryption in-place for buffers. | |
// Cannot be @require-d, as it is part of worker code. Workers share NO data so everything must be embedded. | |
const Blowfish = function(key, mode) { | |
this.key = key; | |
if (mode === 'ecb' || mode === 'cbc') this.mode = mode; | |
this.sBox0 = Blowfish.sBox0.slice(); | |
this.sBox1 = Blowfish.sBox1.slice(); | |
this.sBox2 = Blowfish.sBox2.slice(); | |
this.sBox3 = Blowfish.sBox3.slice(); | |
this.pArray = Blowfish.pArray.slice(); | |
this.generateSubkeys(key); | |
}; | |
Blowfish.prototype = { | |
sBox0: null, | |
sBox1: null, | |
sBox2: null, | |
sBox3: null, | |
pArray: null, | |
key: null, | |
mode: 'ecb', | |
iv: 'abc12345', | |
trimZeros: (input) => input.replace(/\0+$/g, ''), | |
fixNegative: (number) => (number >>> 0), | |
num2block32: (num) => [num >>> 24, num << 8 >>> 24, num << 16 >>> 24, num << 24 >>> 24], | |
block32toNum: function(block32) { | |
return this.fixNegative(block32[0] << 24 | block32[1] << 16 | block32[2] << 8 | block32[3]); | |
}, | |
xor: function(a, b) { | |
return this.fixNegative(a ^ b); | |
}, | |
addMod32: function(a, b) { | |
return this.fixNegative((a + b) | 0); | |
}, | |
split64by32: function(block64) { | |
return [this.block32toNum(block64.slice(0, 4)), this.block32toNum(block64.slice(4, 8))]; | |
}, | |
encrypt: function(data, iv) { | |
if (this.mode === 'ecb') return this.encryptECB(data); | |
if (this.mode === 'cbc') return this.encryptCBC(data, iv); | |
throw new Error( `BF: unknown cipher mode` ); | |
}, | |
decrypt: function(data, iv) { | |
if (this.mode === 'ecb') return this.decryptECB(data); | |
if (this.mode === 'cbc') return this.decryptCBC(data, iv); | |
throw new Error('BF: unknown cipher mode'); | |
}, | |
encryptECB: function(data) { | |
let blocks = Math.ceil(data.length / 8), | |
encrypted = []; | |
for (var i = 0; i < blocks; i++) { | |
var block = data.slice(i * 8, (i + 1) * 8); | |
if (block.length < 8) throw new Error( `BF: data length not multiple of 8` ); // var count = 8 - block.length; while (0 < count--) block += "\0"; | |
var xL, | |
xR, | |
xLxR = this.split64by32(block); | |
xL = xLxR[0], | |
xR = xLxR[1], | |
xLxR = this.encipher(xL, xR), | |
xL = xLxR[0], | |
xR = xLxR[1]; | |
if (data instanceof Uint8Array) { | |
data.set(this.num2block32(xL), i * 8), | |
data.set(this.num2block32(xR), i * 8 + 4); | |
} else encrypted = encrypted.concat(this.num2block32(xL), this.num2block32(xR)); | |
} | |
if (!(data instanceof Uint8Array)) return encrypted; | |
}, | |
encryptCBC: function(data, iv) { | |
let blocks = Math.ceil(data.length / 8), | |
ivL, | |
ivR, | |
ivLivR, | |
encrypted = []; | |
ivLivR = this.split64by32(iv), | |
ivL = ivLivR[0], | |
ivR = ivLivR[1]; | |
for (var i = 0; i < blocks; i++) { | |
var block = data.slice(i * 8, (i + 1) * 8); | |
if (block.length < 8) throw new Error( `BF: data length not multiple of 8` ); // var count = 8 - block.length; while (0 < count--) block += "\0"; | |
var xL, | |
xR, | |
xLxR; | |
xLxR = this.split64by32(block), | |
xL = xLxR[0], | |
xR = xLxR[1], | |
xL = this.xor(xL, ivL), | |
xR = this.xor(xR, ivR), | |
xLxR = this.encipher(xL, xR), | |
xL = xLxR[0], | |
xR = xLxR[1], | |
ivL = xL, | |
ivR = xR; | |
if (data instanceof Uint8Array) { | |
data.set(this.num2block32(xL), i * 8), | |
data.set(this.num2block32(xR), i * 8 + 4); | |
} else encrypted = encrypted.concat(this.num2block32(xL), this.num2block32(xR)); | |
} | |
if (!(data instanceof Uint8Array)) return encrypted; | |
}, | |
decryptECB: function(data) { | |
let blocks = Math.ceil(data.length / 8), | |
decrypted = []; | |
for (var i = 0; i < blocks; i++) { | |
var block = data.slice(i * 8, (i + 1) * 8); | |
if (block.length < 8) throw new Error( `BF: ciphertext too short (must be multiple of 8 bytes)` ); | |
var xL, | |
xR, | |
xLxR; | |
xLxR = this.split64by32(block), | |
xL = xLxR[0], | |
xR = xLxR[1], | |
xLxR = this.decipher(xL, xR), | |
xL = xLxR[0], | |
xR = xLxR[1]; | |
if (data instanceof Uint8Array) { | |
data.set(this.num2block32(xL), i * 8), | |
data.set(this.num2block32(xR), i * 8 + 4); | |
} else decrypted = decrypted.concat(this.num2block32(xL), this.num2block32(xR)); | |
} | |
if (!(data instanceof Uint8Array)) return decrypted; | |
}, | |
decryptCBC: function(data, iv) { | |
let blocks = Math.ceil(data.length / 8), | |
ivL, | |
ivR, | |
ivLtmp, | |
ivRtmp, | |
ivLivR, | |
decrypted = []; | |
ivLivR = this.split64by32(iv), | |
ivL = ivLivR[0], | |
ivR = ivLivR[1]; | |
for (var i = 0; i < blocks; i++) { | |
var block = data.slice(i * 8, (i + 1) * 8); | |
if (block.length < 8) throw new Error( `BF: ciphertext too short (must be multiple of 8 bytes)` ); | |
var xL, | |
xR, | |
xLxR; | |
xLxR = this.split64by32(block), | |
xL = xLxR[0], | |
xR = xLxR[1], | |
ivLtmp = xL, | |
ivRtmp = xR, | |
xLxR = this.decipher(xL, xR), | |
xL = xLxR[0], | |
xR = xLxR[1], | |
xL = this.xor(xL, ivL), | |
xR = this.xor(xR, ivR), | |
ivL = ivLtmp, | |
ivR = ivRtmp; | |
if (data instanceof Uint8Array) { | |
data.set(this.num2block32(xL), i * 8), | |
data.set(this.num2block32(xR), i * 8 + 4); | |
} else decrypted = decrypted.concat(this.num2block32(xL), this.num2block32(xR)); | |
} | |
if (!(data instanceof Uint8Array)) return decrypted; | |
}, | |
F: function(xL) { | |
let a = xL >>> 24, | |
b = xL << 8 >>> 24, | |
c = xL << 16 >>> 24, | |
d = xL << 24 >>> 24, | |
res = this.addMod32(this.sBox0[a], this.sBox1[b]); | |
res = this.xor(res, this.sBox2[c]), | |
res = this.addMod32(res, this.sBox3[d]); | |
return res; | |
}, | |
encipher: function(xL, xR) { | |
let tmp; | |
for (var i = 0; i < 16; i++) { | |
xL = this.xor(xL, this.pArray[i]), | |
xR = this.xor(this.F(xL), xR), | |
tmp = xL, | |
xL = xR, | |
xR = tmp; | |
} | |
tmp = xL, | |
xL = xR, | |
xR = tmp, | |
xR = this.xor(xR, this.pArray[16]), | |
xL = this.xor(xL, this.pArray[17]); | |
return [xL, xR]; | |
}, | |
decipher: function(xL, xR) { | |
let tmp; | |
xL = this.xor(xL, this.pArray[17]), | |
xR = this.xor(xR, this.pArray[16]), | |
tmp = xL, | |
xL = xR, | |
xR = tmp; | |
for (var i = 15; i >= 0; i--) { | |
tmp = xL, | |
xL = xR, | |
xR = tmp, | |
xR = this.xor(this.F(xL), xR), | |
xL = this.xor(xL, this.pArray[i]); | |
} | |
return [xL, xR]; | |
}, | |
generateSubkeys: function(key) { | |
var data = 0, | |
k = 0, | |
i, | |
j; | |
for (i = 0; i < 18; i++) { | |
for (j = 4; j > 0; j--) { | |
data = this.fixNegative(data << 8 | key[k]), | |
k = (k + 1) % key.length; | |
} | |
this.pArray[i] = this.xor(this.pArray[i], data), | |
data = 0; | |
} | |
let block64 = [0, 0]; | |
for (i = 0; i < 18; i += 2) { | |
block64 = this.encipher(block64[0], block64[1]); | |
this.pArray[i] = block64[0]; | |
this.pArray[i + 1] = block64[1]; | |
} | |
for (i = 0; i < 256; i += 2) { | |
block64 = this.encipher(block64[0], block64[1]); | |
this.sBox0[i] = block64[0]; | |
this.sBox0[i + 1] = block64[1]; | |
} | |
for (i = 0; i < 256; i += 2) { | |
block64 = this.encipher(block64[0], block64[1]); | |
this.sBox1[i] = block64[0]; | |
this.sBox1[i + 1] = block64[1]; | |
} | |
for (i = 0; i < 256; i += 2) { | |
block64 = this.encipher(block64[0], block64[1]); | |
this.sBox2[i] = block64[0]; | |
this.sBox2[i + 1] = block64[1]; | |
} | |
for (i = 0; i < 256; i += 2) { | |
block64 = this.encipher(block64[0], block64[1]); | |
this.sBox3[i] = block64[0]; | |
this.sBox3[i + 1] = block64[1]; | |
} | |
} | |
};// End Blowfish.prototype | |
Blowfish.pArray = [0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b], | |
Blowfish.sBox0 = [0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a], | |
Blowfish.sBox1 = [0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7], | |
Blowfish.sBox2 = [0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0], | |
Blowfish.sBox3 = [0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6]; | |
function xhrProgress(e, userdata) { | |
if (e.lengthComputable) { | |
let percent = e.loaded * 100.0 / e.total; | |
postMessage([userdata, `Downloading` + ` ` + Math.floor(percent) + `%`]); | |
} else { | |
postMessage([userdata, `Downloading` + ` ` + Math.round(e.loaded * 10.0 / 1024 / 1024) / 10.0 + `M`]); | |
} | |
} | |
function xhrComplete(e, key, userdata) { | |
postMessage([userdata, `Decrypting`]); | |
let data = new Uint8Array(e.target.response), | |
L = data.length; | |
if(WORKER_Debug) { console.log( `Data length`, data.length ); } | |
for (var i = 0; i < L; i += 6144) { | |
if (i % (6144 * 20) == 6144 * 19) // let it display state at every 120K | |
postMessage([userdata, `Decrypting ` + Math.floor(i * 100.0 / L) + '%']); | |
if (i + 2048 <= L) { | |
var D = data.slice(i, i + 2048), //data.substr(i, 2048); | |
bf = new Blowfish(key, 'cbc'); | |
if (WORKER_Debug && (i===0)) { console.log( D.toString(), D.length ); } | |
bf.decrypt(D, [0, 1, 2, 3, 4, 5, 6, 7]), | |
data.set(D, i); //for (var j=0; j<2048; j++) data[i+j]=DD.charCodeAt(j); | |
//dd += DD; | |
//if (i+6144<L) dd += data.substr(i+2048, 4096); | |
//else dd += data.substr(i+2048); | |
} // else { dd += data.substr(i); } | |
} | |
let B = new Blob([data], { | |
type: 'audio/mpeg' | |
}), //dd | |
burl = URL.createObjectURL(B); | |
postMessage([userdata, `DONE`, burl]); | |
if(WORKER_Debug) { console.log( `Listen at`, burl ); } | |
} | |
function xhrError(e, userdata) { | |
postMessage([userdata, `ERROR`]); | |
} | |
function xhrCancelled(e, userdata) { | |
postMessage([userdata, `ABORT`]); | |
} | |
self.onmessage = function(m) { | |
if(WORKER_Debug) { console.log( m.origin, m.data ); } | |
let url = m.data[0], | |
key = m.data[1], | |
userdata = m.data[2], | |
rq = new XMLHttpRequest(); | |
rq.responseType = 'arraybuffer'; | |
rq.addEventListener('progress', function(e) { | |
xhrProgress(e, userdata); | |
}); // xhr.onprogress = xhrProgress; | |
rq.addEventListener('load', function(e) { | |
xhrComplete(e, key, userdata); | |
}); | |
rq.addEventListener('error', function(e) { | |
xhrError(e, userdata); | |
}); | |
rq.addEventListener('abort', function(e) { | |
xhrCancelled(e, userdata); | |
}); | |
rq.open('get', url); | |
rq.send(); | |
}; | |
});//End var mainWk | |
// DOWNLOADER LOGIC: URL ENCRYPTION | |
const urlCryptor = new aesjs.ModeOfOperation.ecb(aesjs.util.convertStringToBytes('jo6aey6haid2Teih')), | |
hex2bin = function(h) { | |
return aesjs.util.convertStringToBytes(h, 'hex'); | |
}, | |
bin2hex = function(b) { | |
return aesjs.util.convertBytesToString(b, 'hex'); | |
}, | |
bin2str = function(b) { | |
return b.map(c => String.fromCharCode(c)).join(''); | |
}, //aesjs.util.convertBytesToString(b); | |
str2bin = function(s) { | |
return s.split('').map(c => c.charCodeAt(0)); | |
}, //aesjs.util.convertStringToBytes(s)); | |
aesBS = 16, | |
zeroPad = function(b) { | |
let l = b.length; | |
if (l % aesBS !== 0) { | |
if (typeof(b) === 'string') b += '\0'.repeat(aesBS - (l % aesBS)); | |
else b = b.concat(Array.apply(null, Array(aesBS - (l % aesBS))).map(() => 0)); | |
} | |
return b; | |
}, | |
zeroUnpad = (s => s.replace(/\0+$/, '')), | |
urlsep = '\xa4'; | |
function decryptURL(hasTrack, url, raw) { | |
var i = url.lastIndexOf('/'); | |
if (i >= 0) url = url.substr(i + 1); | |
let decrypted = zeroUnpad(bin2str(urlCryptor.decrypt(hex2bin(url)))).split(urlsep); | |
if (raw) return decrypted; | |
if (hasTrack(decrypted[3])) return getTrack(decrypted[3]); | |
return { | |
SNG_ID: decrypted[3], | |
MD5_ORIGIN: decrypted[1], | |
MEDIA_VERSION: decrypted[4], | |
chosen_fmt: decrypted[2], | |
h: decrypted[0], | |
e: decrypted[5] | |
}; | |
} | |
function encryptURL(track, fmt) { | |
let str = [track.MD5_ORIGIN, fmt, track.SNG_ID, track.MEDIA_VERSION].join(urlsep); | |
str = zeroPad([hex_md5(str), str, ''].join(urlsep)); | |
return bin2hex(urlCryptor.encrypt(str2bin(str))); | |
} | |
// DOWNLOADER LOGIC: GLOBAL VARIABLES AND HELPER METHODS | |
const fmtMisc = 0, fmtLow = 10, fmtLow32 = 11, fmtMed = 1, fmtHQ = 3, fmtFLAC = 9, | |
flagTitle = 1, flagArt = 3, flagHash = 16, flagVer = 32, flagFmt = 64; | |
function generateName(track, fmt, flags) { | |
var name = ''; //Replace track.SNG_ID with '' | |
if ((flags & flagHash) == flagHash && (name += `[` + track.SNG_ID + `_` + track.MD5_ORIGIN + `] `), | |
(flags & flagVer) == flagVer && (name += `[` + track.SNG_ID + `_v` + track.MEDIA_VERSION + `] `), | |
(flags & flagArt) == flagArt && (name += track.ART_NAME.toUpperCase() + ` - `), //Having .toUpperCase() for generate Name > NAME, and invert place with '-' | |
(flags & flagTitle) == flagTitle && (name += ` ` + track.SNG_TITLE, track.VERSION && '' !== track.VERSION && (name += ` ` + track.VERSION)), fmt == fmtFLAC) name += '.flac'; | |
else if ((flags & flagFmt) == flagFmt) { | |
switch (fmt) { | |
case fmtMisc: | |
name += '.default'; | |
break; | |
case fmtLow: | |
name += '.lq'; | |
break; | |
case fmtLow32: | |
name += '.32'; | |
break; | |
case fmtMed: | |
name += '.sq'; | |
break; | |
case fmtHQ: | |
name += '.hq'; | |
break; | |
} | |
name += '.mp3'; | |
} else if (fmt >= 0) name += '.mp3'; | |
return name; | |
} | |
let bfGK = 'g4el58wc0zvf9na1'; | |
function bfGenKey2(h1, h2) { | |
let l = h1.length, | |
s = []; | |
for (var i = 0; i < l; i++) s.push(bfGK.charCodeAt(i) ^ h1.charCodeAt(i) ^ h2.charCodeAt(i)); | |
return s; | |
} | |
function bfGenKey(id, format) { | |
let h = hex_md5(id + ''), | |
h1 = h.substr(0, 16), | |
h2 = h.substr(16, 16), | |
k = bfGenKey2(h1, h2); | |
if (!format) return k; | |
return k.map(format == 'hex' ? (a => (a + 256).toString(16).substr(-2)) : (a => String.fromCharCode(a))).join(''); | |
} | |
const trackDB = {}, | |
urlDB = {}; | |
function hasTrack(id) { | |
return trackDB.hasOwnProperty(id + ''); | |
} | |
function getTrack(id) { | |
return hasTrack(id) ? trackDB[id + ''] : null; | |
} | |
function hasDownloaded(id, fmt) { | |
if (!urlDB.hasOwnProperty(id + '')) return false; | |
let urls = urlDB[id + '']; | |
return urls.hasOwnProperty(fmt); | |
} | |
function getDownloaded(id, fmt) { | |
return hasDownloaded(id, fmt) ? urlDB[id + ''][fmt] : null; | |
} | |
function FileConvertSize(aSize) { | |
aSize = Math.abs(parseInt(aSize, 10)); | |
const def = [ | |
[1, 'octets'], | |
[1024, 'ko'], | |
[1024 * 1024, 'Mo'], | |
[1024 * 1024 * 1024, 'Go'], | |
[1024 * 1024 * 1024 * 1024, 'To'] | |
]; | |
for (var i = 0; i < def.length; i++) { | |
if (aSize < def[i][0]) return (aSize / def[i - 1][ | |
0 | |
]).toFixed(2) + ' ' + def[i - 1] | |
[1]; | |
} | |
} | |
// DOWNLOAD ENTRY POINT | |
function dzDownload(obj, userdata, fmt, size) { | |
const msg = [ | |
'https://e-cdns-proxy-' + obj.MD5_ORIGIN.charAt(0) + '.dzcdn.net' + | |
'/mobile/1/' + encryptURL(obj, fmt), | |
bfGenKey(obj.SNG_ID), | |
userdata + ',' + fmt | |
]; | |
if(GM_Debug) { gm_win.console.log(msg); } | |
mainWk.postMessage(msg); | |
} | |
// DOWNLOADER WORKER CALLBACK | |
mainWk.onmessage = function(msg) { | |
let userdata = msg.data[0].split(','), | |
elId = userdata[0], | |
fmt = userdata[1], | |
trackEl = D.querySelector('#' + elId); | |
if (trackEl === null) return; | |
let trackId = trackEl.dataset.trackId, | |
state = msg.data[1]; | |
if (state == 'DONE') { | |
if (!hasTrack(trackId)) { | |
gm_win.console.error( `On download: MISSING TRACK INFO!`, trackId ),//Required (not only in debug mode) | |
trackEl.querySelector('.status').style.display = 'none', | |
trackEl.querySelector('.links').style.display = 'block'; | |
return; | |
} | |
if (!urlDB[trackId + '']) urlDB[trackId + ''] = {}; | |
urlDB[trackId + ''][fmt] = msg.data[2]; | |
let links = trackEl.querySelectorAll('a.dl'); | |
for (var i = 0; i < links.length; i++) { | |
if (links[i].dataset.fmt == fmt) { | |
links[i].download = generateName(getTrack(trackId), fmt, W.dzDL.dlFlags), | |
links[i].href = msg.data[2], | |
W.setTimeout((function(l) { | |
return function() { | |
l.click(); | |
}; | |
})(links[i]), 10); | |
break; | |
} | |
} | |
} else if (state == 'ABORT') { | |
gm_win.console.error( `Download abort:`, trackId, fmt );//Required (not only in debug mode) | |
} else if (state == 'ERROR') { | |
gm_win.console.error( `Download ERROR!`, trackId, fmt );//Required (not only in debug mode) | |
} else { | |
trackEl.querySelector('.status').innerHTML = state; | |
return; | |
} | |
trackEl.querySelector('.status').style.display = 'none', | |
trackEl.querySelector('.links').style.display = 'block'; | |
}; | |
// DOWNLOADER LOGIC: HTML GENERATOR | |
const rootEl = D.createElement('div'); | |
function getFilesize(track, fmt_name) { | |
if (!track.hasOwnProperty('FILESIZE_' + fmt_name)) return 0; | |
if (track['FILESIZE_' + fmt_name] === '' || track['FILESIZE_' + fmt_name] === 0) return 0; | |
let size = parseInt(track['FILESIZE_' + fmt_name]); | |
return isNaN(size) ? 0 : size; | |
} | |
function generateDl(track, fmt_name, fmt_id, size) { | |
let msgSize = /*'size: ' +*/ FileConvertSize(size), //Use FileConvertSize fn() to have size 'human readable' | |
el = D.createElement('a'); | |
//el.href = '#', | |
el.className = 'dl', | |
el.dataset.fmt = fmt_id, | |
el.dataset.filesize = size, | |
//el.dataset.trackUrl = '', | |
el.innerHTML = fmt_name, | |
el.title = msgSize, | |
el.onclick = function() { | |
let trackEl = this.parentElement.parentElement; // a <- links <- trackdl | |
if (hasDownloaded(trackEl.dataset.trackId, this.dataset.fmt)) //(this.dataset.trackUrl != "") | |
return true; // we already have a link. href should also be set. | |
else { | |
//this.href = '#'; | |
this.removeAttribute('download'); | |
} | |
if (!hasTrack(trackEl.dataset.trackId)) { | |
gm_win.console.warn(`NO INFORMATION ABOUT TRACK!`, trackEl.dataset.trackId);//Required (not only in debug mode) | |
return false; | |
} | |
let track = trackDB[trackEl.dataset.trackId + '']; | |
trackEl.querySelector('.links').style.display = 'none', | |
trackEl.querySelector('.status').style.display = 'inline-block', | |
dzDownload(track, trackEl.id, this.dataset.fmt, this.dataset.filesize); | |
return false; | |
}; | |
return el; | |
} | |
function generateTrackDiv(track, index) { | |
trackDB[track.SNG_ID + ''] = track; | |
let trackEl = D.createElement('div'); | |
trackEl.className = 'trackdl', | |
trackEl.id = 'trackdl' + Math.floor(Math.random() * 900000 + 100000), | |
trackEl.dataset.trackId = track.SNG_ID; | |
//trackEl.dataset.trackUrl = ''; | |
let nameEl = D.createElement('span'); | |
nameEl.className = 'name', | |
nameEl.innerHTML = (index ? index + '. ' : '') + generateName(track, -1, flagTitle | flagArt); // don't add extension here | |
let linksEl = D.createElement('div'); | |
linksEl.className = 'links', | |
linksEl.innerHTML = '▶ '; | |
let statusEl = D.createElement('div'); | |
statusEl.className = 'status', | |
statusEl.style.display = 'none', | |
statusEl.innerHTML = 'Waiting for download...'; | |
var miscFilesize = 0; | |
if (track.hasOwnProperty('FILESIZE') && track.FILESIZE !== 0 && track.FILESIZE !== '') miscFilesize = parseInt(track.FILESIZE); | |
else miscFilesize = getFilesize(track, 'MISC'); | |
//if (miscFilesize > 0) linksEl.appendChild(generateDl(track, '♪ default ', fmtMisc, miscFilesize));//REMOVE: redundancy | |
if (getFilesize(track, 'MP3_32') > 0) linksEl.appendChild(generateDl(track, '♪ low (32)', fmtLow32, getFilesize(track, 'MP3_32'))); | |
if (getFilesize(track, 'MP3_64') > 0) linksEl.appendChild(generateDl(track, '♪ low (64)', fmtLow, getFilesize(track, 'MP3_64'))); | |
if (getFilesize(track, 'MP3_128') > 0) linksEl.appendChild(generateDl(track, '♪ ' + translate(`standard`), fmtMed, getFilesize(track, 'MP3_128'))); | |
if (getFilesize(track, 'MP3_320') > 0) linksEl.appendChild(generateDl(track, '♪ hq', fmtHQ, getFilesize(track, 'MP3_320'))); | |
if (getFilesize(track, 'FLAC') > 0) linksEl.appendChild(generateDl(track, '♪ flac', fmtFLAC, getFilesize(track, 'FLAC'))); | |
trackEl.appendChild(nameEl), | |
trackEl.appendChild(linksEl), | |
trackEl.appendChild(statusEl), | |
trackEl.appendChild((function() { | |
var el = D.createElement('div'); | |
el.className = 'endfloat'; | |
return el; | |
})()); | |
return trackEl; | |
} | |
// ENTRY POINTS | |
function deleteElement(id) { | |
var trackEl = id; | |
if (typeof(id) === 'string') trackEl = D.querySelector('#' + id); | |
if (hasTrack(trackEl.dataset.trackId)) { | |
let urls = urlDB[trackEl.dataset.trackId + '']; | |
for (var fmt in urls) | |
if (urls.hasOwnProperty(fmt)) { | |
W.URL.revokeObjectURL(urls[fmt]); | |
} | |
} | |
trackEl.parentElement.removeChild(trackEl); | |
} | |
function addTrack(track) { | |
let el = generateTrackDiv(track), | |
delEl = D.createElement('a'); | |
delEl.href = '#', | |
delEl.className = 'deltrack', | |
delEl.onclick = function() { | |
W.dzDL.deleteCustom(this.parentElement); | |
}, | |
delEl.innerHTML = '[X]', | |
el.insertBefore(el.children[0], delEl), | |
D.querySelector('#dlcustomtracks').appendChild(el); //divCustomTracks | |
return el.id; //el | |
} | |
let divCurrentTrack = null, | |
divTracklist = D.createElement('div'); | |
function refreshTracklist() { | |
// Clean up EVERYTHING | |
if (divCurrentTrack !== null) { | |
deleteElement(divCurrentTrack), | |
divCurrentTrack = null; | |
} | |
var tracks = divTracklist.querySelectorAll('.trackdl'); | |
for (var i = 0; i < tracks.length; i++) deleteElement(tracks[i]); | |
//rootEl.appendChild(elH1); | |
if (W.dzPlayer) { | |
var sng = W.dzPlayer.getCurrentSong(); | |
if (sng && sng.SNG_ID) { | |
divCurrentTrack = generateTrackDiv(sng), | |
rootEl //.appendChild | |
.insertBefore(divCurrentTrack, elH2); | |
} | |
} | |
//rootEl.appendChild(elH2); | |
if (W.dzPlayer) { | |
var list = W.dzPlayer.getTrackList(), | |
current = W.dzPlayer.getTrackListIndex(); | |
if (list) { | |
var digits = list.length.toString().length; | |
for (var c = 0; c < list.length; c++) { | |
var trackDiv = generateTrackDiv(list[c], '0'.repeat(digits - (c + 1).toString().length) + (c + 1)); | |
if (c == current) trackDiv.classList.add('current'); | |
divTracklist.appendChild(trackDiv); | |
} | |
} | |
} | |
return false; | |
} | |
// DATA STYLESHEET. | |
function _dzAppCss_(newStyle) { | |
var styleElement = D.getElementById('dzApp_styles_js'), | |
appCss = | |
'.dltitle { font-weight: bolder; padding-top: 3px; padding-bottom: 3px; color: #000000; margin-left: 7px }' + | |
'#dzdownloader { position: fixed; top: 0; z-index: 500; background: #cccccc; padding: 5px; max-height: 90%; overflow-y: auto; min-width:25%; max-width:28% }' + | |
'#dzdltrigger { position: fixed; left: 0; top: 0; z-index: 501; background: #cccccc; padding: 3px; text-align: right }' + | |
'.trackdl .status { float: right } .trackdl .links { float: right } .trackdl:nth-child(even) { background: #dddddd }' + | |
'.trackdl a.dl { margin-left: 4px }' + // Some others 'improvements' | |
'#page_sidebar { z-index: 2; top: 20px !important }' + // Fix Deezer's sidebar height | |
'.trackdl.current { background: #808284; color: #e8eaed; font-weight: lighter } .dl,#dzdltrigger { letter-spacing: -1px } .dltitle:first-letter { font-size: 1.2em }' + | |
'.dl[download]:not(after) { text-decoration: line-through } #dlrefresh { margin-left: 25px } .dltitle,.trackdl { margin-top: .12em } .dl { font-size: 1.02em }' + | |
'.deltrack { margin-right: 3px } #dzdownloader .endfloat { clear: both } .trackdl.current a.dl { color: #e8eaed } #dzdltrigger,#dzdownloader { font-size: 1.05em }' + | |
'.trackdl.current a.dl[download] { color: #595959 } #dzdltrigger:hover { cursor: pointer } select { text-align: center; margin-left: 25px }' + | |
'.dltitle:after { content: " : "; position: relative } .dl:after,.dl[download]:after { content: " "; text-decoration: none!important }'; | |
if (!styleElement) { | |
styleElement = D.createElement('style'), | |
styleElement.type = 'text/css', | |
styleElement.innerHTML = appCss, | |
styleElement.id = 'dzApp_styles_js', | |
D.getElementsByTagName('head')[0].appendChild(styleElement); | |
} | |
styleElement.appendChild(D.createTextNode(newStyle)); | |
} //End _dzAppCss_ fn | |
// THE DOWNLOADER PANEL | |
rootEl.style.left = '220px', | |
rootEl.id = 'dzdownloader', | |
rootEl.className = 'dzdl'; | |
var elCombo = D.createElement('select');var IDtxt="[ID_hash_v] "; | |
elCombo.title = translate(`Choose the file name`), | |
elCombo.innerHTML = | |
//ADDING translation + REMOVE redundancy | |
'<option value="' + (flagTitle) + '" selected>' + `⋯ ` + translate(`Choose`) + ` ⋯` + '</option>' + //Having 'selected' here | |
'<option value="' + (flagTitle) + '">' + translate(`Title`) + '</option>' + | |
'<option value="' + (flagTitle | flagArt) + '">' + translate(`Artist`).toUpperCase() + ` - ` + translate(`Title`) + '</option>' + | |
'<option value="' + (flagVer | flagTitle) + '">' + `[ID_v] ` + translate(`Title`) + '</option>' + | |
'<option value="' + (flagVer | flagArt) + '">' + `[ID_v] ` + translate(`Artist`).toUpperCase() + ` - ` + translate(`Title`) + '</option>' + | |
'<option value="' + (flagHash | flagVer | flagTitle) + '">' + `[ID_hash_v] ` + translate(`Title`) + '</option>' + | |
'<option value="' + (flagHash | flagVer | flagArt) + '">' + `[ID_hash_v] ` + translate(`Artist`).toUpperCase() + ` - ` + translate(`Title`) + '</option>', | |
elCombo.onclick = function() { | |
W.dzDL.dlFlags = parseInt(this.value); | |
}; | |
var elRefresh = D.createElement('a'); | |
elRefresh.id = 'dlrefresh', elRefresh.href = '#', elRefresh.innerHTML = `⟳ ` + translate(`refresh track list`), | |
elRefresh.onclick = function() { | |
if(GM_Debug) { gm_win.console.info( `Refreshing the Track List.` ); } | |
W.dzDL.refreshPlayer(); | |
return false; | |
}; | |
var elH1 = D.createElement('p'); | |
elH1.className = 'dltitle', elH1.innerHTML = `♨ ` + translate(`Current track`); | |
var elH2 = D.createElement('p'); | |
elH2.className = 'dltitle', elH2.innerHTML = `⛃ ` + translate(`Track list`); | |
var elH3 = D.createElement('p'); | |
//elH3.className = 'dltitle', elH3.innerHTML = `⛂ User-added tracks`; | |
divTracklist.id = 'dltracklist', | |
divTracklist.className = 'dllist'; | |
var divCustomTracks = D.createElement('div'); | |
divCustomTracks.id = 'dlcustomtracks', | |
divCustomTracks.className = 'dllist', | |
rootEl.style.display = 'none', | |
rootEl.appendChild(elCombo), | |
rootEl.appendChild(elRefresh), | |
rootEl.appendChild(elH1), | |
rootEl.appendChild(elH2), | |
rootEl.appendChild(divTracklist), | |
//rootEl.appendChild(elH3), | |
rootEl.appendChild(divCustomTracks); | |
refreshTracklist(); | |
W.setTimeout(refreshTracklist, 1000); | |
var triggerEl = D.createElement('div'); | |
triggerEl.style.width = '214px', //padding is 3 => 220 total | |
triggerEl.id = 'dzdltrigger', | |
triggerEl.title = translate(`Click to open the app`), | |
triggerEl.innerHTML = 'D➲wnloader ☰', | |
triggerEl.onclick = function() { | |
var el = D.querySelector('#dzdownloader'); //rootEl | |
el.style.display=(el.style.display!=='block')? 'none' : 'block'; | |
if (el.style && el.style.display) { | |
if (el.style.display == 'none') { | |
el.style.display = 'block', | |
this.innerHTML = 'D➲wnloader ⚟'; | |
} else { | |
el.style.display = 'none', | |
this.innerHTML = 'D➲wnloader ☰'; | |
} | |
} | |
refreshTracklist(); | |
}; | |
D.body.appendChild(triggerEl), D.body.appendChild(rootEl); | |
_dzAppCss_(); // Applying StyleSheet to the app. | |
//ESCAPING DOWNLOADER WINDOW POPUP | |
W.onload = D.addEventListener('keydown', e => { | |
if (e.which === 27) { // escape being used | |
if(GM_Debug) { gm_win.console.info( `key with code ` + e.which + ` (escape) was pressed. We quit the app.` ); } | |
var node = D.querySelector('#dzdownloader'); //rootEl | |
node.style.display=(node.style.display=='block')? 'none' : 'block'; | |
if (node.style && node.style.display) { | |
node.style.display = 'none', triggerEl.innerHTML = 'D➲wnloader ☰'; | |
} | |
e.preventDefault(); | |
} | |
else { // Other keycode pressed. May be useful. | |
if(GM_Debug) { gm_win.console.info( `key with code ` + e.which + ` was pressed.` ); } | |
} | |
},0); | |
// https://blog.sessionstack.com/how-to-track-changes-in-the-dom-using-mutation-observer-bafdac65bca5 | |
// console.log(`content has changed`); | |
// W.dzPlayer.getTrackList();refreshTracklist(); | |
const dzDL = { | |
DEFAULT: fmtMisc, MP3_64: fmtLow, MP3_32: fmtLow32, MP3: fmtMed, MP3_128: fmtMed, MP3_320: fmtHQ, HQ: fmtHQ, FLAC: fmtFLAC, LOSSLESS: fmtFLAC, | |
WITH_TITLE: flagTitle, WITH_ART: flagArt, WITH_HASH: flagHash, WITH_VER: flagVer, WITH_FMT: flagFmt, NAME_DEFAULT: flagTitle | flagArt, NAME_DB: flagHash | flagVer, | |
// CONFIGURATION | |
dlFlags: flagHash | flagVer | flagTitle, | |
// MISCELLANEOUS FUNCTIONS | |
hasTrackInDB: hasTrack, getTrackFromDB: getTrack, hasDownloadedTrack: hasDownloaded, getDownloadedTrack: getDownloaded, generateFileName: generateName, | |
// ENTRY POINTS | |
getFromURL: decryptURL, makeURL: encryptURL, download: dzDownload, deleteCustom: deleteElement, addCustom: addTrack, refreshPlayer: refreshTracklist | |
}; | |
W.dzDL = dzDL; if (!W.dzHAX) W.dzHAX = {}, W.dzHAX.dL = dzDL; | |
} //END _dzApp_ fn | |
function _dzLogoCss_(newStyle) { | |
var styleElement = D.getElementById('dzLogo_styles_js'), | |
LogoCss = | |
'.logo-deezer{ height: 55px }' + | |
'.index-header-logo { width: 227px; max-width: 100%; height: 65px }' + | |
'.logo-deezer.logo,.logo-deezer-hp.logo.index-header-logo { \ | |
background-position: center; background-repeat: no-repeat; background-size: contain; \ | |
background-image:url("https://framapic.org/5RWAy9Zz6ekd/X7hftDFqC9MY.png") \ | |
}' + | |
'.nav-main.nav { margin-top: 8px!important } #menu_search { z-index: 2 } #menu_navigation { top: 135px }'; | |
if (!styleElement) { | |
styleElement = D.createElement('style'), | |
styleElement.type = 'text/css', | |
styleElement.innerHTML = LogoCss, | |
styleElement.id = 'dzLogo_styles_js', | |
D.getElementsByTagName('head')[0].appendChild(styleElement); | |
} | |
styleElement.appendChild(D.createTextNode(newStyle)); | |
} //End _dzLogoCss_ fn | |
function _gmAlertCss_(newStyle) { | |
var styleElement = D.getElementById('GM_styles_js'), | |
FIXME = `( ℹ ) Greasemonkey : Works, but you needs to reclick a second time on the link (after the conversion is done).`, //see document.write | |
gmCss = | |
'#dlrefresh:after { \ | |
content: " (" attr(FIXME) ")"; \ | |
font-size: .7em; top: .8em; right: 0; text-align: left; position: absolute; ligne-height: .82em; \ | |
letter-spacing: -0.5px; display: table; width: 30% \ | |
}'; | |
if (!styleElement) { | |
styleElement = D.createElement('style'), | |
styleElement.type = 'text/css', | |
styleElement.innerHTML = gmCss, | |
styleElement.id = 'GM_styles_js', | |
styleElement.title = FIXME, | |
D.getElementsByTagName('head')[0].appendChild(styleElement); | |
} | |
styleElement.appendChild(D.createTextNode(newStyle)); | |
} //End _gmAlertCss_ fn | |
})(window, 'script', 'css'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment