osu-web

Library to modify static and dynamic components of osu web pages

当前为 2023-08-29 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/473977/1242747/osu-web.js

  1. // ==UserScript==
  2. // @name osu-web
  3. // @namespace osu
  4. // @version 1.0.7
  5. // @description Library to modify static and dynamic components of osu web pages
  6. // @author Magnus Cosmos
  7. // ==/UserScript==
  8. // Utils
  9. function isNonEmptyObj(obj) {
  10. if (obj === null || (typeof obj !== "function" && typeof obj !== "object")) {
  11. return false;
  12. }
  13. for (const _key in obj) {
  14. return true;
  15. }
  16. return false;
  17. }
  18. // Classes
  19. class webpack {
  20. constructor() {
  21. if (this.constructor == webpack) {
  22. throw new Error("webpack class cannot be instantiated.");
  23. }
  24. this.loaded = false;
  25. this.modules = {};
  26. }
  27. inject(entryPoint, data) {
  28. try {
  29. if (unsafeWindow) {
  30. unsafeWindow[entryPoint].push(data);
  31. } else {
  32. window[entryPoint].push(data);
  33. }
  34. } catch (err) {
  35. throw new Error(`Injection failed: ${err.message}`);
  36. }
  37. }
  38. }
  39. // Based on `Webpack-module-crack` and `moduleRaid`
  40. class Webpack extends webpack {
  41. constructor(options) {
  42. super();
  43. if (this.loaded) {
  44. return;
  45. }
  46. let { moduleId, chunkId, entryPoint } = options || {};
  47. moduleId = moduleId || Math.random().toString(36).substring(2, 6);
  48. chunkId = chunkId || Math.floor(101 + Math.random() * 899);
  49. entryPoint = entryPoint || "webpackJsonp";
  50. const data = [
  51. [chunkId],
  52. {
  53. [moduleId]: (_module, _exports, require) => {
  54. const installedModules = require.c;
  55. for (const id in installedModules) {
  56. const exports = installedModules[id].exports;
  57. if (isNonEmptyObj(exports)) {
  58. this.modules[id] = exports;
  59. }
  60. }
  61. },
  62. },
  63. [[moduleId]],
  64. ];
  65. this.inject(entryPoint, data);
  66. this.loaded = true;
  67. }
  68. }
  69. function loaded(selector, parent, callback, options = { childList: true }) {
  70. const el = parent.querySelector(selector);
  71. if (el) {
  72. callback(el);
  73. } else {
  74. new MutationObserver(function (_mutations, observer) {
  75. const el = parent.querySelector(selector);
  76. if (el) {
  77. callback(el);
  78. observer = observer ? observer : this;
  79. observer.disconnect();
  80. }
  81. }).observe(parent, options);
  82. }
  83. }
  84. class Module {
  85. constructor() {
  86. if (this.constructor == Module) {
  87. throw new Error("Module class cannot be instantiated.");
  88. }
  89. this.loaded = false;
  90. this.static = [];
  91. this.dynamic = [];
  92. this.before = {};
  93. this.after = {};
  94. this.keys = [];
  95. this.style = null;
  96. }
  97. init() {
  98. this.webpack = new Webpack();
  99. this.#getTurboLinks();
  100. this.#getReactModules();
  101. this.#appendStyle();
  102. }
  103. #appendStyle() {
  104. const style = document.querySelector("#osu-web");
  105. if (!(style || this.style)) {
  106. this.style = document.createElement("style");
  107. this.style.id = "osu-web";
  108. document.head.append(this.style);
  109. }
  110. }
  111. #getTurboLinks() {
  112. for (const id in this.webpack.modules) {
  113. const exports = this.webpack.modules[id];
  114. if ("controller" in exports) {
  115. this.turbolinks = exports;
  116. return;
  117. }
  118. }
  119. }
  120. #getReactModules() {
  121. const reactModules = new Set();
  122. for (const id in this.webpack.modules) {
  123. const exports = this.webpack.modules[id];
  124. if ("__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED" in exports) {
  125. reactModules.add(exports);
  126. }
  127. }
  128. [this.React, this.ReactDOM] = reactModules;
  129. }
  130. modifyFn(obj, fn, key, _before, _after) {
  131. if (!(key in this.keys)) {
  132. this.keys.push(key);
  133. this.before[key] = [];
  134. this.after[key] = [];
  135. this.#modify(obj, fn, key);
  136. }
  137. if (_before) {
  138. this.before[key].push(_before);
  139. }
  140. if (_after) {
  141. this.after[key].push(_after);
  142. }
  143. }
  144. #modify(obj, fn, key) {
  145. const self = this;
  146. const oldFn = obj[fn];
  147. obj[fn] = function () {
  148. self.#beforeFn(key, arguments);
  149. const r = oldFn.apply(this, arguments);
  150. self.#afterFn(key, arguments, r);
  151. return r;
  152. };
  153. }
  154. #beforeFn(key, args) {
  155. const arr = this.before[key] || [];
  156. for (const fn of arr) {
  157. fn(args);
  158. }
  159. }
  160. #afterFn(key, args, r) {
  161. const arr = this.after[key] || [];
  162. for (const fn of arr) {
  163. fn(args, r);
  164. }
  165. }
  166. }
  167. class OsuWeb extends Module {
  168. constructor(staticFn, dynamicFn) {
  169. super();
  170. this.static = staticFn || (() => {});
  171. this.dynamic = dynamicFn || (() => {});
  172. loaded("html", document, (html) => {
  173. loaded("body", html, () => {
  174. this.init();
  175. this.start();
  176. });
  177. });
  178. }
  179. start() {
  180. this.static(document.body);
  181. const controller = this.turbolinks.controller;
  182. this.modifyFn(controller, "render", "turbolinks.render", null, (args, r) => {
  183. this.static(r.newBody);
  184. });
  185. this.dynamic();
  186. }
  187. addStyle(css) {
  188. this.style.innerHTML += `\n${css}`;
  189. }
  190. }