Bandcamp Random Album

Add link to random album from collection or wishlist

// ==UserScript==
// @name Bandcamp Random Album
// @version 1.0.0
// @description Add link to random album from collection or wishlist
// @namespace bandcamp-random-album
// @license 0BSD
// @match http*://bandcamp.com/*
// @include http*://bandcamp.com/*
// @grant GM_xmlhttpRequest
// ==/UserScript==

if (!document.querySelector('.collection-container')) {
    return;
}

var collectionSummary;
var pageData;
var isOwner = document.querySelector('#fan-banner').classList.contains('owner');
var LOAD_URL_FORMAT = "https://bandcamp.com/api/fancollection/1/{}_items";
var pageTypes = ['collection', 'wishlist'];
var items = pageTypes.reduce((acc, type) => {
    acc[type] = [];
    return acc;
}, []);
var started = {};

var get = function(url, cb) {
    var opts = {
        method: 'GET',
        url: url,
        onload: function (res) {
            cb(res.status, res.responseText, res.finalUrl || url);
        }
    };
    GM_xmlhttpRequest(opts);
};

var 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);
};

var getNext = function(fan_id, older_than_token, type) {
    var url = LOAD_URL_FORMAT.replace('{}', type);
    post(url, '{"fan_id":'+fan_id+',"older_than_token":"'+older_than_token+'","count":100}', function(status, res, url) {
        if (status != 200) {
            console.error("failed to get next " + type, status, res, url);
            return;
        }
        var parsed = JSON.parse(res);
        if (parsed.error) {
            console.error("error when getting next " + type, parsed.error_message, parsed);
            return;
        }
        items[type].push(...parsed.items);
        if (parsed.more_available) {
            // we should be able to use parsed.last_token here, but there is currently a bug
            // in the collection_items endpoint in that it always gives the 20th item's token
            // in last_token even if count is different than 20, so we need to get the actual
            // last item's token
            var last_token = parsed.items[parsed.items.length - 1].token;
            getNext(fan_id, last_token, type);
        } else {
            var button = document.querySelector(`#${type}-search div button`);
            var spinner = document.querySelector(`#${type}-search div span`);
            spinner.style.display = 'none';
            button.style.display = 'block';
        }
    });
};

window.onload = function() {
    console.log('sup');
    pageTypes.forEach((type) => {
        if (!started[type]) {
            if (!pageData) {
                pageData = JSON.parse(document.querySelector('#pagedata').getAttribute('data-blob'));
            }
            var start = function() {
                var now = Math.floor(Date.now()/1000);
                var nowToken = pageData[type+'_data'].last_token.replace(/^\d+/, now);
                getNext(pageData.fan_data.fan_id, nowToken, type);
            };
            if (!collectionSummary) {
                get('https://bandcamp.com/api/fan/2/collection_summary', function(status, res, url) {
                    if (status != 200) {
                        console.warn("unexpected response from " + url, status, res);
                    }
                    var parsedRes = JSON.parse(res);
                    if (parsedRes.error) {
                        console.log(parsedRes.error_message);
                    } else {
                        collectionSummary = parsedRes.collection_summary;
                    }
                    start();
                });
            } else {
                start();
            }
            started[type] = true;
        }
    })
};

var onclick = function(type, e) {
    const randomItem = items[type].sort(function() {return 0.5 - Math.random()})[0];
    window.open(randomItem.item_url, '_blank');
};

pageTypes.forEach(function(type) {
    var searchContainer = document.querySelector(`#${type}-search`);
    searchContainer.style.display = 'flex';
    searchContainer.style.alignItems = 'baseline';
    var buttonContainer = document.createElement('div');
    buttonContainer.className = 'search searching';
    buttonContainer.style.display = 'inline-block';
    var spinner = document.createElement('span');
    spinner.innerHTML = `<svg class="search-spinner" style="position: inherit" width="14px" height="14px" viewBox="0 0 14 14"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-spinner"></use></svg>`;
    spinner.style.marginLeft = '12px';
    var button = document.createElement('button');
    button.innerHTML = 'Open Random Album';
    button.style.marginLeft = '12px';
    button.style.display = 'none';
    button.style.border = 'none';
    button.style.background = 'transparent';
    button.addEventListener('click', onclick.bind(null, type));
    searchContainer.appendChild(buttonContainer);
    buttonContainer.appendChild(spinner);
    buttonContainer.appendChild(button);
    console.log('hello ' + type);
});