osu-web

Library to modify static and dynamic components of osu web pages

2023-08-26 기준 버전입니다. 최신 버전을 확인하세요.

이 스크립트는 직접 설치하는 용도가 아닙니다. 다른 스크립트에서 메타 지시문 // @require https://update.greasyfork.org/scripts/473977/1241412/osu-web.js을(를) 사용하여 포함하는 라이브러리입니다.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         osu-web
// @namespace    osu
// @version      1.0.0
// @description  Library to modify static and dynamic components of osu web pages
// @author       Magnus Cosmos
// ==/UserScript==

function loaded(selector, parent, callback, options = { childList: true }) {
    const el = parent.querySelector(selector);
    if (el) {
        callback(el);
    } else {
        new MutationObserver(function (_mutations, observer) {
            const el = parent.querySelector(selector);
            if (el) {
                callback(el);
                observer = observer ? observer : this;
                observer.disconnect();
            }
        }).observe(parent, options);
    }
}

class Module {
    constructor() {
        if (this.constructor == Module) {
            throw new Error("Module class cannot be instantiated.");
        }
        this.loaded = false;
        this.static = [];
        this.dynamic = [];
        this.before = {};
        this.after = {};
        this.keys = [];
    }

    init() {
        this.webpack = new Webpack();
        this.#getTurboLinks();
        this.#getReactModules();
    }

    #getTurboLinks() {
        for (const id in this.webpack.modules) {
            const exports = this.webpack.modules[id];
            if ("controller" in exports) {
                this.turbolinks = exports;
                return;
            }
        }
    }

    #getReactModules() {
        const reactModules = new Set();
        for (const id in this.webpack.modules) {
            const exports = this.webpack.modules[id];
            if ("__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED" in exports) {
                reactModules.add(exports);
            }
        }
        [this.React, this.ReactDOM] = reactModules;
    }

    modifyFn(obj, fn, key, _before, _after) {
        if (!(key in this.keys)) {
            this.keys.push(key);
            this.before[key] = [];
            this.after[key] = [];
            this.#modify(obj, fn, key);
        }
        if (_before) {
            this.before[key].push(_before);
        }
        if (_after) {
            this.after[key].push(_after);
        }
    }

    #modify(obj, fn, key) {
        const self = this;
        const oldFn = obj[fn];
        obj[fn] = function () {
            self.#beforeFn(key, arguments);
            const r = oldFn.apply(this, arguments);
            self.#afterFn(key, arguments, r);
            return r;
        };
    }

    #beforeFn(key, args) {
        const arr = this.before[key] || [];
        for (const fn of arr) {
            fn(args);
        }
    }

    #afterFn(key, args, r) {
        const arr = this.after[key] || [];
        for (const fn of arr) {
            fn(args, r);
        }
    }
}

class OsuWeb extends Module {
    constructor(staticFn, dynamicFn) {
        super();
        this.static = staticFn || (() => {});
        this.dynamic = dynamicFn || (() => {});
        loaded("html", document, (html) => {
            loaded("body", html, () => {
                this.init();
                this.start();
            });
        });
    }

    start() {
        this.static(document.body);
        const controller = this.turbolinks.controller;
        this.modifyFn(controller, "render", "turbolinks.render", null, (args, r) => {
            this.static(r.newBody);
        });
        this.dynamic();
    }
}