Better Scoreboard [ Geoguessr ] [ geoguessr.com ]

Improved lookup tool for geoguesser challenge leaderboard.

当前为 2022-10-22 提交的版本,查看 最新版本

// ==UserScript==
// @name       Better Scoreboard [ Geoguessr ] [ geoguessr.com ] 
// @version    1.2.2
// @author     Han75 - @Han75#4985
// @description  Improved lookup tool for geoguesser challenge leaderboard.
// @match      https://www.geoguessr.com/results/*
// @require http://code.jquery.com/jquery-latest.js
// @namespace han75.com
// ==/UserScript==
/*
* API endpoint for the Geoguessr challenge mode
*
* Edpoint: api/ v3/ results/ scores/ <match ID>/ <Lowest_Checked(max:50)>
*/
const endpoint = "https://www.geoguessr.com/api/v3/results/scores/";
/**
* data IS
* Map<String : Map<String..(4),Number..(2),Array< Number >..(2) >>
* WHERE
* {ID:DATA}= {Scoreboard_Pos : Name, uid, pfp, Game_Token, Total_Tcore, Total_Distance, All_Scores, All_Distances}}
*/
// This can be refactored as an array of maps instead of a map of then.  as they are already indexed by nonnegative integers
var data = {}
/*
* nameMap IS
* Map< String :  Number >
*  WHERE
*  {NAME : ID} = {name:coreBd_Pos}
*/
var nameMap = {}
// Number of players in the lobby
let numPlayers=0;
$(document).ready(function () {
   
    //render context    
    $('.results_switch__Qj1HI').after('<div id="H75lookup"></div>');
    $('#H75lookup').append('<div id="H75Controls"></div>');
    $('#H75lookup').append('<div class="results_table__FHKQm" id="H75Response"></div>');
    $('#H75Controls').append(`<label for="searchPosition">Rank:</label><input type="number" id="searchPosition"><button id="H75searchPosBtn">Search By Rank</button>`);
    $('#H75Controls').append(`<label for="searchName">Username:</label><input type="text" id="searchName"><button id="H75searchNameBtn">Search By Username</button>`);
    $("#H75searchPosBtn").prop("disabled", true);
    $("#H75searchNameBtn").prop("disabled", true);
    $("#H75searchPosBtn").click(findPosition);
    $("#H75searchNameBtn").click(findUsername);
    id = $(`meta[property='og:url']`).attr("content").split("/")[4];
    // The final stack trace is || RoundUp(NumPlayers/50) fetch requests  <- getData() <- getNumPlayers(recursive)
    // I thought this was a bad choice at first, but it’s probably better than accidentally sending sh*t loads of API requests because of preliminary fetch delays and getting slammed with a rate limit 
    findNumberOfPlayers(0,6000);
    
});


/**
 * Finds how many players are in a lobby
 * @param {2 Numbers} LowerBound,UpperBound
 */
