Agma.io Tools - Fast Eject, Auto Re-spawn, Re-spawn on R, Quick buy

Fast eject, Auto Re-spawn, Re-spawn on R, Quick buy, Freeze on F, and more

// ==UserScript==
// @name         Agma.io Tools - Fast Eject, Auto Re-spawn, Re-spawn on R, Quick buy
// @namespace    http://tampermonkey.net/
// @version      2.52
// @description  Fast eject, Auto Re-spawn, Re-spawn on R, Quick buy, Freeze on F, and more
// @author       reagent
// @match        agma.io
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    let features = [{
        name: "Auto re-spawn",
        enabled: true,
        bootstrap: class {
            constructor() {
                this.playBtn = document.querySelector("#playBtn");
                this.advertContinue = document.querySelector("#advertContinue");
                this.playTimeout = null;
                this.lastClosed = 0;
                this.lastDisplay = "none";
                this.advertWatch = new MutationObserver(this.advertMutation.bind(this));
                this.overlayWatch = new MutationObserver(this.overlayMutation.bind(this));
                this.resumeGame = this.resumeGame.bind(this);
                this.clearAdvert = null;
                this.clearPlay = null;
            }
            closedByDeath() {
                return Date.now() - this.lastClosed < 300;
            }
            advertMutation(mutations) {
                for (let mutation of mutations) {
                    if (mutation.target.style.display !== this.lastDisplay) {
                        this.lastDisplay = mutation.target.style.display;

                        if (this.lastDisplay !== "none") {
                            if (this.playTimeout) window.clearTimeout(this.playTimeout);
                            this.playTimeout = window.setTimeout(this.resumeGame, 5);
                        }
                    }
                }
            }
            overlayMutation(mutations) {
                if (this.overlays.style.display === "none") {
                    this.lastClosed = Date.now();
                }
            }
            resumeGame() {
                if (this.closedByDeath() || !document.hasFocus()) return; // info window was open and closed by dying in the background
                if (!this.clearAdvert) return console.error("Advert continue function still not found");

                this.playTimeout = null;
                this.clearAdvert()
                window.closeAdvert();

                if (this.clearPlay) {
                    this.clearPlay();
                    this.playBtn.click();
                } else
                    return console.error("Play button continue function still not found");
            }

            load() {
                this.advert = document.querySelector("#advert");
                this.overlays = document.querySelector("#overlays");

                this.advertWatch.observe(this.advert, { attributes: true, attributeFilter: ["style"] });
                this.overlayWatch.observe(this.overlays, { attributes: true, attributeFilter: ["style"] });

                const playBtnSearch = globals.agma.getSearchString("#playBtn");
                const continueBtnSearch = globals.agma.getSearchString("#advertContinue");

                const removePlayListener = addTimeoutListener((fn, delay) => {
                    if (fn.toString().indexOf(playBtnSearch) !== -1 && delay > 1600) {
                        this.clearPlay = fn;
                        this.clearPlay();
                        removePlayListener();
                    }
                })

                const removeContinueListener = addTimeoutListener((fn, delay) => {
                    if (fn.toString().indexOf(continueBtnSearch) != -1 && delay > 1600) {
                        this.clearAdvert = fn;
                        removeContinueListener();
                    }
                })
            }

            unload() {
                this.advertWatch.disconnect();
                this.overlayWatch.disconnect();
            }
        }
    }, {
        name: "Uncensor input",
        enabled: true,
        bootstrap: class {
            constructor() {
                this.forbiddenWords = ["fuck", "shit", "ass", "dick", "penis", "dick", "pussy", "fag", "bitch", "sucker", "tits", "porn", "cunt", "cock"];
                this.override = window.getEventListeners("copy")[0];
                this.chatBox = document.querySelector("#chtbox");
                this.nicknameInput = document.querySelector("#nick");
                this.listener = this.listener.bind(this);
            }

            listener(event) {
                //if(event.which !== 13) return;

                const zeroWidth = String.fromCharCode(8203);
                const input = event.currentTarget;
                const text = input.value.toLowerCase();

                for (const forbidden of this.forbiddenWords) {
                    const i = text.indexOf(forbidden);

                    if (i !== -1) {
                        const found = input.value.substr(i, forbidden.length); // retain original to keep case
                        input.value = input.value.replace(found, found.split("").join(zeroWidth))
                    }
                }
            }

            load() {
                if (this.override) {
                    const { fn, capture } = this.override;
                    window.removeEventListener("copy", fn, capture)
                    window.removeEventListener("paste", fn, capture)
                    window.removeEventListener("cut", fn, capture)
                }
                this.chatBox.addEventListener("keydown", this.listener);
                this.nicknameInput.addEventListener("keydown", this.listener);
            }

            unload() {
                if (this.override) {
                    const { fn, capture } = this.override;
                    window.addEventListener("copy", fn, capture)
                    window.addEventListener("paste", fn, capture)
                    window.addEventListener("cut", fn, capture)
                }
                this.chatBox.removeEventListener("keydown", this.listener);
                this.nicknameInput.removeEventListener("keydown", this.listener);
            }
        }
    }, {
        name: "Fast Eject",
        enabled: true,
        bootstrap: class {
            constructor() {
                this.keyDown = false;
                this.syntheticEvent = Object.freeze({ keyCode: 87, synthetic: true });
                this.pressW = this.pressW.bind(this);
                this.downListener = this.downListener.bind(this);
                this.upListener = this.upListener.bind(this);
            }
            pressW() {
                window.onkeydown(this.syntheticEvent);
                window.onkeyup(this.syntheticEvent);
                if (this.keyDown) window.setTimeout(this.pressW, 25);
            }
            downListener(event) {
                if (event.keyCode === 87 && !event.synthetic) {
                    if (this.keyDown) return;
                    this.keyDown = true;
                    window.setTimeout(this.pressW, 25);
                    window.onkeyup(this.syntheticEvent); // complete first press
                }
            }
            upListener(event) {
                if (event.keyCode === 87 && !event.synthetic) {
                    this.keyDown = false;
                }
            }
            load() {
                window.addEventListener("keydown", this.downListener);
                window.addEventListener("keyup", this.upListener);
            }
            unload() {
                window.removeEventListener("keydown", this.downListener);
                window.removeEventListener("keyup", this.upListener);
            }
        }
    }, {
        name: "Re-spawn on R",
        enabled: true,
        bootstrap: class {
            constructor() {
                this.nicknameInput = document.querySelector("#nick");
                this.listener = this.listener.bind(this);
            }
            listener(event) {
                if (event.keyCode === 82 && globals.isGameActive()) {
                    window.rspwn(this.nicknameInput.value);
                }
            }
            load() {
                window.addEventListener("keydown", this.listener);
            }

            unload() {
                window.removeEventListener("keydown", this.listener);
            }
        }

    }, {
        name: "New identity on N | Skin on S",
        enabled: true,
        bootstrap: class {
            constructor() {
                this.nickInput = document.querySelector("#nick");
                this.overlays = document.querySelector("#overlays");
                this.nicks = [];
                this.skins = [];
                this.lastRespawn = 0;
                this.pendingId = false;
                this.pendingSkin = false;
                this.listener = this.listener.bind(this);
                this.hookFillText = this.hookFillText.bind(this);
                this.addNick = this.addNick.bind(this);
            }
            randNum(min, max) {
                return Math.floor(Math.random() * (max - min) + min);
            }
            rankUsername(username) {
                return username
                    .split("")
                    .reduce((cur, char) => cur + (char.charCodeAt(0) > 255 ? 3 : 1), 0)
            }
            sortAndTrim(list) {
                return list
                    .map(username => [this.rankUsername(username), username])
                    .sort(([rankA], [rankB]) => rankA - rankB)
                    .slice(0, 60)
                    .map(([_, username]) => username);
            }
            addNick(name) {
                if (name.length > 3 && this.nicks.indexOf(name) === -1) {
                    this.nicks.push(name);
                    this.nicks = this.sortAndTrim(this.nicks);
                }
            }
            changeSkin() {
                if (this.pendingSkin) return Promise.reject("Skin pending");
                if (this.skins.length) {
                    this.pendingSkin = true;
                    return globals.overlay.open(true)
                        .then(() => window.toggleSkin(this.skins[this.randNum(0, this.skins.length)]))
                        .then(() => globals.overlay.close())
                        .then(() => this.pendingSkin = false)
                } else {
                    return globals.message.error("No skins available, are you logged in?");
                }
            }
            changeNick() {
                this.pendingId = true;
                return new Promise((resolve, reject) => {
                    let newNick = "";
                    for (let i = 0; i < 5; i++) {
                        newNick = this.nicks[this.randNum(0, this.nicks.length)];
                        if (newNick !== this.nickInput.value) break;
                    }
                    this.nickInput.value = newNick

                    globals.message.show("Switching to: " + newNick)
                        .then(resolve)
                        .catch(reject)
                }).then(() => this.pendingId = false)
            }
            listener(event) {
                if (!globals.isGameActive()) return;
                if (event.keyCode === 78) {
                    Promise.all([this.changeSkin(), this.changeNick()]).then(() => {
                        console.log(this.nicks);
                        window.partyDecline();
                        window.rspwn(this.nickInput.value);
                    }).catch(() => { })
                } else if (event.keyCode === 73) {
                    this.changeSkin();
                }
            }

            hookFillText() {
                const origFillText = CanvasRenderingContext2D.prototype.fillText;
                const self = this;
                CanvasRenderingContext2D.prototype.fillText = function () {
                    if (this.canvas.id === "leaderboard") {
                        const item = arguments[0];
                        const start = item.indexOf(". ");
                        if (start !== -1) {
                            const name = item.substring(start + 2);
                            self.addNick(name);
                        }
                    } else if (this.canvas.height === 23) {
                        const item = arguments[0];
                        if (typeof item === "string" && this.fillStyle !== "#f5f6ce" && item !== "Agma.io") {
                            self.addNick(item)
                        }
                    }
                    return origFillText.apply(this, arguments);
                }
                return () => CanvasRenderingContext2D.prototype.fillText = origFillText;
            }
            hookRespawn() {
                const self = this;
                const rspwn = window.rspwn;

                window.rspwn = function () {
                    if (globals.message.rejectLast) {
                        globals.message.rejectLast();
                        globals.message.rejectLast = null;
                    }
                    self.lastRespawn = Date.now();
                    rspwn.apply(this, arguments);
                }
                return () => window.rspwn = rspwn;
            }
            getSkins() {
                const pages = document.querySelectorAll("#skinsCustom [id^=publicSkinsPageContent]:not(#publicSkinsPageContentNew):not(#publicSkinsPageContentPopular)");
                if (!pages.length) return [];
                return Array.from(pages)
                    .map(el => el.textContent.match(/toggleSkin\color{#fff}{([0-9]+)}([0−9]+)/g))
                    .map(matches => matches.map(match => match.substring(11, match.length - 1)))
                    .flat();
            }
            loadSkins() {
                let tries = 0;

                globals.modals.hideAll()
                window.showSkin();

                return new Promise(resolve => {
                    const retry = () => {
                        const skins = this.getSkins();
                        if (skins.length || tries++ > 5) {
                            globals.modals.closeCurrent().then(() => globals.modals.showAll());
                            resolve(skins);
                        } else {
                            window.setTimeout(retry, 2000);
                        }
                    }
                    window.setTimeout(retry, 3000);
                })
            }
            load() {
                globals.status.onLogin(() => this.loadSkins().then(skins => {
                    this.skins = skins;
                }))
                globals.status.onLogout(() => this.skins = []);

                window.addEventListener("keydown", this.listener);
                this.unhookRespawn = this.hookRespawn();
                this.unhookFillText = this.hookFillText();
            }
            unload() {
                window.removeEventListener("keydown", this.listener);
                this.unhookFillText();
                this.unhookRespawn();
            }
        }
    }, {
        name: "Quick-buy powerups with keys 1-4", // rename later
        enabled: true,
        bootstrap: class {
            constructor() {
                this.confirmButton = null;
                this.alert = null;
                this.watcher = new MutationObserver(this.onMutations.bind(this));
                [this.buyRecombine, this.buySpeed, this.buyGrowth, this.buyPushEnemies] = document.querySelectorAll(".purchase-btn.confirmation");
                this.listener = this.listener.bind(this);
            }
            onAlertAvailable() {
                const showingNow = this.alert.classList.contains("showSweetAlert");

                if (showingNow) {
                    this.confirmButton = this.confirmButton || this.alert.querySelector("button.confirm");
                    if (!this.confirmButton) return;

                    // SweetAlert will ignore all clicks until this class is added
                    // which it waits nearly a full second to add.. annoying. Wasted a lot of time debugging this.
                    // https://github.com/lipis/bootstrap-sweetalert/blob/67fdf993b35fa0a9e2c2a34d218cc9d83a59b8bd/dev/modules/handle-click.js#L42
                    this.alert.classList.add("visible");
                    this.confirmButton.click();
                }
            }
            onMutations(mutations) {
                for (const mutation of mutations) {
                    if (mutation.type === "attributes") {
                        if (mutation.target === this.alert) {
                            this.onAlertAvailable();
                        }
                    } else if (mutation.type === "childList") {
                        for (const node of mutation.addedNodes) {
                            if (node.nodeType !== Node.ELEMENT_NODE) continue;
                            if (node.classList.contains("sweet-alert")) {
                                this.alert = node;
                                this.onAlertAvailable();
                                this.watcher.disconnect();
                                this.watcher.observe(node, { attributes: true, attributeFilter: ["class"] })
                            }
                        }
                    }
                }
            }
            listener(event) {
                if (globals.isGameActive()) { // focus is on the game rather than chat or anything else
                    if (event.keyCode === 49 || event.keyCode === 97) {
                        this.buyRecombine.click();
                    } else if (event.keyCode === 50 || event.keyCode === 98) {
                        this.buySpeed.click();
                    } else if (event.keyCode === 51 || event.keyCode === 99) {
                        this.buyGrowth.click();
                    } else if (event.keyCode === 52 || event.keyCode === 100) {
                        this.buyPushEnemies.click();
                    }
                }
            }

            load() {
                this.watcher.observe(document.body, {
                    childList: true
                });
                window.addEventListener("keydown", this.listener);
            }
            unload() {
                this.watcher.disconnect();
                window.removeEventListener("keydown", this.listener);
            }
        }
    }, {
        name: "Freeze on F | Fixate cursor on X",
        enabled: true,
        bootstrap: class {
            constructor() {
                this.release = this.release.bind(this);
                this.listener = this.listener.bind(this);
                this.capturePos = this.capturePos.bind(this);
                this.captureInt = null;
                this.freezing = false;
                this.fixating = false;
                this.currentX = Math.floor(window.innerWidth / 2);
                this.currentY = Math.floor(window.innerHeight / 2);
                //this.overlay = this.createOverlay();
            }
            createOverlay() {
                // This overlay is redundant now that we just splice into the mousemove event
                // Still I'm leaving just in case i want to reimplement it
                const overlay = document.createElement("div");
                overlay.style.position = "fixed";
                overlay.style.width = "100%";
                overlay.style.height = "100%";
                overlay.style.display = "none";
                overlay.style.zIndex = "100";
                overlay.addEventListener("mousemove", event => this.capturePos(event) && event.stopPropagation());
                overlay.addEventListener("mouseenter", event => event.stopPropagation());
                overlay.addEventListener("click", this.release);

                return overlay;
            }
            freeze(delay = 5, complete = false) {
                let iterations = complete ? 30 : 0;
                this.freezing = true;

                this.captureInt = window.setInterval(() => {
                    iterations++;
                    const targetX = window.innerWidth / 2;
                    const targetY = window.innerHeight / 2;

                    document.body.dispatchEvent(new MouseEvent("mousemove", { // simulate mouse slowly moving to center
                        clientX: this.currentX + (((targetX - this.currentX) / 30) * Math.min(iterations, 30)),
                        clientY: this.currentY + (((targetY - this.currentY) / 30) * Math.min(iterations, 30)),
                        detail: 0x0a
                    }))
                    if (delay === 5 && iterations > 500) {
                        window.clearInterval(this.captureInt);
                        this.freeze(100, true); // start over with reduced frequency
                    }
                }, delay);

                //this.overlay.style.display = "block";
            }
            release() {
                window.clearInterval(this.captureInt);
                this.freezing = false;
                this.fixating = false;
                this.captureInt = null;
                //this.overlay.style.display = "none";
            }
            fixate() {
                this.fixating = true;

                const targetX = this.currentX;
                const targetY = this.currentY;

                this.captureInt = window.setInterval(() => {
                    document.body.dispatchEvent(new MouseEvent("mousemove", {
                        clientX: targetX,
                        clientY: targetY,
                        detail: 0x0a
                    }));
                }, 100);

                //this.overlay.style.display = "block";
            }
            capturePos(event) {
                this.currentX = event.clientX;
                this.currentY = event.clientY;
                return true
            }
            listener(event) {
                if (!globals.isGameActive()) return;

                if (event.keyCode === 70) {
                    if (this.freezing) {
                        this.release();
                    } else {
                        if (this.fixating) this.release();
                        this.freeze();
                        globals.message.show("Frozen at current spot. Press F to unlock");
                    }
                } else if (event.keyCode === 88) {
                    if (this.fixating) {
                        this.release();
                    } else {
                        if (this.freezing) this.release();
                        this.fixate();
                        globals.message.show("Locked at current direction. Press X to unlock");
                    }
                }
            }
            hookMouseMove() {
                let orig;
                const self = this;
                // We watch for the "mousedown" event only because we know agma immediately sets it after mousemove
                // If we try to hook into mousemove immediately, it will be undefined
                window.awaitEventListener("mousedown").then(() => {
                    orig = document.body.onmousemove;
                    document.body.onmousemove = document.body.onmouseenter = function (event) {
                        if ((!self.freezing && !self.fixating) || event.detail === 0x0a) {
                            orig.apply(this, arguments);
                        }
                    }
                })
                return () => document.body.onmousemove = document.body.onmouseenter = orig;
            }
            load() {
                window.addEventListener("keydown", this.listener);
                window.addEventListener("mousemove", this.capturePos);
                this.unhookMouseMove = this.hookMouseMove()
            }
            unload() {
                window.removeEventListener("keydown", this.listener);
                window.removeEventListener("mousemove", this.capturePos);
                this.unhookMouseMove();
            }
        }
    }, {
        enabled: true,
        name: "Remove popups",
        bootstrap: function () {
            this.load = () => {
                const popups = document.querySelectorAll("body .modal");
                for (const popup of popups) {
                    if (popup.textContent.indexOf("referral") !== -1) {
                        popup.remove();
                    }
                }
                // const minionui = document.querySelector("#minionUi");
                // minionui && minionui.remove();
                // setTimeout(() => document.querySelectorAll("iframe").forEach(iframe => iframe.remove()), 3000);
            }
            this.unload = () => {

            }
        }
    }];
    const globals = {
        Modals: class {
            constructor() {
                this.modals = document.querySelectorAll(".modal");
            }
            getCurrent() {
                const backdrop = document.querySelector(".modal-backdrop");
                const modal = backdrop.parentElement;
                return modal;
            }
            closeCurrent() {
                const modal = this.getCurrent();
                return new Promise(resolve => {
                    const onClose = () => {
                        window.$(modal).off("hidden.bs.modal", onClose);
                        resolve();
                    }
                    window.$(modal).on("hidden.bs.modal", onClose)
                    window.$(modal).modal("hide");
                })
            }
            hideAll() {
                this.modals.forEach(modal => modal.classList.add("force-hide"));
            }
            showAll() {
                this.modals.forEach(modal => modal.classList.remove("force-hide"));
            }
        },
        Overlay: class {
            constructor() {
                this.openResolvers = new Map();
                this.closeResolvers = new Map();
                this.overlayOpen = true;
                this.covert = false;
                this.playBtn = document.querySelector("#playBtn");
                this.overlays = document.querySelector("#overlays");
                this.watchOverlay();
            }
            watchOverlay() {
                const observer = new MutationObserver(mutations => {
                    if (overlays.style.display === "" || overlays.style.display === "block" && overlays.style.opacity === "") {
                        if (this.overlayOpen) return;
                        this.overlayOpen = true;
                        this.onOpen();
                    } else {
                        if (!this.overlayOpen) return;
                        this.overlayOpen = false;
                        this.onClose();
                    }
                });
                observer.observe(overlays, {
                    attributes: true,
                    attributeFilter: ["style"]
                })
            }
            onOpen() {
                for (const [fn] of this.openResolvers.entries()) {
                    fn()
                    this.openResolvers.delete(fn);
                }
            }
            onClose() {
                for (const [fn] of this.closeResolvers.entries()) {
                    fn();
                    this.closeResolvers.delete(fn);
                }

                if (this.covert) {
                    this.overlays.classList.remove("force-hide");
                    this.covert = false;
                }
            }
            open(covert) {
                return new Promise(resolve => {
                    if (this.overlayOpen) return resolve();
                    this.openResolvers.set(resolve, null);

                    if (covert) {
                        this.overlays.classList.add("force-hide");
                        this.covert = true;
                    } else {
                        this.covert = false;
                    }

                    window.azad(true);
                })
            }
            close() {
                return new Promise(resolve => {
                    if (!this.overlayOpen) return resolve();
                    this.closeResolvers.set(resolve, null);
                    this.playBtn.removeAttribute("disabled");
                    this.playBtn.click();

                    if (this.covert) {
                        this.overlays.style.display = "none";
                    }
                })
            }
        },
        Status: class {
            constructor() {
                this.loggedIn = false;
                this.loginCallbacks = [];
                this.logoutCallbacks = [];
                this.watchPanel();
            }
            watchPanel() {
                const panel = document.querySelector("#dashPanel");
                const observer = new MutationObserver(mutations => {
                    if (panel.style.display === "" || panel.style.display === "block") {
                        if (this.loggedIn) return;
                        this.loggedIn = true;
                        for (const fn of this.loginCallbacks)
                            fn()
                    } else if (panel.style.display === "none") {
                        if (!this.loggedIn) return;
                        this.loggedIn = false;
                        for (const fn of this.logoutCallbacks)
                            fn();
                    }
                });
                observer.observe(panel, {
                    attributes: true,
                    attributeFilter: ["style"]
                })
            }

            onLogin(fn) {
                this.loginCallbacks.push(fn);
                if (this.loggedIn) fn();
            }
            onLogout(fn) {
                this.logoutCallbacks.push(fn)
            }
        },
        Message: class {
            constructor() {
                this.messageBar = document.querySelector("#curser");
            }
            error(message, expires = 5000) {
                this.messageBar.style.color = "rgb(255, 0, 0);"
                return this._show(message, expires);
            }
            show(message, expires = 5000) {
                this.messageBar.style.color = "rgb(0, 192, 0);"
                return this._show(message, expires)
            }
            _show(message, expires = 5000) {
                return new Promise((resolve, reject) => {
                    this.messageBar.textContent = message;

                    if (this.rejectLast) {
                        this.rejectLast();
                    }

                    this.messageBar.style.display = "block";
                    const messageTimeout = window.setTimeout(() => {
                        this.rejectLast = null;
                        this.messageBar.style.display = "none";
                        resolve()
                    }, expires)

                    this.rejectLast = () => {
                        this.messageBar.style.display = "none";
                        window.clearInterval(messageTimeout);
                        reject();
                    }
                })
            }
        },
        Agma: class {
            constructor() {
                this.globalKey = this.getGlobalKey();
                this.setGlobals();
            }
            setGlobals() {
                window.canRunAds=true;
            }
            getGlobalKey() {
                for (let key in window) {
                    if (key.indexOf("_0x") === 0
                        && window[key] instanceof Array
                        && window[key].length > 500)
                        return key;
                }
            }
            getSearchString(str) {
                const i = window[this.globalKey].indexOf(str);
                if (i === -1) return "";
                return this.globalKey + "[" + i + "]";
            }
        },
        isGameActive: () => {
            globals.playBtn = globals.playBtn || document.querySelector("#playBtn");
            return document.activeElement === document.body || document.activeElement === globals.playBtn;
        }
    }
    const hookTimeout = (window) => {
        const originalTimeout = window.setTimeout.bind(window);
        const awaiting = [];
        const nextTimeout = (function (handler, timeout) {
            for (let listener of awaiting) {
                listener(handler, timeout);
            }
            originalTimeout(handler, timeout);
        });

        const addTimeoutListener = (listener) => {
            if (window.setTimeout !== nextTimeout) {
                window.setTimeout = nextTimeout;
            }
            awaiting.push(listener);
            return () => {
                awaiting.splice(awaiting.indexOf(listener), 1);
                if (!awaiting.length) {
                    window.setTimeout = originalTimeout;
                }
            }
        }

        return [() => window.setTimeout = originalTimeout, addTimeoutListener]
    }
    const hookEvents = (EventTarget) => {
        const originalAdd = EventTarget.prototype.addEventListener;
        const originalRemove = EventTarget.prototype.removeEventListener;

        EventTarget.prototype.addEventListener = function () {
            originalAdd.apply(this, arguments); // run first to allow native method to halt execution in case of errors
            let [name, fn, capture] = arguments;
            capture = !!capture
            if (!this.eventListeners) this.eventListeners = [];
            this.eventListeners.push({ name, fn, capture });
            if (this.awaiting) {
                const i = this.awaiting.findIndex(({ eventName }) => eventName === name);
                if (i !== -1) {
                    this.awaiting[i].resolve();
                    this.awaiting.splice(i, 1);
                }
            }
        }

        EventTarget.prototype.removeEventListener = function () {
            originalRemove.apply(this, arguments);
            let [_name, _fn, _capture] = arguments;
            _capture = !!_capture;
            this.eventListeners = this.eventListeners ? this.eventListeners.filter(({ name, fn, capture }) => !(name === _name && fn === _fn && capture === _capture)) : [];
        }

        EventTarget.prototype.getEventListeners = function (eventName) {
            return this.eventListeners ? this.eventListeners.filter(({ name }) => name === eventName) : []
        }

        EventTarget.prototype.awaitEventListener = function (eventName) {
            return new Promise(resolve => {
                const events = this.getEventListeners(eventName);
                if (events.length) {
                    resolve();
                } else {
                    if (!this.awaiting) this.awaiting = [];
                    this.awaiting.push({ eventName, resolve })
                }
            })
        }

        return () => {
            EventTarget.prototype.addEventListener = originalAdd;
            EventTarget.prototype.removeEventListener = originalRemove;
            delete EventTarget.prototype.getEventListeners;
        }
    }
    const hookStyle = () => {
        const style = document.createElement("style")
        style.appendChild(document.createTextNode(".force-hide{display: none !important;}"));

        document.documentElement.appendChild(style);
        return () => document.documentElement.removeChild(style);
    }
    const hookProtections = () => {
        const orig = Document.prototype.createElement;
        Document.prototype.createElement = function (tag) {
            if (tag.toLowerCase() === "iframe") {
                tag = "div";
            }
            return orig.call(this, tag);
        }

        // if push comes to shove, i guess we could also just modify jQuery's selector fn
        // or the underlying document function calls
        // so they return a different iframe that we control

        Object.freeze(Document.prototype);
        Object.freeze(window.EventTarget.prototype);
        Object.freeze(window.EventTarget);
        Object.freeze(window.MutationObserver.prototype);
        Object.freeze(window.MutationObserver)
    }
    const init = () => {
        for (const global in globals) {
            if (global.charAt(0).toLowerCase() !== global.charAt(0))
                globals[global.toLowerCase()] = new globals[global]();
        }
        for (const feature of features) {
            if (feature.enabled) {
                feature.instance = new feature.bootstrap();
                feature.instance.load();
            }
        }
    }

    const hookTimeouts = () => {
        // Dear Agma admin, can we stop this arms race and just let people have fun?
        const timeoutListeners = [hookTimeout(window)];
        const onIframe = (frame) => {
            if (frame.src.indexOf("agma.io") === -1 || frame.contentWindow.hooked) return;
            timeoutListeners.push(hookTimeout(frame.contentWindow))
            hookEvents(frame.contentWindow.EventTarget);
            frame.contentWindow.hooked = true;
        }

        const observer = new MutationObserver(muts =>
            muts.forEach(mut => {
                if (mut.target.nodeType === Node.ELEMENT_NODE) {
                    if (mut.target.localName === "iframe") {
                        onIframe(mut.target);
                    } else {
                        document.querySelectorAll("iframe").forEach(frame => onIframe(frame));
                    }
                }
            })
        );

        observer.observe(document.documentElement, { childList: true, subtree: true });

        const addTimeoutListener = (fn) => {
            const listeners = [];
            for (const [_, addTimeoutListener] of timeoutListeners) {
                listeners.push(addTimeoutListener(fn));
            }
            return () => listeners.forEach(remover => remover());
        }
        const unhook = () => {
            observer.disconnect();
            timeoutListeners.forEach(unhooker => unhooker());
        }

        return [addTimeoutListener, unhook]
    }

    const unhookEvents = hookEvents(window.EventTarget);
    const unhookStyle = hookStyle();
    const [addTimeoutListener, unhookTimeouts] = hookTimeouts();
    hookProtections();

    if (document.readyState === "complete" || document.readyState === "loaded" || document.readyState === "interactive") {
        init();
    } else {
        window.addEventListener("DOMContentLoaded", init);
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址