// ==UserScript==
// @name Soundcloud Downloader Clean
// @namespace https://openuserjs.org/users/webketje
// @version 0.1.1
// @description *This version no longer works due to Soundcloud API changes - will update soon* - An ad-less, multilingual, clean Soundcloud downloader with robust code. Adds a 'Download' button in the toolbar of all single track views.
// @author webketje
// @license MIT
// @icon https://a-v2.sndcdn.com/assets/images/sc-icons/favicon-2cadd14bdb.ico
// @homepageURL https://gist.github.com/webketje/8cd2e6ae8a86dbe0533c5d2c612c42c6
// @supportURL https://gist.github.com/webketje/8cd2e6ae8a86dbe0533c5d2c612c42c6
// @noframes
// @match https://soundcloud.com/*
// @grant unsafeWindow
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/FileSaver.min.js
// ==/UserScript==
/* globals saveAs */
(function() {
'use strict';
var win = unsafeWindow || window;
var containerSelector = '.listenEngagement__footer .sc-button-toolbar';
/**
* @desc Log to console only if debug is true
*/
function log() {
var stamp = new Date().toLocaleString(),
args = [].slice.call(arguments),
prefix = ['SCDLC', stamp, '-'];
scdl.debug && console.log(prefix.concat(args).join(' '));
};
/**
* @desc There is no other way to retrieve a Soundcloud client_id than by spying on existing requests.
* We temporarily patch the XHR.send method to retrieve the url passed to it.
* @param restoreIfTrue - restores the original prototype method when true is returned
* @param onRestore - a function to exec when the restoreIfTrue condition is met
*/
function patchXHR(restoreIfTrue, onRestore) {
var originalXHR = win.XMLHttpRequest.prototype.open;
win.XMLHttpRequest.prototype.open = function() {
originalXHR.apply(this, arguments);
var restore = restoreIfTrue.apply(this, arguments);
if (restore) {
win.XMLHttpRequest.prototype.open = originalXHR;
onRestore(restore);
}
};
};
var scdl = {
debug: false,
client_id: '',
dlButtonId: 'scdlc-btn'
};
scdl.getTrackName = function(trackJSON) {
return [
trackJSON.user.username,
trackJSON.title
].join(' - ');
};
scdl.getStreamURL = function(url, onresolve, onerror) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var trackJSON = JSON.parse(xhr.responseText);
onresolve(trackJSON.errors || !trackJSON.stream_url ? false : {
stream_url: trackJSON.stream_url + '?client_id=' + this.client_id,
track_name: this.getTrackName(trackJSON)
});
}.bind(this);
xhr.onerror = function() {
onerror(false);
};
xhr.open('GET', 'https://api.soundcloud.com/resolve?url=' + encodeURIComponent(url) + '&client_id=' + this.client_id);
xhr.send();
};
scdl.button = {
label: {
en: 'Download',
es: 'Descargar',
fr: 'Télécharger',
nl: 'Download',
de: 'Download',
pl: 'Ściągnij',
it: 'Scaricare',
pt_BR: 'Baixar',
sv: 'Ladda ner'
},
download: function(e) {
e.preventDefault();
saveAs(e.target.href, e.target.dataset.title);
},
render: function(href, title, onClick) {
var label = scdl.button.label[document.documentElement.lang];
var a = document.createElement('a');
a.className = "sc-button";
a.href = href;
a.id = scdl.dlButtonId;
a.textContent = label;
a.dataset.title = title + '.mp3';
a.setAttribute('download', title + '.mp3');
a.target = '_blank';
a.onclick = onClick;
a.style.marginLeft = '5px';
a.style.cssFloat = 'left';
return a;
},
attach:function() {
this.remove();
var f = document.querySelector(containerSelector);
if (f)
f.insertAdjacentElement('afterend', this.render.apply(this, arguments));
},
remove: function() {
var btn = document.getElementById(scdl.dlButtonId);
if (btn)
btn.parentNode.removeChild(btn);
}
};
scdl.parseClientIdFromURL = function(url) {
var search = /client_id=([\w\d]+)&*/;
return url && url.match(search) && url.match(search)[1];
};
scdl.getClientID = function(onClientIDFound) {
patchXHR(function(method, url) {
return scdl.parseClientIdFromURL(url);
}, onClientIDFound);
};
scdl.load = function(url) {
// for now only make available for single track pages
if (/^(\/(you|stations|discover|stream|upload|search|settings))/.test(win.location.pathname)) {
scdl.button.remove();
return;
}
scdl.getStreamURL(url,
function onSuccess(result) {
if (!result) {
scdl.button.remove();
} else {
log('Detected valid Soundcloud artist track URL. Requesting info...');
scdl.button.attach(
result.stream_url,
result.track_name,
scdl.button.download
);
}
},
scdl.button.remove
);
};
// patch front-end navigation
['pushState','replaceState','forward','back','go'].forEach(function(event) {
var tmp = win.history.pushState;
win.history[event] = function() {
tmp.apply(win.history, arguments);
scdl.load(win.location.href);
}
});
scdl.getClientID(function(id) {
log('Found Soundcloud client id:', id, '. Initializing...');
scdl.client_id = id;
scdl.load(win.location.href);
});
})();