Geoguessr rating displayer

Adds the competitive rating to usernames

当前为 2024-08-07 提交的版本,查看 最新版本

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