Geoguessr Location Retriever

Get the actual location Geoguessr gave you from the result screens. Works for games and streaks, solo and challenge.

// ==UserScript==
// @name         Geoguessr Location Retriever
// @match        https://www.geoguessr.com/*
// @description  Get the actual location Geoguessr gave you from the result screens. Works for games and streaks, solo and challenge.
// @version      1.1.6
// @author       victheturtle#5159
// @grant        none
// @license      MIT
// @icon         https://www.svgrepo.com/show/12218/find.svg
// @namespace    https://greasyfork.org/users/967692-victheturtle
// ==/UserScript==

let lastChecked = 0;
let checkedResults = false;

function getPins() {
    return document.querySelectorAll("[class*='map-pin_clickable']");
};

function panoIdDecoder(geoguessrPanoId) {
    let gsvPanoId = "";
    for (let i = 0; i < geoguessrPanoId.length; i+=2) {
        let seq = geoguessrPanoId.substring(i, i+2);
        gsvPanoId += String.fromCharCode(parseInt(seq, 16));
    }
    return gsvPanoId;
}

function linkOfLocation(round, copyToClipboard) {
    if (round.panoId == null) return null;
    let lat = round.lat;
    let lng = round.lng;
    let pid = panoIdDecoder(round.panoId);
    let rh = round.heading;
    let rp = round.pitch;
    let rz = round.zoom;
    let h = Math.round(round.heading * 100) / 100;
    let p = Math.round((90 + round.pitch) * 100) / 100;
    let z = Math.round((90 - round.zoom/2.75*90) * 10) / 10;
    const extra = `"countryCode":null,"stateCode":null,"extra":{"tags":[]}`;
    let link = `https://www.google.com/maps/@${lat},${lng},3a,${z}y,${h}h${(p==90)?"":","+p+"t"}/data=!3m6!1e1!3m4!1s${pid}!2e0!7i13312!8i6656`;
    let loc_json = `{"lat":${lat},"lng":${lng},"heading":${rh},"pitch":${rp},"panoId":"${pid}","zoom":${rz},${extra}}`;
    console.log(link);
    //console.log(loc_json+",");
    if (copyToClipboard) {
        try {
            navigator.clipboard.writeText(loc_json+",");
        } catch (e) {console.log(e)};
    }
    return link;
}

function addFlagOnclicks(rounds) {
    let pin = getPins();
    for (let i = 0; i < pin.length; i++) {
        let link = linkOfLocation(rounds[(pin.length>1) ? pin[i].innerText-1 : rounds.length-1-i], pin.length==1);
        if (link != null) pin[i].onclick = function () {window.open(link, '_blank');};
    }
};

function addStreakChallengeOnclicks(rounds) {
    setTimeout(() => {
        let playersTable = document.querySelectorAll("div[class*='results_highscoreHeader__']")[0].parentElement.children[1];
        let roundsTable = document.querySelectorAll("div[class*='results_highscoreHeader__']")[1].parentElement;
        for (let i = 0; i < playersTable.children.length; i += 2) {
            playersTable.children[i].onclick = function () {addStreakChallengeOnclicks(rounds);};
        }
        for (let i = 1; i < roundsTable.children.length; i++) {
            let link = linkOfLocation(rounds[i-1], false);
            console.log(link);
            if (link != null) roundsTable.children[i].onclick = function () {window.open(link, '_blank');};
            roundsTable.children[i].style="cursor: pointer;";
        }
    }, 200);
}

function check() {
    const game_tag = location.pathname.substr(location.pathname.lastIndexOf("/")+1);
    let api_url = location.origin + "/api/v3/games/" + game_tag;
    if (location.pathname.includes("/challenge") || !!document.querySelector("div[class*='switch_switch__']")) {
        api_url = location.origin + "/api/v3/challenges/" + game_tag + "/game";
    };
    fetch(api_url)
    .then(res => res.json())
    .then(out => {
        addFlagOnclicks(out.rounds.slice(0, out.player.guesses.length));
        if (out.type == "challenge" && out.mode == "streak") {
            let api_url2 = location.origin + "/api/v3/results/highscores/"+game_tag+"?friends=false&limit=1";
            fetch(api_url2)
            .then(res => res.json())
            .then(out => addStreakChallengeOnclicks(out[0].game.rounds))
            .catch(err => { throw err });
        };
    }).catch(err => { throw err });

};

function doCheck() {
    let pinCount = getPins().length;
    if (pinCount == 0) {
        lastChecked = 0;
        checkedResults = false;
    } else if (pinCount != lastChecked || location.pathname.includes("/results") && !checkedResults && document.readyState == "complete") {
        lastChecked = pinCount;
        checkedResults = location.pathname.includes("/results");
        check();
    }
};

function checkGameMode() {
    return location.pathname.includes("/results") || location.pathname.includes("/game") || location.pathname.includes("/challenge")
}


let lastDoCheckCall = 0;
new MutationObserver(async (mutations) => {
    if (!checkGameMode() || lastDoCheckCall >= (Date.now() - 50)) return;
    lastDoCheckCall = Date.now();
    doCheck();
}).observe(document.body, { subtree: true, childList: true });