Diep.io Server Selector + Restore Player Count

Select servers from different gamemodes and regions for Diep. Also restores player count for Diep.io.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Diep.io Server Selector + Restore Player Count
// @namespace    http://tampermonkey.net/
// @version      2.7.0
// @description  Select servers from different gamemodes and regions for Diep. Also restores player count for Diep.io.
// @author       Altanis + Bismuth
// @match        *://diep.io/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        unsafeWindow
// @license      MIT
// ==/UserScript==

(async function() {
    // Special credits to Diep7444 for paving the way to an effective region changer.

    const textShadow = 'text-shadow:black 0.18vh 0, black -0.18vh 0, black 0 -0.18vh, black 0 0.18vh, black 0.18vh 0.18vh, black -0.18vh 0.18vh, black 0.18vh -0.18vh, black -0.18vh -0.18vh, black 0.09vh 0.18vh, black -0.09vh 0.18vh, black 0.09vh -0.18vh, black -0.09vh -0.18vh, black 0.18vh 0.09vh, black -0.18vh 0.09vh, black 0.18vh -0.09vh, black -0.18vh -0.09vh'
    const regions = ["lnd-sfo", "lnd-atl", "lnd-fra", "lnd-syd"];
    const modes = ["ffa", "survival", "teams", "4teams", "dom", "tag", "maze", "sandbox"];
    const colors = ["#E8B18A", "#E666EA", "#9566EA", "#6690EA", "#E7D063", "#EA6666", "#92EA66", "#66EAE6"];

    let key = 'KeyT'; // Go to https://keycode.info, press desired key, copy event.code, paste into quotes.
    let special = 'alt'; // alt, shift, ctrl, meta (Windows/Command)

    let includeUncommon = true;

    if (!['alt', 'shift', 'ctrl', 'meta'].includes(special)) special = 'alt'; // Default = Alt.

    var PLAYER_COUNT = 0;
    unsafeWindow.API_STATE = { code: 0, message: 'Connecting...'};

    const modeHTML = document.createElement("div");
    document.body.appendChild(modeHTML);
    modeHTML.innerHTML = `
<div class='parent' id='ServerSelector' style='user-select:none; position:fixed; top:25%; right:0.5%; text-align:center; width:15vw; font-family:Ubuntu; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'><div class='child' style='line-height:2vh; opacity:75%'>
        TAB to toggle server selector
        <br>
        <p style="font-size:12px;">Created by Altanis and Bismuth</p>
        <p style="font-size:12px;" id="status">Status: ${unsafeWindow.API_STATE.message}</p> 
        <hr>
    </div>
    <p style="font-size:10px">Game Mode</p>
    <button class='child' type='button' id='ffa' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[7]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>FFA</button>
    <button class='child' type='button' id='survival' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[6]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>SURV</button>
    <button class='child' type='button' id='teams' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[5]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>2TDM</button>
    <button class='child' type='button' id='4teams' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[4]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>4TDM</button>
    <button class='child' type='button' id='dom' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[3]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>DOM</button>
    <button class='child' type='button' id='tag' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[2]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>TAG</button>
    <button class='child' type='button' id='maze' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[1]}; color:#FFFFFF; font-style:normal; font-size:0.9vw;${textShadow}'>MAZE</button>
    <button class='child' type='button' id='sandbox' value='mode' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[0]}; color:#FFFFFF; font-style:normal; font-size:0.9vw;${textShadow}'>SBX</button>
    <p style="font-size:10px">Region</p>
    <button class='child' type='button' id=${regions[0]} value='region' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[3]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>SFO</button>
    <button class='child' type='button' id=${regions[1]} value='region' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[2]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>ATL</button>
    <button class='child' type='button' id=${regions[2]} value='region' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[6]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>FRA</button>
    <button class='child' type='button' id=${regions[3]} value='region' style='width:3.5vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[5]}; color:#FFFFFF; font-style:normal; font-size:0.9vw;${textShadow}'>SYD</button>
    <div class='parent' id='choice' style='user-select:none; position:relative; top:65%; left:0.5%; text-align:center; width:15vw; font-family:Ubuntu; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'>
    <br>
</div>
</div>
`
    document.getElementById('ServerSelector').style.display = 'block';
    for (let mode of modes) {
        addButtonListener(mode);
    }
    for (let region of regions) {
        addButtonListener(region);
    }

    function refreshStatus() { document.getElementById('status').innerHTML = `Status: ${unsafeWindow.API_STATE.message}`; }
    function refreshHTML() {
        let json = `<div class='parent' id='choice' style='user-select:none; position:relative; top:65%; left:0.5%; text-align:center; width:15vw; font-family:Ubuntu; color:#FFFFFF; font-style:normal; font-size:0.9vw; ${textShadow}'><p style="font-size:12px">   </p><div class='child' style='line-height:2vh; opacity:75%'> Servers for ${choices.mode} ${choices.region}<hr></div>`;
        for (let n = 0; n < serverWithoutCSS[choices.mode][choices.region].lobbies.length; n++) {
            json += `<button class='child' type='button' id='choice${n}'value='${n}'style='width:9vw; height:3vh; border-radius: 0.5vw; font-family:Ubuntu; opacity:60%; background:${colors[n % 8]}; color:#FFFFFF; font-style:normal; font-size:0.9vw; filter: ${currentServer.includes(serverWithoutCSS[choices.mode][choices.region].lobbies[n].slice(0,8))? 'brightness(100%)': 'brightness(50%)'}; ${textShadow}'>${serverWithoutCSS[choices.mode][choices.region].lobbies[n].slice(0,8)} (${serverWithoutCSS[choices.mode][choices.region].info[serverWithoutCSS[choices.mode][choices.region].lobbies[n].slice(0, 36)].total_player_count || '??'}/${serverWithoutCSS[choices.mode][choices.region].info[serverWithoutCSS[choices.mode][choices.region].lobbies[n].slice(0, 36)].max_players_direct || '??'})</button>`;
        }

        document.getElementById('choice').innerHTML = `${json}`;

        for (let n = 0; n < serverWithoutCSS[choices.mode][choices.region].lobbies.length; n++) {
            addButtonListener('choice' + n);
        }
    }

    function buttonAction(id) {
        let button = document.getElementById(id);
        if (button.value === 'mode') {
            choices.mode = id;
            fetchServer(choices.mode, choices.region, 3);
        } else if (button.value === 'region') {
            choices.region = id;
            fetchServer(choices.mode, choices.region, 3);
        } else {
            (connectTo(choices.mode, choices.region, button.value));
        }
        refreshHTML();
    }

    function addButtonListener(id) {
        document.getElementById(id).addEventListener("click", function() {
            buttonAction(id)
        });
        document.getElementById(id).addEventListener("mouseenter", function() {
            lightenColor(id)
        });
        document.getElementById(id).addEventListener("mouseleave", function() {
            resetColor(id)
        });
    }

    function lightenColor(id) {
        document.getElementById(id).style.opacity = '100%'
    }

    function resetColor(id) {
        document.getElementById(id).style.opacity = '60%'
    }

    function getServerLink(server) {
        let link = '';
        for (const char of server) {
            const code = char.charCodeAt(0);
            const value = (`00${code.toString(16)}`).slice(-2);
            link += value.split("").reverse().join("");
        }
        return link;
    }

    async function calcPlayerCount() {
        const body = JSON.parse(localStorage.lists)?.list;

        if (!body.hasOwnProperty('game_modes') && !body.hasOwnProperty('message')) {
            unsafeWindow.API_STATE.code = 2; return;
        };
        if (body.message === 'too many requests') {
            unsafeWindow.API_STATE.code = 3; return;
        };

        PLAYER_COUNT = 0;
        body.game_modes.forEach(function(g) {
            if (['dom', 'sandbox', 'survival', 'tag'].includes(g.game_mode_name) && !includeUncommon) return;
            g.regions.forEach(function(r) {
                r.lobbies.forEach(function(l) {
                    PLAYER_COUNT += l.total_player_count;
                });
            });
        });
    }

    const choices = {
        mode: modes[0],
        region: regions[0],
    }
    const serverWithoutCSS = {};

    modes.forEach(function(gamemode) {
        serverWithoutCSS[gamemode] = {};
        regions.forEach(function(region) {
            serverWithoutCSS[gamemode][region] = { lobbies: [], info: {} };
        });
    });

    unsafeWindow.API_STATE = new Proxy(unsafeWindow.API_STATE, {
        set: function(t, k, v) {
            t[k] = v;

            if ([0, 1, 2, 4].includes(v)) {
                t.message = v === 0 ? 'Connecting...' : (v === 1 ? 'Ready!' : (v === 2 ? 'Endpoint is down.' : 'Lobbies don\'t exist.'))
            } else if (v === 3) {
                const date = new Date(Date.now());

                const curMin = date.getMinutes();
                const intervals = [0, 15, 30, 45];

                intervals.sort((a, b) => {
                    return Math.abs(curMin - a) - Math.abs(curMin - b);
                });

                let retry_at = intervals[0] < curMin ? intervals[1] : intervals[0];
                let time = `${retry_at === 0 ? date.getHours() + 1 : date.getHours()}:${retry_at.length === 1 ? `0${retry_at}` : retry_at}`;
                t.message = `Ratelimited! Retry-At: ${time}.`;

                const interval = setInterval(function() {
                    let [hours, minutes] = time.split(':').map(t => parseInt(t));
                    if (new Date(Date.now()).getHours() >= hours && new Date(Date.now()).getMinutes() >= minutes) {
                        t.code = 1;
                        t.message = 'Ready!';
                        refreshStatus();
                        clearInterval(interval);
                    }
                }, 150);
            }
            refreshStatus();
            return true;
        }
    });

    if (!localStorage.lists) {
        var _resp = await fetch(`https://api-game.rivet.gg/v1/matchmaker/lobbies/list`);
        var lists = await _resp.json();

        unsafeWindow.API_STATE.code = _resp.status === 200 ? 1 : (_resp.status === 429 ? 3 : 2);

        localStorage.lists = JSON.stringify({
            timestamp: Date.now(),
            list: lists,
        });
    }

    setInterval(async function() {
        if (!localStorage.lists) {
            var _resp = await fetch(`https://api-game.rivet.gg/v1/matchmaker/lobbies/list`);
            var lists = await _resp.json();

            unsafeWindow.API_STATE.code = _resp.status === 200 ? 1 : (_resp.status === 429 ? 3 : 2);
            localStorage.lists = JSON.stringify({
                timestamp: Date.now(),
                list: lists,
            });
        }

        if (Date.now() - JSON.parse(localStorage.lists)?.timestamp > 3e5) {
            var _resp = await fetch(`https://api-game.rivet.gg/v1/matchmaker/lobbies/list`);
            var lists = await _resp.json();

            unsafeWindow.API_STATE.code = _resp.status === 200 ? 1 : (_resp.status === 429 ? 3 : 2);

            localStorage.lists = JSON.stringify({
                timestamp: Date.now(),
                list: lists,
            });

            console.log('Refreshed server list.');
        }
        if (unsafeWindow.API_STATE.code === 0) unsafeWindow.API_STATE.code = 1;
    }, 5000);

    const GAMEMODES_MAP = [];
    const REGIONS_MAP = [];

    try {
        JSON.parse(localStorage.lists).list.game_modes.forEach(function(data) {
            GAMEMODES_MAP.push(data.game_mode_name);
            if (REGIONS_MAP.length === 0) {
                data.regions.forEach(function(data2) {
                    REGIONS_MAP.push(data2.region_name);
                });
            }
        });
    } catch (er) {
        alert('API of Rivet is down!');
        unsafeWindow.API_STATE.code = 2;
    }

    var do_connect = false;
    var currentServer = '';
    var override = false;
    async function fetchServer(mode, region) {
        const body = JSON.parse(localStorage.lists)?.list;

        if (!body.hasOwnProperty('game_modes') && !body.hasOwnProperty('message')) {
            unsafeWindow.API_STATE.code = 2;
            return;
        }
        if (body.message === 'too many requests') {
            unsafeWindow.API_STATE.code = 3;
            return;
        }

        calcPlayerCount();

        const lobbies = body.game_modes[GAMEMODES_MAP.indexOf(mode)]?.regions[REGIONS_MAP.indexOf(region)]?.lobbies;

        if (!lobbies) {
            unsafeWindow.API_STATE.code = 4;
            return;
        };

        lobbies.forEach(function({
            lobby_id, total_player_count, max_players_direct
        }) {
            serverWithoutCSS[mode][region].info[lobby_id] = { total_player_count, max_players_direct };

            if (!serverWithoutCSS[mode][region].lobbies.some(function(host) {
                return host === `${lobby_id}-80.lobby.${region}.hiss.io:443`
            })) {
                serverWithoutCSS[mode][region].lobbies.push(`${lobby_id}-80.lobby.${region}.hiss.io:443`);
            }
        });
        unsafeWindow.API_STATE.code = 1;

        refreshHTML();
    }

    function appendServers(mode, region) {
        fetchServer(mode, region);
    }

    function connectTo(mode, region, number) {
        if (!serverWithoutCSS[mode][region].lobbies[number]) return;
        currentServer = "wss://" + serverWithoutCSS[mode][region].lobbies[number];
        do_connect = true;
        unsafeWindow.input.execute('lb_reconnect');
    }
    unsafeWindow.servers = serverWithoutCSS;

    var _WebSocket = unsafeWindow.WebSocket;
    unsafeWindow.WebSocket = function(wss) {
        let triggered = do_connect;

        if (do_connect) wss = currentServer;
        currentServer = wss;

        const socket = new _WebSocket(wss);
        socket.addEventListener('error', function(er) {
            if (triggered)
                alert('Connection to lobby failed, redirecting...'); // This is most likely due to the target server closing or reaching player limit.
        });

        do_connect = false;
        return socket;
    };

    (function(realHTMLInputElement) {
        Object.defineProperty(HTMLTextAreaElement.prototype, 'value', {
            set: function(value) {
                if (!value.startsWith('diep.io/#')) return realHTMLInputElement.set.call(this, value);

                let [serverID, party] = value.replace('diep.io/#', '').split('00');
                serverID = getServerLink(currentServer.split("://")[1].split("-80.lobby")[0]).toUpperCase();
                value = `https://diep.io/#${serverID}00${party}`;

                return realHTMLInputElement.set.call(this, value);
            },
        });
    }(Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')));

    document.body.onkeydown = function(e) {
        if (String.fromCharCode(e.keyCode) === '\t') {
            if (document.getElementById('ServerSelector').style.display === "none") {
                document.getElementById('ServerSelector').style.display = "block";
                document.getElementById('choice').style.display = "block";
            } else {
                document.getElementById('ServerSelector').style.display = "none";
                document.getElementById('choice').style.display = "none";
            }
        }

        if (e.code === key && e[`${special}Key`]) {
            includeUncommon = !includeUncommon;
            calcPlayerCount();
        }
    }

    calcPlayerCount();
    setInterval(calcPlayerCount, 60000);

    const crx = CanvasRenderingContext2D.prototype;

    crx.fillText = new Proxy(crx.fillText, {
        apply(f, _this, args) {
            if (args[0].includes('ms lnd'))
                args[0] += ` ‖ ${PLAYER_COUNT} players`;
            return f.apply(_this, args);
        }
    });

    crx.strokeText = new Proxy(crx.strokeText, {
        apply(f, _this, args) {
            if (args[0].includes('ms lnd'))
                args[0] += ` ‖ ${PLAYER_COUNT} players`;
            return f.apply(_this, args);
        }
    });

    crx.measureText = new Proxy(crx.measureText, {
        apply(f, _this, args) {
            if (args[0].includes('ms lnd'))
                args[0] += ` ‖ ${PLAYER_COUNT} players`;
            return f.apply(_this, args);
        }
    });
})();