Better Scoreboard [ Geoguessr ] [ geoguessr.com ]

Improved lookup tool for geoguesser challenge leaderboard.

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name       Better Scoreboard [ Geoguessr ] [ geoguessr.com ] 
// @version    1.3.5
// @author     Han75 - @Han75#4985
// @description  Improved lookup tool for geoguesser challenge leaderboard.
// @match      https://www.geoguessr.com/*
// @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 holds every record collected from geoguessr's API.
* 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, {All API "guesses" data}}}
*/
var data = {}
/*
* nameMap IS
* Map< String :  STRING >
*  WHERE
*  {NAME : ID} = {name:coreBd_Pos}
*/
var nameMap = {}
// Number of players in the lobby
let numPlayers = 0;
$(document).ready(function () {

    //render context    
    waitForKeyElements(".results_switch__Qj1HI", start);

});
function start() {
    id = $(`meta[property='og:url']`).attr("content").split("/")[4];
    $('.results_switch__Qj1HI').after('<div id="bsbHeaderContainer"></div>');
    $('#bsbHeaderContainer').after('<div id="bsbBodyContainer"></div>');
    console.log('should now set css');

    $('#bsbHeaderContainer').append('<div id="bsbHeader"></div>');
    /** HEADER */
    $('#bsbHeader').append(`<div id="bsbInfo"></div>`);
    $('#bsbHeader').append('<span class="bsbSearchTab" id="singularTab">Search Single Record</span>');
    $('#bsbHeader').append(`<div class="bsbSearchContainer" id="singularContainer"></div>`);
    $('#bsbHeader').append('<span class="bsbSearchTab" id="rangeTab">Search Record Range(max:50)</span>');
    $('#bsbHeader').append(`<div class="bsbSearchContainer" id="rangeContainer"></div>`);


    $('#bsbInfo').append('<h2 class=`bsbInfoText`>Better Scoreboard</h2>');
    $('#bsbInfo').append('<h4 class=`bsbInfoText`>By Han75</h4>');

    

    $('#singularContainer').append(`<div class="bsbSearchOption" name='rankSearch'></div>`);
    $('#singularContainer').append(`<div class="bsbSearchOption" name='nameSearch'></div>`);

    $('.bsbSearchOption[name="rankSearch"]').append(`<label class="bsbInputLbl" for="searchPosition">Rank:</label><input type="number" min="1" id="searchPosition"><button class="bsbGuiBtn" id="bsbSearchPosBtn">Search By Rank</button>`);
    $('.bsbSearchOption[name="nameSearch"]').append(`<label class="bsbInputLbl for="searchName">Username:</label><input type="text" min="1" id="searchName"><button class="bsbGuiBtn" id="bsbSearchNameBtn">Search By Username</button>`);

   
    $('#rangeContainer').append(`<div class="bsbSearchOption" name='rangeFirst'></div>`);
    $('#rangeContainer').append(`<div class="bsbSearchOption" name='rangeLast'></div>`);
    $('.bsbSearchOption[name="rangeFirst"]').append(`<label class="bsbInputLbl for="searchRangeFirst">First:</label><input type="Number" id="searchRangeFirst">`);
    $('.bsbSearchOption[name="rangeLast"]').append(`<label class="bsbInputLbl for="searchRangeLast">Last:</label><input type="Number" id="searchRangeLast"><button class="bsbGuiBtn" id="bsbSearchRangeBtn">Search Range</button>`);
    $('#bsbHeader').append('<a id="exportRecords">Export All Records As .json</a>')
    
    /**BODY */
    $('#bsbBodyContainer').append('<div class="results_table__FHKQm" id="bsbBody"></div>');
    $('#bsbBodyContainer').after('<span id="closeDefaultScoreboard">Click to Toggle Default Scoreboard</span>')

    /* ALL HTML GOES ABOVE THIS LINE*/
    /* ALL CSS GOES BELOW THIS LINE*/
    $('#bsbHeader').css({ "display": "flex", 'flex-direction': 'column',"background-color": "#4D5180", "text-align": "center","border-radius":"25px" });
    
    $('#bsbHeaderContainer').css({"width":"60%", "display": "flex", 'flex-direction': 'column' });
    $('.bsbSearchContainer').css({ "display": "flex", "flex-direction": "row" });
    $('.bsbSearchTab').css({ "display": "block", "text-align": "left", "background-color": "rgb(121,80,229)", "padding": "5px" });
    
    $('.bsbSearchTab').mouseover(function () {
        $(this).css({ "background-color": "rgb(157,41,56)", "cursor": "pointer" });
    }).mouseout(function () {
        $(this).css({ "background-color": "rgb(121,80,229)", "cursor": "auto" });
    });
    /***Search Singular Tab Animate Open and Close */
    $('#singularTab').click(function () {
        $('#singularContainer').slideToggle({
            "opacity": "show",
            "bottom": "100"
        }, 500);
        //$('#singularTab').css("background-color","#4D5180");
        $('#singularTab').addClass('singularTriggerClose');
    });
    
    $('.singularTriggerClose').click(function () {
        $('#singularContainer').slideToggle({ "opacity": "show", "top": "100" }, 500);
        //$('#singularTab').css("background-color","rgb(121,80,229)");
        $('#singularTab').removeClass('singularTriggerClose');
    });
    /***Search Range Tab Animate Open and Close */
    $('#rangeTab').click(function () {
        $('#rangeContainer').slideToggle({
            "opacity": "show",
            "bottom": "100"
        }, 500);
        //$('#rangeTab').css("background-color","#4D5180");
        $('#rangeTab').addClass('rangeTriggerClose');
    });
    $('.rangeTriggerClose').click(function () {
        $('#rangeContainer').slideToggle({ "opacity": "show", "top": "100" }, 500);
        //$('#rangeTab').css("background-color","rgb(121,80,229)");
        $('#rangeTab').removeClass('rangeTriggerClose');
    });
    $('#rangeTab').click();
    $('.bsbSearchOption').css({"width":"50%", "border": "1px solid rgb(70,35,57)", "border-radius": "5px", "display": "flex", "flex-direction": "column" });
    $(".bsgGuiBtn").css({"padding":"10px"})
    $("#bsbSearchPosBtn").prop("disabled", true);
    $("#bsbSearchNameBtn").prop("disabled", true);
    $("#bsbSearchRangeBtn").prop("disabled", true);
    $("#bsbSearchPosBtn").click(findPosition);
    $("#bsbSearchNameBtn").click(findUsername);
    $("#bsbSearchRangeBtn").click(findRange);
    $("#exportRecords").click(download);
    $("#exportRecords").css({
        "color": "white",
        "padding": "10px",
        "font-weight": "bold"
    });
    $(".bsb")
    $('.bsbGuiBtn').css({"border":"none","padding":"10px","font-family":"var(--font-neo-sans);","background-color": "rgb(71,62,96)","color":"white"})
    $('.bsbGuiBtn').mouseover(function () {
        $(this).css({ "background-color": "rgb(26,26,46)", "cursor": "pointer" });
    }).mouseout(function () {
        $(this).css({ "background-color": "rgb(71,62,96)", "cursor": "auto" });
    });
    $('#exportRecords').mouseover(function () {
        $(this).css({"cursor": "pointer" });
    }).mouseout(function () {
        $(this).css({"cursor": "auto" });
    });
    $('#bsbBodyContainer').css("width","100%");
    $('.bsbInputLbl').css({"padding":"4px","font-weight":"bold"});
    $('#bsbInfo').css("padding","10px");
    
    $('#closeDefaultScoreboard').css({ "font-weight":"bold","display": "block", "text-align": "left", "background-color": "rgb(121,80,229)", "padding": "10px","width":"100%" });
    
    $('#closeDefaultScoreboard').click(function () {
        $('.results_container__9fcR8').find("> .results_table__FHKQm").slideToggle({
            "opacity": "show",
            "bottom": "100"
        }, 500);
        //$('#singularTab').css("background-color","#4D5180");
    });
    $('.cldTriggerClose').click(function () {
        $('.results_container__9fcR8').find("> .results_table__FHKQm").slideToggle({ "opacity": "show", "top": "100" }, 500);
        //$('#singularTab').css("background-color","rgb(121,80,229)");
        $('#closeDefaultScoreboard').removeClass('cldTriggerClose');
    });
    $('#closeDefaultScoreboard').mouseover(function () {
        $(this).css({ "background-color": "rgb(157,41,56)", "cursor": "pointer" });
    }).mouseout(function () {
        $(this).css({ "background-color": "rgb(121,80,229)", "cursor": "auto" });
    });
    // The final stack trace is || RoundUp(NumPlayers/50) api requests  <- getData() <- getNumPlayers(log(playercount) api requests)
    // 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 
    //This ones for testing, only 100 records
    //findNumberOfPlayers(0,100);
    //This ones for final
    
    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 whyd i do that 
            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, "guesses":resp[k]["game"]["player"]["guesses"]}
                // Map user name to score for 1 : 1 correlation 
                nameMap[resp[k]["playerName"].toLowerCase()] = i + k;
            }

        })
    }
    
    $('#bsbInfo').append(`<p>${nPlayers + 1} players </p>`);
    $("#bsbSearchPosBtn").prop("disabled", false);
    $("#bsbSearchNameBtn").prop("disabled", false);
    $('#bsbSearchRangeBtn').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`);
    $('#exportRecords').attr("href", dataStr);
    $('#exportRecords').attr("download", `Geoguessr_Export_n${numPlayers}.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? 😈
        $("#bsbBody").empty();
        $("#bsbBody").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

        $("#bsbBody").append(`<div class="results_row__2iTV4" id="bsbEntry"><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 
        $("#bsbEntry").click(function (e) {
            e.preventDefault();
            window.open(`https://www.geoguessr.com/results/${a["gametoken"]}`);
        });
    } else if (position < 1 || position > numPlayers) {

        $('#searchPosition').val("");
    } else {
        $("#bsbBody").empty();
        $("#bsbBody").append(`<div class="results_row__2iTV4" id="bsbEntry">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

        $("#bsbBody").empty();
        $("#bsbBody").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>`);
        $("#bsbBody").append(`<div class="results_row__2iTV4" id="bsbEntry"><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>`)

        $("#bsbEntry").click(function (e) {
            e.preventDefault();
            window.open(`https://www.geoguessr.com/results/${a["gametoken"]}`);

        });

    } else {
        $("#bsbBody").empty();
        $("#bsbBody").append(`<div class="results_row__2iTV4" id="bsbEntry">User not found</div>`);
    }

}
function findRange(){
    let rStart = Math.floor($("#searchRangeFirst").val())-1;
    let rEnd = Math.floor($("#searchRangeLast").val())-1;
    let n = rEnd-rStart;
    $("#bsbBody").empty();
    if(rEnd>numPlayers||rEnd<0||rStart>numPlayers||rStart<0){
        $("#bsbBody").append(`<div class="results_row__2iTV4" id="bsbEntry">Invalid search bounds. Please change your search</div>`);
    }else if(n<=0||n>50){
        $("#bsbBody").append(`<div class="results_row__2iTV4" id="bsbEntry">Number of records to search must be between 0 and 50</div>`);
    }else{
        $("#bsbBody").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>`);   
        for(let i=rStart;i<rEnd;i++){
            let a = data[i];
           
            $("#bsbBody").append(`<div class="results_row__2iTV4" id="bsbEntry${i}"><div class="results_column__BTeok results_player__F8U_T"><span class="results_position__KWMOY">${i + 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>`);
            $("#bsbBody").append(`<div class="results_rowDivider__py9ZY"></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 
            $(`#bsbEntry${i}`).click(function (e) {
                e.preventDefault();
                window.open(`https://www.geoguessr.com/results/${a["gametoken"]}`);
            });
        }
    }
}

