osu-web

Library to modify static and dynamic components of osu web pages

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

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