Geoguessr rating displayer

Adds the competitive rating to usernames

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Geoguessr rating displayer
// @description  Adds the competitive rating to usernames
// @version      1.2.9
// @license      MIT
// @author       joniber
// @namespace
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @require      https://gf.qytechs.cn/scripts/460322-geoguessr-styles-scan/code/Geoguessr%20Styles%20Scan.js?version=1151668

// @namespace https://gf.qytechs.cn/users/1072330
// ==/UserScript==

//=====================================================================================\\
//    change these values however you like (make sure to hit ctrl+s afterwards)        \\
//=====================================================================================\\



const RATING_IN_FRIENDS_TAB = true;
//                            ^^^^^ set this to 'false' if you don't want to display rating in the friends tab
const RATING_IN_LEADERBOARD = true;
//                            ^^^^  set this to 'false' if you don't want to display rating in leaderboards
const RATING_IN_MATCHMAKING = true;
//                            ^^^^  set this to 'false' if you don't want to display rating in matchmaking lobbies
const RATING_IN_INGAME_PAGE = true;
//                            ^^^^  set this to 'false' if you don't want to display rating ingame

const RATING_COLOR_CHANGE = true;
//                          ^^^^ set this to 'false' if you don't want to change the color based on the ranking

const RATING_IN_BREAKDOWN = true;
//                          ^^^^ set this to 'false' if you don't want to see rating in the breakdown menu

const RATING_IN_SUGGESTIONS = true;
//                           ^^^^ set this to 'false' if you don't want to see rating in the friend suggestion list

const RATING_IN_PROFILES = true;
//                           ^^^^ set this to >>false<< if you don't want to see rating in the profile list
const GAME_MODE_RATING = true;
//                       ^^^^^ set to false if you don't want to see the game mode ratings

const OVERALL_RATING = true;
//                     ^^^^^ set to false if you don't want to see overall rating

//=====================================================================================\\
//  don't edit anything after this point unless you know what you're doing             \\
//=====================================================================================\\

const GEOGUESSR_USER_ENDPOINT = 'https://geoguessr.com/api/v3/users';
const CHAT_ENDPOINT = 'https://game-server.geoguessr.com/api/lobby';

const SCRIPT_PREFIX = 'ur__';
const PROFIlE_RATING_ID = SCRIPT_PREFIX + 'profileRating';
const USER_RATING_CLASS = SCRIPT_PREFIX + 'userRating';




const stylesUsed = [

    "user-nick_nickWrapper__",
    "distance-player-list_name__",
    "countries-player-list_playerName__",
    "hud_root__",
    "health-bar_player__",
    "user-nick_nick__",
    "round-score_container__",
    "game_hud__",
    "chat-friend_name__",
    "leaderboard_row__",
    "leaderboard_rowColumnCount3__",
    "leaderboard_columnContent__",
    "leaderboard_alignStart",
    "map-highscore_switchContainer__",
    "switch_show__",
    "map-highscore_userWrapper__",
    "results_userLink__",
    "friend_name__",
    "avatar-lobby_wrapper__",
    "avatar-title_titleWrapper__"
];



const OBSERVER_CONFIG = {
    characterDataOldValue: false,
    subtree: true,
    childList: true,
    characterData: false,
};

const ERROR_MESSAGE = (wrong) => '${wrong}';

function pathMatches(path) {
    return location.pathname.match(new RegExp(`^/(?:[^/]+/)?${path}$`));
}

function ratingText() {
    return `<p
        class="${USER_RATING_CLASS}"
        style="margin-left: .25rem; margin-right:.25rem; margin-top:0px; display: none;"
        onerror="this.style.display = 'none'"
    ></p>`;
}

function ratingText1() {
    return `<p
        id="${PROFIlE_RATING_ID}"
        style="margin-left: .25rem; margin-right:.25rem; margin-top:0px; display: none;"
        onerror="this.style.display = 'none'"
    ></p>`;
}

let flag = true
let flag1 = false

