Bandcamp Collection Filters

List items in a collection or wishlist that match certain filters (free, in common, etc)

当前为 2019-10-22 提交的版本,查看 最新版本

// ==UserScript==
// @name Bandcamp Collection Filters
// @version 1.0.0
// @description List items in a collection or wishlist that match certain filters (free, in common, etc)
// @namespace 289690-squeek502
// @license 0BSD
// @match http*://bandcamp.com/*
// @include http*://bandcamp.com/*
// @grant GM_xmlhttpRequest
// ==/UserScript==

(function() {
  var document = window.top.document;
  if (!document.querySelector('#collection-grid') || !document.querySelector('#wishlist-grid'))
    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 buttonTypes = ['free', 'purchased', 'wishlisted'];

  var inCommonSeparator = document.createElement('span');
  inCommonSeparator.style.color = '#828282';
  inCommonSeparator.style.marginRight = '16px';
  inCommonSeparator.textContent = 'in common:';
  var separatorsAfter = {'free': inCommonSeparator};

  var buttons = {};
  var results = {};
  var started = {};

  pageTypes.forEach(function(type) {
    buttons[type] = {};
    results[type] = {};

    var buttonContainer = document.createElement('div');
    buttonContainer.style.marginTop = '0px';
    buttonContainer.classList.add('wishlist-controls');
    buttonContainer.classList.add('owner-controls');

    var grid = document.querySelector('#'+type+'-grid');
    var itemsContainer = grid.querySelector('#'+type+'-items-container') || grid.querySelector(':scope > .inner');
    var items = itemsContainer.querySelector('#'+type+'-items');

    var resultContainer = document.createElement('div');

    buttonTypes.forEach(function(button) {
      var buttonElement = document.createElement('a');
      buttonElement.style.marginRight = '16px';
      buttonElement.innerHTML = button;
      buttonContainer.appendChild(buttonElement);

      var list = document.createElement('ul');
      list.style.display = 'none';
      resultContainer.appendChild(list);

      buttons[type][button] = buttonElement;
      results[type][button] = list;

      if (separatorsAfter[button]) {
        buttonContainer.appendChild(separatorsAfter[button].cloneNode(true));
      }
    });

    itemsContainer.insertBefore(buttonContainer, items);
    itemsContainer.insertBefore(resultContainer, items);
  });

  var onSummaryError = function(errmsg) {
    pageTypes.forEach(function(type) {
      buttonTypes.forEach(function(button) {
        // free doesn't depend on summary
        if (button == 'free') return;
        results[type][button].innerHTML = errmsg;
      });
    });
  };

  var handleItems = function(items, type) {
    items.forEach(function(item) {
      var isFree = item.price === 0;
      var lookupKey = item.tralbum_type + "" + item.tralbum_id;
      var tralbum = collectionSummary && collectionSummary.tralbum_lookup[lookupKey];
      var isPurchased = tralbum && tralbum.purchased;
      var isWishlisted = tralbum && !tralbum.purchased;
      if (!(isFree || isPurchased || isWishlisted))
        return;

      var li = document.createElement('li');
      var a = document.createElement('a');
      a.href = item.item_url;
      a.textContent = item.band_name + ' - ' + item.item_title;
      li.appendChild(a);

      if (isFree) {
        results[type].free.appendChild(li.cloneNode(true));
      }
      if (isPurchased) {
        results[type].purchased.appendChild(li.cloneNode(true));
      }
      if (isWishlisted) {
        results[type].wishlisted.appendChild(li.cloneNode(true));
      }
    });
    buttonTypes.forEach(function(button) {
       buttons[type][button].textContent = button + " (" + results[type][button].childElementCount + ")";
    });
  };

  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":40}', 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;
      }
      handleItems(parsed.items, type);
      if (parsed.more_available) {
        getNext(fan_id, parsed.last_token, type);
      }
    });
  };

  var setState = function(type, button, state) {
    if (state) {
      // hide all of the same type
      buttonTypes.forEach(function(other) {
        if (other == button) return;
        setState(type, other, false);
      });
      results[type][button].style.display = 'block';
      buttons[type][button].style.fontWeight = 'bold';
      buttons[type][button].style.textDecoration = 'underline';
    } else {
      results[type][button].style.display = 'none';
      buttons[type][button].style.fontWeight = 'normal';
      buttons[type][button].style.textDecoration = 'none';
    }
  };

  var getState = function(type, button) {
    return results[type][button].style.display != 'none';
  };

  var onclick = function(type, button, e) {
    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);
            onSummaryError("unexpected http status code: " + status);
          }
          var parsedRes = JSON.parse(res);
          if (parsedRes.error) {
            onSummaryError(parsedRes.error_message);
          } else {
            collectionSummary = parsedRes.collection_summary;
          }
          start();
        });
      } else {
        start();
      }
      setState(type, button, true);
      started[type] = true;
    } else {
      setState(type, button, !getState(type, button));
    }
  };
  pageTypes.forEach(function(type) {
    buttonTypes.forEach(function(button) {
      buttons[type][button].addEventListener("click", onclick.bind(null, type, button));
    });
  });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址