osu-web

Library to modify static and dynamic components of osu web pages

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/473977/1249295/osu-web.js

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