NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Bandcamp Downloader // @namespace ProxyFiend.BandcampDownloader // @author ProxyFiend // @homepage https://openuserjs.org/scripts/ProxyFiend/Bandcamp_Downloader // @supportURL https://openuserjs.org/scripts/ProxyFiend/Bandcamp_Downloader/issues // @copyright 2021, ProxyFiend (https://twitter.com/ProxyFiend) // @license MIT // @version 2.1 // @description Adds a sidebar to download songs from Bandcamp. // @include *://*.bandcamp.com/* // @updateURL https://openuserjs.org/meta/ProxyFiend/Bandcamp_Downloader.meta.js // @downloadURL https://openuserjs.org/src/scripts/ProxyFiend/Bandcamp_Downloader.user.js // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.min.js // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_addStyle // @grant GM_download // ==/UserScript== // Register Globals var sidebar_visible = false; var tracklist = TralbumData.trackinfo; (function () { "use strict"; // Sidebar construction $("body") .append( $("<div/>") .addClass("dl-sidebar-container dl-sidebar dl-color-1 hidden") .append( $("<p/>").text( "Click the files to download, or use the download album button." ) ) .append($("<ol/>").addClass("dl-tracklist")) .append( $("<div/>") .addClass("dl-all-button dl-progress dl-color-2") .append($("<div/>").addClass("dl-progress-bar")) .append( $("<div/>").addClass("dl-progress-text").text("Download Album") ) ) ) .append( $("<div/>") .addClass("dl-sidebar-toggle dl-sidebar dl-color-1 hidden") .text("Bandcamp Downloader") ); tracklist.forEach((track) => { $(".dl-tracklist").append( $("<li/>") .attr("id", "track-" + track.track_num) .append( $("<a/>") .addClass("dl-link custom-color") .attr("data-url", track.file["mp3-128"]) .attr("data-trackid", tracklist.indexOf(track)) .text(track.title) ) ); }); // Style sidebar to match page GM_addStyle( ".dl-sidebar-toggle{font-family:biolinum;left:0;top:50px;position:fixed;transform:rotate(90deg);transform-origin:left bottom 0;border-top-right-radius:5px;border-top-left-radius:5px;margin:0;padding:5px;z-index:5001;background:inherit;border:1px solid #000000;border-bottom:none;cursor:pointer}.dl-sidebar-toggle.active{left:400px}.dl-sidebar-container{width:390px;min-height:300px;left:-400px;top:50px;max-height:500px;overflow-y:scroll;padding:5px;position:fixed;z-index:5000;border:1px solid #000000;border-left:none;user-select:none;cursor:default}.dl-sidebar-container.active{left:0}.dl-all-button{margin-left:0;margin-right:0;cursor:pointer;background-color:rgb(255, 255, 255);color:rgb(0, 0, 0);border-color:rgb(0, 0, 0)}.dl-progress{position:relative;z-index:5001}.dl-progress,.dl-progress-bar,.dl-progress-text{height:25px;left:0;right:0;top:0;bottom:0}.dl-progress-bar{width:0;overflow:hidden;z-index:5002;-moz-transition:width 0.2s linear;-webkit-transition:width 0.2s linear;-o-transition:width 0.2s linear;transition:width 0.2s linear;background-color:rgb(0, 0, 0)}.dl-progress-text{text-align:center;z-index:5003;position:absolute;line-height:25px;color:inherit}.dl-sidebar{opacity:1;transition:opacity 1s, left 0.5s}.dl-sidebar.hidden{opacity:0}.dl-sidebar::-webkit-scrollbar{width:10px}.dl-sidebar::-webkit-scrollbar-track{background:rgb(0, 0, 0)}.dl-sidebar::-webkit-scrollbar-thumb{background:rgb(255, 255, 255)}" ); $(".dl-color-1").css({ "background-color": $("#pgBd").css("background-color"), color: $("#pgBd").css("color"), "border-color": $("#pgBd").css("color"), }); $(".dl-color-2").css({ "background-color": $("#follow-unfollow").css("background-color"), color: $("#follow-unfollow").css("color"), "border-color": $("#follow-unfollow").css("border-color"), }); $(".dl-progress-bar").css({ "background-color": $("#pgFt").css("background-color"), }); $(".dl-sidebar").removeClass("hidden"); // Handle events $(".dl-sidebar-toggle").click((e) => { $(".dl-sidebar").toggleClass("active"); }); $(".dl-link").click((e) => { GM_download( $(e.target).attr("data-url"), `[${pad(tracklist[$(e.target).attr("data-trackid")].track_num, 2)}] ${ TralbumData.artist } - ${tracklist[$(e.target).attr("data-trackid")].title}.mp3` ); }); $(".dl-all-button").click((e) => { downloadAlbum(); }); // Main functions function downloadAlbum() { // Emergency polyfill var Promise = window.Promise; if (!Promise) { Promise = JSZip.external.Promise; } // Fetch binary data using GM_xmlhttprequest function urlToPromise(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: url, responseType: "arraybuffer", onload: (response) => { resolve(response.response); }, onerror: (response) => { reject(response.response); }, }); }); } var zip = new JSZip(); tracklist.forEach((track) => { zip.file( `[${track.track_num}] ${TralbumData.artist} - ${track.title}.mp3`, urlToPromise(track.file["mp3-128"]), { binary: true } ); }); zip .generateAsync({ type: "blob" }, (metadata) => { $(".dl-progress-bar").css("width", `${metadata.percent}%`); }) .then( (blob) => { GM_download( URL.createObjectURL(blob), `${BandData.name} - ${TralbumData.current.title}.zip` ); }, (e) => { console.error(e); } ); } // Supporting functions function pad(n, width = 3, z = 0) { return (String(z).repeat(width) + String(n)).slice(String(n).length); } })();