Lists ALL releases at the artist page, not just hand picked ones by deezer.
// ==UserScript==
// @name Deezer List All Releases
// @description Lists ALL releases at the artist page, not just hand picked ones by deezer.
// @author bertigert
// @version 1.0.2
// @icon https://www.google.com/s2/favicons?sz=64&domain=deezer.com
// @namespace Violentmonkey Scripts
// @match https://www.deezer.com/*
// @grant none
// ==/UserScript==
(function() {
class Logger {
static PREFIX = "[Display All Songs]";
constructor(do_debug) {
this.do_debug = do_debug;
}
log(...args) {
console.log(Logger.PREFIX, ...args);
}
warn(...args) {
console.warn(Logger.PREFIX, ...args);
}
error(...args) {
console.error(Logger.PREFIX, ...args);
}
debug(...args) {
if (this.do_debug) console.debug(Logger.PREFIX, ...args);
}
}
class Hooks {
static HOOK_INDEXES = Object.freeze({
FETCH: 0,
ALL: 1
});
// we use this approach to unhook to avoid unhooking hooks created after our hooks
static is_hooked = [false];
static hook_fetch() {
const orig_fetch = window.fetch;
async function hooked_fetch(...args) {
if (!Hooks.is_hooked[Hooks.HOOK_INDEXES.FETCH]) return orig_fetch.apply(this, args);
try {
if (args.length !== 2 ||
args[0] !== "https://pipe.deezer.com/api" ||
args[1].method !== "POST" ||
typeof args[1].body !== "string"
) {
return orig_fetch.apply(this, args);
}
const operation_name = args[1].body.match(/"operationName":\s*"(.*?)"/);
if (operation_name && operation_name[1] === "ArtistDiscographyByType") {
// logger.debug('Caught original artist page fetch call');
args[1].body = args[1].body.replace(/"subType":\s*"(.*?)"/, '"subType": null')
.replace(/"mode":\s*"(.*?)"/, '"mode": "ALL"');
}
return orig_fetch.apply(this, args);
} catch (e) {
logger.error("Error in fetch hook:", e);
return orig_fetch.apply(this, args);
}
}
// only change the function which gets called, not the attributes of the original fetch function
Object.setPrototypeOf(hooked_fetch, orig_fetch);
Object.getOwnPropertyNames(orig_fetch).forEach(prop => {
try {
hooked_fetch[prop] = orig_fetch[prop];
} catch (e) {}
});
window.fetch = hooked_fetch;
window.fetch._modified_by_display_all_songs = true;
}
static ensure_hooks() {
if (!window.fetch._modified_by_display_all_songs) {
Hooks.hook_fetch();
}
}
static toggle_hooks(enabled, ...args) {
for (const arg of args) {
switch (arg) {
case Hooks.HOOK_INDEXES.ALL:
Hooks.is_hooked.fill(enabled);
return;
case Hooks.HOOK_INDEXES.FETCH:
Hooks.is_hooked[arg] = enabled;
break;
}
}
}
}
const logger = new Logger(false);
function artist_main() {
logger.debug("Enabling hooks for artist page");
Hooks.toggle_hooks(true, Hooks.HOOK_INDEXES.ALL);
}
function cleanup() {
logger.debug("Disabling hooks");
Hooks.toggle_hooks(false, Hooks.HOOK_INDEXES.ALL);
}
window.history.pushState = new Proxy(window.history.pushState, {
apply: (target, thisArg, argArray) => {
if (location.href.includes("/artist/")) {
artist_main();
} else {
cleanup();
}
return target.apply(thisArg, argArray);
},
});
window.addEventListener("popstate", (e) => {
if (location.href.includes("/artist/")) {
artist_main();
} else {
cleanup();
}
});
logger.log("Initializing");
Hooks.hook_fetch();
setTimeout(Hooks.ensure_hooks, 5000);
if (location.href.includes("/artist/")) {
artist_main();
}
})();