Ranked History

This script will generate a log of all locations. where you played ranked matches. To access the map, wait for the game page to load completely and press the "H" key on your keyboard.

// ==UserScript==
// @name         Ranked History
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  This script will generate a log of all locations. where you played ranked matches. To access the map, wait for the game page to load completely and press the "H" key on your keyboard.
// @author       HenriqueM
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @require      https://cdn.jsdelivr.net/npm/jsvectormap@1.5.3/dist/js/jsvectormap.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jsvectormap/1.4.3/maps/world.js
// @resource     IMPORTED_CSS https://cdn.jsdelivr.net/npm/jsvectormap@1.5.3/dist/css/jsvectormap.min.css
// @license MIT
// ==/UserScript==

let globalUserId

function showRanked() {
    // Seleciona a div com id "world-map-normal"
    var normalMap = document.getElementById('world-map-normal');
    // Seleciona a div com id "world-map-ranked"
    var rankedMap = document.getElementById('world-map-ranked');
    
    // Adiciona "display: none" à div "world-map-normal"
    normalMap.style.display = 'none';
    // Adiciona "display: block" à div "world-map-ranked"
    rankedMap.style.display = 'block';
}

function showNormal() {
    // Seleciona a div com id "world-map-normal"
    var normalMap = document.getElementById('world-map-normal');
    // Seleciona a div com id "world-map-ranked"
    var rankedMap = document.getElementById('world-map-ranked');
    
    // Adiciona "display: block" à div "world-map-normal"
    normalMap.style.display = 'block';
    // Adiciona "display: none" à div "world-map-ranked"
    rankedMap.style.display = 'none';
}

function loadMap() {
    // Criação da div do modal
    var modalDiv = document.createElement("div");
    modalDiv.id = "map-modal";
    modalDiv.style.position = "fixed";
    modalDiv.style.top = "0";
    modalDiv.style.right = "0";
    modalDiv.style.display = "flex";
    modalDiv.style.zIndex = "100";
    modalDiv.style.background = "white";
    modalDiv.style.justifyContent = "center";
    // modalDiv.style.padding = "1rem";
    modalDiv.style.margin = "1rem";
    modalDiv.style.left = "50%";
    modalDiv.style.transform = "translate(-50%, 0)";
    modalDiv.style.width = "90vw";
    modalDiv.style.height = "90vh";

    // Adicionando a div do modal ao final do body do documento
    document.body.appendChild(modalDiv);

    document.getElementById('map-modal').innerHTML = `
        <div id="map-holder">
            <header style="background: lightgray; display: flex; gap: 1rem; padding: 1rem;">
                <button class="button-59" id="btn-ranked">Ranked</button>
                <button class="button-59" id="btn-normal">Normal</button>
            </header>
            
            <div id="world-map-ranked" style="width: 90vw; height: calc(90vh - 80px); display: block;"></div>
            <div id="world-map-normal" style="width: 90vw; height: calc(90vh - 80px); display: block;"></div>
        </div>
    `;

    var btnRanked = document.getElementById('btn-ranked');
    btnRanked.addEventListener('click', function() {
        showRanked();
    });

    var btnNormal = document.getElementById('btn-normal');
    btnNormal.addEventListener('click', function() {
        showNormal();
    });

    const map = new jsVectorMap({
        selector: '#world-map-normal',
        map: 'world',
        markerStyle: {
            initial: {
                strokeWidth: 0,
                fill: '#ff5566',
                fillOpacity: 1,
                r: 4,
            },
            hover: {},
        },
        showTooltip: false,
        markers: getAllGameLocations()
    })

    const mapRanked = new jsVectorMap({
        selector: '#world-map-ranked',
        map: 'world',
        markerStyle: {
            initial: {
                strokeWidth: 0,
                fill: '#ff5566',
                fillOpacity: 1,
                r: 4,
            },
            hover: {},
        },
        showTooltip: false,
        markers: getAllCompetitiveLocations()
    })
  

}