async function fillRating(ratingNumber, userId) {
    const userData = await getUserData(userId);
    let divisionNumber = userData.divisionNumber;
    if (divisionNumber && userData.rating) {
        if (RATING_COLOR_CHANGE) {
            let color;
            switch (divisionNumber) {
                case 10: /* Bronze     */ color = '#ba682e'; break;
                case 9: /* Silver III */
                case 8: /* Silver II  */
                case 7: /* Silver I   */ color = '#8e8e8e'; break;
                case 6: /* Gold III   */
                case 5: /* Gold II    */
                case 4: /* Gold I     */ color = '#e8ac06'; break;
                case 3: /* Master II  */
                case 2: /* Master I   */ color = '#e04781'; break;
                case 1: /* Champion   */ color = '#a994e3'; break;
                default: color = 'hsla(0,0%,100%,.9)';
            }
            ratingNumber.style.color = color;

            if(OVERALL_RATING){
                ratingNumber.innerHTML = userData.rating;
            }
        }
        if(GAME_MODE_RATING){
            if (userData.gameModeRatings && Object.keys(userData.gameModeRatings).length > 0) {
                ratingNumber.innerHTML += " (" +
                    (userData.gameModeRatings.standardDuels || "&ndash;") + "/" +
                    (userData.gameModeRatings.noMoveDuels || "&ndash;") + "/" +
                    (userData.gameModeRatings.nmpzDuels || "&ndash;") + ")";
            }
        }
    } else {
        // v4 API did not return data, user is either not active in ranked at the moment or in silver who in v4 have no rating
        // fall back on v3 and use gray color to indicate inactive rating or silver to display the rating of silver player
        if(userData.rating == null && userData.divisionNumber){
            //make sure user has v4 data with a rating of null
            const playerApiUrl = `https://www.geoguessr.com/api/v3/users/${userId}`;
            try {
                const response = await fetch(playerApiUrl, { method: "GET", "credentials": "include" });
                const data = await response.json();
                ratingNumber.innerHTML = data.competitive.rating;
                if(RATING_COLOR_CHANGE) {
                    ratingNumber.style.color = '#8e8e8e';
                }
            }
            catch(error){
                console.error('Failed to fetch player data:', error);
                return null;
            }
        }
        else if (! (userData.competitive.elo == 0 && userData.competitive.rating == 0)) {
            // user has played ranked in the past
            ratingNumber.innerHTML = userData.competitive.rating;
            if (RATING_COLOR_CHANGE) {
                ratingNumber.style.color = 'hsla(0,0%,100%,.9)';
            }
        }

    }

    ratingNumber.style.display = 'inline';
}

function retrieveIdFromLink(link) {
    if (link.endsWith('/me/profile')) {
        const data = document.querySelector('#__NEXT_DATA__').text;
        const json = JSON.parse(data);
        return json.props.accountProps.account.user.userId;
    }
    return link.split('/').at(-1);
}

function isOtherProfile() {
    return pathMatches('user/.+');
}

function isOwnProfile() {
    return pathMatches('me/profile');
}

function isProfile() {
    return isOwnProfile() || isOtherProfile();
}

function isBattleRoyale() {
    return pathMatches('battle-royale/.+');
}

function isDuels() {
    return pathMatches('duels/.+');
}
function isReplay() {
    return pathMatches('duels/.+/replay');
}


async function getUserData(id) {
    const playerApiUrl = `https://www.geoguessr.com/api/v4/ranked-system/progress/${id}`;

    try {
        const response = await fetch(playerApiUrl, { method: "GET", "credentials": "include" });
        const data = await response.json();
        return data;
    } catch (error) {
        const playerApiUrl = `https://www.geoguessr.com/api/v3/users/${id}`;
        try {
            const response = await fetch(playerApiUrl, { method: "GET", "credentials": "include" });
            const data = await response.json();
            return data;
        }
        catch(error){
            console.error('Failed to fetch player data:', error);
            return null;
        }
    }
}

function addRatingToUsername(link) {
    if (!link.querySelector(`.${USER_RATING_CLASS}`)) {
        const destination = link.querySelector('[class*=user-nick_nickWrapper__]');
        destination.insertAdjacentHTML('beforeend', ratingText());
        const ratingNumber = destination.lastChild;
        if(destination.children[2]){
            destination.insertBefore(
                ratingNumber,
                destination.children[2]
            );

        }

        fillRating(ratingNumber, retrieveIdFromLink(link.href));
    }
}

function addRatingToIngameUsername(link) {
    if (!link.querySelector(`.${USER_RATING_CLASS}`)) {
        const destination = link.querySelector('span');
        destination.style.display = 'flex';
        destination.innerHTML += '&nbsp;';
        destination.insertAdjacentHTML('beforeend', ratingText());
        const ratingNumber = destination.lastChild;
        if (destination.childElementCount == 4) {
            destination.insertBefore(
                ratingNumber,
                ratingNumber.previousElementSibling.previousElementSibling
            );
        } else if (destination.childElementCount > 2) {
            destination.insertBefore(ratingNumber, ratingNumber.previousElementSibling);
        }

        fillRating(ratingNumber, retrieveIdFromLink(link.href));
    }
}

