// ==UserScript== // @name User Flags // @description Adds a flag to (almost) all usernames on Geoguessr // @version 1.0.4 // @license MIT // @author zorby#1431 // @namespace https://greasyfork.org/en/users/986787-zorby // @match https://www.geoguessr.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com // @downloadURL https://update.greasyfork.icu/scripts/463916/User%20Flags.user.js // @updateURL https://update.greasyfork.icu/scripts/463916/User%20Flags.meta.js // ==/UserScript== //=====================================================================================\\ // change these values however you like (make sure to hit ctrl+s afterwards) :^) \\ //=====================================================================================\\ const BIG_FLAG_TYPE = "flagpedia" // ^^^^^^^^^ set this to either 'flagpedia' or 'geoguessr' // flagpedia flags are more detailed but harder to identify at low resolution (subjective) const SMALL_FLAG_TYPE = "geoguessr" // ^^^^^^^^^ set this to either 'flagpedia' or 'geoguessr' // geoguessr flags are less accurate but easier to identify at low resolution (subjective) const USE_IFOPE_IF_FLAG_IS_MISSING = true // ^^^^^ set this to 'true' if you want to use the IFOPE for users who don't have their coutry set // https://www.flagofplanetearth.com/ const FLAGS_IN_FRIENDS_TAB = true // ^^^^^ set this to 'true' if you want to display flags in the friends tab // not recommended if you have many friends :^) const FLAGS_IN_LEADERBOARD = true // ^^^^ set this to 'false' if you don't want to display flags in leaderboards const FLAGS_IN_PROFLE_PAGE = true // ^^^^ set this to 'false' if you don't want to display flags in profile pages const FLAGS_IN_MATCHMAKING = true // ^^^^ set this to 'false' if you don't want to display flags in matchmaking lobbies const FLAGS_IN_INGAME_PAGE = true // ^^^^ set this to 'false' if you don't want to display flags ingame //=====================================================================================\\ // don't edit anything after this point unless you know what you're doing please :^) \\ //=====================================================================================\\ /// CONSTANTS /// const FLAGPEDIA_FLAG_ENDPOINT = "https://flagcdn.com" const GEOGUESSR_FLAG_ENDPOINT = "https://www.geoguessr.com/static/flags" const GEOGUESSR_USER_ENDPOINT = "https://geoguessr.com/api/v3/users" const SCRIPT_PREFIX = "uf__" const PROFIlE_FLAG_ID = SCRIPT_PREFIX + "profileFlag" const USER_FLAG_CLASS = SCRIPT_PREFIX + "userFlag" const OBSERVER_CONFIG = { characterDataOldValue: false, subtree: true, childList: true, characterData: false } const ERROR_MESSAGE = (wrong) => `looks like you made a typo! :O\n\nmake sure to set the big flag type to either 'flagpedia' or 'geoguessr'\n(you typed '${wrong}')\n\nyours truly, user flags script :^)` /// MAIN /// let bigFlagEndpoint, smallFlagEndpoint if (BIG_FLAG_TYPE == "flagpedia") { bigFlagEndpoint = FLAGPEDIA_FLAG_ENDPOINT } else if (BIG_FLAG_TYPE == "geoguessr") { bigFlagEndpoint = GEOGUESSR_FLAG_ENDPOINT } else { alert(ERROR_MESSAGE(BIG_FLAG_TYPE)) throw new Error() } if (SMALL_FLAG_TYPE == "flagpedia") { smallFlagEndpoint = FLAGPEDIA_FLAG_ENDPOINT } else if (SMALL_FLAG_TYPE == "geoguessr") { smallFlagEndpoint = GEOGUESSR_FLAG_ENDPOINT } else { alert(ERROR_MESSAGE(SMALL_FLAG_TYPE)) throw new Error() } function bigFlag() { return `` } function smallFlag() { return `` } function pathMatches(path) { return location.pathname.match(new RegExp(`^/(?:[^/]+/)?${path}$`)) } function getFlagSvg(flagType, countryCode) { if (countryCode == null && USE_IFOPE_IF_FLAG_IS_MISSING) { return "https://upload.wikimedia.org/wikipedia/commons/e/ef/International_Flag_of_Planet_Earth.svg" } const endpoint = flagType == "big" ? bigFlagEndpoint : smallFlagEndpoint const svgName = endpoint == GEOGUESSR_FLAG_ENDPOINT ? countryCode.toUpperCase() : countryCode return `${endpoint}/${svgName}.svg` } async function fillFlag(flagImage, flagType, userId) { const userData = await getUserData(userId) const countryCode = userData.countryCode flagImage.setAttribute("src", getFlagSvg(flagType, countryCode)) flagImage.style.display = "block" } function retrieveIdFromLink(link) { if (link.endsWith("/me/profile")) { const data = document.querySelector("#__NEXT_DATA__").text const json = JSON.parse(data) return json.props.middlewareResults[1].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/.+") } async function getUserData(id) { const response = await fetch(`${GEOGUESSR_USER_ENDPOINT}/${id}`) const json = await response.json() return json } function addFlagToUsername(link, position) { position = position == null ? "beforeend" : position const dry = !link.querySelector(`.${USER_FLAG_CLASS}`) if (dry) { const destination = link.querySelector(".user-nick_nickWrapper__8Tnk4") destination.insertAdjacentHTML(position, smallFlag()) const flagImage = destination.querySelector(`.${USER_FLAG_CLASS}`) if (destination.childElementCount > 2) { destination.insertBefore(flagImage, flagImage.previousElementSibling) } fillFlag(flagImage, "small", retrieveIdFromLink(link.href)) } return dry } function addFlagToIngameUsername(link) { if (!link.querySelector(`.${USER_FLAG_CLASS}`)) { const destination = link.querySelector("span") destination.style.display = "flex" destination.innerHTML += " " destination.insertAdjacentHTML("beforeend", smallFlag()) const flagImage = destination.lastChild if (destination.childElementCount > 2) { destination.insertBefore(flagImage, flagImage.previousElementSibling) } fillFlag(flagImage, "small", retrieveIdFromLink(link.href)) } } let inBattleRoyale = false let inDuels = false let lastOpenedMapHighscoreTab = 0 function onMutationsBr(mutations, observer) { if (FLAGS_IN_INGAME_PAGE) { // battle royale distance for (const link of document.querySelectorAll(".distance-player-list_name__fPSwC a")) { addFlagToIngameUsername(link) } // battle royale countries for (const link of document.querySelectorAll(".countries-player-list_playerName__g4tnM a")) { addFlagToIngameUsername(link) } } } function onMutationsDuels(mutations, observer) { if (FLAGS_IN_INGAME_PAGE) { const hud = document.querySelector(".hud_root__RY5pu") const firstPlayerLink = hud.firstChild?.querySelector(".health-bar_player__9j0Vu a") if (firstPlayerLink) { addFlagToUsername(firstPlayerLink) } const secondPlayerLink = hud.lastChild?.querySelector(".health-bar_player__9j0Vu a") if (secondPlayerLink) { if(addFlagToUsername(secondPlayerLink, "afterbegin")) { const name = secondPlayerLink.querySelector(".user-nick_nick__y4VIt") name.innerHTML = " " + name.innerHTML } } if (document.querySelector(".round-score_container__s6qNg")) { const leftLink = document.querySelector(".round-score_healthLeft__TT8Kk .health-bar_player__9j0Vu a") addFlagToUsername(leftLink) const rightLink = document.querySelector(".round-score_healthRight__qgBbv .health-bar_player__9j0Vu a") if(addFlagToUsername(rightLink, "afterbegin")) { const name = rightLink.querySelector(".user-nick_nick__y4VIt") name.innerHTML = " " + name.innerHTML } } } } function onMutationsStandard(mutations, observer) { if (isBattleRoyale() && document.querySelector(".game_hud__h3YxY ul") && !inBattleRoyale) { console.log("Switching to br mode!") inBattleRoyale = true const brObserver = new MutationObserver(onMutationsBr) brObserver.observe(document.querySelector(".game_hud__h3YxY ul"), OBSERVER_CONFIG) } else if (isDuels() && document.querySelector(".game_hud__fhdo5") && !inDuels) { console.log("Switching to duels mode!") inDuels = true const duelsObserver = new MutationObserver(onMutationsDuels) duelsObserver.observe(document.querySelector(".game_hud__fhdo5"), OBSERVER_CONFIG) } else if (inBattleRoyale && !document.querySelector(".game_hud__h3YxY ul")) { console.log("Switching to standard mode!") inBattleRoyale = false } else if (inDuels && !document.querySelector(".game_hud__fhdo5")) { console.log("Switching to standard mode!") inDuels = false } if (inBattleRoyale || inDuels) { return } if (FLAGS_IN_PROFLE_PAGE && isProfile()) { // user profile if (!document.querySelector(`#${PROFIlE_FLAG_ID}`)) { const destination = document.querySelector(".headline_heading__c6HiU.headline_sizeLarge__DqYNn .user-nick_root__DUfvc") destination.insertAdjacentHTML("beforeend", bigFlag()) const flagImage = destination.lastChild fillFlag(flagImage, "big", retrieveIdFromLink(location.href)) } } if (FLAGS_IN_FRIENDS_TAB) { // friends tab for (const link of document.querySelectorAll(".chat-friend_name__6GRE_ a")) { addFlagToUsername(link) } } if (FLAGS_IN_LEADERBOARD) { // generic leaderboard for (const link of document.querySelectorAll(".leaderboard_columnContent__yA6b_.leaderboard_alignStart__KChAa a")) { addFlagToUsername(link) } // map highscore leaderboard let tabSwitch = document.querySelector(".map-highscore_switchContainer__wCDRH div") if (tabSwitch) { const openedMapHighscoreTab = +tabSwitch.firstChild.firstChild.classList.contains("switch_hide__OuYfZ") if (openedMapHighscoreTab != lastOpenedMapHighscoreTab) { lastOpenedMapHighscoreTab = openedMapHighscoreTab for (const link of document.querySelectorAll(".map-highscore_userWrapper__aHpCF a")) { const flag = link.querySelector(`.${USER_FLAG_CLASS}`) if (flag) { flag.remove() } } } } for (const link of document.querySelectorAll(".map-highscore_userWrapper__aHpCF a")) { addFlagToUsername(link) } } if (FLAGS_IN_MATCHMAKING) { // battle royale matchmaking for (const link of document.querySelectorAll(".player-card_userLink__HhoDo")) { addFlagToUsername(link) } } } const observer = new MutationObserver(onMutationsStandard) observer.observe(document.body, OBSERVER_CONFIG)