NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Bandcamp download mp3s // @description Shows download links for the preview files on bandcamp albums // @namespace cuzi // @oujs:author cuzi // @homepageURL https://openuserjs.org/scripts/cuzi/Bandcamp_download_mp3s // @version 9 // @license GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt // @match https://*.bandcamp.com/* // @grant unsafeWindow // @grant GM.xmlHttpRequest // @grant GM_download // @connect bandcamp.com // @connect bcbits.com // @icon https://bandcamp.com/img/favicon/apple-touch-icon.png // ==/UserScript== /* globals GM, unsafeWindow, GM_download */ /* jshint asi: true, esversion: 8 */ (function () { // Base64 function from https://gist.github.com/stubbetje/229984 function base64BinaryString (s) { const base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split('') const l = s.length const o = [] let char0, char1, char2, char3 let byte0, byte1, byte2 let t for (let i = 0; i < l; i++) { byte0 = s.charCodeAt(i++) & 0xff byte1 = i < l ? s.charCodeAt(i++) & 0xff : 0 byte2 = i < l ? s.charCodeAt(i) & 0xff : 0 char0 = byte0 >> 2 char1 = ((byte0 & 0x3) << 4) | (byte1 >> 4) char2 = ((byte1 & 0x0f) << 2) | (byte2 >> 6) char3 = byte2 & 0x3f t = i - l if (t === 1) { char3 = 64 } else if (t === 2) { char3 = 64 char2 = 64 } o.push(base64[char0], base64[char1], base64[char2], base64[char3]) } return o.join('') } function fixFilename (s) { const forbidden = '*"/\\[]:|,<>?\n\t\0'.split('') forbidden.forEach(function (char) { s = s.replace(char, '') }) return s } const SPINNERCSS = '/* http://www.designcouch.com/home/why/2013/05/23/dead-simple-pure-css-loading-spinner/ */\n' + '.download-col .downloaddisk:hover {\n' + ' text-decoration:none\n' + '}\n' + '.downspinner {\n' + ' margin:0px auto;\n' + ' position:relative;\n' + ' display:inline-block;\n' + ' animation: spinnerrotation 3s infinite linear;\n' + ' cursor:wait;\n' + '}\n' + '@keyframes spinnerrotation {\n' + ' from {transform: rotate(0deg)}\n' + ' to {transform: rotate(359deg)}\n' + '}' function addSpinner (el) { el.style = '' el.classList.add('downspinner') } function removeSpinner (el) { el.classList.remove('downspinner') el.style = 'background:#1cea1c;border-radius:5px;padding:1px;opacity:0.5' } function download (ev) { const a = this const url = a.href if (GM_download) { // Use Tampermonkey GM_download function ev.preventDefault() addSpinner(a) GM_download({ url, name: a.download, onerror: function () { window.alert('Could not download via GM_download') document.location.href = url }, ontimeout: function () { window.alert('Could not download via GM_download. Time out.') document.location.href = url }, onload: function () { window.setTimeout(() => removeSpinner(a), 500) } }) return } if (!url.startsWith('http') || navigator.userAgent.indexOf('Chrome') !== -1) { // Just open the link normally (no prevent default) addSpinner(a) window.setTimeout(() => removeSpinner(a), 1000) return } // Use GM.xmlHttpRequest to download and offer data uri ev.preventDefault() addSpinner(a) GM.xmlHttpRequest({ method: 'GET', overrideMimeType: 'text/plain; charset=x-user-defined', url, onload: function (response) { a.href = 'data:audio/mpeg;base64,' + base64BinaryString(response.responseText) window.setTimeout(() => a.click(), 10) }, onerror: function (response) { window.alert('Could not download via GM.xmlHttpRequest') document.location.href = url } }) } const style = document.createElement('style') style.appendChild(document.createTextNode(SPINNERCSS)) document.head.appendChild(style) window.setTimeout(function () { const TralbumData = unsafeWindow.TralbumData if (TralbumData && TralbumData.hasAudio && !TralbumData.freeDownloadPage && TralbumData.trackinfo) { let hoverdiv = document.querySelectorAll('.download-col div') if (hoverdiv.length === 0) { hoverdiv = document.querySelectorAll('h2.trackTitle') } for (let i = 0; i < TralbumData.trackinfo.length; i++) { const t = TralbumData.trackinfo[i] for (const prop in t.file) { const mp3 = t.file[prop].replace(/^\/\//, 'http://') const a = document.createElement('a') a.className = 'downloaddisk' a.href = mp3 a.download = (t.track_num > 9 ? '' : '0') + t.track_num + '. ' + fixFilename(TralbumData.artist + ' - ' + t.title) + '.mp3' a.title = 'Download ' + prop a.appendChild(document.createTextNode('\uD83D\uDCBE')) a.addEventListener('click', download) hoverdiv[i].appendChild(a) break } } } }, 500) })()