osu beatmap filter

Filter beatmap by favorites (過濾 beatmap 依照收藏數) (osu! website only) (僅限 osu! 網站)

目前为 2023-11-05 提交的版本。查看 最新版本

// ==UserScript==
// @name         osu beatmap filter
// @namespace    https://greasyfork.org/zh-TW/users/891293
// @version      0.2
// @description  Filter beatmap by favorites (過濾 beatmap 依照收藏數) (osu! website only) (僅限 osu! 網站)
// @author       Archer_Wn
// @match        https://osu.ppy.sh/beatmapsets
// @match        https://osu.ppy.sh/beatmapsets?*
// @grant        none
// ==/UserScript==

// Options (選項)
const options = {
  // global options (全域選項)
  global: {
    // opacity of filtered beatmap [0~1] (過濾後的 beatmap 透明度 [0~1])
    opacity: 0.15,
  },
  // favorites filter (收藏數過濾)
  favorites: {
    // enable or disable [true/false] (啟用或停用 [true/false])
    enable: true,
    // favorites (收藏數)
    favorites: 100,
  },
};

(function () {
  "use strict";

  // wait for beatmapList loaded
  waitForElement(".beatmapsets__items", 0).then(() => {
    main();
  });

  // main function
  function main() {
    // get beatmap list
    const beatmapList = document.querySelector(".beatmapsets__items");

    // filter beatmap items that already loaded
    const beatmapItems = beatmapList.querySelectorAll(".beatmapsets__item");
    for (const beatmapItem of beatmapItems) {
      const beatmapInfo = beatmapParser(beatmapItem);
      filterBeatmap(beatmapItem, beatmapInfo);
    }

    // filter new beatmap items
    new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type !== "childList") return;
        if (mutation.addedNodes.length < 1) return;

        for (const beatmapItem of mutation.addedNodes[0].querySelectorAll(
          ".beatmapsets__item"
        )) {
          const beatmapInfo = beatmapParser(beatmapItem);
          filterBeatmap(beatmapItem, beatmapInfo);
        }
      });
    }).observe(beatmapList, {
      attributes: true,
      childList: true,
      subtree: true,
    });
  }
})();

/**
 * Wait for an element before resolving a promise
 * @param {String} querySelector - Selector of element to wait for
 * @param {Integer} timeout - Milliseconds to wait before timing out, or 0 for no timeout
 * @returns {Promise}
 *
 * @ref https://stackoverflow.com/questions/34863788/how-to-check-if-an-element-has-been-loaded-on-a-page-before-running-a-script
 */
function waitForElement(querySelector, timeout) {
  return new Promise((resolve, reject) => {
    var timer = false;
    if (document.querySelectorAll(querySelector).length) return resolve();
    const observer = new MutationObserver(() => {
      if (document.querySelectorAll(querySelector).length) {
        observer.disconnect();
        if (timer !== false) clearTimeout(timer);
        return resolve();
      }
    });
    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });
    if (timeout) {
      timer = setTimeout(() => {
        observer.disconnect();
        reject();
      }, timeout);
    }
  });
}

// beatmap parser
function beatmapParser(beatmapItem) {
  const beatmapInfo = {};

  // Extract necessary information
  beatmapInfo.favoriteCount = parseInt(
    beatmapItem.querySelectorAll(
      ".beatmapset-panel__stats-item--favourite-count span"
    )[1].textContent
  );

  return beatmapInfo;
}

// filter beatmap
function filterBeatmap(beatmapItem, beatmapInfo) {
  let setOpacity = false;

  // filter by favorites
  if (options.favorites.enable) {
    if (beatmapInfo.favoriteCount < options.favorites.favorites) {
      setOpacity = true;
    }
  }

  // set opacity
  if (setOpacity) {
    beatmapItem.style.opacity = options.global.opacity;
  }
}