GeoGuessr More Player Stats

Show more information about a geoguessr player on their profile page

// ==UserScript==
// @name         GeoGuessr More Player Stats
// @namespace    http://tampermonkey.net/
// @version      0.0.5
// @description  Show more information about a geoguessr player on their profile page
// @author       btastic
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @grant        none
// @license      MIT
// ==/UserScript==

function checkURL() {
    return location.pathname.includes("/user") || location.pathname.includes("/me/profile");
}

function insertAfter(newNode, existingNode) {
    existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);
}

async function getUserProgress(profileId) {
    return fetch(location.origin + "/api/v4/ranked-system/progress/" + profileId)
        .then(out => out.json())
        .catch(err => { console.log(err); return null; });
}

async function getUserStats(profileId) {
    return fetch(location.origin + "/api/v3/users/" + profileId + "/stats")
        .then(out => out.json())
        .catch(err => { console.log(err); return null; });
}

function appendDivWithText(divToAppendTo, divId, text) {
    let div = document.createElement("div");
    insertAfter(div, divToAppendTo);
    div.innerHTML = '<div id="' + divId + '"></div>';
    document.getElementById(divId).innerText = text;
}

function displayAverageGameScore(moreInfoDiv, stats) {
    let averageGameScoreText = "Average game score: " + stats.averageGameScore.amount + " " + stats.averageGameScore.unit + " (" + Math.round(stats.averageGameScore.percentage, 2) + "%)";
    appendDivWithText(moreInfoDiv, "average-gs", averageGameScoreText)
}

function displayAverageTimeToPlonk(moreInfoDiv, stats) {
    let averageTimeToPlonkText = "Average time to plonk: " + stats.averageTime;
    appendDivWithText(moreInfoDiv, "average-ttp", averageTimeToPlonkText)
}

function displayAverageDistance(moreInfoDiv, stats) {
    let averageDistanceText = "Average guess distance: " + stats.averageDistance.meters.amount + " " + stats.averageDistance.meters.unit;
    appendDivWithText(moreInfoDiv, "average-distance", averageDistanceText)
}

function displayGuessesFirstPercentage(moreInfoDiv, user) {
    let percentageText = null;

    if(user) {
        percentageText = Math.round(user.guessedFirstRate * 100) + "%";
    }

    let guessFirstPercentage = percentageText ?? "N/A";
    appendDivWithText(moreInfoDiv, "guesses-first", "Guesses first: " + guessFirstPercentage)
}

function createMoreInfoDiv(baseDiv, proDiv) {
    let moreInfoDiv = document.createElement("div");
    moreInfoDiv.innerHTML = `<div id="more-info"></div>`;

    if (proDiv) {
        baseDiv.style = "display: inline-block; margin-right: 10px";
        moreInfoDiv.style.display = "inline-block";
    }

    insertAfter(moreInfoDiv, baseDiv);

    return moreInfoDiv;
}

let observer = new MutationObserver(async (mutations) => {
    if (!checkURL()) {
        return;
    }

    const profileLink = (location.pathname.includes("/me/profile")) ? document.querySelector('[name="copy-link"]').value : location.href;
    const profileId = profileLink.substr(profileLink.lastIndexOf("/") + 1);

    let user = await getUserProgress(profileId);
    let stats = await getUserStats(profileId);

    if(document.getElementById("more-info") != null)
    {
        return;
    }

    let proDiv = document.querySelector("[class*='profile-header_proBadgeWrapper__']");
    let baseDiv = (proDiv) ? proDiv.firstChild : document.querySelector("[data-qa='user-card-title']");

    let moreInfoDiv = createMoreInfoDiv(baseDiv, proDiv);

    displayAverageGameScore(moreInfoDiv, stats);
    displayAverageTimeToPlonk(moreInfoDiv, stats);
    displayAverageDistance(moreInfoDiv, stats);
    displayGuessesFirstPercentage(moreInfoDiv, user);
})

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