async function findNumberOfPlayers(lowerBound, upperBound) {
    
    //Binary search babyy 
    if (Math.ceil(lowerBound) >= Math.floor(upperBound) - 1) {
        getData(Math.ceil(lowerBound));
    } else {
        let midpoint = Math.ceil((lowerBound + upperBound) / 2);
        //Fetch 1 entry from API, change search bounds and search again
        fetch(`${endpoint}${id}/${midpoint}/1`, { accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" }).then((response) => response.json())
            .then((resp) => {
                if (resp.length == 0) {
                    findNumberOfPlayers(lowerBound, midpoint);
                } else {
                    findNumberOfPlayers(midpoint, upperBound);
                }

            });;
    }


}
/**
 * Loads all of the data and stores it in data dictionary.
 * 
 */
async function getData(nPlayers) {
    numPlayers=nPlayers;
    //Fetch all the players.
    //Can fetch a max of 50 datapoints at a time 
    for(let i = 0; i < nPlayers; i += 50) {
        //accept parameter is used by native application and I added it to ensure that it Satan himself(CORS) doesn't stop me
        fetch(`${endpoint}${id}/${i}/50`, { accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" }).then((response) => response.json()).then((resp) => {
             // I hate the variable name. resp ? Like ew 
            for (let k = 0; k < resp.length; k++) {
                let scores = [];
                let dists = [];
                let tDist=0;
                for (let round = 0; round < resp[k]["game"]["player"]["guesses"].length; round++) {
                   
                    scores.push(resp[k]["game"]["player"]["guesses"][round]["roundScoreInPoints"]);
                    dists.push((resp[k]["game"]["player"]["guesses"][round]["distanceInMeters"] / 1000).toFixed(3));
                    tDist+=resp[k]["game"]["player"]["guesses"][round]["distanceInMeters"] / 1000;
                }
                data[i + k] = {"name": resp[k]["playerName"], "uid":resp[k]["userId"],"pic":resp[k]["pinUrl"],"gametoken": resp[k]["gameToken"], "totscore": resp[k]["game"]["player"]["totalScore"]["amount"],"totDist":tDist.toFixed(3), "scores": scores, "dists": dists }
                // Map user name to score for 1 : 1 correlation 
                nameMap[resp[k]["playerName"].toLowerCase()] = i + k;
            }

        })
    }
    /*
     * Display number of players in the lobby,
     * H75Lookup >> H75Control Context Map:
     * @p[lobby size]
     * @label@input[Number, GetInfoBySB_Position]
     * @button[Call findPosition]
     * @labrel@input[text, GetInfoByUsername]
     * @button[
    */
    $('#H75Controls').prepend(`<p>${nPlayers} players</p>`); 
    $("#H75searchPosBtn").prop("disabled", false);
    $("#H75searchNameBtn").prop("disabled", false);
}
// TODO: I want the @p to say “N records found (download)”
function download(){
    //TODO: do this function later take a break, jesus.
    console.log(`File will have ${Object.keys(data).length} entries`);
    var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(data));
    console.log(`File will have ${Object.keys(data).length} entries`);
    var dlAnchorElem = document.getElementById('H75exportData');
    dlAnchorElem.setAttribute("href",     dataStr     );
    dlAnchorElem.setAttribute("download", "data_.json");
}
/**
 * Searches data by Scoreboard Position from the input box
 */
// refactor to a better name
function findPosition() {
    let position = Math.floor($('#searchPosition').val() - 1);
    if (position in data) {
        let a = data[position];
        // I’m not gonna cap I straight up ripped these fancy ahh stylized ahh divs straight from the results table, whose gonna stop me? 😈
        $("#H75Response").empty();
        $("#H75Response").append(`<div class="results_row__2iTV4 results_headerRow__C91Ks"><div></div><div class="results_hideOnSmallScreen__hrv5O">Round 1</div><div class="results_hideOnSmallScreen__hrv5O">Round 2</div><div class="results_hideOnSmallScreen__hrv5O">Round 3</div><div class="results_hideOnSmallScreen__hrv5O">Round 4</div><div class="results_hideOnSmallScreen__hrv5O">Round 5</div><div>Total</div></div>`);
         //Literally what is this naming convention? Nothing about this makes sense. You don’t have to make it this hard on yourself, @GeoGuessr’s singular front end engineer
        
        $("#H75Response").append(`<div class="results_row__2iTV4" id="H75Entry"><div class="results_column__BTeok results_player__F8U_T"><span class="results_position__KWMOY">${position+1}.</span><div class="results_userLink__V6cBI"><a target="_blank" href="/user/${a["uid"]}"><div class="user-nick_root__DUfvc"><div class="user-nick_avatar__lW3e2"><div class="styles_rectangle___6gqv" style="padding-top: 100%;"><div class="styles_circle__QFYEk styles_variantFloating__Srm_N styles_colorWhite__Y640w styles_borderSizeFactorOne__8iP_3"><div class="styles_rectangle___6gqv" style="padding-top: 100%;"><div class="styles_innerCircle__Y_L_e"><div class="styles_content__otIVG"><img src="/images/auto/48/48/ce/0/plain/${a["pic"]}" class="styles_image__8M_kp" alt="Efesoturum" loading="auto" style="object-fit: cover;"></div></div></div></div></div></div><div class="user-nick_nickWrapper__8Tnk4"><div class="user-nick_nick__y4VIt">${a["name"]}&nbsp;</div></div></div></a></div></div><div class="results_column__BTeok results_hideOnSmallScreen__hrv5O"><div class="results_score__jUqyZ">${a["scores"][0]} pts</div><div class="results_scoreDetails__rvWSm">${a["dists"][0]} km</div></div><div class="results_column__BTeok results_hideOnSmallScreen__hrv5O"><div class="results_score__jUqyZ">${a["scores"][1]} pts</div><div class="results_scoreDetails__rvWSm">${a["dists"][1]} km</div></div><div class="results_column__BTeok results_hideOnSmallScreen__hrv5O"><div class="results_score__jUqyZ">${a["scores"][2]} pts</div><div class="results_scoreDetails__rvWSm">${a["dists"][2]} km</div></div><div class="results_column__BTeok results_hideOnSmallScreen__hrv5O"><div class="results_score__jUqyZ">${a["scores"][3]} pts</div><div class="results_scoreDetails__rvWSm">${a["dists"][3]} km</div></div><div class="results_column__BTeok results_hideOnSmallScreen__hrv5O"><div class="results_score__jUqyZ">${a["scores"][4]} pts</div><div class="results_scoreDetails__rvWSm">${a["dists"][4]} km</div></div><div class="results_column__BTeok"><div class="results_score__jUqyZ">${a["totscore"]} pts</div><div class="results_scoreDetails__rvWSm">${a["totDist"]} km</div></div></div>`);

        // When I click the div I open game overview in a new tab
        // I can’t show it on this page because I can’t add the the native event listener to the new div because it’s disgustingly obfuscated 
        $("#H75Entry").click(function(e){
            e.preventDefault();
        window.open(`https://www.geoguessr.com/results/${a["gametoken"]}`);    
   });
    } else if(position<1||position>numPlayers) {
        
        $('#searchPosition').val("");
    }else{
        $("#H75Response").empty();
        $("#H75Response").append(`<div class="results_row__2iTV4" id="H75Entry">Undefined error or you broke the script probably. This isn’t gonna show up any other way </div>`); 
    }
}
/**
 * searches data by username
 */
function findUsername() {
    // Get that shi from the input box. MAN I love jquery 
    let name = $('#searchName').val();
    if (name.toLowerCase() in nameMap) {
        let position=nameMap[name];
        let a=data[nameMap[name]];
        // Everything below this like is exactly the same as searchPosition
       
        $("#H75Response").empty();
        $("#H75Response").append(`<div class="results_row__2iTV4 results_headerRow__C91Ks"><div></div><div class="results_hideOnSmallScreen__hrv5O">Round 1</div><div class="results_hideOnSmallScreen__hrv5O">Round 2</div><div class="results_hideOnSmallScreen__hrv5O">Round 3</div><div class="results_hideOnSmallScreen__hrv5O">Round 4</div><div class="results_hideOnSmallScreen__hrv5O">Round 5</div><div>Total</div></div>`);
        $("#H75Response").append(`<div class="results_row__2iTV4" id="H75Entry"><div class="results_column__BTeok results_player__F8U_T"><span class="results_position__KWMOY">${position+1}.</span><div class="results_userLink__V6cBI"><a target="_blank" href="/user/${a["uid"]}"><div class="user-nick_root__DUfvc"><div class="user-nick_avatar__lW3e2"><div class="styles_rectangle___6gqv" style="padding-top: 100%;"><div class="styles_circle__QFYEk styles_variantFloating__Srm_N styles_colorWhite__Y640w styles_borderSizeFactorOne__8iP_3"><div class="styles_rectangle___6gqv" style="padding-top: 100%;"><div class="styles_innerCircle__Y_L_e"><div class="styles_content__otIVG"><img src="/images/auto/48/48/ce/0/plain/${a["pic"]}" class="styles_image__8M_kp" alt="Efesoturum" loading="auto" style="object-fit: cover;"></div></div></div></div></div></div><div class="user-nick_nickWrapper__8Tnk4"><div class="user-nick_nick__y4VIt">${a["name"]}&nbsp;</div></div></div></a></div></div><div class="results_column__BTeok results_hideOnSmallScreen__hrv5O"><div class="results_score__jUqyZ">${a["scores"][0]} pts</div><div class="results_scoreDetails__rvWSm">${a["dists"][0]} km</div></div><div class="results_column__BTeok results_hideOnSmallScreen__hrv5O"><div class="results_score__jUqyZ">${a["scores"][1]} pts</div><div class="results_scoreDetails__rvWSm">${a["dists"][1]} km</div></div><div class="results_column__BTeok results_hideOnSmallScreen__hrv5O"><div class="results_score__jUqyZ">${a["scores"][2]} pts</div><div class="results_scoreDetails__rvWSm">${a["dists"][2]} km</div></div><div class="results_column__BTeok results_hideOnSmallScreen__hrv5O"><div class="results_score__jUqyZ">${a["scores"][3]} pts</div><div class="results_scoreDetails__rvWSm">${a["dists"][3]} km</div></div><div class="results_column__BTeok results_hideOnSmallScreen__hrv5O"><div class="results_score__jUqyZ">${a["scores"][4]} pts</div><div class="results_scoreDetails__rvWSm">${a["dists"][4]} km</div></div><div class="results_column__BTeok"><div class="results_score__jUqyZ">${a["totscore"]} pts</div><div class="results_scoreDetails__rvWSm">${a["totDist"]} km</div></div></div>`)
        
        $("#H75Entry").click(function(e){
            e.preventDefault();
            window.open(`https://www.geoguessr.com/results/${a["gametoken"]}`);
            
   });
        
    } else {
        $("#H75Response").empty();
        $("#H75Response").append(`<div class="results_row__2iTV4" id="H75Entry">User not found</div>`);  
    }

}


QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址