Bandcamp Tools

Adds QoL improvements to Bandcamp.

当前为 2024-09-09 提交的版本,查看 最新版本

// ==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;
}