osu beatmap filter

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

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

  1. // ==UserScript==
  2. // @name osu beatmap filter
  3. // @namespace https://greasyfork.org/zh-TW/users/891293
  4. // @version 0.2
  5. // @description Filter beatmap by favorites (過濾 beatmap 依照收藏數) (osu! website only) (僅限 osu! 網站)
  6. // @author Archer_Wn
  7. // @match https://osu.ppy.sh/beatmapsets
  8. // @match https://osu.ppy.sh/beatmapsets?*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. // Options (選項)
  13. const options = {
  14. // global options (全域選項)
  15. global: {
  16. // opacity of filtered beatmap [0~1] (過濾後的 beatmap 透明度 [0~1])
  17. opacity: 0.15,
  18. },
  19. // favorites filter (收藏數過濾)
  20. favorites: {
  21. // enable or disable [true/false] (啟用或停用 [true/false])
  22. enable: true,
  23. // favorites (收藏數)
  24. favorites: 100,
  25. },
  26. };
  27.  
  28. (function () {
  29. "use strict";
  30.  
  31. // wait for beatmapList loaded
  32. waitForElement(".beatmapsets__items", 0).then(() => {
  33. main();
  34. });
  35.  
  36. // main function
  37. function main() {
  38. // get beatmap list
  39. const beatmapList = document.querySelector(".beatmapsets__items");
  40.  
  41. // filter beatmap items that already loaded
  42. const beatmapItems = beatmapList.querySelectorAll(".beatmapsets__item");
  43. for (const beatmapItem of beatmapItems) {
  44. const beatmapInfo = beatmapParser(beatmapItem);
  45. filterBeatmap(beatmapItem, beatmapInfo);
  46. }
  47.  
  48. // filter new beatmap items
  49. new MutationObserver((mutations) => {
  50. mutations.forEach((mutation) => {
  51. if (mutation.type !== "childList") return;
  52. if (mutation.addedNodes.length < 1) return;
  53.  
  54. for (const beatmapItem of mutation.addedNodes[0].querySelectorAll(
  55. ".beatmapsets__item"
  56. )) {
  57. const beatmapInfo = beatmapParser(beatmapItem);
  58. filterBeatmap(beatmapItem, beatmapInfo);
  59. }
  60. });
  61. }).observe(beatmapList, {
  62. attributes: true,
  63. childList: true,
  64. subtree: true,
  65. });
  66. }
  67. })();
  68.  
  69. /**
  70. * Wait for an element before resolving a promise
  71. * @param {String} querySelector - Selector of element to wait for
  72. * @param {Integer} timeout - Milliseconds to wait before timing out, or 0 for no timeout
  73. * @returns {Promise}
  74. *
  75. * @ref https://stackoverflow.com/questions/34863788/how-to-check-if-an-element-has-been-loaded-on-a-page-before-running-a-script
  76. */
  77. function waitForElement(querySelector, timeout) {
  78. return new Promise((resolve, reject) => {
  79. var timer = false;
  80. if (document.querySelectorAll(querySelector).length) return resolve();
  81. const observer = new MutationObserver(() => {
  82. if (document.querySelectorAll(querySelector).length) {
  83. observer.disconnect();
  84. if (timer !== false) clearTimeout(timer);
  85. return resolve();
  86. }
  87. });
  88. observer.observe(document.body, {
  89. childList: true,
  90. subtree: true,
  91. });
  92. if (timeout) {
  93. timer = setTimeout(() => {
  94. observer.disconnect();
  95. reject();
  96. }, timeout);
  97. }
  98. });
  99. }
  100.  
  101. // beatmap parser
  102. function beatmapParser(beatmapItem) {
  103. const beatmapInfo = {};
  104.  
  105. // Extract necessary information
  106. beatmapInfo.favoriteCount = parseInt(
  107. beatmapItem.querySelectorAll(
  108. ".beatmapset-panel__stats-item--favourite-count span"
  109. )[1].textContent
  110. );
  111.  
  112. return beatmapInfo;
  113. }
  114.  
  115. // filter beatmap
  116. function filterBeatmap(beatmapItem, beatmapInfo) {
  117. let setOpacity = false;
  118.  
  119. // filter by favorites
  120. if (options.favorites.enable) {
  121. if (beatmapInfo.favoriteCount < options.favorites.favorites) {
  122. setOpacity = true;
  123. }
  124. }
  125.  
  126. // set opacity
  127. if (setOpacity) {
  128. beatmapItem.style.opacity = options.global.opacity;
  129. }
  130. }