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-08 提交的版本,查看 最新版本

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