Geoguessr Resolver Hack (Works in all game modes)

Features: Automatically score 5000 Points | Score randomly between 4500 and 5000 points | Open in Google Maps | See enemy guess Distance

当前为 2023-09-24 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Geoguessr Resolver Hack (Works in all game modes)
  3. // @namespace http://tampermonkey.net/
  4. // @version 10.4_Beta
  5. // @description Features: Automatically score 5000 Points | Score randomly between 4500 and 5000 points | Open in Google Maps | See enemy guess Distance
  6. // @author 0x978
  7. // @match https://www.geoguessr.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
  9. // @grant GM_webRequest
  10. // ==/UserScript==
  11.  
  12. window.alert = function (message) { // Devs tried to overwrite alert to detect script. I had already stopped using alert, but i'd rather they didn't override this anyway.
  13. nativeAlert(message)
  14. };
  15.  
  16. const originalFetch = window.fetch;
  17. window.fetch = function (url, options) {
  18. if (url === "https://www.geoguessr.com/api/v4/cd0d1298-a3aa-4bd0-be09-ccf513ad14b1") { // devs using this endpoint for Anticheat. Block all calls to it.
  19. return
  20. }
  21. return originalFetch.call(this, url, options);
  22. };
  23.  
  24. async function getAddress(lat, lon) {
  25. const response = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json`)
  26. return await response.json();
  27. }
  28.  
  29. function displayLocationInfo() {
  30. const coordinates = coordinateClimber()
  31. // api call with the lat lon to retrieve data.
  32. getAddress(coordinates[0], coordinates[1]).then(data => {
  33. alert(`
  34. Country: ${data.address.country}
  35. County: ${data.address.county}
  36. City: ${data.address.city}
  37. Road: ${data.address.road}
  38. State: ${data.address.state}
  39. Postcode: ${data.address.postcode}
  40. Village/Suburb: ${(data.address.village || data.address.suburb)}
  41.  
  42. Postal Address: ${data.display_name}
  43. `)
  44. });
  45.  
  46. }
  47.  
  48. function placeMarker(safeMode, skipGet, coords) {
  49. const isPanic = document.getElementsByClassName("coordinate-map_canvasContainer__7d8Yw")[0]
  50. if(isPanic){panicPlaceMarker(isPanic); return;}
  51. const isStreaks = document.getElementsByClassName("guess-map__canvas-container")[0] === undefined
  52. let location = skipGet ? coords : coordinateClimber(isStreaks)
  53. if (isStreaks) {
  54. placeMarkerStreaksMode(location)
  55. return;
  56. }
  57. let [lat, lng] = location
  58.  
  59. if (safeMode) {
  60. const sway = [Math.random() > 0.5,Math.random() > 0.5]
  61. const multiplier = Math.random() * 4
  62. const horizontalAmount = Math.random() * multiplier
  63. const verticalAmount = Math.random() * multiplier
  64. sway[0] ? lat += verticalAmount : lat -= verticalAmount
  65. sway[1] ? lng += horizontalAmount : lat -= horizontalAmount
  66. }
  67.  
  68. const element = document.getElementsByClassName("guess-map__canvas-container")[0] // html element containing needed props.
  69. const keys = Object.keys(element) // all keys
  70. const key = keys.find(key => key.startsWith("__reactFiber$")) // the React key I need to access props
  71. const place = element[key].return.memoizedProps.onMarkerLocationChanged // getting the function which will allow me to place a marker on the map
  72.  
  73. flag = false;
  74. place({lat: lat, lng: lng}) // placing the marker on the map at the correct coordinates given by getCoordinates(). Must be passed as an Object.
  75. toggleClick(({lat: lat, lng: lng}))
  76. displayDistanceFromCorrect({lat: lat, lng: lng})
  77. injectOverride()
  78. }
  79.  
  80. function placeMarkerStreaksMode(code) {
  81. let element = document.getElementsByClassName("region-map_map__5e4h8")[0] // this map is unique to streaks mode, however, is similar to that found in normal modes.
  82. if(!element){
  83. element = document.getElementsByClassName("region-map_map__7jxcD")[0]
  84. }
  85. const keys = Object.keys(element)
  86. const reactKey = keys.find(key => key.startsWith("__reactFiber$"))
  87. const placeMarkerFunction = element[reactKey].return.memoizedProps.onRegionSelected // This map works by selecting regions, not exact coordinates on a map, which is handles by the "onRegionSelected" function.
  88.  
  89. if(typeof code !== "string"){
  90. let [lat,lng] = code
  91. getAddress(lat, lng).then(data => { // using API to retrieve the country code at the current coordinates.
  92. const countryCode = data.address.country_code
  93. placeMarkerFunction(countryCode) // passing the country code into the onRegionSelected method.
  94. })
  95. return
  96. }
  97.  
  98. placeMarkerFunction(code)
  99. }
  100.  
  101. function panicPlaceMarker(element){ // Currently only used in map runner.
  102. const keys = Object.keys(element)
  103. const key = keys.find(key => key.startsWith("__reactFiber$"))
  104. const props = element[key]
  105.  
  106. const clickProperty = props.return.memoizedProps.map.__e3_.click // taking the maps click property directly.
  107. const clickFunction = clickProperty[getDynamicIndex(Object.keys(clickProperty),clickProperty)].xe
  108. console.log(clickFunction)
  109. let [lat,lng] = coordinateClimber()
  110.  
  111. // for some reason, submitting near-perfect guesses causes Chromium browsers to crash, the following will offset it to avoid this.
  112. // There is probably some other way to avoid this, but I have no idea why this happens. See GitHub issue.
  113. lat += 0.1
  114. lng += 0.1
  115.  
  116. let y = {
  117. "latLng": {
  118. "lat": () => lat,
  119. "lng": () => lng,
  120. }
  121. }
  122. clickFunction(y)
  123. }
  124.  
  125. function getDynamicIndex(indexArray,clickProperty){
  126. for(let i = 0; i < indexArray.length;i++){
  127. if(clickProperty[indexArray[i]]?.xe.toString().slice(0,20) === "l=>{let e={lat:l.lat"){
  128. return indexArray[i]
  129. }
  130. }
  131. alert("Maprunner Placer failed. \n Please report this on GitHub or Greasyfork.")
  132. }
  133.  
  134. function coordinateClimber(isStreaks){
  135. let timeout = 10
  136. let path = document.querySelector('div[data-qa="panorama"]');
  137. while (timeout > 0){
  138. const props = path[Object.keys(path).find(key => key.startsWith("__reactFiber$"))]
  139. const checkReturns = iterateReturns(props,isStreaks)
  140. if(checkReturns){
  141. return checkReturns
  142. }
  143. path = path.parentNode
  144. timeout--
  145. }
  146. alert("Failed to find co-ordinates. Please make an issue on GitHub or GreasyFork. " +
  147. "Please make sure you mention the game mode in your report.")
  148. }
  149.  
  150. function iterateReturns(element,isStreaks){
  151. let timeout = 10
  152. let path = element
  153. while(timeout > 0){
  154. if(path){
  155. const coords = checkProps(path.memoizedProps,isStreaks)
  156. if(coords){
  157. return coords
  158. }
  159. }
  160. if(!path["return"]){
  161. return
  162. }
  163. path = path["return"]
  164. timeout--
  165. }
  166. }
  167.  
  168. function checkProps(props,isStreaks){
  169. if(props?.panoramaRef){
  170. const found = props.panoramaRef.current.location.latLng
  171. return [found.lat(),found.lng()]
  172. }
  173. if(props.streakLocationCode && isStreaks){
  174. return props.streakLocationCode
  175. }
  176. if(props.gameState){
  177. const x = props.gameState[props.gameState.rounds.length-1]
  178. return [x.lat,x.lng]
  179. }
  180. if(props.lat){
  181. return [props.lat,props.lng]
  182. }
  183. }
  184.  
  185. function mapsFromCoords() { // opens new Google Maps location using coords.
  186. const [lat, lon] = coordinateClimber()
  187. if (!lat || !lon) {
  188. return;
  189. }
  190. window.open(`https://www.google.com/maps/place/${lat},${lon}`);
  191. }
  192.  
  193. function getGuessDistance(manual) {
  194. const coords = coordinateClimber()
  195. const clat = coords[0] * (Math.PI / 180)
  196. const clng = coords[1] * (Math.PI / 180)
  197. const y = document.getElementsByClassName("guess-map__canvas-container")[0]
  198. const keys = Object.keys(y)
  199. const key = keys.find(key => key.startsWith("__reactFiber$"))
  200. const props = y[key]
  201. const user = manual ?? props.return.memoizedProps.markers[0]
  202. if (!coords || !user) {
  203. return null
  204. }
  205. const ulat = user.lat * (Math.PI / 180)
  206. const ulng = user.lng * (Math.PI / 180)
  207.  
  208. const distance = Math.acos(Math.sin(clat) * Math.sin(ulat) + Math.cos(clat) * Math.cos(ulat) * Math.cos(ulng - clng)) * 6371
  209. return distance
  210. }
  211.  
  212. function displayDistanceFromCorrect(manual) {
  213. let unRoundedDistance = getGuessDistance(manual) // need unrounded distance for precise calculations later.
  214. let distance = Math.round(unRoundedDistance)
  215. if (distance === null) {
  216. return
  217. }
  218. let text = `${distance} km (${Math.round(distance * 0.621371)} miles)`
  219. setGuessButtonText(text)
  220. }
  221.  
  222. function setGuessButtonText(text) {
  223. let x = document.querySelector('button[data-qa="perform-guess"]');
  224. if(!x){
  225. return null}
  226. x.innerText = text
  227. }
  228.  
  229. function toggleClick(coords) { // prevents user from making 5k guess to prevent bans.
  230. const disableSpaceBar = (e) => {
  231. if (e.keyCode === 32) {
  232. const distance = getGuessDistance()
  233. if ((distance < 1 || isNaN(distance)) && !flag) {
  234. e.stopImmediatePropagation();
  235. preventedActionPopup()
  236. document.removeEventListener("keyup", disableSpaceBar);
  237. flag = true
  238. }
  239. }
  240. };
  241. document.addEventListener("keyup", disableSpaceBar);
  242. setTimeout(() => {
  243. const distance = getGuessDistance()
  244. if ((distance < 1 || isNaN(distance)) && !flag) {
  245. let old = document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0][Object.keys(document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0])[1]].onClick
  246. document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0][Object.keys(document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0])[1]].onClick = (() => {
  247. flag = true
  248. preventedActionPopup()
  249. document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0][Object.keys(document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0])[1]].onClick = (() => old())
  250. })
  251. }
  252. }, 500)
  253. }
  254.  
  255. function preventedActionPopup() {
  256. alert(`Geoguessr Resolver has prevented you from making a perfect guess.
  257.  
  258. Making perfect guesses will very likely result in a ban from competitive.
  259.  
  260. Press "guess" again to proceed anyway.`)
  261. }
  262.  
  263. function injectOverride() {
  264. document.getElementsByClassName("guess-map__canvas-container")[0].onpointermove = (() => { // this is called wayyyyy too many times (thousands) but fixes a lot of issues over using onClick.
  265. displayDistanceFromCorrect()
  266. })
  267. }
  268.  
  269. function getBRCoordinateGuesses() {
  270. const gameRoot = document.getElementsByClassName("game_root__2vV1H")[0]
  271. const props = gameRoot[Object.keys(document.getElementsByClassName("game_root__2vV1H")[0])[0]]
  272. const gameProps = props.return.return.memoizedProps.value.gameState
  273. const roundNumber = gameProps.currentRoundNumber
  274. const playerArray = gameProps.players
  275.  
  276. let bestGuessDistance = Number.MAX_SAFE_INTEGER
  277.  
  278. playerArray.forEach((player) => {
  279. const guesses = player.coordinateGuesses
  280. if(guesses){
  281. const guess = guesses[guesses.length - 1]
  282. if(guess && guess.roundNumber === roundNumber){
  283. if(guess.distance < bestGuessDistance){
  284. bestGuessDistance = guess.distance
  285. }
  286. }
  287. }
  288. })
  289.  
  290. if (bestGuessDistance === Number.MAX_SAFE_INTEGER) {
  291. return null;
  292. }
  293. return Math.round(bestGuessDistance / 1000)
  294. }
  295.  
  296. function displayBRGuesses(){
  297. const distance = getBRCoordinateGuesses()
  298. if (distance === null) {
  299. alert("There have been no guesses this round")
  300. return;
  301. }
  302. alert(`The best guess this round is ${distance} km from the correct location. (This may include your guess)`)
  303. }
  304.  
  305. function setInnerText(){
  306. const text = `
  307. Geoguessr Resolver Loaded Successfully
  308.  
  309. IMPORTANT GEOGUESSR RESOLVER UPDATE INFORMATION (I predicted it lol):
  310. Geoguessr Duels now has a replay mode. Try to look legit. Use the google map option more.
  311. https://www.reddit.com/r/geoguessr/comments/16oganx/exciting_update_duel_replays_now_available_in/
  312. MAPRUNNER IS NOW SUPPORTED (BETA). Please try it out and report issues on GitHub
  313. `
  314. if(document.getElementsByClassName("header_logo__vV0HK")[0]){
  315. document.getElementsByClassName("header_logo__vV0HK")[0].innerText = text
  316. }
  317. }
  318.  
  319. GM_webRequest([
  320. { selector: 'https://www.geoguessr.com/api/v4/trails', action: 'cancel' },
  321. ]);
  322.  
  323. let onKeyDown = (e) => {
  324. if (e.keyCode === 49) {
  325. e.stopImmediatePropagation(); // tries to prevent the key from being hijacked by geoguessr
  326. placeMarker(true, false, undefined)
  327. }
  328. if (e.keyCode === 50) {
  329. e.stopImmediatePropagation();
  330. placeMarker(false, false, undefined)
  331. }
  332. if (e.keyCode === 51) {
  333. e.stopImmediatePropagation();
  334. displayLocationInfo()
  335. }
  336. if (e.keyCode === 52) {
  337. e.stopImmediatePropagation();
  338. mapsFromCoords()
  339. }
  340. if (e.keyCode === 53) {
  341. e.stopImmediatePropagation();
  342. displayBRGuesses()
  343. }
  344. }
  345. setInnerText()
  346. document.addEventListener("keydown", onKeyDown);
  347. let flag = false