Embedded diep lobby selector

Embed diep lobby selector into diep.io page

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Embedded diep lobby selector
// @namespace    http://tampermonkey.net/
// @version      3.1
// @description  Embed diep lobby selector into diep.io page
// @author       Eclipsia discord@cz_eclipsia reddit@Ok-Wing4342
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @match        *://*.diep.io/*
// @license      MIT
// @grant        none
// @run-at       document-start
// ==/UserScript==

/* 
TODO: 
make the inner div contained in a space (done)
stop execution if display is none (done)
add a way to choose a team in teams and 4teams (done)
edit css of buttons (done)
test future events and beta data (done)
game version button switcher mostly done ready to test (done)
add options to enable and disable render latency and net predict (done)

add special to sandboxes
highlighted mode now matches the color of your team
same with score and background

// https://stackoverflow.com/questions/79614720/how-do-i-add-hover-and-active-events-to-my-tampermonkey-injected-script
*/

(async () => {
    'use strict';
    // region Top
    // Top level variables, change TOGGLE_KEYBIND to whatever you want (to change display visibility)
    const TOGGLE_KEYBIND = 'q';
    const UPDATE_DELAY = 1000; // miliseconds
    const BASE_API = "https://lb.diep.io/api/lb/"
    const BETA_API = "https://master-dev.diep.io/api/lb/"
    const storageKey = "diepioLobbyselect_"
    const re_isBeta = /https?:\/\/(?<beta>beta\.)?diep\.io\/?.*/
    
    // region local storage management
    const storage = {
        get(key) {
            return localStorage.getItem(storageKey + key)
        },
        
        async set(key, value) {
            localStorage.setItem(storageKey + key, value);
            console.log('set', key, 'to', value);
        },

        getBool(key, defaultValue) {
            let value = this.get(key, null);
            return value === null ? defaultValue : JSON.parse(value)
        },
        
        /**
         * @returns {number}
         */
        getNumber(key, defaultValue)  {
            let value = this.get(key, null);
            return value === null ? defaultValue : parseFloat(value);
        },

    }

    // region unfuck diep
    const PREDICT_MOVEMENT = "net_predict_movement";
    const REN_LATENCY = "ren_latency";

    const consoleLog = console.log;
    let [backgroundSuccess, windowSuccess, scoreSuccess] = [false, false, false];
    let renderLatency = storage.getBool("renLatency") ?? true;
    let predictMovement = storage.getBool("predictMovement") ?? false;
    storage.set("predictMovement", predictMovement);
    storage.set("renLatency", renderLatency);

    async function unfuckDiep() {
        if (!windowSuccess && window.input) {
            // force predict_movement off, bring back latency statistic 
            input.inGameNotification
            window.input.set_convar(REN_LATENCY, renderLatency);
            window.input.set_convar(PREDICT_MOVEMENT, predictMovement);
            windowSuccess = true;
        }

        let backgroundElem;
        if (!backgroundSuccess && (backgroundElem = document.getElementById("game-over-screen"))) {
            // remove blurry deathscreen background
            backgroundElem.style.setProperty("backdrop-filter", "none", "important");
            backgroundSuccess = true;

        }
        let scoreTextElem;
        if (!scoreSuccess && (scoreTextElem = document.getElementById("game-over-stats-player-score"))) {
            // unfuck the score
            let oldValue = scoreTextElem.textContent;
            const callback = () => {
                let newValue = scoreTextElem.textContent;
                if (newValue == oldValue) return;
                scoreTextElem.textContent = scoreTextElem.textContent.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
                oldValue = newValue;
            }

            new MutationObserver(callback).observe(scoreTextElem, {
                childList: true,    
                subtree: true,
                characterData: true
            })
            scoreSuccess = true
        }

        if (backgroundSuccess && windowSuccess && scoreSuccess) {
            consoleLog(`Finished unfucking job.`)
            return;
        }

        await Promise.resolve(new Promise(r => setTimeout(r, 500)));
        return await unfuckDiep();
    };
    await unfuckDiep();
    
    // region Colors
    const intToTeam = [
        "blue",
        "red",
        "purple",
        "green",
    ];
    
    const intToColor = [
        "rgba(33, 143, 221, 1)", // blue
        "rgba(232, 27, 27, 1)",  // red
        "rgba(184, 13, 207, 1)", // purple
        "rgba(81, 220, 34, 1)",  // green
    ];

    const GREEN = "rgba(0, 255, 0, 1)"
    const RED = "rgba(255, 0, 0, 1)"

    let colors = {
        fra: "rgba(255, 0, 0, 0.5)",
        atl: "rgba(255, 255, 0, 0.5)",
        sgp: "rgba(50, 50, 200, 0.5)",
        syd: "rgba(0, 255, 0, 0.5)"
    }

    const defaultColor = "rgba(255, 255, 255, 0.5)"

    function offsetRGBA(rgba = "rgba(0, 0, 0, 0)", offset = 0, alphaOffset = 0.0) {
        const regex = /rgba\((?<r>\d{1,3})\D*,\D*(?<g>\d{1,3})\D*,\D*(?<b>\d{1,3})\D*,\D*(?<a>[0-1](\.\d)?)\)/
        const res = regex.exec(rgba);
        if (!res?.groups) throw Error(`failed regex: '${rgba}'`);

        const grps = res.groups;
        const r = parseInt(grps.r) + offset;
        const g = parseInt(grps.g) + offset;
        const b = parseInt(grps.b) + offset;
        const a = parseFloat(grps.a) + alphaOffset;
        return `rgba(${r}, ${g}, ${b}, ${a})`
    }

    const commonlySharedButtonCSS = `
        padding: 5px;
        border: 1px solid black;
        background-color: rgba(255, 255, 255, 0.5);
        cursor: pointer;
        transition: background-color 0.2s ease;
        margin-left: 5px;
        margin-bottom: 2px;
        border-radius: 5px;
    `;

    const thisURL = new URL(document.URL)

    // TODO: sandboxes
    let currentLobbyIp = thisURL.searchParams.get("ip")
    const lobbyInfo = thisURL.searchParams.get("l")?.split("x").map(v => parseInt(v))
    const [lobbyId, currentTeamIndex] = lobbyInfo ?? [0, 0];

    // region storage get
    /** @type {"verbose" | "concise"} */
    let layoutStyle = storage.get("layoutStyle") ?? "verbose"
    let buttonIndex = storage.getNumber('teamIndex', 0);
    let apiDevice = storage.get('device') || (thisURL.host.startsWith("mobile") ? "mobile" : "pc");
    let showUI = true;

    storage.set("layoutStyle", layoutStyle);
    storage.set("teamIndex", buttonIndex);
    storage.set("device", apiDevice);

    /** @type {"beta" | "main"} */
    let APItarget = !!re_isBeta.exec(document.URL)?.groups?.beta ? "beta" : "main"
    if (APItarget == "main") {
        APItarget = storage.get('APItarget') ?? "main"
        console.log(APItarget)
        storage.set('APItarget', APItarget)
    }

    // region helper funcs

    function getAPIEndpoint(device) {
        return (APItarget == "beta" ? BETA_API : BASE_API) + (device ?? apiDevice)
    }

    function getTextForNumPlayers(numPlayers) {
        // this function was vibe coded
        if (numPlayers < 850) return "Very Low";
        if (numPlayers < 900) return "Low";
        if (numPlayers < 950) return "Below Average";
        if (numPlayers < 1050) return "Average";
        if (numPlayers < 1100) return "Above Average";
        if (numPlayers < 1200) return "High";
        return "Very High";
    }

    function formatSeconds(seconds) {
        const days = Math.floor(seconds / (3600 * 24));
        const hours = Math.floor(seconds / 3600 % 24);
        const minutes = Math.floor(seconds / 60 % 60);
        seconds = Math.floor(seconds % 60);

        let output = "";
        if (days) output += `${days}d `
        if (hours) output += `${hours}h `
        if (minutes) output += `${minutes}m `
        if (seconds) output += `${seconds}s`
        return output.trim();
    }

    // region createLobbyButton
    function createLobbyButton(region, lobby) {
        const button = document.createElement('button');
        button.style.cssText = commonlySharedButtonCSS;
;
        let gamemodeName;
        if (layoutStyle == "verbose") {
            gamemodeName = lobby.gamemodeName || lobby.gamemode;
        } else {
            gamemodeName = lobby.gamemode;
            if (
                lobby.gamemodeName.toLowerCase().includes("maze") 
            ) gamemodeName = lobby.gamemodeName.toLowerCase();
        }
            
        button.textContent = `${gamemodeName} ${lobby.numPlayers}p`;

        let futureEventButton;
        if (lobby.nextGamemodeName) {
            futureEventButton = document.createElement("button")
            futureEventButton.style.cssText = commonlySharedButtonCSS;
            const secondsUntilText = formatSeconds(lobby.secondsUntilNextGamemode);
            futureEventButton.textContent = 
            `${lobby.gamemodeName ?? lobby.gamemode} => ${lobby.nextGamemodeName} in ${secondsUntilText}`
        }

        const buttonProxyHandler = {
            set(target, p, newValue, receiver) {
                button.style[p] = newValue
                if (futureEventButton) futureEventButton.style[p] = newValue
                return true
            },
        }

        const bothButtonStyle = new Proxy({}, buttonProxyHandler);

        const bgColor = colors[region.region] || defaultColor
        button.style.backgroundColor = bgColor;
        
        if (lobby.ip === currentLobbyIp) {
            bothButtonStyle.border = "5px solid black";

            if (lobby.gamemode.includes("teams") || currentTeamIndex > 0) {
                bothButtonStyle.backgroundColor = intToColor[currentTeamIndex];
                button.textContent += ` (${intToTeam[currentTeamIndex]})`;
            } else {
                bothButtonStyle.backgroundColor = offsetRGBA(bgColor, -40)
            }

        } else {
            bothButtonStyle.border = "1px solid black";
        }

        if (futureEventButton) {
            futureEventButton.style.backgroundColor = offsetRGBA(bgColor, 120)
        }

        return {button, futureEventButton};
    }

    // region async update()
    async function update() {
        const response = await fetch(getAPIEndpoint());
        const value = await response.json();
        /** @type {object[]} */
        const data = value.regions;

        const count = data.map(region => region.numPlayers).reduce((prv, cur) => prv + cur, 0);
        numPlayers.textContent = `Players Active: ${count} (${getTextForNumPlayers(count)})`        

        if (!count) {
            buttons.style.display = "none"
            return
        }
        buttons.style.display = "block";
        // https://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript
        buttons.textContent = "";

        for (const region of data) {
            const countryCode = region.countryCode ? ` (${region.countryCode})` : ""
            
            const playerCount = document.createElement('p');
            playerCount.textContent = `${region.regionName}${countryCode} ${region.numPlayers} Players`
            playerCount.style.cssText = `
                margin-right: 0px;
                background-color: ${colors[region.region] ?? defaultColor};
            `;
            playerCount.style.color = offsetRGBA(playerCount.style.backgroundColor, 200)
            buttons.appendChild(playerCount);
            
            const futureEventButtons = [];

            for (let index = 0; index < region.lobbies.length; index++) {
                // put every sixth lobby on a new row
                if (index % 5 == 1 && index > 5) {
                    buttons.appendChild(document.createElement('br'));
                }

                const lobby = region.lobbies[index];
                const {button, futureEventButton} = createLobbyButton(region, lobby);

                button.addEventListener('click', () => {
                    let teamIndex = buttonIndex;

                    if (lobby.gamemode === "teams" 
                        && teamIndex > 1 
                        || !lobby.gamemode.includes("teams")
                        && lobby.gamemode !== "event"
                    ) {
                        teamIndex = 0;
                    }
                    // https://diep.io/?lobby=fra_teams_fra-43ce658e19c456d0.diep.io:2001_55544_0
                    const searchParams = new URLSearchParams();
                    searchParams.set("lobby", `${region.region}_${lobby.gamemode}_${lobby.ip}_${"x" || "lobbyid"}_${teamIndex}`)

                    window.open(`https://${apiDevice == 'mobile' ? 'mobile.' : ''}diep.io/?${searchParams.toString()}`)
                });

                buttons.appendChild(button);
                if (futureEventButton) futureEventButtons.push(futureEventButton);
            }
            if (layoutStyle == "verbose") {
                buttons.appendChild(document.createElement('br'));
                futureEventButtons.forEach(button => buttons.appendChild(button))
            }
            buttons.appendChild(document.createElement('br'));
        }
        
    }
    update()
    let IntervalID = setInterval(update, UPDATE_DELAY);
    
    // toggle display visibility
    async function toggle() {
        const display = container.style.display;
        container.style.display = display == 'block' ? 'none' : 'block';
        showUI = !showUI;
        if (showUI) {
            update();
            IntervalID = setInterval(update, UPDATE_DELAY);
        } else {
            clearInterval(IntervalID);
        }
    }

    document.addEventListener('keydown', (ev) => {
        if (ev.key === TOGGLE_KEYBIND) {
            toggle();
        }
    })

    // region elements




    // seperate each element with 2 empty lines

    // region text

    const container = document.createElement("div");
    container.style.cssText = `
        position: absolute;
        left: 0%;
        top: 0%;
        z-index: 999999;
        background-color: rgba(255, 255, 255, 0.5);
        padding: 5px;
        border: 1px solid black;
        border-radius: 5px;
        display: block;
    `;
    document.body.append(container);


    const title = document.createElement("p");
    title.textContent = 'Diep lobby selector by Eclipsia';
    title.style.cssText = `
        position: relative;
        z-index: 999999;
        padding: 0px;
        margin: 0px;
        color: black;
    `;
    container.appendChild(title);


    const numPlayers = document.createElement('p');
    numPlayers.textContent = "Players Active: ?";
    numPlayers.style.cssText = `
        position: relative;
        z-index: 999999;
        padding: 0px;
        margin: 0px;
        margin-bottom: 3px;
        color: black;
    `;
    container.appendChild(numPlayers);




    // region button row
    const buttonDiv = document.createElement('div');
    buttonDiv.style.cssText = `
        right: 0%;
        display: flex;
    `;
    container.appendChild(buttonDiv);


    const apiDeviceBtn = document.createElement("button");
    apiDeviceBtn.textContent = "device: " + apiDevice;
    apiDeviceBtn.style.cssText = `
        border: 3px solid black;
        background-color: rgba(255, 255, 255, 0.8);
        margin-right: 3px;
        padding: 2px;
    `;
    apiDeviceBtn.addEventListener("click", () => {
        apiDevice = apiDevice === "mobile" ? "pc" : "mobile";
        apiDeviceBtn.textContent = "device: " + apiDevice;
        storage.set('device', apiDevice);
    });
    buttonDiv.appendChild(apiDeviceBtn);


    const gameReleaseBtn = document.createElement('button');
    gameReleaseBtn.textContent = "API: " + APItarget;
    gameReleaseBtn.style.cssText = `
        border: 3px solid black;
        background-color: rgba(255, 255, 255, 0.8);
        display: flex;
        padding: 2px;
        margin-right: 3px;
    `;
    gameReleaseBtn.addEventListener('click', () => {
        APItarget = APItarget == "beta" ? "main" : "beta";
        gameReleaseBtn.textContent = "API: " + APItarget;
        storage.set('APItarget', APItarget);
    });
    buttonDiv.appendChild(gameReleaseBtn);


    const layoutStyleBtn = document.createElement("button");
    layoutStyleBtn.textContent = "style: " + layoutStyle;
    layoutStyleBtn.style.cssText = `
        border: 3px solid black;
        background-color: rgba(255, 255, 255, 0.8);
        display: flex;
        padding: 2px;
    `;
    layoutStyleBtn.addEventListener("click", () => {
        layoutStyle = layoutStyle == "verbose" ? "concise" : "verbose";
        layoutStyleBtn.textContent = "style: " + layoutStyle;
        storage.set("layoutStyle", layoutStyle);
    })
    buttonDiv.appendChild(layoutStyleBtn)


    // region config buttons
    const teamDiv = document.createElement('div');
    teamDiv.style.cssText = `
        z-index: 999999;
        background-color: rgba(25, 160, 205, 1);
        border: 1px solid black;
        border-radius: 5px;
        display: flex;
        height: 20px;
    `;
    container.appendChild(teamDiv);

    const teamTitle = document.createElement('p');
    teamTitle.textContent = "target team: ";
    teamTitle.style.cssText = `
        margin-top: 0px;
        margin-left: 3px;
    `;
    teamDiv.appendChild(teamTitle);


    const teamButton = document.createElement('button');
    teamButton.textContent = intToTeam[buttonIndex];
    teamButton.style.cssText = `
        margin-left: 5px;
        background-color: ${intToColor[buttonIndex]};
    `;
    teamButton.addEventListener('click', () => {
        buttonIndex = (buttonIndex + 1) % intToTeam.length;
        teamButton.textContent = intToTeam[buttonIndex];
        teamButton.style.backgroundColor = intToColor[buttonIndex];
        storage.set('teamIndex', buttonIndex);
    });
    teamDiv.appendChild(teamButton);




    // region net_predict
    const divPredictMovement = document.createElement("div");
    divPredictMovement.style.cssText = `
        z-index: 999999;
        background-color: rgba(222, 52, 248, 0.55);
        border: 1px solid black;
        border-radius: 5px;
        display: flex;
        height: 20px;
    `;
    container.appendChild(divPredictMovement)

    
    const netPredictLabel = document.createElement("p")
    netPredictLabel.textContent = "net_predict_movement: "
    netPredictLabel.style.cssText = `
        margin-top: 0px;
        margin-left: 3px;
    `;
    divPredictMovement.appendChild(netPredictLabel)


    const netPredictBtn = document.createElement("button");
    netPredictBtn.textContent = predictMovement
    netPredictBtn.style.cssText = `
        background-color: ${predictMovement ? GREEN : RED};
        margin-left: 5px;
    `;
    netPredictBtn.addEventListener('click', () => {
        predictMovement = !predictMovement;
        netPredictBtn.textContent = predictMovement;
        netPredictBtn.style.backgroundColor = predictMovement ? GREEN : RED;
        input.set_convar(PREDICT_MOVEMENT, predictMovement);
        storage.set("predictMovement", predictMovement);
    });
    divPredictMovement.appendChild(netPredictBtn);




    // render latency
    const divRenderLatency = document.createElement("div");
    divRenderLatency.style.cssText = `
        z-index: 999999;
        background-color: rgba(200, 200, 40, 0.55);
        border: 1px solid black;
        border-radius: 5px;
        display: flex;
        height: 20px;
    `;
    container.appendChild(divRenderLatency);

    const renLatencyLabel = document.createElement("p")
    renLatencyLabel.textContent = "render_latency: "
    renLatencyLabel.style.cssText = `
        margin-top: 0px;
        margin-left: 3px;
    `;
    divRenderLatency.appendChild(renLatencyLabel)


    const renderLatencyBtn = document.createElement("button");
    renderLatencyBtn.textContent = renderLatency
    renderLatencyBtn.style.cssText = `
        background-color: ${renderLatency ? GREEN : RED};
        margin-left: 5px;
    `;
    renderLatencyBtn.addEventListener('click', () => {
        renderLatency = !renderLatency;
        renderLatencyBtn.textContent = renderLatency;
        renderLatencyBtn.style.backgroundColor = renderLatency ? GREEN : RED;
        input.set_convar(REN_LATENCY, renderLatency);
        storage.set("renLatency", renderLatency);
    });
    divRenderLatency.appendChild(renderLatencyBtn);




    // region buttons div
    const buttons = document.createElement('div');
    buttons.style.cssText = `
        right: 0%;
        top: 0%;
        z-index: 999999;
        background-color: rgba(141, 24, 187, 1);
        padding: 5px;
        border: 1px solid black;
        border-radius: 5px;
        display: block;
    `;
    container.appendChild(buttons);


    // region end
    // synchronise graphics
    window.console.log = consoleLog
    console.log("Initial showUi is", showUI);
    toggle()
    toggle()
})();