let inBattleRoyale = false;
let inDuels = false;
let inReplay = false;
let lastOpenedMapHighscoreTab = 0;
async function getUsersInGame(id) {
    const fetchurl = `${CHAT_ENDPOINT}/${id}`;

    try {
        const response = await fetch(fetchurl, { method: "GET", "credentials": "include" });
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Failed to fetch player data:', error);
        return null;
    }

}


async function onMutationsBr(mutations, observer) {
    if (RATING_IN_INGAME_PAGE) {
        //battle royale distance
        for (const link of document.querySelectorAll('[class*=distance-player-list_name__] a')) {
            addRatingToIngameUsername(link);
        }

        // battle royale countries
        for (const link of document.querySelectorAll(
            '[class*=countries-player-list_playerName__] a'
        )) {
            addRatingToIngameUsername(link);
        }


    }
}

function onMutationsDuels(mutations, observer) {
    console.log("TEST")
    if (RATING_IN_INGAME_PAGE) {
        // duels
        const hud = document.querySelector('[class*=hud_root__]');

        const firstPlayerLink = hud.firstChild?.querySelector('[class*=health-bar_player__] a');
        if (firstPlayerLink) {
            addRatingToUsername(firstPlayerLink);
        }

        const secondPlayerLink = hud.lastChild?.querySelector('[class*=health-bar_player__] a');
        if (secondPlayerLink) {
            if (addRatingToUsername(secondPlayerLink, 'afterbegin')) {
                const name = secondPlayerLink.querySelector('[class*=user-nick_nick__]');
                name.innerHTML = '&nbsp;' + name.innerHTML;
            }
        }





    }
}

function onMutationsReplay(mutations, observer) {
    if (RATING_IN_INGAME_PAGE) {
        // duels
        const hud = document.querySelector('[class*=hud_healthBars__]');

        const firstPlayerLink = hud.firstChild?.querySelector('[class*=health-bar_player__] a');
        if (firstPlayerLink) {
            addRatingToUsername(firstPlayerLink);
        }

        const secondPlayerLink = hud.lastChild?.querySelector('[class*=health-bar_player__] a');
        if (secondPlayerLink) {
            if (addRatingToUsername(secondPlayerLink, 'afterbegin')) {
                const name = secondPlayerLink.querySelector('[class*=user-nick_nick__]');
                name.innerHTML = '&nbsp;' + name.innerHTML;
            }
        }





    }
}


