Better otservlist.org

Improves the display of servers on otservlist.org. Example features include filtering, sorting, hiding ads, and displaying all servers on one large page.

/******/ (() => { // webpackBootstrap
/******/ 	"use strict";

;// ./src/Core.ts
const debugMode = true;
function debug(message) {
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
    if (debugMode)
        console.log(message);
}
/** Inherit from this class if you want to ensure your type won't be assignable to other types with identical structure (and vice-versa). */
class Distinct {
}
function warnAlert(message) {
    message = `Better otservlist error:\n${message}`;
    console.warn(message);
    alert(message);
}
function as(obj, type) {
    return obj instanceof type
        ? obj
        : undefined;
}
function cast(obj, type) {
    if (obj instanceof type)
        return obj;
    else
        throw new Error(`Could not cast an object of type ${typeof (obj)} to ${type.constructor.name}.`);
}
function assertNotNull(obj, errorMessage = "Object is null!") {
    if (obj === null)
        throw new Error(errorMessage);
    else
        return obj;
}
function assertDefined(obj, errorMessage = "Object is undefined!") {
    if (obj === undefined)
        throw new Error(errorMessage);
    else
        return obj;
}
function ofType(items, type) {
    const filteredItems = [];
    for (const item of items) {
        if (item instanceof type)
            filteredItems.push(item);
    }
    return filteredItems;
}
function withoutUndefined(items) {
    const filteredItems = [];
    for (const item of items) {
        if (item !== undefined)
            filteredItems.push(item);
    }
    return filteredItems;
}
function withoutNulls(items) {
    const filteredItems = [];
    for (const item of items) {
        if (item !== null)
            filteredItems.push(item);
    }
    return filteredItems;
}
function safeQuerySelector(node, selector) {
    const foundElement = node.querySelector(selector);
    if (foundElement === null)
        throw new Error(`${selector} not found!`);
    else
        return foundElement;
}
function tryParseInt(str) {
    const num = parseInt(str);
    return isNaN(num)
        ? undefined
        : num;
}
function tryParseFloat(str) {
    const num = parseFloat(str);
    return isNaN(num)
        ? undefined
        : num;
}
function tryParseNullableFloat(str) {
    return str === ""
        ? null
        : tryParseFloat(str);
}
function asString(number) {
    return number?.toString() ?? "";
}
function edit(obj, action) {
    action(obj);
    return obj;
}
function last(array) {
    return array[array.length - 1];
}
function lazy(getter) {
    let value;
    let isValueCreated = false;
    return () => {
        if (!isValueCreated) {
            value = getter();
            isValueCreated = true;
        }
        return value;
    };
}
/** min/max == null: any non-null value is fine | value == null: value never fulfills condition, unless both min and max are null */
function withinRange(valueFunc, minFunc, maxFunc) {
    let value, min, max;
    return ((min = minFunc()) === null || ((value = valueFunc()) !== null && min <= value))
        && ((max = maxFunc()) === null || ((value = valueFunc()) !== null && value <= max));
}

;// ./src/UI.ts

function withClass(element, className) {
    element.className = className;
    return element;
}
function addStyle(cssCode) {
    const style = document.createElement("style");
    style.type = "text/css";
    style.innerHTML = `${cssCode}`;
    assertDefined(document.getElementsByTagName("head")[0]).appendChild(style);
    return style;
}
function div(...nodes) {
    const div = document.createElement("div");
    div.append(...withoutUndefined(nodes));
    return div;
}
function label(text, content) {
    const label = document.createElement("label");
    label.textContent = text;
    if (content !== undefined)
        label.insertBefore(content, label.firstChild);
    return label;
}
function UI_checkbox(boolProperty, settingApplier) {
    const checkbox = document.createElement("input");
    checkbox.type = "checkbox";
    checkbox.checked = boolProperty.get();
    checkbox.addEventListener("change", () => {
        boolProperty.set(checkbox.checked);
        settingApplier.apply();
    });
    return checkbox;
}
function numberInput(numberProperty, settingApplier) {
    const numberInput = document.createElement("input");
    numberInput.type = "number";
    numberInput.min = "0";
    numberInput.size = 8;
    numberInput.value = asString(numberProperty.get());
    numberInput.addEventListener("change", () => {
        const number = assertDefined(tryParseNullableFloat(numberInput.value));
        numberProperty.set(number);
        settingApplier.apply();
    });
    return numberInput;
}
function enumSingleSelect(enum_, enumProperty, settingApplier) {
    const singleSelect = document.createElement("select");
    const chosenValue = enumProperty.get();
    for (const enumValue of enum_.all()) {
        const option = new Option(
        /* text: */ enumValue.description, 
        /* value: */ enumValue.id, 
        /* defaultSelected: */ undefined, 
        /* selected: */ enumValue == chosenValue);
        singleSelect.add(option);
    }
    singleSelect.addEventListener("change", () => {
        const enumValue = assertDefined(enum_.byId(singleSelect.value));
        enumProperty.set(enumValue);
        settingApplier.apply();
    });
    return singleSelect;
}

;// ./src/Enum.ts

class EnumValue extends Distinct {
    constructor(id, description) {
        super();
        this.id = id;
        this.description = description;
    }
}
class Enum {
    constructor(type) {
        this._dictionary = null;
        this._type = type;
    }
    /** Key is the enum value's id, not property's name! */
    _getDictionary() {
        if (this._dictionary === null) {
            this._dictionary = {};
            const values = ofType(Object.values(this), this._type);
            for (const value of values)
                this._dictionary[value.id] = value;
        }
        return this._dictionary;
    }
    all() {
        return Object.values(this._getDictionary());
    }
    byId(id) {
        return this._getDictionary()[id];
    }
}

;// ./src/ServerSorts.ts

class ServerPropertyForSorting {
    constructor(getter, highestFirst) {
        this._getter = getter;
        this._highestFirst = highestFirst;
    }
    /** -1: show s1 first, 1: show s2 first */
    compareFn() {
        return (s1, s2) => {
            const s1Value = this._getter(s1);
            const s2Value = this._getter(s2);
            let result;
            if (s1Value > s2Value)
                result = this._highestFirst ? -1 : 1;
            else if (s1Value < s2Value)
                result = this._highestFirst ? 1 : -1;
            else
                result = 0;
            return result;
        };
    }
}
class ServerSort extends EnumValue {
    constructor(id, description, propertyForSorting) {
        super(id, description);
        this.propertyForSorting = propertyForSorting;
    }
}
class _ServerSorts extends Enum {
    constructor() {
        super(...arguments);
        this.Default = new ServerSort("Default", "(default)", null);
        this.DateAddedNewestFirst = new ServerSort("DateAddedNewestFirst", "Date added (newest first)", new ServerPropertyForSorting(s => s.id(), true));
        this.DateAddedOldestFirst = new ServerSort("DateAddedOldestFirst", "Date added (oldest first)", new ServerPropertyForSorting(s => s.id(), false));
        this.ExpRateHighestFirst = new ServerSort("ExpRateHighestFirst", "Exp rate (highest first)", new ServerPropertyForSorting(s => s.expRate(), true));
        this.ExpRateLowestFirst = new ServerSort("ExpRateLowestFirst", "Exp rate (lowest first)", new ServerPropertyForSorting(s => s.expRate(), false));
        this.PlayerCountNowHighestFirst = new ServerSort("PlayerCountNowHighestFirst", "Players now (highest first)", new ServerPropertyForSorting(s => s.playerCount().now ?? -1, true));
        this.PlayerCountNowLowestFirst = new ServerSort("PlayerCountNowLowestFirst", "Players now (lowest first)", new ServerPropertyForSorting(s => s.playerCount().now ?? -1, false));
        this.UptimeHighestFirst = new ServerSort("UptimeHighestFirst", "Uptime (highest first)", new ServerPropertyForSorting(s => s.uptimePercent(), true));
        this.UptimeLowestFirst = new ServerSort("UptimeLowestFirst", "Uptime (lowest first)", new ServerPropertyForSorting(s => s.uptimePercent(), false));
        this.PointsHighestFirst = new ServerSort("PointsHighestFirst", "Points (highest first)", new ServerPropertyForSorting(s => s.points() ?? -1, true));
        this.PointsLowestFirst = new ServerSort("PointsLowestFirst", "Points (lowest first)", new ServerPropertyForSorting(s => s.points() ?? 1, false));
        this.VersionNewestFirst = new ServerSort("VersionNewestFirst", "Version (newest first)", new ServerPropertyForSorting(s => s.clientVersion() ?? -1, true));
        this.VersionOldestFirst = new ServerSort("VersionOldestFirst", "Version (oldest first)", new ServerPropertyForSorting(s => s.clientVersion() ?? -1, false));
    }
}
const ServerSorts = new _ServerSorts(ServerSort);
/* harmony default export */ const src_ServerSorts = (ServerSorts);

;// ./src/Settings.ts


class StoredVariable {
    constructor(key, defaultValue) {
        //if (this._allKeys.has(key))
        //    throw new Error(`There is already a StoredVariable with key "${key}"!`);
        //this._allKeys.add(key);
        /** @readonly */
        this.key = key;
        /** @readonly */
        this.defaultValue = defaultValue;
    }
    //private readonly _allKeys: Set<string> = new Set<string>();
    get() {
        const storedString = localStorage.getItem(this.key);
        if (storedString === null) // No stored value
            return this.defaultValue;
        const deserializedValue = this.deserialize(storedString);
        if (deserializedValue === undefined) // Stored string couldn't be deserialized.
         {
            console.warn(`Found value "${storedString}" for ${this.key}, but it couldn't be deserialized. Returning default value.`);
            return this.defaultValue;
        }
        else
            return deserializedValue;
    }
    set(value) {
        localStorage.setItem(this.key, this.serialize(value));
    }
}
class StoredBool extends StoredVariable {
    serialize(value) {
        return value ? "1" : "0";
    }
    deserialize(storedString) {
        return (storedString === "1"
            ? true
            : storedString === "0"
                ? false
                // Else
                : undefined);
    }
}
class StoredNumber extends StoredVariable {
    serialize(value) {
        return asString(value);
    }
    deserialize(storedString) {
        return tryParseNullableFloat(storedString);
    }
}
class StoredEnum extends StoredVariable {
    constructor(enum_, key, defaultValue) {
        super(key, defaultValue);
        this._enum = enum_;
    }
    serialize(value) {
        return value.id;
    }
    deserialize(storedString) {
        return this._enum.byId(storedString);
    }
}
class Settings {
}
Settings.hideAds = new StoredBool("hideAds", false);
Settings.unpromoteServers = new StoredBool("unpromoteServer", false); // Hides "Starting soon", "Show promoted servers" and removes the yellow highlight from promoted servers.
Settings.showOfflineServers = new StoredBool("showOfflineServers", true);
Settings.minExpRate = new StoredNumber("minExpRate", null);
Settings.maxExpRate = new StoredNumber("maxExpRate", null);
Settings.minVersion = new StoredNumber("minVersion", null);
Settings.maxVersion = new StoredNumber("maxVersion", null);
Settings.minPlayerCountNow = new StoredNumber("minPlayerCountNow", null);
Settings.maxPlayerCountNow = new StoredNumber("maxPlayerCountNow", null);
Settings.sortBy = new StoredEnum(src_ServerSorts, "sortBy", src_ServerSorts.Default);

;// ./src/SettingAppliers.ts


class SettingApplier {
    constructor() {
        this.hiddenElements = [];
    }
    hide(htmlElement) {
        if (htmlElement !== null && htmlElement !== undefined) {
            htmlElement.hidden = true;
            this.hiddenElements.push(htmlElement);
        }
    }
    hideIfHtmlElement(element) {
        if (element instanceof HTMLElement)
            this.hide(element);
    }
    undoHidingElements() {
        for (const element of this.hiddenElements)
            element.hidden = false;
        this.hiddenElements = [];
    }
}
class HideAds extends SettingApplier {
    apply() {
        if (Settings.hideAds.get()) {
            // All pages
            this.hideIfHtmlElement(document.querySelector("#homeboxes"));
            // Servlist pages
            this.hideIfHtmlElement(document.querySelector("#banner_cont"));
            // Specific server pages
            const serverAboutTable = as(document.querySelector("#serverabout"), HTMLTableElement);
            if (serverAboutTable !== undefined) {
                const probablyAdBannerRow = as(serverAboutTable.tBodies[0]?.children[0], HTMLTableRowElement);
                const probablyAdBannerLink = as(probablyAdBannerRow?.querySelector("a"), HTMLAnchorElement);
                if (probablyAdBannerLink?.href.includes("/adv/") === true)
                    this.hide(probablyAdBannerRow);
                document.querySelectorAll("#summary_banner").forEach(banner => { this.hideIfHtmlElement(banner); });
                const serverIpLink = as(document.querySelector("#content")?.querySelector(".servname")?.querySelector("a"), HTMLAnchorElement);
                if (serverIpLink !== undefined) {
                    debug("serverIpLink");
                    debug(serverIpLink.text);
                    const ip = serverIpLink.text.match(/(.*):/)?.[1];
                    if (ip !== undefined)
                        serverIpLink.href = `http://${ip}`;
                }
            }
        }
        else
            this.undoHidingElements();
    }
}
class UnpromoteServers extends SettingApplier {
    constructor(servers) {
        super();
        this._unpromotedServers = [];
        this._servers = servers;
    }
    apply() {
        if (Settings.unpromoteServers.get()) {
            const contentChildren = document.querySelector("#content")?.children;
            if (contentChildren === undefined) {
                console.warn("Unpromoting servers failed because an element with id 'content' wasn't found.");
                return;
            }
            const startingSoonServlist = as(contentChildren[0], HTMLTableElement);
            const firstBr = as(contentChildren[1], HTMLBRElement);
            const secondBr = as(contentChildren[2], HTMLBRElement);
            const showPromotedServersBar = as(contentChildren[3], HTMLDivElement);
            const thirdBr = as(contentChildren[4], HTMLBRElement);
            if (startingSoonServlist !== undefined && startingSoonServlist.id == "servlist") {
                this.hide(startingSoonServlist);
                this.hide(firstBr);
                this.hide(secondBr);
            }
            if (showPromotedServersBar !== undefined && showPromotedServersBar.className == "promoted_bar") {
                this.hide(showPromotedServersBar);
                this.hide(thirdBr);
            }
            for (const promotedServer of this._servers.filter(s => s.element.id == "prom")) {
                promotedServer.element.id = "s";
                this._unpromotedServers.push(promotedServer);
            }
        }
        else {
            this.undoHidingElements();
            for (const unpromotedServer of this._unpromotedServers)
                unpromotedServer.element.id = "prom";
            this._unpromotedServers = [];
        }
    }
}
class FilterAndSortServers extends SettingApplier {
    constructor(servers, mainServlist) {
        super();
        this._servers = servers;
        this._mainServlist = mainServlist;
    }
    apply() {
        const filteredServers = [];
        const minExpRate = lazy(() => Settings.minExpRate.get());
        const maxExpRate = lazy(() => Settings.maxExpRate.get());
        const minVersion = lazy(() => Settings.minVersion.get());
        const maxVersion = lazy(() => Settings.maxVersion.get());
        const minPlayerCountNow = lazy(() => Settings.minPlayerCountNow.get());
        const maxPlayerCountNow = lazy(() => Settings.maxPlayerCountNow.get());
        const showOfflineServers = lazy(() => Settings.showOfflineServers.get());
        for (const server of this._servers) {
            const criteriaFulfilled = withinRange(server.expRate, minExpRate, maxExpRate)
                && withinRange(server.clientVersion, minVersion, maxVersion)
                && withinRange(() => server.playerCount().now, minPlayerCountNow, maxPlayerCountNow)
                && (showOfflineServers() || server.isOnline());
            if (criteriaFulfilled)
                filteredServers.push(server);
        }
        const propertyForSorting = Settings.sortBy.get().propertyForSorting;
        const mainServlistBody = safeQuerySelector(this._mainServlist, "tbody");
        const mainServlistHeader = assertNotNull(mainServlistBody.firstChild);
        let newServers = filteredServers;
        if (propertyForSorting !== null)
            newServers = newServers.sort(propertyForSorting.compareFn());
        mainServlistBody.replaceChildren(...[mainServlistHeader].concat(newServers.map(server => server.element)));
    }
}

;// ./src/Server.ts

class PlayerCount {
    constructor(now, highestEver, limit) {
        this.now = now;
        this.highestEver = highestEver;
        this.limit = limit;
    }
}
class Server {
    constructor(serverElement) {
        //let flagUrl = server.children[0].querySelector('img').src;
        //let ip = server.children[1].childNodes[0].text;
        this.id = lazy(() => this.parseServerProperty("otservlistUrl", cast(this.element.children[1]?.firstChild, HTMLAnchorElement).href, // e.g. "https://otservlist.org/ots/1234567"
        // e.g. "https://otservlist.org/ots/1234567"
        str => tryParseInt(last(str.split("ots/")) ?? ""))); // e.g. 1234567
        // childNodes[2] isn't useful
        //let serverName = server.children[3].childNodes[0].data;
        this.playerCount = lazy(() => this.parseServerProperty("playerCountString", this.element.children[4]?.textContent, // e.g. "8 (13) / 100" or "?? (13) / ??"
        // e.g. "8 (13) / 100" or "?? (13) / ??"
        str => {
            const regExpMatchArray = str.match(/(\d+|\?\?) \((\d+)\) \/ (\d+|\?\?)/);
            // We skip result 0 because it contains entire string.
            const nowString = regExpMatchArray?.[1]; // e.g. "8" or "??"
            const highestEverString = regExpMatchArray?.[2]; // e.g. "13" 
            const limitString = regExpMatchArray?.[3]; // e.g. "100" or "??"
            if (nowString === undefined || highestEverString === undefined || limitString === undefined)
                return undefined;
            const now = nowString == "??" ? null : tryParseInt(nowString);
            const highestEver = tryParseInt(highestEverString);
            const limit = limitString == "??" ? null : tryParseInt(limitString);
            if (now === undefined || highestEver === undefined || limit === undefined)
                return undefined;
            return new PlayerCount(now, highestEver, limit);
        }));
        this.uptimePercent = lazy(() => this.parseServerProperty("uptimeString", this.element.children[5]?.textContent, // e.g. "99.80%"
        // e.g. "99.80%"
        str => tryParseFloat(str))); // e.g. 99.80
        this.points = lazy(() => this.parseServerProperty("pointsString", this.element.children[6]?.textContent, // e.g. "120" or "??"
        // e.g. "120" or "??"
        str => tryParseInt(str) ?? (str == "??" ? null : undefined))); // e.g. 120 or null
        this.expRate = lazy(() => this.parseServerProperty("expRateString", this.element.children[7]?.textContent, // e.g. "x1.5"
        // e.g. "x1.5"
        str => tryParseFloat(str.substring(1)))); // e.g. 1.5)
        //let serverType = server.children[8].childNodes[0].data;
        this.clientVersion = lazy(() => this.parseServerProperty("clientVersionString", this.element.children[9]?.textContent, // e.g. "[ 8.54 ]" or "[ n/a ]"
        // e.g. "[ 8.54 ]" or "[ n/a ]"
        str => {
            const innerClientVersionString = str.slice(2, -2); // e.g. "8.54" or "n/a"
            // return: e.g. 8.54 or null
            return innerClientVersionString == "n/a"
                ? null
                : tryParseFloat(innerClientVersionString);
        }));
        this.element = serverElement;
    }
    isOnline() {
        return this.points() !== null;
    }
    parseServerProperty(stringToParseName, stringToParse, tryParseAction) {
        stringToParse = assertNotNull(assertDefined(stringToParse));
        return assertDefined(tryParseAction(stringToParse), `${stringToParseName} = "${stringToParse}": Incorrect format!`);
    }
}
/* harmony default export */ const src_Server = (Server);

;// ./src/_main.ts
// ==UserScript==
// @name         Better otservlist.org
// @namespace    BetterOtservlist
// @version      0.7.6
// @description  Improves the display of servers on otservlist.org. Example features include filtering, sorting, hiding ads, and displaying all servers on one large page.
// @author       Wirox
// @match        https://otservlist.org/*
// @match        https://*.otservlist.org/*
// @match        http://otservlist.org/*
// @match        http://*.otservlist.org/*
// @grant        none
// ==/UserScript==







// eslint-disable-next-line @typescript-eslint/no-floating-promises
(async function () {
    try {
        await main();
    }
    catch (error) {
        const errorMessage = error instanceof Error
            ? `BetterOtservlist error:\n${error.message}`
            : "BetterOtservlist error:\nUnknown error";
        alert(errorMessage);
        throw error;
    }
})();
async function main() {
    const hideAds = new HideAds();
    hideAds.apply();
    const servlistsOnThisPage = Array.from(document.querySelectorAll("#servlist"));
    const mainServlist = last(servlistsOnThisPage); // We get the last servList because if there are two, the first one is only promotional.
    if (mainServlist === undefined)
        return; // If there are no servlists, the plugin isn't supposed to do anything besides possibly hiding ads.
    // If the search includes the 'allServers' parameter, we transform it into a page with all servers.
    const showAllServers = new URLSearchParams(window.location.search).has("allServers");
    if (showAllServers) {
        document.title = "otservlist.org - All servers";
        safeQuerySelector(safeQuerySelector(document, "#content"), "#title").textContent = "All servers";
        const mainServlistBody = safeQuerySelector(mainServlist, "tbody");
        mainServlistBody.replaceChildren(assertNotNull(mainServlistBody.firstChild)); // We show an empty list (first child is the header) while we load all servers.
    }
    let loader;
    if (showAllServers) {
        addStyle(`
            .loader
            {
                position: fixed;
                margin: auto;
                top: -10%;
                right: 0;
                bottom: 0;
                left: 0;
                z-index: 2;
                width: 80px;
                padding: 12px;
                aspect-ratio: 1;
                border-radius: 50%;
                background: #2a5872;
                --_m: 
                    conic-gradient(#0000 10%,#000),
                    linear-gradient(#000 0 0) content-box;
                -webkit-mask: var(--_m);
                mask: var(--_m);
                -webkit-mask-composite: source-out;
                mask-composite: subtract;
                animation: l3 1s infinite linear;
            }
            @keyframes l3 {to{transform: rotate(1turn)}}
        `);
        loader = withClass(div(), "loader");
        safeQuerySelector(document, "body").appendChild(loader);
    }
    const servlists = showAllServers
        ? await getAllServlists()
        : [mainServlist];
    const serverElements = [];
    for (const servlist of servlists) {
        serverElements.push(...Array.from(servlist.querySelectorAll("#s"))); // Regular servers
        serverElements.push(...Array.from(servlist.querySelectorAll("#prom"))); // Promoted servers
    }
    const servers = serverElements.filter(e => e.children.length == 10).map(e => new src_Server(e));
    const unpromoteServers = new UnpromoteServers(servers);
    unpromoteServers.apply();
    const filterAndSortServers = new FilterAndSortServers(servers, mainServlist);
    filterAndSortServers.apply();
    addUIBeforeElement(document.querySelector(".pager") ?? mainServlist, showAllServers, hideAds, unpromoteServers, filterAndSortServers);
    loader?.remove();
}
function addUIBeforeElement(element, showAllServers, hideAds, unpromoteServers, filterAndSortServers) {
    const center = document.createElement("center");
    const divs = [];
    const expRateDiv = div(label("Exp rate: "), numberInput(Settings.minExpRate, filterAndSortServers), " – ", numberInput(Settings.maxExpRate, filterAndSortServers));
    divs.push(expRateDiv);
    const versionDiv = div(label("Version: "), numberInput(Settings.minVersion, filterAndSortServers), " – ", numberInput(Settings.maxVersion, filterAndSortServers));
    divs.push(versionDiv);
    const playerCountNowDiv = div(label("Players now: "), numberInput(Settings.minPlayerCountNow, filterAndSortServers), " – ", numberInput(Settings.maxPlayerCountNow, filterAndSortServers));
    divs.push(playerCountNowDiv);
    const sortByDiv = div(label("Sort by: "), enumSingleSelect(src_ServerSorts, Settings.sortBy, filterAndSortServers));
    divs.push(sortByDiv);
    const checkboxesDiv = div(label("Hide ads", UI_checkbox(Settings.hideAds, hideAds)), label("Unpromote servers", UI_checkbox(Settings.unpromoteServers, unpromoteServers)), showAllServers
        ? label("Show offline servers", UI_checkbox(Settings.showOfflineServers, filterAndSortServers))
        : undefined);
    divs.push(checkboxesDiv);
    if (!showAllServers) {
        const allServersLinkDiv = div(edit(document.createElement("a"), a => {
            a.href = "/search/5c7a8d09ac62611ea14b103e54b7a53b?allServers=true"; // Used to be 000[...] but for some reason that stopped working.
            a.textContent = "All servers";
        }));
        divs.push(allServersLinkDiv);
    }
    center.append(...divs);
    element.insertAdjacentElement("beforebegin", center);
}
async function getAllServlists() {
    const domParser = new DOMParser();
    const links = [
        "/search/5c7a8d09ac62611ea14b103e54b7a53b", // PVP
        "/search/bde9f97e98ae103e3ba4317f3567f4f7", // nPVP
        "/search/f495a91e7b674d49a59594ba016d061e", // PVPe
        "/search/58aad7c689271c21017df46ca43f2388", // WAR
        "/search/32f3ac635aa8045e6f6241ea14019e85", // FUN
    ];
    const promises = [];
    for (const link of links) {
        const promise = fetch(link).then(response => response.text()).catch(() => null);
        promises.push(promise);
    }
    return Promise.all(promises)
        .then(responseTexts => {
        const servLists = [];
        for (const responseText of withoutNulls(responseTexts))
            servLists.push(...Array.from(domParser.parseFromString(responseText, "text/html").querySelectorAll("#servlist")));
        if (responseTexts.filter(text => text === null).length > 0)
            warnAlert("Couldn't load some servers. Try refreshing the page.");
        return servLists;
    })
        .catch(() => {
        warnAlert("Couldn't load any servers. Try refreshing the page.");
        return [];
    });
}

/******/ })()
;