Bandcamp Tools

Adds QoL improvements to Bandcamp.

Version au 09/09/2024. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Bandcamp Tools
// @namespace    http://violentmonkey.net/
// @version      1.0
// @description  Adds QoL improvements to Bandcamp.
// @author       InariOkami
// @match        *://*.bandcamp.com/*
// @grant        none
// @icon          https://cdn-icons-png.flaticon.com/512/21/21496.png
// ==/UserScript==

(function() {
    'use strict';

    var app = {
        id: "bcp-sp"
    };

    app.debug = false;

    var cls = {
        price: app.id + '-price',
        handled: app.id + '-handled'
    };

    var selectors = {
        product: 'li[data-trackid]:not(.' + cls.handled + ')'
    };

    function findOne(selector, context, dontYell) {
        context = context || document;
        var item = context.querySelector(selector);
        if (item && app.debug) {
            console.log(app.id, ': found element matching "' + selector + '"');
        } else if (!item && !dontYell) {
            console.warn(app.id, ': found no element for selector "' + selector + '"');
        }
        return item;
    }

    function findFirst(selector, context) {
        return findAll(selector, context)[0];
    }

    function findAll(selector, context, dontYell) {
        if (!selector || !selector.length || selector.length === 1) {
            console.error(app.id, ': incorrect selector : ', selector);
        }
        context = context || document;
        var items = Array.prototype.slice.call(context.querySelectorAll(selector));
        if (items.length && app.debug) {
            console.log(app.id, ': found', items.length, 'elements matching "' + selector + '"');
        } else if (!items.length && !dontYell) {
            console.warn(app.id, ': found no elements for selector "' + selector + '"');
        }
        return items;
    }

    function debounce(func, wait, immediate) {
        var timeout;
        return function () {
            var context = this;
            var args = arguments;
            var later = function later() {
                timeout = null;
                if (!immediate) {
                    func.apply(context, args);
                }
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) {
                func.apply(context, args);
            }
        };
    }

    function cleanPrevious() {
        findAll('[class^="' + cls.price + '"]', document, true).forEach(function (node) {
            return node.remove();
        });
    }

    function displayPrice(product, price) {
        var tag = document.createElement('div');
        tag.innerHTML = price.value + ' <small>' + price.currency + '</small>';
        tag.style = 'position: absolute; top: 0; right: 0; background-color: green; color: white;';
        tag.classList.add(cls.price, 'col-edit-box');
        product.appendChild(tag);
        if (price.value > 2) {
            product.style.filter = 'grayscale(1) opacity(.5)';
        }
        product.classList.add(cls.handled);
    }

    function displayPrices() {
        findAll(selectors.product, document, true).forEach(function (product) {
            var trackid = parseInt(product.getAttribute('data-trackid'));
            if (trackid) {
                if (app.debug) {
                    console.log(app.id, ': adding price for', trackid);
                }
                if (!app.tracks.hasOwnProperty(trackid)) {
                    throw new Error('failed at gettting track price');
                }
                var price = app.tracks[trackid];
                displayPrice(product, price);
            }
        });
    }

    function setTracksFromList(list) {
        if (!app.tracks) {
            app.tracks = {};
        }
        var added = 0;
        list.map(function (track) {
            var trackid = track.track_id;
            if (!app.tracks.hasOwnProperty(trackid)) {
                app.tracks[trackid] = {
                    value: Math.round(track.price),
                    currency: track.currency
                };
                added++;
            }
        });
        console.log(app.id, ': added', added, 'tracks to local db :D');
    }

    function getDataFromApi() {
        fetch('https://bandcamp.com/api/fancollection/1/wishlist_items', {
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                fan_id: app.userid,
                older_than_token: app.token
            })
        }).then(function (json) {
            return json.json();
        }).then(function (data) {
            app.token = data.last_token;
            setTracksFromList(data.track_list);
            if (data.more_available) {
                getDataFromApi();
            }
        });
    }

    function getDataFromPage() {
        var dataEl = findOne('#pagedata');
        var data = JSON.parse(dataEl.getAttribute('data-blob'));
        setTracksFromList(data.track_list);
        app.token = data.wishlist_data.last_token;
        app.userid = data.fan_data.fan_id;
    }

    function process() {
        displayPrices();
    }

    function init() {
        console.log(app.id, ': init !');
        cleanPrevious();
        getDataFromPage();
        getDataFromApi();
        process();
    }

    init();

    var processDebounced = debounce(process, 500);
    document.addEventListener('scroll', processDebounced);

})();

(function() {
    'use strict';
    var table = $('#track_table tbody tr')
    , tdata = table ? table.each(function(){}) : []
    , adata = window.TralbumData || false;
    if (table.length > 1 && adata) {
        for(var i = 0; adata.trackinfo[i]; i++) {
            var p = $($('tr td .dl_link')[i]), link = document.createElement("a"), track = adata.trackinfo[i];
            link.href = track.file["mp3-128"];
            link.title = link.download = track.track_num + " - " + track.title + ".mp3";
            link.alt = 'If left clicking opens song, right click to download.';
            link.innerHTML = 'Download!';
            p.html(link);
        }
    } else {
        $('.inline_player').append('<br /><strong><a href="#" class="font-size: 18px;" onclick="location.href=TralbumData.trackinfo[0].file["mp3-128"]">Download Now! (128kb MP3)</a></strong><br />');
    }
    alert("jestem");

})();

