NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Bandcamp Supporters You Follow // @version 1.0.3 // @description Show supporters of an album/track that you follow // @namespace 289690-squeek502 // @license 0BSD // @match http*://*.bandcamp.com/* // @include http*://*.bandcamp.com/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // ==/UserScript== // messy code ahead var OPTION_AUTOLOAD = 'autoload'; var SMALL_SIZE = '28px'; var LARGE_SIZE = 'auto'; var DEFAULT_SIZE = SMALL_SIZE; var OPTION_SIZE = 'size'; var DEFAULT_AUTOLOAD = false; var collectedByContainer = document.querySelector('.collected-by.tralbum.collectors'); if (!collectedByContainer) return; var get; var post; var loadedimgs = []; var existingMessage = collectedByContainer.querySelector('.message'); var optionsMenu = document.createElement('div'); optionsMenu.style.display = 'none'; optionsMenu.style.marginTop = '10px'; optionsMenu.style.marginBottom = '10px'; optionsMenu.style.position = 'relative'; var toggleAutoload = document.createElement('input'); toggleAutoload.setAttribute('type', 'checkbox'); toggleAutoload.checked = GM_getValue("autoload", false); toggleAutoload.id = 'supporters-you-follow-autoload'; var toggleAutoloadLabel = document.createElement('label'); toggleAutoloadLabel.setAttribute('for', 'supporters-you-follow-autoload'); toggleAutoloadLabel.textContent = 'autoload'; toggleAutoloadLabel.style.fontWeight = 'bold'; var toggleAutoloadContainer = document.createElement('div'); toggleAutoloadContainer.style.display = 'inline-block'; toggleAutoloadContainer.style.marginRight = '30px'; toggleAutoloadContainer.appendChild(toggleAutoload); toggleAutoloadContainer.appendChild(toggleAutoloadLabel); toggleAutoload.addEventListener('change', function() { GM_setValue(OPTION_AUTOLOAD, this.checked); }); var onSizeChanged = function() { if (this.checked) { GM_setValue(OPTION_SIZE, this.value); loadedimgs.forEach(function(img) { img.style.width = this.value; img.style.height = this.value; }.bind(this)); } }; var curSizeSetting = GM_getValue(OPTION_SIZE, DEFAULT_SIZE); var sizeSmall = document.createElement('input'); sizeSmall.setAttribute('type', 'radio'); sizeSmall.name = 'supporters-you-follow-size'; sizeSmall.value = SMALL_SIZE; sizeSmall.id = 'supporters-you-follow-size-small'; sizeSmall.style.marginLeft = '10px'; sizeSmall.checked = curSizeSetting == SMALL_SIZE; sizeSmall.addEventListener('change', onSizeChanged); var sizeSmallLabel = document.createElement('label'); sizeSmallLabel.setAttribute('for', 'supporters-you-follow-size-small'); sizeSmallLabel.textContent = 'small'; sizeSmallLabel.style.marginRight = '10px'; sizeSmallLabel.style.fontWeight = 'bold'; var sizeLarge = document.createElement('input'); sizeLarge.setAttribute('type', 'radio'); sizeLarge.name = 'supporters-you-follow-size'; sizeLarge.value = LARGE_SIZE; sizeLarge.id = 'supporters-you-follow-size-large'; sizeLarge.checked = curSizeSetting == LARGE_SIZE; sizeLarge.addEventListener('change', onSizeChanged); var sizeLargeLabel = document.createElement('label'); sizeLargeLabel.setAttribute('for', 'supporters-you-follow-size-large'); sizeLargeLabel.textContent = 'large'; sizeLargeLabel.style.fontWeight = 'bold'; var sizeContainer = document.createElement('div'); sizeContainer.style.display = 'inline-block'; sizeContainer.appendChild(sizeSmall); sizeContainer.appendChild(sizeSmallLabel); sizeContainer.appendChild(sizeLarge); sizeContainer.appendChild(sizeLargeLabel); optionsMenu.appendChild(toggleAutoloadContainer); optionsMenu.appendChild(document.createTextNode("thumb size: ")); optionsMenu.appendChild(sizeContainer); optionsMenu.appendChild(document.createElement('hr')); var optionsLink = document.createElement('a'); optionsLink.textContent = 'options'; optionsLink.style.float = 'right'; collectedByContainer.insertBefore(optionsLink, existingMessage); optionsLink.addEventListener('click', function() { var wasHidden = optionsMenu.style.display == 'none'; optionsMenu.style.display = wasHidden ? 'block' : 'none'; optionsLink.style.fontWeight = wasHidden ? 'bold' : 'normal'; }); var message = existingMessage.cloneNode(true); message.textContent = 'supporters you follow'; collectedByContainer.insertBefore(message, existingMessage); collectedByContainer.insertBefore(optionsMenu, existingMessage); var existingDeets = collectedByContainer.querySelector('.deets'); var deets = document.createElement('div'); deets.style.marginBottom = '16px'; deets.style.marginTop = '10px'; deets.style.lineHeight = '0'; var statusElement = document.createElement('div'); statusElement.style.lineHeight = '100%'; statusElement.style.opacity = '0.5'; statusElement.textContent = 'loading'; statusElement.style.display = 'none'; deets.appendChild(statusElement); collectedByContainer.insertBefore(deets, existingMessage); var makeThumb = function(fan) { var a = document.createElement('a'); a.href = fan.url; a.title = fan.name; a.style.margin = '0 7px 7px 0px'; a.style.display = 'inline-block'; var img = document.createElement('img'); img.src = "https://f4.bcbits.com/img/" + fan.image_id.toString().padStart(10, '0') + "_42.jpg"; img.setAttribute('alt', fan.name + " thumbnail"); img.style.display = 'block'; var size = GM_getValue("size", DEFAULT_SIZE); img.style.width = size; img.style.height = size; a.appendChild(img); loadedimgs.push(img); return a; }; var handleSupporter = function(supporter) { if (this[supporter.fan_id]) { if (statusElement.parentNode) { deets.removeChild(statusElement); } deets.appendChild(makeThumb(supporter)); } }; var onEnd = function() { if (loadedimgs.length === 0) { statusElement.textContent = 'none found'; } else if (statusElement.parentNode) { deets.removeChild(statusElement); } }; var onError = function(err) { statusElement.textContent = err; }; var getNext = function(tralbum_type, tralbum_id, token, lookup) { var data; if (token !== undefined) { data = '{"tralbum_type":"'+tralbum_type+'","tralbum_id":'+tralbum_id+',"token":"'+token+'","count":80}'; } else { data = '{"tralbum_type":"'+tralbum_type+'","tralbum_id":'+tralbum_id+',"count":80}'; } var url = (new URL("/api/tralbumcollectors/2/thumbs", document.location)).href; post(url, data, function(status, res, url) { if (status != 200) { console.warn("failed to get more supporters", status, res, url); onError("unexpected http status: " + status); return; } var parsed = JSON.parse(res); if (parsed.error) { console.warn("error when getting more supporters", parsed.error_message, parsed); onError(parsed.error_message); return; } parsed.results.forEach(handleSupporter.bind(lookup)); if (parsed.more_available) { var lastToken = parsed.results[parsed.results.length - 1].token; getNext(tralbum_type, tralbum_id, lastToken, lookup); } else { onEnd(); } }); }; var onSummary = function(err, summary) { if (err) { onError(err); return; } var collectorsData = JSON.parse(document.querySelector('#collectors-data').getAttribute('data-blob')); var supporters = collectorsData.thumbs; var lookup = summary.follows.following; var pageData = JSON.parse(document.querySelector('#pagedata').getAttribute('data-blob')); var tralbum = pageData.fan_tralbum_data; // collectorsData.thumbs has only minimal data now, so we have to re-get the first page using no token getNext(tralbum.tralbum_type, tralbum.tralbum_id, undefined, lookup); }; var go = function() { statusElement.style.display = 'block'; var url = (new URL("/api/fan/2/collection_summary", document.location)).href; get(url, function(status, res, url) { if (status != 200) { onSummary("unexpected http status code: " + status); return; } var parsedRes = JSON.parse(res); onSummary(parsedRes.error_message, parsedRes.collection_summary); }); }; get = function(url, cb) { var opts = { method: 'GET', url: url, onload: function (res) { cb(res.status, res.responseText, res.finalUrl || url); } }; GM_xmlhttpRequest(opts); }; post = function(url, data, cb) { var opts = { method: 'POST', url: url, data: data, headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" }, onload: function (res) { cb(res.status, res.responseText, res.finalUrl || url); } }; GM_xmlhttpRequest(opts); }; if (GM_getValue("autoload", false)) { go(); } else { var loadLink = document.createElement('a'); loadLink.textContent = 'load...'; loadLink.style.lineHeight = '100%'; var handleAutoloadChecked; var load = function() { go(); deets.removeChild(loadLink); toggleAutoload.removeEventListener('change', handleAutoloadChecked); }; loadLink.addEventListener('click', function(e) { load(); e.preventDefault(); e.stopImmediatePropagation(); }, true); deets.appendChild(loadLink); handleAutoloadChecked = function() { if(this.checked && loadLink.parentNode) { load(); } }; toggleAutoload.addEventListener('change', handleAutoloadChecked); }