User Flags

Adds a flag to (almost) all usernames

当前为 2023-04-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name User Flags
  3. // @description Adds a flag to (almost) all usernames
  4. // @version 1.0.1
  5. // @license MIT
  6. // @author zorby#1431
  7. // @namespace https://greasyfork.org/en/users/986787-zorby
  8. // @match https://www.geoguessr.com/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
  10. // ==/UserScript==
  11.  
  12. //=====================================================================================\\
  13. // change these values however you like (make sure to hit ctrl+s afterwards) :^) \\
  14. //=====================================================================================\\
  15.  
  16.  
  17.  
  18. const BIG_FLAG_TYPE = "flagpedia"
  19. // ^^^^^^^^^ set this to either 'flagpedia' or 'geoguessr'
  20. // flagpedia flags are more detailed but harder to identify at low resolution (subjective)
  21. const SMALL_FLAG_TYPE = "geoguessr"
  22. // ^^^^^^^^^ set this to either 'flagpedia' or 'geoguessr'
  23. // geoguessr flags are less accurate but easier to identify at low resolution (subjective)
  24.  
  25.  
  26. const USE_IFOPE_IF_FLAG_IS_MISSING = false
  27. // ^^^^^ set this to 'true' if you want to use the IFOPE for users who don't have their coutry set
  28. // https://www.flagofplanetearth.com/
  29.  
  30.  
  31. const FLAGS_IN_FRIENDS_TAB = false
  32. // ^^^^^ set this to 'true' if you want to display flags in the friends tab
  33. // not recommended if you have many friends :^)
  34. const FLAGS_IN_LEADERBOARD = true
  35. // ^^^^ set this to 'false' if you don't want to display flags in leaderboards
  36. const FLAGS_IN_PROFLE_PAGE = true
  37. // ^^^^ set this to 'false' if you don't want to display flags in profile pages
  38. const FLAGS_IN_MATCHMAKING = true
  39. // ^^^^ set this to 'false' if you don't want to display flags in matchmaking lobbies
  40. const FLAGS_IN_INGAME_PAGE = true
  41. // ^^^^ set this to 'false' if you don't want to display flags ingame
  42.  
  43.  
  44.  
  45. //=====================================================================================\\
  46. // don't edit anything after this point unless you know what you're doing please :^) \\
  47. //=====================================================================================\\
  48.  
  49.  
  50.  
  51. /// CONSTANTS ///
  52. const FLAGPEDIA_FLAG_ENDPOINT = "https://flagcdn.com"
  53. const GEOGUESSR_FLAG_ENDPOINT = "https://www.geoguessr.com/static/flags"
  54. const GEOGUESSR_USER_ENDPOINT = "https://geoguessr.com/api/v3/users"
  55.  
  56. const SCRIPT_PREFIX = "uf__"
  57. const PROFIlE_FLAG_ID = SCRIPT_PREFIX + "profileFlag"
  58. const USER_FLAG_CLASS = SCRIPT_PREFIX + "userFlag"
  59.  
  60. const OBSERVER_CONFIG = {
  61. characterDataOldValue: false,
  62. subtree: true,
  63. childList: true,
  64. characterData: false
  65. }
  66.  
  67. 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 :^)`
  68.  
  69.  
  70. /// MAIN ///
  71. let bigFlagEndpoint, smallFlagEndpoint
  72.  
  73. if (BIG_FLAG_TYPE == "flagpedia") {
  74. bigFlagEndpoint = FLAGPEDIA_FLAG_ENDPOINT
  75. } else if (BIG_FLAG_TYPE == "geoguessr") {
  76. bigFlagEndpoint = GEOGUESSR_FLAG_ENDPOINT
  77. } else {
  78. alert(ERROR_MESSAGE(BIG_FLAG_TYPE))
  79. throw new Error()
  80. }
  81.  
  82. if (SMALL_FLAG_TYPE == "flagpedia") {
  83. smallFlagEndpoint = FLAGPEDIA_FLAG_ENDPOINT
  84. } else if (SMALL_FLAG_TYPE == "geoguessr") {
  85. smallFlagEndpoint = GEOGUESSR_FLAG_ENDPOINT
  86. } else {
  87. alert(ERROR_MESSAGE(SMALL_FLAG_TYPE))
  88. throw new Error()
  89. }
  90.  
  91. function bigFlag() {
  92. return `<img
  93. id="${PROFIlE_FLAG_ID}"
  94. style="margin-left: 0.4rem; vertical-align: middle; border-radius: 0.125rem; display: none;"
  95. width=30
  96. onerror="this.style.display = 'none'"
  97. >`
  98. }
  99.  
  100. function smallFlag() {
  101. return `<img
  102. class="${USER_FLAG_CLASS}"
  103. style="margin-left: 0.1rem; vertical-align: middle; border-radius: 0.08rem; display: none;"
  104. width=13
  105. onerror="this.style.display = 'none'"
  106. >`
  107. }
  108.  
  109. function pathMatches(path) {
  110. return location.pathname.match(new RegExp(`^/(?:[^/]+/)?${path}$`))
  111. }
  112.  
  113. function getFlagSvg(flagType, countryCode) {
  114. if (countryCode == null && USE_IFOPE_IF_FLAG_IS_MISSING) {
  115. return "https://upload.wikimedia.org/wikipedia/commons/e/ef/International_Flag_of_Planet_Earth.svg"
  116. }
  117.  
  118. const endpoint = flagType == "big" ? bigFlagEndpoint : smallFlagEndpoint
  119. const svgName = endpoint == GEOGUESSR_FLAG_ENDPOINT ? countryCode.toUpperCase() : countryCode
  120.  
  121. return `${endpoint}/${svgName}.svg`
  122. }
  123.  
  124. async function fillFlag(flagImage, flagType, userId) {
  125. const userData = await getUserData(userId)
  126. const countryCode = userData.countryCode
  127.  
  128. flagImage.setAttribute("src", getFlagSvg(flagType, countryCode))
  129. flagImage.style.display = "block"
  130. }
  131.  
  132. function retrieveIdFromLink(link) {
  133. if (link.endsWith("/me/profile")) {
  134. const data = document.querySelector("#__NEXT_DATA__").text
  135. const json = JSON.parse(data)
  136. return json.props.middlewareResults[1].account.user.userId
  137. }
  138. return link.split("/").at(-1)
  139. }
  140.  
  141. function isOtherProfile() {
  142. return pathMatches("user/.+")
  143. }
  144.  
  145. function isOwnProfile() {
  146. return pathMatches("me/profile")
  147. }
  148.  
  149. function isProfile() {
  150. return isOwnProfile() || isOtherProfile()
  151. }
  152.  
  153. function isBattleRoyale() {
  154. return pathMatches("battle-royale/.+")
  155. }
  156.  
  157. function isDuels() {
  158. return pathMatches("duels/.+")
  159. }
  160.  
  161. async function getUserData(id) {
  162. const response = await fetch(`${GEOGUESSR_USER_ENDPOINT}/${id}`)
  163. const json = await response.json()
  164.  
  165. return json
  166. }
  167.  
  168. function addFlagToUsername(link) {
  169. if (!link.querySelector(`.${USER_FLAG_CLASS}`)) {
  170. const destination = link.querySelector(".user-nick_nickWrapper__8Tnk4")
  171. destination.insertAdjacentHTML("beforeend", smallFlag())
  172. const flagImage = destination.lastChild
  173.  
  174. if (destination.childElementCount > 2) {
  175. destination.insertBefore(flagImage, flagImage.previousElementSibling)
  176. }
  177.  
  178. fillFlag(flagImage, "small", retrieveIdFromLink(link.href))
  179. }
  180. }
  181.  
  182. function addFlagToIngameUsername(link) {
  183. if (!link.querySelector(`.${USER_FLAG_CLASS}`)) {
  184. const destination = link.querySelector("span")
  185. destination.style.display = "flex"
  186. destination.innerHTML += "&nbsp;"
  187. destination.insertAdjacentHTML("beforeend", smallFlag())
  188. const flagImage = destination.lastChild
  189.  
  190. if (destination.childElementCount > 2) {
  191. destination.insertBefore(flagImage, flagImage.previousElementSibling)
  192. }
  193.  
  194. fillFlag(flagImage, "small", retrieveIdFromLink(link.href))
  195. }
  196. }
  197.  
  198. let inBattleRoyale = false
  199. let inDuels = false
  200. let lastOpenedMapHighscoreTab = 0
  201.  
  202. function onMutationsBr(mutations, observer) {
  203. if (FLAGS_IN_INGAME_PAGE) {
  204. // battle royale distance
  205. for (const link of document.querySelectorAll(".distance-player-list_name__fPSwC a")) {
  206. addFlagToIngameUsername(link)
  207. }
  208.  
  209. // battle royale countries
  210. for (const link of document.querySelectorAll(".countries-player-list_playerName__g4tnM a")) {
  211. addFlagToIngameUsername(link)
  212. }
  213. }
  214. }
  215.  
  216. function onMutationsDuels(mutations, observer) {
  217. if (FLAGS_IN_INGAME_PAGE) {
  218. // duels
  219. for (const link of document.querySelectorAll(".health-bar_player__9j0Vu a")) {
  220. addFlagToIngameUsername(link)
  221. }
  222. }
  223. }
  224.  
  225. function onMutationsStandard(mutations, observer) {
  226. if (isBattleRoyale() && document.querySelector(".game_hud__h3YxY ul") && !inBattleRoyale) {
  227. console.log("Switching to br mode!")
  228. inBattleRoyale = true
  229.  
  230. const brObserver = new MutationObserver(onMutationsBr)
  231. brObserver.observe(document.querySelector(".game_hud__h3YxY ul"), OBSERVER_CONFIG)
  232. } else if (isDuels() && document.querySelector(".game_hud__fhdo5") && !inDuels) {
  233. console.log("Switching to duels mode!")
  234. inDuels = true
  235.  
  236. const duelsObserver = new MutationObserver(onMutationsDuels)
  237. duelsObserver.observe(document.querySelector(".game_hud__fhdo5"), OBSERVER_CONFIG)
  238. } else if (inBattleRoyale && !document.querySelector(".game_hud__h3YxY ul")) {
  239. console.log("Switching to standard mode!")
  240. inBattleRoyale = false
  241. } else if (inDuels && !document.querySelector(".game_hud__fhdo5")) {
  242. console.log("Switching to standard mode!")
  243. inDuels = false
  244. }
  245.  
  246. if (inBattleRoyale || inDuels) {
  247. return
  248. }
  249.  
  250. if (FLAGS_IN_PROFLE_PAGE && isProfile()) {
  251. // user profile
  252. if (!document.querySelector(`#${PROFIlE_FLAG_ID}`)) {
  253. const destination = document.querySelector(".headline_heading__c6HiU.headline_sizeLarge__DqYNn .user-nick_root__DUfvc")
  254. destination.insertAdjacentHTML("beforeend", bigFlag())
  255. const flagImage = destination.lastChild
  256.  
  257. fillFlag(flagImage, "big", retrieveIdFromLink(location.href))
  258. }
  259. }
  260.  
  261. if (FLAGS_IN_FRIENDS_TAB) {
  262. // friends tab
  263. for (const link of document.querySelectorAll(".friend2_name__m43Za .anchor_variantNoUnderline__SPwsd")) {
  264. addFlagToUsername(link)
  265. }
  266. }
  267.  
  268. if (FLAGS_IN_LEADERBOARD) {
  269. // generic leaderboard
  270. for (const link of document.querySelectorAll(".leaderboard_columnContent__yA6b_.leaderboard_alignStart__KChAa a")) {
  271. addFlagToUsername(link)
  272. }
  273.  
  274. // map highscore leaderboard
  275. let tabSwitch = document.querySelector(".map-highscore_switchContainer__wCDRH div")
  276. if (tabSwitch) {
  277. const openedMapHighscoreTab = +tabSwitch.firstChild.firstChild.classList.contains("switch_hide__OuYfZ")
  278.  
  279. if (openedMapHighscoreTab != lastOpenedMapHighscoreTab) {
  280. lastOpenedMapHighscoreTab = openedMapHighscoreTab
  281. for (const link of document.querySelectorAll(".map-highscore_userWrapper__aHpCF a")) {
  282. const flag = link.querySelector(`.${USER_FLAG_CLASS}`)
  283. if (flag) {
  284. flag.remove()
  285. }
  286. }
  287. }
  288. }
  289.  
  290. for (const link of document.querySelectorAll(".map-highscore_userWrapper__aHpCF a")) {
  291. addFlagToUsername(link)
  292. }
  293. }
  294.  
  295. if (FLAGS_IN_MATCHMAKING) {
  296. // battle royale matchmaking
  297. for (const link of document.querySelectorAll(".player-card_userLink__HhoDo")) {
  298. addFlagToUsername(link)
  299. }
  300. }
  301. }
  302.  
  303. const observer = new MutationObserver(onMutationsStandard)
  304.  
  305. observer.observe(document.body, OBSERVER_CONFIG)