Greasy Fork

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.

目前为 2024-04-17 提交的版本。查看 最新版本

// ==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/[email protected]/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/[email protected]/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);