Geoguessr Location Retriever

Get the actual location Geoguessr gave you from the result screens. Works for games and streaks, solo and challenge.

  1. // ==UserScript==
  2. // @name Geoguessr Location Retriever
  3. // @match https://www.geoguessr.com/*
  4. // @description Get the actual location Geoguessr gave you from the result screens. Works for games and streaks, solo and challenge.
  5. // @version 1.1.6
  6. // @author victheturtle#5159
  7. // @grant none
  8. // @license MIT
  9. // @icon https://www.svgrepo.com/show/12218/find.svg
  10. // @namespace https://greasyfork.org/users/967692-victheturtle
  11. // ==/UserScript==
  12.  
  13. let lastChecked = 0;
  14. let checkedResults = false;
  15.  
  16. function getPins() {
  17. return document.querySelectorAll("[class*='map-pin_clickable']");
  18. };
  19.  
  20. function panoIdDecoder(geoguessrPanoId) {
  21. let gsvPanoId = "";
  22. for (let i = 0; i < geoguessrPanoId.length; i+=2) {
  23. let seq = geoguessrPanoId.substring(i, i+2);
  24. gsvPanoId += String.fromCharCode(parseInt(seq, 16));
  25. }
  26. return gsvPanoId;
  27. }
  28.  
  29. function linkOfLocation(round, copyToClipboard) {
  30. if (round.panoId == null) return null;
  31. let lat = round.lat;
  32. let lng = round.lng;
  33. let pid = panoIdDecoder(round.panoId);
  34. let rh = round.heading;
  35. let rp = round.pitch;
  36. let rz = round.zoom;
  37. let h = Math.round(round.heading * 100) / 100;
  38. let p = Math.round((90 + round.pitch) * 100) / 100;
  39. let z = Math.round((90 - round.zoom/2.75*90) * 10) / 10;
  40. const extra = `"countryCode":null,"stateCode":null,"extra":{"tags":[]}`;
  41. let link = `https://www.google.com/maps/@${lat},${lng},3a,${z}y,${h}h${(p==90)?"":","+p+"t"}/data=!3m6!1e1!3m4!1s${pid}!2e0!7i13312!8i6656`;
  42. let loc_json = `{"lat":${lat},"lng":${lng},"heading":${rh},"pitch":${rp},"panoId":"${pid}","zoom":${rz},${extra}}`;
  43. console.log(link);
  44. //console.log(loc_json+",");
  45. if (copyToClipboard) {
  46. try {
  47. navigator.clipboard.writeText(loc_json+",");
  48. } catch (e) {console.log(e)};
  49. }
  50. return link;
  51. }
  52.  
  53. function addFlagOnclicks(rounds) {
  54. let pin = getPins();
  55. for (let i = 0; i < pin.length; i++) {
  56. let link = linkOfLocation(rounds[(pin.length>1) ? pin[i].innerText-1 : rounds.length-1-i], pin.length==1);
  57. if (link != null) pin[i].onclick = function () {window.open(link, '_blank');};
  58. }
  59. };
  60.  
  61. function addStreakChallengeOnclicks(rounds) {
  62. setTimeout(() => {
  63. let playersTable = document.querySelectorAll("div[class*='results_highscoreHeader__']")[0].parentElement.children[1];
  64. let roundsTable = document.querySelectorAll("div[class*='results_highscoreHeader__']")[1].parentElement;
  65. for (let i = 0; i < playersTable.children.length; i += 2) {
  66. playersTable.children[i].onclick = function () {addStreakChallengeOnclicks(rounds);};
  67. }
  68. for (let i = 1; i < roundsTable.children.length; i++) {
  69. let link = linkOfLocation(rounds[i-1], false);
  70. console.log(link);
  71. if (link != null) roundsTable.children[i].onclick = function () {window.open(link, '_blank');};
  72. roundsTable.children[i].style="cursor: pointer;";
  73. }
  74. }, 200);
  75. }
  76.  
  77. function check() {
  78. const game_tag = location.pathname.substr(location.pathname.lastIndexOf("/")+1);
  79. let api_url = location.origin + "/api/v3/games/" + game_tag;
  80. if (location.pathname.includes("/challenge") || !!document.querySelector("div[class*='switch_switch__']")) {
  81. api_url = location.origin + "/api/v3/challenges/" + game_tag + "/game";
  82. };
  83. fetch(api_url)
  84. .then(res => res.json())
  85. .then(out => {
  86. addFlagOnclicks(out.rounds.slice(0, out.player.guesses.length));
  87. if (out.type == "challenge" && out.mode == "streak") {
  88. let api_url2 = location.origin + "/api/v3/results/highscores/"+game_tag+"?friends=false&limit=1";
  89. fetch(api_url2)
  90. .then(res => res.json())
  91. .then(out => addStreakChallengeOnclicks(out[0].game.rounds))
  92. .catch(err => { throw err });
  93. };
  94. }).catch(err => { throw err });
  95.  
  96. };
  97.  
  98. function doCheck() {
  99. let pinCount = getPins().length;
  100. if (pinCount == 0) {
  101. lastChecked = 0;
  102. checkedResults = false;
  103. } else if (pinCount != lastChecked || location.pathname.includes("/results") && !checkedResults && document.readyState == "complete") {
  104. lastChecked = pinCount;
  105. checkedResults = location.pathname.includes("/results");
  106. check();
  107. }
  108. };
  109.  
  110. function checkGameMode() {
  111. return location.pathname.includes("/results") || location.pathname.includes("/game") || location.pathname.includes("/challenge")
  112. }
  113.  
  114.  
  115. let lastDoCheckCall = 0;
  116. new MutationObserver(async (mutations) => {
  117. if (!checkGameMode() || lastDoCheckCall >= (Date.now() - 50)) return;
  118. lastDoCheckCall = Date.now();
  119. doCheck();
  120. }).observe(document.body, { subtree: true, childList: true });