Geoguessr Resolver Release

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

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

  1. // ==UserScript==
  2. // @name Geoguessr Resolver Release
  3. // @namespace http://tampermonkey.net/
  4. // @version 9.8.3
  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 = getUserCoordinates()
  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.  
  50. let [lat, lng] = skipGet ? coords : getUserCoordinates()
  51. if (document.getElementsByClassName("guess-map__canvas-container")[0] === undefined) { // if this is not defined, the user must be in a streaks game, streaks mode uses a different map and therefore is calculated in a different function
  52. placeMarkerStreaksMode([lat, lng])
  53. return;
  54. }
  55.  
  56. if (safeMode) {
  57. lat += (Math.random() / 2);
  58. lng += (Math.random() / 2);
  59. }
  60.  
  61. const element = document.getElementsByClassName("guess-map__canvas-container")[0] // html element containing needed props.
  62. const keys = Object.keys(element) // all keys
  63. const key = keys.find(key => key.startsWith("__reactFiber$")) // the React key I need to access props
  64. const place = element[key].return.memoizedProps.onMarkerLocationChanged // getting the function which will allow me to place a marker on the map
  65.  
  66. flag = false;
  67. place({lat: lat, lng: lng}) // placing the marker on the map at the correct coordinates given by getCoordinates(). Must be passed as an Object.
  68. toggleClick(({lat: lat, lng: lng}))
  69. displayDistanceFromCorrect({lat: lat, lng: lng})
  70. injectOverride()
  71. }
  72.  
  73. function placeMarkerStreaksMode([lat, lng]) {
  74.  
  75. 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.
  76. if(!element){
  77. element = document.getElementsByClassName("region-map_map__7jxcD")[0]
  78. }
  79. const keys = Object.keys(element)
  80. const reactKey = keys.find(key => key.startsWith("__reactFiber$"))
  81. 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.
  82.  
  83. // the onRegionSelected method of the streaks map doesn't accept an object containing coordinates like the other game-modes.
  84. // Instead, it accepts the country's 2-digit IBAN country code.
  85. // For now, I will pass the location coordinates into an API to retrieve the correct Country code, but I believe I can find a way without the API in the future.
  86. // TODO: find a method without using an API since the API is never guaranteed.
  87.  
  88. getAddress(lat, lng).then(data => { // using API to retrieve the country code at the current coordinates.
  89. const countryCode = data.address.country_code
  90. placeMarkerFunction(countryCode) // passing the country code into the onRegionSelected method.
  91. })
  92.  
  93. }
  94.  
  95. // detects game mode and return appropriate coordinates.
  96. function getUserCoordinates() {
  97. const x = document.querySelectorAll('[class^="game-panorama_panorama__"]')[0]
  98. if(x === undefined){
  99. return backupGetUserCoordinates()
  100. }
  101. const keys = Object.keys(x)
  102. const key = keys.find(key => key.startsWith("__reactFiber$"))
  103. const props = x[key]
  104. const found = props.return.memoizedProps.panoramaRef.current.location.latLng
  105. return ([found.lat(), found.lng()]) // lat and lng are functions returning the lat/lng values
  106. }
  107.  
  108. function backupGetUserCoordinates(){
  109. let x = document.querySelector('div[data-qa="panorama"]');
  110. const keys = Object.keys(x)
  111. const key = keys.find(key => key.startsWith("__reactFiber$"))
  112. const props = x[key]
  113. const found = props.return.memoizedProps
  114. return [found.lat,found.lng]
  115. }
  116.  
  117. function mapsFromCoords() { // opens new Google Maps location using coords.
  118. const [lat, lon] = getUserCoordinates()
  119. if (!lat || !lon) {
  120. return;
  121. }
  122. window.open(`https://www.google.com/maps/place/${lat},${lon}`);
  123. }
  124.  
  125. // function matchEnemyGuess(){ broken due to geoguessr AC
  126. // const enemyGuess = getEnemyGuess()
  127. // console.log(enemyGuess)
  128. // let eLat = enemyGuess.lat
  129. // let eLng = enemyGuess.lng
  130. // console.log(eLat,eLng)
  131. // placeMarker(false,true,[eLat,eLng])
  132. // }
  133.  
  134. // Broken due to geoguessr fixes
  135. // function fetchEnemyDistance() { // OUTPUT WILL NEED TO BE ROUNDED IF TO BE DISPLAYED
  136. // const guessDistance = getEnemyGuess()
  137. // if (guessDistance === null) {
  138. // return;
  139. // }
  140. // const km = guessDistance / 1000
  141. // const miles = km * 0.621371
  142. // return [km, miles]
  143. // }
  144. //
  145. // function getEnemyGuess() {
  146. // const x = document.getElementsByClassName("game_layout__TO_jf")[0]
  147. // if (!x) {
  148. // return null
  149. // }
  150. // const keys = Object.keys(x)
  151. // const key = keys.find(key => key.startsWith("__reactFiber$"))
  152. // const props = x[key]
  153. // const teamArr = props.return.memoizedProps.gameState.teams
  154. // const enemyTeam = findEnemyTeam(teamArr, findID())
  155. // const enemyGuesses = enemyTeam.players[0].guesses
  156. // const recentGuess = enemyGuesses[enemyGuesses.length - 1]
  157. //
  158. // if (!isRoundValid(props.return.memoizedProps.gameState, enemyGuesses)) {
  159. // return null;
  160. // }
  161. // return recentGuess.distance
  162. // }
  163.  
  164. function findID() {
  165. const y = document.getElementsByClassName("user-nick_root__DUfvc")[0]
  166. const keys = Object.keys(y)
  167. const key = keys.find(key => key.startsWith("__reactFiber$"))
  168. const props = y[key]
  169. const id = props.return.memoizedProps.userId
  170. return id
  171. }
  172.  
  173. function findEnemyTeam(teams, userID) {
  174. const player0 = teams[0].players[0].playerId
  175. if (player0 !== userID) {
  176. return teams[0]
  177. } else {
  178. return teams[1]
  179. }
  180. }
  181.  
  182. function isRoundValid(gameState, guesses) { // returns true if the given guess occurred in the current round.
  183. const currentRound = gameState.currentRoundNumber
  184. const numOfUserGuesses = guesses ? guesses.length : 0;
  185. return currentRound === numOfUserGuesses
  186. }
  187.  
  188. function getGuessDistance(manual) {
  189. const coords = getUserCoordinates()
  190. const clat = coords[0] * (Math.PI / 180)
  191. const clng = coords[1] * (Math.PI / 180)
  192. const y = document.getElementsByClassName("guess-map__canvas-container")[0]
  193. const keys = Object.keys(y)
  194. const key = keys.find(key => key.startsWith("__reactFiber$"))
  195. const props = y[key]
  196. const user = manual ?? props.return.memoizedProps.markers[0]
  197. if (!coords || !user) {
  198. return null
  199. }
  200. const ulat = user.lat * (Math.PI / 180)
  201. const ulng = user.lng * (Math.PI / 180)
  202.  
  203. const distance = Math.acos(Math.sin(clat) * Math.sin(ulat) + Math.cos(clat) * Math.cos(ulat) * Math.cos(ulng - clng)) * 6371
  204. return distance
  205. }
  206.  
  207. function displayDistanceFromCorrect(manual) {
  208. let unRoundedDistance = getGuessDistance(manual) // need unrounded distance for precise calculations later.
  209. let distance = Math.round(unRoundedDistance)
  210. if (distance === null) {
  211. return
  212. }
  213. // let enemy = fetchEnemyDistance(true) patched
  214. // const BR = getBRGuesses() patched
  215. let text = `${distance} km (${Math.round(distance * 0.621371)} miles)`
  216. setGuessButtonText(text)
  217. //alert(`Your marker is ${distance} km (${Math.round(distance * 0.621371)} miles) away from the correct guess`)
  218. }
  219.  
  220. function setGuessButtonText(text) {
  221. let x = document.getElementsByClassName("button_button__aR6_e button_variantPrimary__u3WzI")[0]
  222. if(!x){
  223. return null}
  224. x.innerText = text
  225. }
  226.  
  227. function toggleClick(coords) { // prevents user from making 5k guess to prevent bans.
  228. const disableSpaceBar = (e) => {
  229. if (e.keyCode === 32) {
  230. const distance = getGuessDistance()
  231. if ((distance < 1 || isNaN(distance)) && !flag) {
  232. e.stopImmediatePropagation();
  233. preventedActionPopup()
  234. document.removeEventListener("keyup", disableSpaceBar);
  235. flag = true
  236. }
  237. }
  238. };
  239. document.addEventListener("keyup", disableSpaceBar);
  240. setTimeout(() => {
  241. const distance = getGuessDistance()
  242. if ((distance < 1 || isNaN(distance)) && !flag) {
  243. let old = document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0][Object.keys(document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0])[1]].onClick
  244. document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0][Object.keys(document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0])[1]].onClick = (() => {
  245. flag = true
  246. preventedActionPopup()
  247. document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0][Object.keys(document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0])[1]].onClick = (() => old())
  248. })
  249. }
  250. }, 500)
  251. }
  252.  
  253. function preventedActionPopup() {
  254. alert(`Geoguessr Resolver has prevented you from making a perfect guess.
  255.  
  256. Making perfect guesses will very likely result in a ban from competitive.
  257.  
  258. Press "guess" again to proceed anyway.`)
  259. }
  260.  
  261. function injectOverride() {
  262. 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.
  263. displayDistanceFromCorrect()
  264. })
  265. }
  266.  
  267. function getBRGuesses() {
  268. let gameRoot = document.getElementsByClassName("game_root__2vV1H")[0]
  269. if(!gameRoot){
  270. return null
  271. }
  272. let keys = gameRoot[Object.keys(document.getElementsByClassName("game_root__2vV1H")[0])[0]]
  273. let gameState = keys.return.memoizedProps.gameState
  274. let players = gameState.players
  275. let bestGuessDistance = Number.MAX_SAFE_INTEGER
  276. players.forEach(player => {
  277. let currGuess = player.coordinateGuesses[player.coordinateGuesses.length - 1]
  278. if(currGuess){
  279. let currDistance = currGuess.distance
  280. if ((currDistance < bestGuessDistance) && currGuess.roundNumber === gameState.currentRoundNumber && player.playerId !== gameRoot.return.memoizedProps.currentPlayer.playerId) {
  281. bestGuessDistance = currDistance
  282. }
  283. }
  284. })
  285. if (bestGuessDistance === Number.MAX_SAFE_INTEGER) {
  286. return null;
  287. }
  288. return Math.round(bestGuessDistance / 1000)
  289. }
  290.  
  291. function displayBRGuesses(){
  292. const distance = getBRGuesses()
  293. if (distance === null) {
  294. alert("There have been no guesses this round")
  295. return;
  296. }
  297. alert(`The best guess this round is ${distance} km from the correct location. (Not including your guess)`)
  298. }
  299.  
  300. // Useless after changes by geoguessr
  301. // function calculateScore(Udistance,eDistance){
  302. // let userScore = Math.round(5000*Math.exp((-10*Udistance/14916.862))) // Thank you to this reddit comment for laying out the math so beautifully after I failed to do so myself: https://www.reddit.com/r/geoguessr/comments/zqwgnr/how_the_hell_does_this_game_calculate_damage/j12rjkq/?context=3
  303. // let enemyScore = Math.round(5000*Math.exp((-10*eDistance/14916.862)))
  304. // let damage = (userScore - enemyScore) * getMultiplier()
  305. // //console.log("distances:",Udistance, eDistance, "||", "scores:", userScore, enemyScore, "x:",getMultiplier(), "Damage:",damage)
  306. // return damage
  307. // }
  308.  
  309. function getMultiplier(){
  310. let obj = document.getElementsByClassName("round-icon_container__bNbtn")[0]
  311. if(!obj){return 1}
  312. let prop = obj[Object?.keys(document.getElementsByClassName("round-icon_container__bNbtn")[0])[0]]?.return?.memoizedProps
  313. return prop?.multiplier ?? 1
  314. }
  315.  
  316. function setInnerText(){
  317. const text = `
  318. Geoguessr Resolver Loaded Successfully
  319.  
  320. IMPORTANT GEOGUESSR RESOLVER UPDATE INFORMATION: https://text.0x978.com/geoGuessr
  321.  
  322. Please read the above update to GeoGuessr anticheat
  323. `
  324. if(document.getElementsByClassName("header_logo__vV0HK")[0]){
  325. document.getElementsByClassName("header_logo__vV0HK")[0].innerText = text
  326. }
  327. }
  328.  
  329. GM_webRequest([
  330. { selector: 'https://www.geoguessr.com/api/v4/trails', action: 'cancel' },
  331. ]);
  332.  
  333. let onKeyDown = (e) => {
  334. if (e.keyCode === 49) {
  335. placeMarker(true, false, undefined)
  336. }
  337. if (e.keyCode === 50) {
  338. placeMarker(false, false, undefined)
  339. }
  340. if (e.keyCode === 51) {
  341. displayLocationInfo()
  342. }
  343. if (e.keyCode === 52) {
  344. mapsFromCoords()
  345. }
  346. if (e.keyCode === 53) {
  347. displayBRGuesses()
  348. }
  349. }
  350. setInnerText()
  351. document.addEventListener("keydown", onKeyDown);
  352. let flag = false
  353.