Bandcamp Tools

Adds QoL improvements to Bandcamp.

09.09.2024 itibariyledir. En son verisyonu görün.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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