osu-web

Library to modify static and dynamic components of osu web pages

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

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

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