Better GeoLeaderboard

Improved lookup tool for geoguesser challenge leaderboard.

目前為 2022-10-22 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name       Better GeoLeaderboard
// @version    1.1
// @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==

//geoguessr result API endpoint
//API structure: api/v3/results/scores/<match ID>/<start position>/<number of records(max:50)>
let endpoint = "https://www.geoguessr.com/api/v3/results/scores/";
//Stores {leaderboard position:{name,uid,pfp,gametoken,total score,total distance,all scores,all distances}}
var data = {}
//Stores {username:leaderboard position}
var nameMap = {}
//Match id. geoguessr.com/results/<id>
var id;
//num players in lobby
let numPlayers;
$(document).ready(function () {
    var hrefs = new Array();
    //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 = document.querySelector("meta[property='og:url']").getAttribute("content").split("/")[4];
    //Get up to 6000 players. Average lobby size is much much smaller.
    findNumberOfPlayers(0, 6000);

});


/**
 * Finds how many players are in a lobby
 * @param {String} id - the match ID 
 */
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.
 * @param {Number} nPlayers - How many players have played the challenge.
 */
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) => {
            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++) {
                    //Get scores for each 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 }
                nameMap[resp[k]["playerName"].toLowerCase()] = i + k;
            }

        })
    }
    $('#H75Controls').prepend(`<p>${nPlayers} players</p>`);
    
    
    $("#H75searchPosBtn").prop("disabled", false);
    $("#H75searchNameBtn").prop("disabled", false);
}
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 position from the input box
 */
function findPosition() {
    let position = Math.floor($('#searchPosition').val() - 1);
    if (position in data) {
        let a = data[position];
        $("#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 if(position<1||position>numPlayers) {
        $('#searchPosition').val("");
    }else{
        $("#H75Response").empty();
        $("#H75Response").append(`<div class="results_row__2iTV4" id="H75Entry">Something went horribly wrong</div>`); 
    }
}
/**
 * searches data by username from input box
 */
function findUsername() {
    let name = $('#searchName').val();
    if (name.toLowerCase() in nameMap) {
        let a=data[nameMap[name]];
        let position=nameMap[name];
        $("#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>`);  
    }

}