(function() {
    'use strict';
    const DBG = false;

    let log = function(s) {
            return (DBG && console.log(s));
        },
        qS = function(el, scope) {
            scope = (typeof scope === 'object') ? scope : document;
            return scope.querySelector(el) || false;
        },
        qSall = function(els, scope) {
            scope = (typeof scope === 'object') ? scope : document;
            return scope.querySelectorAll(els) || false;
        },
        hidden, visibilityChange, state,
        tabFocused = function(evt) {
            log("tab has focus!");
            if (document !== evt.target) {
                log("warning, document not equal to document. it is ");
                log(evt.target); // should be document
            }
            if (evt.target.body !== document.activeElement) {
                log("warning, document.activeElement not equal to document.body. it is ");
                log(evt.target.body); // should be document.body
            }
        },
        elmTarget = qS("#trackInfoInner > div.inline_player > table > tbody > tr:nth-child(1) > td.play_cell > a > div");
    if (!elmTarget) {
        log('main play button not found, exiting');
        return;
    }

    if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
        hidden = "hidden";
        visibilityChange = "visibilitychange";
        state = "visibilityState";
    } else if (typeof document.mozHidden !== "undefined") {
        hidden = "mozHidden";
        visibilityChange = "mozvisibilitychange";
        state = "mozVisibilityState";
    } else if (typeof document.msHidden !== "undefined") {
        hidden = "msHidden";
        visibilityChange = "msvisibilitychange";
        state = "msVisibilityState";
    } else if (typeof document.webkitHidden !== "undefined") {
        hidden = "webkitHidden";
        visibilityChange = "webkitvisibilitychange";
        state = "webkitVisibilityState";
    }

    if ('undefined' === typeof hidden) {
        log('document.hidden not found, exiting');
        return;
    }

    document.addEventListener(visibilityChange, function(e) {
        return (false === document[hidden]) && tabFocused(e);
    });
    window.addEventListener('keydown', function(e) {
        log("in keydown");
        if(e.key === " " && e.target === document.body) {
            log("keydown ok");
            e.preventDefault();
        }
    });
    qS('body').addEventListener("keyup", function(e) {
        log("in keyup");
        if (e.key === " " && e.target === document.body) {
            e.preventDefault();
            elmTarget.focus();
            elmTarget.click();
            elmTarget.blur();
            log("keyup ok");
        }
    });
})();

var gen = document.querySelector("meta[name=generator]");
if(!gen || gen.content != "Bandcamp") {
    return;
}

var style = document.createElement("style");
style.textContent = ".volumeControl{align-items:center;display:flex;height:52px;margin-top:1em}.volumeControl .thumb{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.volumeControl>.speaker{background:#fff;border:1px solid #d9d9d9;border-radius:2px;color:#000;cursor:pointer;font-size:32px;height:54px;line-height:54px;position:relative;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:54px}.volumeControl>.speaker>svg{margin:11px}";
document.head.appendChild(style);

var dragWidth = 226;
var dragging = false;
var dragPos = 0;
var percentage = parseFloat(localStorage.getItem("volume")) || 0.5;
var speaker, volumeInner, audio, volume;

function onLoad() {
    audio = document.getElementsByTagName("audio")[0];
    updateVolume();

    var container = document.createElement("div");
    container.classList.add("volumeControl");

    speaker = document.createElement("i");
    speaker.classList.add("speaker");
    speaker.addEventListener("click", function () {
        audio.muted = !audio.muted;
        updateHtml();
    });
    container.appendChild(speaker);

    var volume = document.createElement("div");
    volume.classList.add("progbar");
    container.appendChild(volume);

    var fill = document.createElement("div");
    fill.classList.add("progbar_empty");
    fill.style.width = "248px";
    volume.appendChild(fill);

    volumeInner = document.createElement("div");
    volumeInner.classList.add("thumb");
    
    volumeInner.addEventListener("mousedown", function (e) {
        dragging = true;
        dragPos = e.offsetX;
    });
    fill.appendChild(volumeInner);
    
    document.querySelector(".inline_player").appendChild(container);

    updateHtml();

    document.addEventListener("mouseup", function () {
        if (dragging) {
            localStorage.setItem("volume", percentage);
            dragging = false;
        }
    });
    document.addEventListener("mousemove", function (e) {
        if (dragging) {
            var pos = volume.getBoundingClientRect();

            audio.muted = false;
            percentage = clamp(((e.pageX - pos.left) - dragPos) / dragWidth, 0, 1);
            updateVolume();
            updateHtml();
        }
    });
}

if (document.readyState == 'complete') {
    onLoad();
} else {
    window.addEventListener("load", onLoad);
}

function updateVolume() {
    audio.volume = (Math.exp(percentage) - 1) / (Math.E - 1);
}

function updateHtml() {
    // svgs from https://www.material.io/resources/icons
    if (audio.muted) {
        speaker.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 24 24" width="32"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/><path d="M0 0h24v24H0z" fill="none"/></svg>';
        volumeInner.style.left = "0%";
    } else {
        if (percentage <= 0) {
            speaker.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 24 24" width="32"><path d="M7 9v6h4l5 5V4l-5 5H7z"/><path d="M0 0h24v24H0z" fill="none"/></svg>';
        } else if (percentage < 0.5) {
            speaker.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 24 24" width="32"><path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>';
        } else {
            speaker.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 0 24 24" width="32"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/><path d="M0 0h24v24H0z" fill="none"/></svg>';
        }
        volumeInner.style.left = dragWidth * percentage + 'px';
    }
}

function clamp(num, min, max) {
    return num <= min ? min : num >= max ? max : num;
}