function getAllGameLocations() {
    let storageData = localStorage.getItem('gameLocations');
        if (storageData) {
            // A LocalStorage está definida, então analisar o conteúdo como JSON
            storageData = JSON.parse(storageData);
    
            // Verificar se o conteúdo é um array
            if (!Array.isArray(storageData)) {
                return []
            } else {
                const arrayDeArrays = storageData.map(game => game.rounds)
                const arrayDeArrays2 = storageData.map(game => game.player.guesses)

                const arrayAchatado = arrayDeArrays.reduce(function(acumulador, valorAtual) {
                    // Concatenar cada array no acumulador
                    return acumulador.concat(valorAtual);
                }, []);

                const arrayAchatado2 = arrayDeArrays2.reduce(function(acumulador, valorAtual) {
                    // Concatenar cada array no acumulador
                    return acumulador.concat(valorAtual);
                }, []);

                const formatado = arrayAchatado.map((l, i) => {
                    return {
                        coords: [l.lat, l.lng],
                        style: { fill: calcularCor(arrayAchatado2[i].roundScore.amount) }
                    }
                })

                return formatado;
            }
        } else {
            return []
        }
}

function getAllCompetitiveLocations() {
    let storageData = localStorage.getItem('competitiveLocations');
        if (storageData) {
            // A LocalStorage está definida, então analisar o conteúdo como JSON
            storageData = JSON.parse(storageData);
    
            // Verificar se o conteúdo é um array
            if (!Array.isArray(storageData)) {
                return []
            } else {
                const arrayDeArrays = storageData.map(game => game.rounds)
                const arrayDeArrays2 = storageData.map(game => {

                    const index = game.teams.findIndex(team => team.players[0].playerId === globalUserId);
                    return game.teams[index].roundResults;
                })

                const arrayAchatado = arrayDeArrays.reduce(function(acumulador, valorAtual) {
                    // Concatenar cada array no acumulador
                    return acumulador.concat(valorAtual);
                }, []);

                const arrayAchatado2 = arrayDeArrays2.reduce(function(acumulador, valorAtual) {
                    // Concatenar cada array no acumulador
                    return acumulador.concat(valorAtual);
                }, []);

                const formatado = arrayAchatado.map((l, i) => {
                    return {
                        coords: [l.panorama.lat, l.panorama.lng],
                        style: { fill: calcularCor(arrayAchatado2[i].score) }
                    }
                })

                return formatado;
            }
        } else {
            return []
        }
}

function getGameMode() {
    if(location.pathname.includes("/battle-royale/") ) {
        return 'battle-royale'
    }
    if(location.pathname.includes("/duels/") ) {
        return 'duels'
    }

    return null
}

function calcularCor(valor) {
    if(valor == 5000) {
        return 'yellow';
    }
    // Normalizar o valor para um intervalo entre 0 e 1
    const normalizedValue = valor / 5000;

    // Calcular os componentes RGB da cor
    const red = Math.round(255 * (1 - normalizedValue));
    const green = Math.round(255 * normalizedValue);
    const blue = 0; // Sem azul para esta transição

    // Formatar a cor no formato RGB
    const cor = `rgb(${red}, ${green}, ${blue})`;

    return cor;
}