async function onMutationsStandard(mutations, observer) {
    if (isBattleRoyale() && document.querySelector('[class*=game_hud__] ul') && !inBattleRoyale) {
        inBattleRoyale = true;
        const brObserver = new MutationObserver(onMutationsBr);
        brObserver.observe(document.querySelector('[class*=game_hud__] ul'), OBSERVER_CONFIG);
    } else if (isDuels() && document.querySelector('[class*=duels_hud__]') && !inDuels) {
        inDuels = true;
        const duelsObserver = new MutationObserver(onMutationsDuels);
        duelsObserver.observe(document.querySelector('[class*=duels_hud__]'), OBSERVER_CONFIG);
    } else if (inBattleRoyale && !document.querySelector('[class*=game_hud__] ul')) {
        inBattleRoyale = false;
    } else if (inDuels && !document.querySelector('[class*=duels_hud__]')) {
        inDuels = false;
    }
    else if (isReplay() && document.querySelector('[class*=replay_main__]') && !inReplay) {
        inReplay = true;
        const replayOberserver = new MutationObserver(onMutationsReplay);
        replayOberserver.observe(document.querySelector('[class*=replay_main__]'), OBSERVER_CONFIG);

    }
    else if (inReplay && !document.querySelector('[class*=replay_main__]')) {
        inReplay = false;
    }


    if (inBattleRoyale || inDuels) {
        return;
    }

    if (RATING_IN_FRIENDS_TAB) {
        // friends tab
        for (const link of document.querySelectorAll('[class*=chat-friend_name__] a')) {
            addRatingToUsername(link);
        }
    }

    if (isProfile() && RATING_IN_PROFILES) {
        // user profile
        if (!document.querySelector(`#${PROFIlE_RATING_ID}`)) {

            const destination = document.querySelector("[class*=headline_heading__] [class*=user-nick_nick__]")
            destination.insertAdjacentHTML("beforeend", ratingText1())

            const yk = destination.lastChild

            fillRating(yk, retrieveIdFromLink(location.href))


        }

    }

    if (RATING_IN_LEADERBOARD) {
        // map highscore leaderboard
        let tabSwitch = document.querySelector('[class*=map-highscore_switchContainer__] div');
        if (tabSwitch) {
            const openedMapHighscoreTab =
                  +tabSwitch.firstChild.firstChild.classList.contains('[class*=switch_hide__]');
            if (openedMapHighscoreTab != lastOpenedMapHighscoreTab) {
                lastOpenedMapHighscoreTab = openedMapHighscoreTab;
                for (const link of document.querySelectorAll(
                    '[class*=map-highscore_userWrapper__] a'
                )) {
                    const rating = link.querySelector(`.${USER_RATING_CLASS}`);
                    if (rating) {
                        rating.remove();
                    }
                }
            }
            let tabSwitch1 = document.querySelector('[class*=map-highscore_switchContainer__]');
            const openFriendHighscoreTab = tabSwitch1.lastChild.lastChild
            if(openFriendHighscoreTab){
                if(openFriendHighscoreTab.firstChild.classList.contains('[class*=switch_show__]') && flag){
                    flag = false
                    flag1 = true
                    for (const link of document.querySelectorAll(
                        '[class*=map-highscore_userWrapper__] a'
                    )) {
                        const rating = link.querySelector(`.${USER_RATING_CLASS}`);
                        if (rating) {
                            rating.remove();
                        }
                    }
                }
            }

            const openFriendHighscoreTab1 = tabSwitch1.lastChild.firstChild
            if(openFriendHighscoreTab1){
                if(openFriendHighscoreTab1.firstChild.classList.contains('[class*=switch_show__]') && flag1){
                    flag = true
                    flag1 = false
                    for (const link of document.querySelectorAll(
                        '[class*=map-highscore_userWrapper__] a'
                    )) {
                        const rating = link.querySelector(`.${USER_RATING_CLASS}`);
                        if (rating) {
                            rating.remove();
                        }
                    }
                }
            }

        }

        for (const link of document.querySelectorAll('[class*=map-highscore_userWrapper__] a')) {
            addRatingToUsername(link);
        }
    }

    if (RATING_IN_BREAKDOWN) {
        for (const link of document.querySelectorAll('[class*=results_userLink__] a')) {
            addRatingToUsername(link);
        }
    }

    if (RATING_IN_SUGGESTIONS) {
        for (const link of document.querySelectorAll('[class*=friend_name__] a')) {
            addRatingToUsername(link);
        }
    }

    if (RATING_IN_MATCHMAKING) {
        if(document.querySelector('[class*=avatar-lobby_wrapper__]')){
            let lobbyWrapper = document.querySelector('[class*=avatar-lobby_wrapper__]')

            for (let link of document
                 .querySelector('[class*=avatar-lobby_wrapper__]')
                 .querySelectorAll('[class*=avatar-title_titleWrapper__]')) {
                if (!link.querySelector(`.${USER_RATING_CLASS}`)) {
                    if (link.querySelector('[class*=user-nick_nick__]')) {
                        const lobby = await getUsersInGame(location.href.slice(-36));
                        for (const player of lobby.players) {
                            if (!link.querySelector(`.${USER_RATING_CLASS}`)) {

                                const nickElement = link.querySelector('class*=[user-nick_nick__]');
                                const isPlayerNick = nickElement.textContent.trim() == player.nick;

                                if (isPlayerNick) {
                                    let destination = nickElement
                                    destination.insertAdjacentHTML('beforeend', ratingText());
                                    const ratingNumber = destination.lastChild;
                                    ratingNumber.style.marginLeft = "0px";
                                    if(destination.children[2]){
                                        destination.insertBefore(
                                            ratingNumber,
                                            destination.children[2]
                                        );

                                    }

                                    fillRating(ratingNumber, player.playerId);
                                }

                            }
                        }
                    }
                }
            }


        }
    }
}


const observer = new MutationObserver(onMutationsStandard);

observer.observe(document.body, OBSERVER_CONFIG);

QingJ © 2025

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