// jshint browser: true, jquery: true
// ==UserScript==
// @name SoundCloud Downloader
// @namespace http://www.dieterholvoet.com
// @author Dieter Holvoet
// @description Adds a direct download button to all the tracks on SoundCloud (works with the new SoundCloud interface)
// @require https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js
// @include http://www.soundcloud.com/*
// @include http://soundcloud.com/*
// @include https://www.soundcloud.com/*
// @include https://soundcloud.com/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_openInTab
// @version 0.6
// ==/UserScript==
//-----------------------------------------------------------------------------------
jQuery.noConflict();
(function ($) {
$(function () {
/** Append download buttons */
setInterval(function () {
/**
* Playlist page
* - with official downloads: e.g. https://soundcloud.com/alexdoanofficial/sets/into-the-void-ep
* - without official downloads: e.g. https://soundcloud.com/mule-z/sets/tropical
* */
$(".trackList").find(".trackList__item").each(function () {
$item = $(this).find(".trackItem__trackTitle").eq(0);
title = cleanTitle($item.text());
url = $item.attr("href");
appendButton($(this), url, 'small', true);
});
/**
* Track page
* e.g. https://soundcloud.com/mkjaff/dyrisk-the-tallest-man-mkj-remix
* */
if($(".listenDetails .commentsList").exists()) {
title = cleanTitle($(".soundTitle__title").eq(0).text());
url = document.location.href;
appendButton($(".listenEngagement__footer"), url, 'medium', false);
}
/**
* Homepage
* https://soundcloud.com/stream
*
* Likes
* https://soundcloud.com/you/likes
*
* Overview
* https://soundcloud.com/you/collection
* */
$(".lazyLoadingList").find(".soundList__item").each(function () {
if (!$(this).find(".sound").is(".playlist")) {
title = cleanTitle($(this).find(".soundTitle__title").eq(0).text());
url = $(this).find(".soundTitle__title").eq(0).attr("href");
appendButton($(this), url, 'small', false);
}
});
/**
* Charts
* e.g. https://soundcloud.com/charts/top
* */
$(".chartTracks").find(".chartTracks__item > .chartTrack").each(function () {
$item = $(this).find(".chartTrack__title a").eq(0);
title = cleanTitle($item.text());
url = $item.attr("href");
appendButton($(this), url, 'small', true);
});
/**
* Play history
* https://soundcloud.com/you/history
* */
$(".historicalPlays").find(".historicalPlays__item").each(function () {
$item = $(this).find("a.soundTitle__title").eq(0);
title = cleanTitle($item.text());
url = $item.attr("href");
appendButton($(this), url, 'small', false);
});
/**
* User profile page
* e.g. https://soundcloud.com/kiyokomusik
* */
$(".userStream").find(".soundList__item > .userStreamItem").each(function () {
if (!$(this).find(".sound").is(".playlist")) {
title = cleanTitle($(this).find(".soundTitle__title").eq(0).text());
url = $(this).find(".soundTitle__title").eq(0).attr("href");
appendButton($(this), url, 'small', false);
}
});
/**
* Search page
* e.g. https://soundcloud.com/search?q=addal
* */
$(".searchList").find(".searchList__item").each(function () {
if ($(this).find(".sound").is(".track")) {
title = cleanTitle($(this).find(".soundTitle__title").eq(0).text());
url = $(this).find(".soundTitle__title").eq(0).attr("href");
appendButton($(this), url, 'small', false);
} else if ($(this).find(".sound").is(".playlist")) {
// TO DO: Download playlist
}
});
}, 2000);
});
function appendButton($parent, url, size, iconOnly) {
/** Find button-group and test for preview-only/geoblocked tracks */
var $small = $parent.find('.soundActions .sc-button-group-small'),
$medium = $parent.find('.soundActions .sc-button-group-medium');
if($small.exists()) {
$parent = $small;
} else if($medium.exists()) {
$parent = $medium;
} else {
if($parent.prop('dl-checked')) return;
if(isPreview($parent)) {
console.error("Track is preview-only, can't be downloaded: " + url);
$parent.prop('dl-checked', true);
} else if(isGeoblocked($parent)) {
console.error("Track is geoblocked, can't be downloaded: " + url);
$parent.prop('dl-checked', true);
} else {
console.error("No button-group found. Please verify selector.");
}
return;
}
/** Return if already checked */
if($parent.prop('dl-checked')) return;
/** Clean URL */
url = cleanURL(url);
/** Check presence of download button */
if($parent.find(".sc-button-download").length > 0) {
console.error("Download button already present.");
/** Check presence of external free download link */
} else if(hasExternalFreeDownload($parent)) {
var $buylink = $parent.parent().find('.soundActions__purchaseLink'),
$button = makeDownloadButton($parent, $buylink.prop('href'), size, iconOnly);
$buylink.remove();
/** Check URL */
} else if(!isValidTrackURL(url)) {
console.error("Track URL is invalid: " + url);
/** Fetch download URL */
} else {
makeDownloadButton($parent, url, size, iconOnly);
}
/** Check presence of external stream/buy link */
if(hasExternalBuyLink($parent)) {
var $buylink = $parent.parent().find('.soundActions__purchaseLink'),
$button = makeBuyButton($parent, $buylink.prop('href'), size, iconOnly);
$buylink.remove();
}
$parent.prop('dl-checked', true);
}
function makeDownloadButton($parent, url, size, iconOnly) {
$button = $('<a class="sc-button sc-button-'+size+' sc-button-responsive sc-button-download'+(iconOnly ? ' sc-button-icon' : '')+'" title="Download ' + title + '" >Download</a>');
if(isValidTrackURL(url)) {
url = "https://mrvv.net/scdl/scdlSC.php?url=" + url;
$button.on("click", function() {
$.get(url, function (data) {
if (data.hasOwnProperty('error')) {
console.error("Fetching download URL failed: " + data.error + " ("+this.url+")");
} else {
GM_openInTab("https://mrvv.net/scdl/scdlDL.php?url=" + data.dlfileurl, true)
}
}, "json");
});
} else {
$button.attr("href", url);
$button.attr("target", '_blank');
}
$button.appendTo($parent.eq(0));
return $button;
}
function makeBuyButton($parent, url, size, iconOnly) {
$button = $('<a href="'+url+'" target="_blank" class="sc-button sc-button-'+size+' sc-button-responsive sc-button-buy'+(iconOnly ? ' sc-button-icon' : '')+'" title="Buy ' + title + '" >Buy</a>');
$button.appendTo($parent.eq(0));
return $button;
}
function getIcon() {
return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+UmVjdGFuZ2xlIDMxPC90aXRsZT48cGF0aCBkPSJNMyAxMXYyaDEwdi0ySDN6bTAtN2gxMGwtNSA2LTUtNnptMy0ydjJoNFYySDZ6IiBmaWxsPSIjMjIyIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=";
}
function cleanTitle(title) {
title = title.replace(/"/g, "'");
title = $.trim(title);
return title;
}
function cleanURL(url) {
url = url.split(/[?#]/)[0]; // Strip query string
url = relativeToAbsoluteURL(url);
return url;
}
function isValidTrackURL(url) {
if(!url.match(/^(http|https):\/\/soundcloud\.com\/.+\/.+$/g)) return false;
if(url.match(/^(http|https):\/\/soundcloud\.com\/.+\/sets\/.+$/)) return false;
return true;
}
function isPreview($item) {
if($item.find(".sc-snippet-badge").exists()) {
$item = $item.find(".sc-snippet-badge");
} else if($item.parent("trackItem__additional").find(".sc-snippet-badge").exists()) {
$item = $item.parent("trackItem__additional").find(".sc-snippet-badge");
}
return $item.eq(0).text() === "Preview";
}
function isGeoblocked($item) {
if($item.find(".g-geoblocked-icon").exists()) {
return true;
} else if($item.parent("trackItem__additional").find(".g-geoblocked-icon").exists()) {
return true;
}
return false;
}
function hasExternalFreeDownload($item) {
var $buylink = $item.parent().find('.soundActions__purchaseLink').eq(0),
strings = ['free download', 'free dl'],
websites = ['theartistunion', 'toneden', 'artistsunlimited.co', 'melodicsoundsnetwork.com', 'edmlead.net', 'click.dj', 'woox.agency', 'hypeddit.com', 'hive.co'],
hasExternalFreeDownload = false;
if($buylink.exists()) {
strings.forEach(function(elem, i, array) {
if($buylink.text().toLowerCase().indexOf(elem) !== -1) hasExternalFreeDownload = true;
});
websites.forEach(function(elem, i, array) {
if($buylink.attr('href').toLowerCase().indexOf(elem) !== -1) hasExternalFreeDownload = true;
});
}
return hasExternalFreeDownload;
}
function hasExternalBuyLink($item) {
var $buylink = $item.parent().find('.soundActions__purchaseLink').eq(0),
strings = ['buy', 'spotify', 'beatport', 'juno', 'stream'],
websites = ['lnk.to', 'open.spotify.com', 'spoti.fi', 'junodownload.com', 'beatport.com', 'itunes.apple.com', 'play.google.com', 'deezer.com', 'napster.com', 'music.microsoft.com'],
hasExternalFreeDownload = false;
if($buylink.exists()) {
strings.forEach(function(elem, i, array) {
if($buylink.text().toLowerCase().indexOf(elem) !== -1) hasExternalFreeDownload = true;
});
websites.forEach(function(elem, i, array) {
if($buylink.attr('href').toLowerCase().indexOf(elem) !== -1) hasExternalFreeDownload = true;
});
}
return hasExternalFreeDownload;
}
function relativeToAbsoluteURL(url) {
if(url.substr(0, 1) === '/')
return 'https://soundcloud.com'+url;
else
return url;
}
$.fn.exists = function () {
return this.length !== 0;
};
})(jQuery);