Diep.io Server Selector + Restore Player Count

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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);
        }
    });
})();