diff --git a/bandcamp_downloader.user.js b/bandcamp_downloader.user.js
index a8c9428..54c0efe 100644
--- a/bandcamp_downloader.user.js
+++ b/bandcamp_downloader.user.js
@@ -4,157 +4,236 @@
// @author Big Duckie
// @copyright 2025, Big Duckie (https://bigduckie.dev)
// @license MIT
-// @version 2025.1.13
+// @version 2025.6.6
// @description Adds a sidebar to download songs from Bandcamp.
// @include *://*.bandcamp.com/*
-// @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
+// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
+// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js
// @grant GM_xmlhttpRequest
-// @grant GM_notification
// @grant GM_addStyle
// @grant GM_download
+// @connect bcbits.com
// ==/UserScript==
// Register Globals
-var sidebar_visible = false;
-var tracklist = TralbumData.trackinfo;
+let tracklist = TralbumData.trackinfo;
+unsafeWindow.GM_download = GM_download;
(function () {
- "use strict";
+ "use strict";
- // Sidebar construction
- $("body")
- .append(
- $("
")
- .addClass("dl-sidebar-container dl-sidebar dl-color-1 hidden")
- .append(
- $("").text(
- "Click the files to download, or use the download album button."
- )
- )
- .append($("
").addClass("dl-tracklist"))
- .append(
- $("")
- .addClass("dl-all-button dl-progress dl-color-2")
- .append($("").addClass("dl-progress-bar"))
- .append(
- $("").addClass("dl-progress-text").text("Download Album")
- )
- )
- )
- .append(
- $("")
- .addClass("dl-sidebar-toggle dl-sidebar dl-color-1 hidden")
- .text("Bandcamp Downloader")
- );
-
- tracklist.forEach((track) => {
- $(".dl-tracklist").append(
- $("")
- .attr("id", "track-" + track.track_num)
- .append(
- $("")
- .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();
+ // Sidebar construction
+ jQuery("body")
+ .append(jQuery("")
+ .addClass("dl-sidebar-container dl-sidebar dl-color-1 hidden")
+ .append(jQuery("").text("Click the files to download, or use the download album button."))
+ .append(jQuery("
").addClass("dl-tracklist"))
+ .append(jQuery("")
+ .addClass("dl-all-button dl-progress dl-color-2")
+ .append(jQuery("").addClass("dl-progress-bar"))
+ .append(jQuery("").addClass("dl-progress-text").text("Download Album"))))
+ .append(jQuery("")
+ .addClass("dl-sidebar-toggle dl-sidebar dl-color-1 hidden")
+ .text("Bandcamp Downloader"));
tracklist.forEach((track) => {
- zip.file(
- `[${track.track_num}] ${TralbumData.artist} - ${track.title}.mp3`,
- urlToPromise(track.file["mp3-128"]),
- { binary: true }
- );
+ jQuery(".dl-tracklist").append(jQuery("")
+ .attr("id", "track-" + track.track_num)
+ .append(jQuery("")
+ .addClass("dl-link custom-color")
+ .attr("data-url", track.file["mp3-128"])
+ .attr("data-trackid", tracklist.indexOf(track))
+ .text(track.title)));
});
- 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);
- }
- );
- }
+ function css(strings) {
+ return strings;
+ }
- // Supporting functions
- function pad(n, width = 3, z = 0) {
- return (String(z).repeat(width) + String(n)).slice(String(n).length);
- }
-})();
+ // Style sidebar to match page
+ GM_addStyle(css`.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 {
+ display: flex;
+ flex-direction: column;
+ 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-tracklist {
+ flex-grow: 1;
+ }
+
+ .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)
+ }`);
+
+ jQuery(".dl-color-1").css({
+ "background-color": jQuery("#pgBd").css("background-color"),
+ color: jQuery("#pgBd").css("color"),
+ "border-color": jQuery("#pgBd").css("color"),
+ });
+ jQuery(".dl-color-2").css({
+ "background-color": jQuery("#follow-unfollow").css("background-color"),
+ color: jQuery("#follow-unfollow").css("color"),
+ "border-color": jQuery("#follow-unfollow").css("border-color"),
+ });
+ jQuery(".dl-progress-bar").css({
+ "background-color": jQuery("#recommendations_container").css("background-color"),
+ });
+
+ jQuery(".dl-sidebar").removeClass("hidden");
+
+ // Handle events
+ jQuery(".dl-sidebar-toggle").click((e) => {
+ jQuery(".dl-sidebar").toggleClass("active");
+ });
+
+ jQuery(".dl-link").click((e) => {
+ GM_download(jQuery(e.target).attr("data-url"), `[${pad(tracklist[jQuery(e.target).attr("data-trackid")].track_num, 2)}] ${TralbumData.artist} - ${tracklist[jQuery(e.target).attr("data-trackid")].title}.mp3`);
+ });
+
+ jQuery(".dl-all-button").click((e) => {
+ console.debug("downloading album");
+ downloadAlbum();
+ });
+
+ // Main functions
+ function downloadAlbum() {
+ let zip = new JSZip();
+
+ tracklist.forEach((track) => {
+ console.debug("downloading track", track);
+ zip.file(
+ `[${track.track_num}] ${TralbumData.artist} - ${track.title}.mp3`,
+ urlToPromise(track.file["mp3-128"]));
+ });
+
+ console.debug("generating zip");
+ zip.generateAsync({type: "blob"}, (metadata) => {
+ console.debug("processing zip");
+ jQuery(".dl-progress-bar").css("width", `${metadata.percent}%`);
+ })
+ .then((blob) => {
+ let url = URL.createObjectURL(blob);
+ console.debug("compiled zip", url);
+
+ GM_download(url, `${TralbumData.artist} - ${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);
+ }
+
+ function urlToPromise(url) {
+ return new Promise((resolve, reject) => {
+ GM.xmlHttpRequest({
+ method: "GET", url: url, responseType: "blob", onload: (response) => {
+ console.debug("fetched song", url, response.response);
+ resolve(response.response);
+ }, onerror: (response) => {
+ reject(response.response);
+ },
+ });
+ });
+ }
+})();
\ No newline at end of file