Geoguessr rating displayer

Adds the competitive rating to usernames

当前为 2023-10-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Geoguessr rating displayer
  3. // @description Adds the competitive rating to usernames
  4. // @version 1.2.1
  5. // @license MIT
  6. // @author joniber
  7. // @namespace
  8. // @match https://www.geoguessr.com/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
  10. // @namespace https://greasyfork.org/users/1072330
  11. // ==/UserScript==
  12.  
  13. //=====================================================================================\\
  14. // change these values however you like (make sure to hit ctrl+s afterwards) \\
  15. //=====================================================================================\\
  16.  
  17.  
  18.  
  19. const RATING_IN_FRIENDS_TAB = true;
  20. // ^^^^^ set this to 'false' if you don't want to display rating in the friends tab
  21. const RATING_IN_LEADERBOARD = true;
  22. // ^^^^ set this to 'false' if you don't want to display rating in leaderboards
  23. const RATING_IN_MATCHMAKING = true;
  24. // ^^^^ set this to 'false' if you don't want to display rating in matchmaking lobbies
  25. const RATING_IN_INGAME_PAGE = true;
  26. // ^^^^ set this to 'false' if you don't want to display rating ingame
  27.  
  28. const RATING_COLOR_CHANGE = true;
  29. // ^^^^ set this to 'false' if you don't want to change the color based on the ranking
  30.  
  31. const RATING_IN_BREAKDOWN = true;
  32. // ^^^^ set this to 'false' if you don't want to see rating in the breakdown menu
  33.  
  34. const RATING_IN_SUGGESTIONS = true;
  35. // ^^^^ set this to 'false' if you don't want to see rating in the friend suggestion list
  36.  
  37. //=====================================================================================\\
  38. // don't edit anything after this point unless you know what you're doing \\
  39. //=====================================================================================\\
  40.  
  41. const GEOGUESSR_USER_ENDPOINT = 'https://geoguessr.com/api/v3/users';
  42. const CHAT_ENDPOINT = 'https://game-server.geoguessr.com/api/lobby';
  43.  
  44. const SCRIPT_PREFIX = 'ur__';
  45. const PROFIlE_RATING_ID = SCRIPT_PREFIX + 'profileRating';
  46. const USER_RATING_CLASS = SCRIPT_PREFIX + 'userRating';
  47.  
  48. const OBSERVER_CONFIG = {
  49. characterDataOldValue: false,
  50. subtree: true,
  51. childList: true,
  52. characterData: false,
  53. };
  54.  
  55. const ERROR_MESSAGE = (wrong) => '${wrong}';
  56.  
  57. function pathMatches(path) {
  58. return location.pathname.match(new RegExp(`^/(?:[^/]+/)?${path}$`));
  59. }
  60.  
  61. function ratingText() {
  62. return `<p
  63. class="${USER_RATING_CLASS}"
  64. style="margin-left: .25rem; margin-right:.25rem; margin-top:0px; display: none;"
  65. onerror="this.style.display = 'none'"
  66. ></p>`;
  67. }
  68.  
  69. let flag = true
  70. let flag1 = false
  71.  
  72. async function fillRating(ratingNumber, userId) {
  73. const userData = await getUserData(userId);
  74. const rating = userData.competitive.rating;
  75.  
  76. ratingNumber.innerHTML = rating;
  77. let color;
  78. if (RATING_COLOR_CHANGE) {
  79. if (rating < 450) {
  80. color = '#ba682e';
  81. } else if (rating < 650) {
  82. color = '#8e8e8e';
  83. } else if (rating < 850) {
  84. color = '#e8ac06';
  85. } else if (rating < 1100) {
  86. color = '#e04781';
  87. } else if (rating >= 1100) {
  88. color = '#a994e3';
  89. }
  90.  
  91. ratingNumber.style.color = color;
  92. }
  93. ratingNumber.style.display = 'block';
  94. }
  95.  
  96. function retrieveIdFromLink(link) {
  97. if (link.endsWith('/me/profile')) {
  98. const data = document.querySelector('#__NEXT_DATA__').text;
  99. const json = JSON.parse(data);
  100. return json.props.middlewareResults[1].account.user.userId;
  101. }
  102. return link.split('/').at(-1);
  103. }
  104.  
  105. function isOtherProfile() {
  106. return pathMatches('user/.+');
  107. }
  108.  
  109. function isOwnProfile() {
  110. return pathMatches('me/profile');
  111. }
  112.  
  113. function isProfile() {
  114. return isOwnProfile() || isOtherProfile();
  115. }
  116.  
  117. function isBattleRoyale() {
  118. return pathMatches('battle-royale/.+');
  119. }
  120.  
  121. function isDuels() {
  122. return pathMatches('duels/.+');
  123. }
  124.  
  125. async function getUserData(id) {
  126. const response = await fetch(`${GEOGUESSR_USER_ENDPOINT}/${id}`);
  127. const json = await response.json();
  128.  
  129. return json;
  130. }
  131.  
  132. function addRatingToUsername(link) {
  133. if (!link.querySelector(`.${USER_RATING_CLASS}`)) {
  134. const destination = link.querySelector('.user-nick_nickWrapper__yneIk');
  135. destination.insertAdjacentHTML('beforeend', ratingText());
  136. const ratingNumber = destination.lastChild;
  137. if(destination.children[2]){
  138. destination.insertBefore(
  139. ratingNumber,
  140. destination.children[2]
  141. );
  142.  
  143. }
  144.  
  145. fillRating(ratingNumber, retrieveIdFromLink(link.href));
  146. }
  147. }
  148.  
  149. function addRatingToIngameUsername(link) {
  150. if (!link.querySelector(`.${USER_RATING_CLASS}`)) {
  151. const destination = link.querySelector('span');
  152. destination.style.display = 'flex';
  153. destination.innerHTML += '&nbsp;';
  154. destination.insertAdjacentHTML('beforeend', ratingText());
  155. const ratingNumber = destination.lastChild;
  156. if (destination.childElementCount == 4) {
  157. destination.insertBefore(
  158. ratingNumber,
  159. ratingNumber.previousElementSibling.previousElementSibling
  160. );
  161. } else if (destination.childElementCount > 2) {
  162. destination.insertBefore(ratingNumber, ratingNumber.previousElementSibling);
  163. }
  164.  
  165. fillRating(ratingNumber, retrieveIdFromLink(link.href));
  166. }
  167. }
  168.  
  169. let inBattleRoyale = false;
  170. let inDuels = false;
  171. let lastOpenedMapHighscoreTab = 0;
  172. async function getUsersInGame(id) {
  173. const response = await fetch(`${CHAT_ENDPOINT}/${id}`);
  174. const json = await response.json();
  175. return json;
  176.  
  177. }
  178.  
  179.  
  180. async function onMutationsBr(mutations, observer) {
  181. if (RATING_IN_INGAME_PAGE) {
  182. //battle royale distance
  183. for (const link of document.querySelectorAll('.distance-player-list_name__Wczob a')) {
  184. addRatingToIngameUsername(link);
  185. }
  186.  
  187. // battle royale countries
  188. for (const link of document.querySelectorAll(
  189. '.countries-player-list_playerName__oo1GB a'
  190. )) {
  191. addRatingToIngameUsername(link);
  192. }
  193.  
  194.  
  195. }
  196. }
  197.  
  198. function onMutationsDuels(mutations, observer) {
  199. if (RATING_IN_INGAME_PAGE) {
  200. // duels
  201. const hud = document.querySelector('.hud_root__sYAUU');
  202.  
  203. const firstPlayerLink = hud.firstChild?.querySelector('.health-bar_player__HXWq0 a');
  204. if (firstPlayerLink) {
  205. addRatingToUsername(firstPlayerLink);
  206. }
  207.  
  208. const secondPlayerLink = hud.lastChild?.querySelector('.health-bar_player__HXWq0 a');
  209. if (secondPlayerLink) {
  210. if (addRatingToUsername(secondPlayerLink, 'afterbegin')) {
  211. const name = secondPlayerLink.querySelector('.user-nick_nick__y4VIt');
  212. name.innerHTML = '&nbsp;' + name.innerHTML;
  213. }
  214. }
  215.  
  216. if (document.querySelector('.round-score_container__NL817')) {
  217. const leftLink = document.querySelector(
  218. '.round-score_healthLeft__FJXTY .health-bar_player__9j0Vu a'
  219. );
  220. addRatingToUsername(leftLink);
  221. const rightLink = document.querySelector(
  222. '.round-score_healthRight__eyrD0 .health-bar_player__9j0Vu a'
  223. );
  224. if (addRatingToUsername(rightLink, 'afterbegin')) {
  225. const name = rightLink.querySelector('.user-nick_nick__y4VIt');
  226. name.innerHTML = '&nbsp;' + name.innerHTML;
  227. }
  228. }
  229. }
  230. }
  231.  
  232. async function onMutationsStandard(mutations, observer) {
  233. if (isBattleRoyale() && document.querySelector('.game_hud__3EFVC ul') && !inBattleRoyale) {
  234. inBattleRoyale = true;
  235. const brObserver = new MutationObserver(onMutationsBr);
  236. brObserver.observe(document.querySelector('.game_hud__3EFVC ul'), OBSERVER_CONFIG);
  237. } else if (isDuels() && document.querySelector('.game_hud__Mt1qE') && !inDuels) {
  238. inDuels = true;
  239. const duelsObserver = new MutationObserver(onMutationsDuels);
  240. duelsObserver.observe(document.querySelector('.game_hud__Mt1qE'), OBSERVER_CONFIG);
  241. } else if (inBattleRoyale && !document.querySelector('.game_hud__3EFVC ul')) {
  242. inBattleRoyale = false;
  243. } else if (inDuels && !document.querySelector('.game_hud__Mt1qE')) {
  244. inDuels = false;
  245. }
  246.  
  247. if (inBattleRoyale || inDuels) {
  248. return;
  249. }
  250.  
  251. if (RATING_IN_FRIENDS_TAB) {
  252. // friends tab
  253. for (const link of document.querySelectorAll('.chat-friend_name__V0nzL a')) {
  254. addRatingToUsername(link);
  255. }
  256. }
  257.  
  258. if (RATING_IN_LEADERBOARD) {
  259. // generic leaderboard
  260.  
  261. for (let link of document.querySelectorAll('.leaderboard_row__Swnpj')) {
  262. if(link.classList.contains('leaderboard_rowColumnCount3__B_RSQ')){
  263. return;
  264. }
  265. else{
  266. if(link.querySelector('.leaderboard_columnContent__HpvuQ.leaderboard_alignStart__SjLEZ a')){
  267. addRatingToUsername(link.querySelector('.leaderboard_columnContent__HpvuQ.leaderboard_alignStart__SjLEZ a'));
  268. }
  269. }
  270. }
  271.  
  272. // map highscore leaderboard
  273. let tabSwitch = document.querySelector('.map-highscore_switchContainer__OyyvP div');
  274. if (tabSwitch) {
  275. const openedMapHighscoreTab =
  276. +tabSwitch.firstChild.firstChild.classList.contains('switch_hide__KyksT');
  277. if (openedMapHighscoreTab != lastOpenedMapHighscoreTab) {
  278. lastOpenedMapHighscoreTab = openedMapHighscoreTab;
  279. for (const link of document.querySelectorAll(
  280. '.map-highscore_userWrapper__tmjOe a'
  281. )) {
  282. const rating = link.querySelector(`.${USER_RATING_CLASS}`);
  283. if (rating) {
  284. rating.remove();
  285. }
  286. }
  287. }
  288. let tabSwitch1 = document.querySelector('.map-highscore_switchContainer__OyyvP');
  289. const openFriendHighscoreTab = tabSwitch1.lastChild.lastChild
  290. if(openFriendHighscoreTab){
  291. if(openFriendHighscoreTab.firstChild.classList.contains('switch_show__V6W5T') && flag){
  292. flag = false
  293. flag1 = true
  294. for (const link of document.querySelectorAll(
  295. '.map-highscore_userWrapper__tmjOe a'
  296. )) {
  297. const rating = link.querySelector(`.${USER_RATING_CLASS}`);
  298. if (rating) {
  299. rating.remove();
  300. }
  301. }
  302. }
  303. }
  304.  
  305. const openFriendHighscoreTab1 = tabSwitch1.lastChild.firstChild
  306. if(openFriendHighscoreTab1){
  307. if(openFriendHighscoreTab1.firstChild.classList.contains('switch_show__V6W5T') && flag1){
  308. flag = true
  309. flag1 = false
  310. for (const link of document.querySelectorAll(
  311. '.map-highscore_userWrapper__tmjOe a'
  312. )) {
  313. const rating = link.querySelector(`.${USER_RATING_CLASS}`);
  314. if (rating) {
  315. rating.remove();
  316. }
  317. }
  318. }
  319. }
  320.  
  321. }
  322.  
  323. for (const link of document.querySelectorAll('.map-highscore_userWrapper__tmjOe a')) {
  324. addRatingToUsername(link);
  325. }
  326. }
  327.  
  328. if (RATING_IN_BREAKDOWN) {
  329. for (const link of document.querySelectorAll('.results_userLink__1k2fP a')) {
  330. addRatingToUsername(link);
  331. }
  332. }
  333.  
  334. if (RATING_IN_SUGGESTIONS) {
  335. for (const link of document.querySelectorAll('.friend_name__VJYzL a')) {
  336. addRatingToUsername(link);
  337. }
  338. }
  339.  
  340. if (RATING_IN_MATCHMAKING) {
  341. if(document.querySelector('.avatar-lobby_wrapper__diFl_')){
  342. let lobbyWrapper = document.querySelector('.avatar-lobby_wrapper__diFl_')
  343.  
  344. for (let link of document
  345. .querySelector('.avatar-lobby_wrapper__diFl_')
  346. .querySelectorAll('.avatar-title_titleWrapper___gYm9')) {
  347. if (!link.querySelector(`.${USER_RATING_CLASS}`)) {
  348. if (link.querySelector('.user-nick_nick__RdsmT')) {
  349. const lobby = await getUsersInGame(location.href.slice(-36));
  350. for (const player of lobby.players) {
  351. if (!link.querySelector(`.${USER_RATING_CLASS}`)) {
  352.  
  353. const nickElement = link.querySelector('.user-nick_nick__RdsmT');
  354. const isPlayerNick = nickElement.textContent.trim() == player.nick;
  355.  
  356. if (isPlayerNick) {
  357. let destination = nickElement
  358. destination.insertAdjacentHTML('beforeend', ratingText());
  359. const ratingNumber = destination.lastChild;
  360. ratingNumber.style.marginLeft = "0px";
  361. if(destination.children[2]){
  362. destination.insertBefore(
  363. ratingNumber,
  364. destination.children[2]
  365. );
  366.  
  367. }
  368.  
  369. fillRating(ratingNumber, player.playerId);
  370. }
  371.  
  372. }
  373. }
  374. }
  375. }
  376. }
  377.  
  378.  
  379. }
  380. }
  381. }
  382.  
  383.  
  384. const observer = new MutationObserver(onMutationsStandard);
  385.  
  386. observer.observe(document.body, OBSERVER_CONFIG);