/*****waitforkeyelements.js Used to combat no page refresh due to AJAX. *
 *      @Author BrockA
 * /
/*--- waitForKeyElements():  A utility function, for Greasemonkey scripts,
    that detects and handles AJAXed content.
    
    Usage example:

        waitForKeyElements (
            "div.comments"
            , commentCallbackFunction
        );

        //--- Page-specific function to do what we want when the node is found.
        function commentCallbackFunction (jNode) {
            jNode.text ("This comment changed by waitForKeyElements().");
        }

    IMPORTANT: This function requires your script to have loaded jQuery.
*/
function waitForKeyElements (
    selectorTxt,    /* Required: The jQuery selector string that
                        specifies the desired element(s).
                    */
    actionFunction, /* Required: The code to run when elements are
                        found. It is passed a jNode to the matched
                        element.
                    */
    bWaitOnce,      /* Optional: If false, will continue to scan for
                        new elements even after the first match is
                        found.
                    */
    iframeSelector  /* Optional: If set, identifies the iframe to
                        search.
                    */
) {
    var targetNodes, btargetsFound;

    if (typeof iframeSelector == "undefined")
        targetNodes     = $(selectorTxt);
    else
        targetNodes     = $(iframeSelector).contents ()
                                           .find (selectorTxt);

    if (targetNodes  &&  targetNodes.length > 0) {
        btargetsFound   = true;
        /*--- Found target node(s).  Go through each and act if they
            are new.
        */
        targetNodes.each ( function () {
            var jThis        = $(this);
            var alreadyFound = jThis.data ('alreadyFound')  ||  false;

            if (!alreadyFound) {
                //--- Call the payload function.
                var cancelFound     = actionFunction (jThis);
                if (cancelFound)
                    btargetsFound   = false;
                else
                    jThis.data ('alreadyFound', true);
            }
        } );
    }
    else {
        btargetsFound   = false;
    }

    //--- Get the timer-control variable for this selector.
    var controlObj      = waitForKeyElements.controlObj  ||  {};
    var controlKey      = selectorTxt.replace (/[^\w]/g, "_");
    var timeControl     = controlObj [controlKey];

    //--- Now set or clear the timer as appropriate.
    if (btargetsFound  &&  bWaitOnce  &&  timeControl) {
        //--- The only condition where we need to clear the timer.
        clearInterval (timeControl);
        delete controlObj [controlKey]
    }
    else {
        //--- Set a timer, if needed.
        if ( ! timeControl) {
            timeControl = setInterval ( function () {
                    waitForKeyElements (    selectorTxt,
                                            actionFunction,
                                            bWaitOnce,
                                            iframeSelector
                                        );
                },
                300
            );
            controlObj [controlKey] = timeControl;
        }
    }
    waitForKeyElements.controlObj   = controlObj;
}