(function() {
    'use strict';

    const myCss = GM_getResourceText("IMPORTED_CSS");
    GM_addStyle(myCss);

    async function getLocationObjectGame() {
        const tag = window.location.href.substring(window.location.href.lastIndexOf('/') + 1)
        const gameMode = getGameMode()
        let game_endpoint = "https://www.geoguessr.com/api/v3/games/" + tag;
        if(gameMode) {
            game_endpoint = `https://game-server.geoguessr.com/api/${gameMode}/${tag}`
        }
        const api_url = game_endpoint

        const res = await fetch(api_url, {
            method: 'GET',
            credentials: 'include'
        });
        return await res.json();
    }

    async function getUserId() {
        const api_url = 'https://geoguessr.com/api/v3/profiles'

        const res = await fetch(api_url, {
            method: 'GET',
            credentials: 'include'
        });
        return await res.json();
    }

    async function getUserIdFromLocalStorage() {
        // Verifica se há um valor na chave "user_id" na LocalStorage
        const userId = localStorage.getItem("user_id");
    
        if (userId) {
            // Se o valor existir, retorna o valor encontrado
            return userId;
        } else {
            // Se o valor não existir, chama a função getUserId()
            const userIdObject = await getUserId();
    
            // Obtém o ID do objeto retornado pela função getUserId()
            const newUserId = userIdObject.user.id;
    
            // Salva o novo ID na LocalStorage
            localStorage.setItem("user_id", newUserId);
    
            // Retorna o novo ID
            return newUserId;
        }
    }

    getUserIdFromLocalStorage().then(userId => {
        globalUserId = userId
    }).catch(error => {
        console.error("Erro ao obter o User ID:", error);
    });

    async function saveGameLocations() {
        const chave = getGameMode() ? 'competitiveLocations' : 'gameLocations'
        const chavePrimaria = getGameMode() ? 'gameId' : 'token'
        // Obter o objeto de localização do jogo
        const gameinfo = await getLocationObjectGame();
    
        // Verificar se a LocalStorage está definida e se é um array
        let storageData = localStorage.getItem(chave);
        if (storageData) {
            // A LocalStorage está definida, então analisar o conteúdo como JSON
            storageData = JSON.parse(storageData);
    
            // Verificar se o conteúdo é um array
            if (!Array.isArray(storageData)) {
                // Se não for um array, definir como um array vazio
                storageData = [];
            } else {
                // Verificar se já existe um objeto com o mesmo token na LocalStorage
                const existingIndex = storageData.findIndex(item => item[chavePrimaria] === gameinfo[chavePrimaria]);
                if (existingIndex !== -1) {
                    // Se um objeto com o mesmo token foi encontrado, substituí-lo pelo novo objeto
                    storageData[existingIndex] = gameinfo;
                } else {
                    // Se não houver um objeto com o mesmo token, adicionar o novo objeto ao array
                    storageData.push(gameinfo);
                }
            }
        } else {
            // Se a LocalStorage não estiver definida, definir como um array contendo apenas o novo objeto
            storageData = [gameinfo];
        }
    
        // Salvar o array atualizado de volta na LocalStorage
        localStorage.setItem(chave, JSON.stringify(storageData));
    }

    

    function checkGameMode() {
        return location.pathname.includes("/game/") 
            || location.pathname.includes("/challenge/") 
            || location.pathname.includes("/battle-royale/") 
            || location.pathname.includes("/duels/");

    };

    

    function doCheck() {
        if (!document.querySelector('div[class*="result-layout_root__"]') && !document.querySelector('div[class*="game-finished-ranked_"]')) {
            sessionStorage.setItem("Checked", 0);
        } else if ((sessionStorage.getItem("Checked") || 0) == 0) {
            saveGameLocations();
            sessionStorage.setItem("Checked", 1);
        }
    };

    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
    });

    // ---------------
    
    document.addEventListener("keypress", function handleKeyPress(event) {
        // Verificar se a tecla pressionada é "h"
        if (event.key === "h") {
            loadMap()
        }
        if (event.key === "ç") {
            saveGameLocations();
        }
    });
})();

// Crie uma nova tag <style>
var styleTag = document.createElement('style');

// Defina o conteúdo da tag <style> como uma string
var cssContent = `
    .button-59 {
    align-items: center;
    background-color: #fff;
    border: 2px solid #000;
    box-sizing: border-box;
    color: #000;
    cursor: pointer;
    display: inline-flex;
    fill: #000;
    font-family: Inter,sans-serif;
    font-size: 16px;
    font-weight: 600;
    height: 48px;
    justify-content: center;
    letter-spacing: -.8px;
    line-height: 24px;
    min-width: 140px;
    outline: 0;
    padding: 0 17px;
    text-align: center;
    text-decoration: none;
    transition: all .3s;
    user-select: none;
    -webkit-user-select: none;
    touch-action: manipulation;
    }

    .button-59:focus {
    color: #171e29;
    }

    .button-59:hover {
    border-color: #06f;
    color: #06f;
    fill: #06f;
    }

    .button-59:active {
    border-color: #06f;
    color: #06f;
    fill: #06f;
    }

    @media (min-width: 768px) {
    .button-59 {
        min-width: 170px;
    }
    }
`;

// Adicione o conteúdo à tag <style>
styleTag.innerHTML = cssContent;

// Adicione a tag <style> ao corpo da página
document.body.appendChild(styleTag);