osu-web

Library to modify static and dynamic components of osu web pages

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

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