GeoHintr

GeoHintr - Allows you to play maps that contain hints on the locations including written hints or pictures or videos

当前为 2021-08-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GeoHintr
  3. // @version 0.2
  4. // @description GeoHintr - Allows you to play maps that contain hints on the locations including written hints or pictures or videos
  5. // @author MrAmericanMike and Alok
  6. // @include /^(https?)?(\:)?(\/\/)?([^\/]*\.)?geoguessr\.com($|\/.*)/
  7. // @grant none
  8. // @run-at document-start
  9. // @namespace https://greasyfork.org/users/250979
  10. // ==/UserScript==
  11. console.log("GeoHintr");
  12.  
  13. const GH = {
  14. MYHINTS: [],
  15. round: 0,
  16. hintDiv: null,
  17. uiDiv: null,
  18. tokens: [],
  19. setHints: (hints) => {
  20. GH.MYHINTS = hints;
  21. },
  22. init: () => {
  23. console.log("GH Init");
  24. const targetNode = document.getElementsByTagName("body")[0];
  25. const config = {
  26. attributes: false,
  27. childList: true,
  28. subtree: false,
  29. characterData: false
  30. };
  31. const observer = new MutationObserver((mutationsList, observer) => {
  32. for (const mutation of mutationsList) {
  33. if (mutation.type === "childList") {
  34. GH.checkRound();
  35. }
  36. }
  37. });
  38. observer.observe(targetNode, config);
  39. if (!GH.hintDiv) {
  40. GH.hintDiv = document.createElement("div");
  41. GH.hintDiv.setAttribute("class", "game-statuses");
  42. GH.hintDiv.setAttribute("id", "GH-hint");
  43. GH.hintDiv.setAttribute("style", "display: inline-flex; padding: 5px 0.5rem 0px; margin-top: 2px;");
  44. GH.hintDiv.style.display = "none";
  45. }
  46. GH.handleStorage();
  47. GH.createUI();
  48. GH.keyListener();
  49. },
  50. handleStorage: () => {
  51. GH.tokens = JSON.parse(sessionStorage.getItem("GH-TOKENS"));
  52. if (GH.tokens && GH.tokens.length > 0) {
  53. console.log("Tokens stored");
  54. console.log(GH.tokens);
  55. }
  56. else {
  57. sessionStorage.setItem("GH-TOKENS", JSON.stringify([]));
  58. GH.tokens = JSON.parse(sessionStorage.getItem("GH-TOKENS"));
  59. console.log("Tokens set");
  60. console.log(GH.tokens);
  61. }
  62. },
  63. storeTokens: () => {
  64. sessionStorage.setItem("GH-TOKENS", JSON.stringify(GH.tokens));
  65. console.log("Tokens stored");
  66. console.log(GH.tokens);
  67. },
  68. removeToken: (token) => {
  69. for (let i = 0; i < GH.tokens.length; i++) {
  70. if (GH.tokens[i].token === token) {
  71. GH.tokens.splice(i, 1);
  72. }
  73. }
  74. GH.storeTokens();
  75. GH.redrawUI();
  76. },
  77. checkRound: () => {
  78. const roundData = document.querySelector("div[data-qa='round-number']");
  79. if (roundData) {
  80. let roundElement = roundData.querySelector(".game-status__body");
  81. if (roundElement) {
  82. let round = parseInt(roundElement.innerText.charAt(0));
  83. if (!isNaN(round) && round >= 1 && round <= 5) {
  84. if (round != GH.round) {
  85. console.log("GH Round Changed");
  86. GH.round = round;
  87. GH.doMagic();
  88. }
  89. }
  90. }
  91. }
  92. },
  93. doMagic: () => {
  94. let URL = null;
  95. if (window.location.pathname.includes("game")) {
  96. URL = `https://www.geoguessr.com/api/v3/games/${window.location.pathname.substring(6)}`;
  97. }
  98. else if (window.location.pathname.includes("challenge")) {
  99. URL = `https://www.geoguessr.com/api/v3/challenges/${window.location.pathname.substring(11)}/game`;
  100. }
  101. if (URL) {
  102. fetch(URL)
  103. .then((response) => response.json())
  104. .then((data) => {
  105. const { lat, lng } = data.rounds[data.round - 1];
  106. let coordinates = { lat, lng };
  107. GH.checkForHints(coordinates);
  108. })
  109. .catch((error) => {
  110. console.log("Something went wrong");
  111. console.log(error);
  112. });
  113. }
  114. },
  115. checkForHints: (coordinates) => {
  116. GH.MYHINTS.forEach(hint => {
  117. if(GH.coordinatesInRange(coordinates, { lat: hint.lat, lng: hint.lng })){
  118. GH.updateHint(hint);
  119. }
  120. });
  121. },
  122. updateHint: (hint) => {
  123. GH.hintDiv.innerHTML = `
  124. <div class="game-status__body">
  125. <style>
  126. #wrapper {
  127. width: 25vw;
  128. text-align: center;
  129. }
  130. </style>
  131. <div id="wrapper">
  132. <p id="hint"></p>
  133. <div class="spacer" style="clear: both;"></div>
  134. </div>
  135. </div>
  136. `;
  137.  
  138. if (document.getElementById("GH-hint")) {
  139. GH.hintDiv.style.display = "";
  140. }
  141. else {
  142. document.querySelector(".game-layout__status").appendChild(GH.hintDiv);
  143. GH.hintDiv.style.display = "";
  144. }
  145.  
  146. document.getElementById("hint").innerText = hint.hint;
  147.  
  148. if (hint.img) {
  149. console.log(true);
  150. let image = document.createElement("img");
  151. image.src = hint.img;
  152. image.style.maxHeight = "25vh";
  153. document.getElementById("wrapper").appendChild(image);
  154. }
  155. if (hint.video) {
  156. let iframe = document.createElement("iframe");
  157. iframe.src = `https://www.youtube.com/embed/${hint.video}`;
  158. iframe.width = "100%";
  159. iframe.style.minHeight = "25vh";
  160. iframe.setAttribute("allowfullscreen", "");
  161. iframe.setAttribute("frameborder", "0");
  162. document.getElementById("wrapper").appendChild(iframe);
  163. }
  164. },
  165. loadHints: (token, name) => {
  166. document.getElementById("GH-message").innerText = "Loading...";
  167. fetch(`https://dongles.vercel.app/dongle/paste`, {
  168. method: "POST",
  169. headers: {
  170. "Content-Type": "application/json"
  171. },
  172. body: JSON.stringify({ token: token })
  173. })
  174. .then((results) => {
  175. return results.json();
  176. })
  177. .then((data) => {
  178. if (data.error) {
  179. console.log(data);
  180. throw new Error(data.error.message);
  181. }
  182. GH.setHints(data);
  183. document.getElementById("GH-message").innerText = "Hints loaded...";
  184. GH.doMagic();
  185. setTimeout(() => {
  186. GH.hideUI();
  187. }, 2000);
  188. })
  189. .catch((error) => {
  190. console.log(error);
  191. document.getElementById("GH-message").innerText = "Something went wrong...";
  192. });
  193. let exists = false;
  194. GH.tokens.forEach((tok) => {
  195. if(tok.token === token) {
  196. exists = true;
  197. }
  198. });
  199. if(!exists){
  200. GH.tokens.push({ token, name });
  201. }
  202. GH.storeTokens();
  203. GH.redrawUI();
  204. },
  205. coordinatesInRange: (original, hint) => {
  206. let ky = 40000 / 360;
  207. let kx = Math.cos(Math.PI * hint.lat / 180.0) * ky;
  208. let dx = Math.abs(hint.lng - original.lng) * kx;
  209. let dy = Math.abs(hint.lat - original.lat) * ky;
  210. return Math.sqrt(dx * dx + dy * dy) <= 0.050;
  211. },
  212. keyListener: () => {
  213. document.addEventListener("keydown", (event) => {
  214. if (event.code === "KeyH" && event.shiftKey && event.altKey && !event.ctrlKey && !event.metaKey && !event.repeat) {
  215. if (GH.uiDiv.style.display === "block") {
  216. GH.hideUI();
  217. }
  218. else {
  219. GH.showUI();
  220. }
  221. }
  222. });
  223. },
  224. createUI: () => {
  225. if (!GH.uiDiv) {
  226. GH.uiDiv = document.createElement("div");
  227. GH.uiDiv.setAttribute("id", "GH-ui")
  228.  
  229. Object.assign(GH.uiDiv.style, {
  230. display: "none",
  231. position: "fixed",
  232. backgroundColor: "#eee9e0",
  233. zIndex: "1000",
  234. width: "fit-content",
  235. height: "fit-content",
  236. top: "48px",
  237. left: "8px",
  238. padding: "20px",
  239. borderRadius: "10px",
  240. boxShadow: "0 2px 2px 0",
  241. overflow: "hidden"
  242. });
  243.  
  244. GH.uiDiv.innerHTML = ``;
  245. GH.tokens.forEach((token) => {
  246. GH.uiDiv.innerHTML += `
  247. <input type="text" size="10" id="${token.token}" value="${token.token}" />
  248. <input type="text" value="${token.name}" />
  249. <button id="GH-${token.token}">LOAD</button>
  250. <button id="GHR-${token.token}">X</button>
  251. <br />
  252. `;
  253. });
  254. GH.uiDiv.innerHTML += `
  255. <input type="text" placeholder="Pastebin Token" size="10" id="pastebin_token" />
  256. <input type="text" placeholder="Description" id="pastebin_name" />
  257. <button id="GH-load">LOAD</button>
  258. <br />
  259. `;
  260. GH.uiDiv.innerHTML += `
  261. <div style="text-align: center; margin-top: 8px">
  262. <p id="GH-message"></p>
  263. </div>
  264. `;
  265. }
  266. document.body.appendChild(GH.uiDiv);
  267. document.getElementById("GH-load").addEventListener("click", () => {
  268. GH.loadHints(document.getElementById("pastebin_token").value, document.getElementById("pastebin_name").value);
  269. });
  270. GH.tokens.forEach((token) => {
  271. document.getElementById(`GH-${token.token}`).addEventListener("click", () => GH.loadHints(token.token, token.name));
  272. });
  273. GH.tokens.forEach((token) => {
  274. document.getElementById(`GHR-${token.token}`).addEventListener("click", () => GH.removeToken(token.token));
  275. });
  276. },
  277. redrawUI: () => {
  278. GH.hideUI();
  279. document.getElementById("GH-ui").remove();
  280. GH.uiDiv = null;
  281. GH.createUI();
  282. GH.showUI();
  283. },
  284. showUI: () => {
  285. GH.uiDiv.style.display = "block";
  286. },
  287. hideUI: () => {
  288. GH.uiDiv.style.display = "none";
  289. }
  290. }
  291.  
  292. GH.init();