// ==UserScript== // @name Geoguessr Resolver Release // @namespace http://tampermonkey.net/ // @version 9.8.3 // @description Features: Automatically score 5000 Points | Score randomly between 4500 and 5000 points | Open in Google Maps | See enemy guess Distance // @author 0x978 // @match https://www.geoguessr.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com // @grant GM_webRequest // @downloadURL none // ==/UserScript== 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. nativeAlert(message) }; const originalFetch = window.fetch; window.fetch = function (url, options) { if (url === "https://www.geoguessr.com/api/v4/cd0d1298-a3aa-4bd0-be09-ccf513ad14b1") { // devs using this endpoint for Anticheat. Block all calls to it. return } return originalFetch.call(this, url, options); }; async function getAddress(lat, lon) { const response = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json`) return await response.json(); } function displayLocationInfo() { const coordinates = getUserCoordinates() // api call with the lat lon to retrieve data. getAddress(coordinates[0], coordinates[1]).then(data => { alert(` Country: ${data.address.country} County: ${data.address.county} City: ${data.address.city} Road: ${data.address.road} State: ${data.address.state} Postcode: ${data.address.postcode} Village/Suburb: ${(data.address.village || data.address.suburb)} Postal Address: ${data.display_name} `) }); } function placeMarker(safeMode, skipGet, coords) { let [lat, lng] = skipGet ? coords : getUserCoordinates() 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 placeMarkerStreaksMode([lat, lng]) return; } if (safeMode) { lat += (Math.random() / 2); lng += (Math.random() / 2); } const element = document.getElementsByClassName("guess-map__canvas-container")[0] // html element containing needed props. const keys = Object.keys(element) // all keys const key = keys.find(key => key.startsWith("__reactFiber$")) // the React key I need to access props const place = element[key].return.memoizedProps.onMarkerLocationChanged // getting the function which will allow me to place a marker on the map flag = false; place({lat: lat, lng: lng}) // placing the marker on the map at the correct coordinates given by getCoordinates(). Must be passed as an Object. toggleClick(({lat: lat, lng: lng})) displayDistanceFromCorrect({lat: lat, lng: lng}) injectOverride() } function placeMarkerStreaksMode([lat, lng]) { 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. if(!element){ element = document.getElementsByClassName("region-map_map__7jxcD")[0] } const keys = Object.keys(element) const reactKey = keys.find(key => key.startsWith("__reactFiber$")) 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. // the onRegionSelected method of the streaks map doesn't accept an object containing coordinates like the other game-modes. // Instead, it accepts the country's 2-digit IBAN country code. // 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. // TODO: find a method without using an API since the API is never guaranteed. getAddress(lat, lng).then(data => { // using API to retrieve the country code at the current coordinates. const countryCode = data.address.country_code placeMarkerFunction(countryCode) // passing the country code into the onRegionSelected method. }) } // detects game mode and return appropriate coordinates. function getUserCoordinates() { const x = document.querySelectorAll('[class^="game-panorama_panorama__"]')[0] if(x === undefined){ return backupGetUserCoordinates() } const keys = Object.keys(x) const key = keys.find(key => key.startsWith("__reactFiber$")) const props = x[key] const found = props.return.memoizedProps.panoramaRef.current.location.latLng return ([found.lat(), found.lng()]) // lat and lng are functions returning the lat/lng values } function backupGetUserCoordinates(){ let x = document.querySelector('div[data-qa="panorama"]'); const keys = Object.keys(x) const key = keys.find(key => key.startsWith("__reactFiber$")) const props = x[key] const found = props.return.memoizedProps return [found.lat,found.lng] } function mapsFromCoords() { // opens new Google Maps location using coords. const [lat, lon] = getUserCoordinates() if (!lat || !lon) { return; } window.open(`https://www.google.com/maps/place/${lat},${lon}`); } // function matchEnemyGuess(){ broken due to geoguessr AC // const enemyGuess = getEnemyGuess() // console.log(enemyGuess) // let eLat = enemyGuess.lat // let eLng = enemyGuess.lng // console.log(eLat,eLng) // placeMarker(false,true,[eLat,eLng]) // } // Broken due to geoguessr fixes // function fetchEnemyDistance() { // OUTPUT WILL NEED TO BE ROUNDED IF TO BE DISPLAYED // const guessDistance = getEnemyGuess() // if (guessDistance === null) { // return; // } // const km = guessDistance / 1000 // const miles = km * 0.621371 // return [km, miles] // } // // function getEnemyGuess() { // const x = document.getElementsByClassName("game_layout__TO_jf")[0] // if (!x) { // return null // } // const keys = Object.keys(x) // const key = keys.find(key => key.startsWith("__reactFiber$")) // const props = x[key] // const teamArr = props.return.memoizedProps.gameState.teams // const enemyTeam = findEnemyTeam(teamArr, findID()) // const enemyGuesses = enemyTeam.players[0].guesses // const recentGuess = enemyGuesses[enemyGuesses.length - 1] // // if (!isRoundValid(props.return.memoizedProps.gameState, enemyGuesses)) { // return null; // } // return recentGuess.distance // } function findID() { const y = document.getElementsByClassName("user-nick_root__DUfvc")[0] const keys = Object.keys(y) const key = keys.find(key => key.startsWith("__reactFiber$")) const props = y[key] const id = props.return.memoizedProps.userId return id } function findEnemyTeam(teams, userID) { const player0 = teams[0].players[0].playerId if (player0 !== userID) { return teams[0] } else { return teams[1] } } function isRoundValid(gameState, guesses) { // returns true if the given guess occurred in the current round. const currentRound = gameState.currentRoundNumber const numOfUserGuesses = guesses ? guesses.length : 0; return currentRound === numOfUserGuesses } function getGuessDistance(manual) { const coords = getUserCoordinates() const clat = coords[0] * (Math.PI / 180) const clng = coords[1] * (Math.PI / 180) const y = document.getElementsByClassName("guess-map__canvas-container")[0] const keys = Object.keys(y) const key = keys.find(key => key.startsWith("__reactFiber$")) const props = y[key] const user = manual ?? props.return.memoizedProps.markers[0] if (!coords || !user) { return null } const ulat = user.lat * (Math.PI / 180) const ulng = user.lng * (Math.PI / 180) const distance = Math.acos(Math.sin(clat) * Math.sin(ulat) + Math.cos(clat) * Math.cos(ulat) * Math.cos(ulng - clng)) * 6371 return distance } function displayDistanceFromCorrect(manual) { let unRoundedDistance = getGuessDistance(manual) // need unrounded distance for precise calculations later. let distance = Math.round(unRoundedDistance) if (distance === null) { return } // let enemy = fetchEnemyDistance(true) patched // const BR = getBRGuesses() patched let text = `${distance} km (${Math.round(distance * 0.621371)} miles)` setGuessButtonText(text) //alert(`Your marker is ${distance} km (${Math.round(distance * 0.621371)} miles) away from the correct guess`) } function setGuessButtonText(text) { let x = document.getElementsByClassName("button_button__aR6_e button_variantPrimary__u3WzI")[0] if(!x){ return null} x.innerText = text } function toggleClick(coords) { // prevents user from making 5k guess to prevent bans. const disableSpaceBar = (e) => { if (e.keyCode === 32) { const distance = getGuessDistance() if ((distance < 1 || isNaN(distance)) && !flag) { e.stopImmediatePropagation(); preventedActionPopup() document.removeEventListener("keyup", disableSpaceBar); flag = true } } }; document.addEventListener("keyup", disableSpaceBar); setTimeout(() => { const distance = getGuessDistance() if ((distance < 1 || isNaN(distance)) && !flag) { let old = document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0][Object.keys(document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0])[1]].onClick document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0][Object.keys(document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0])[1]].onClick = (() => { flag = true preventedActionPopup() document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0][Object.keys(document.getElementsByClassName("button_button__CnARx button_variantPrimary__xc8Hp")[0])[1]].onClick = (() => old()) }) } }, 500) } function preventedActionPopup() { alert(`Geoguessr Resolver has prevented you from making a perfect guess. Making perfect guesses will very likely result in a ban from competitive. Press "guess" again to proceed anyway.`) } function injectOverride() { 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. displayDistanceFromCorrect() }) } function getBRGuesses() { let gameRoot = document.getElementsByClassName("game_root__2vV1H")[0] if(!gameRoot){ return null } let keys = gameRoot[Object.keys(document.getElementsByClassName("game_root__2vV1H")[0])[0]] let gameState = keys.return.memoizedProps.gameState let players = gameState.players let bestGuessDistance = Number.MAX_SAFE_INTEGER players.forEach(player => { let currGuess = player.coordinateGuesses[player.coordinateGuesses.length - 1] if(currGuess){ let currDistance = currGuess.distance if ((currDistance < bestGuessDistance) && currGuess.roundNumber === gameState.currentRoundNumber && player.playerId !== gameRoot.return.memoizedProps.currentPlayer.playerId) { bestGuessDistance = currDistance } } }) if (bestGuessDistance === Number.MAX_SAFE_INTEGER) { return null; } return Math.round(bestGuessDistance / 1000) } function displayBRGuesses(){ const distance = getBRGuesses() if (distance === null) { alert("There have been no guesses this round") return; } alert(`The best guess this round is ${distance} km from the correct location. (Not including your guess)`) } // Useless after changes by geoguessr // function calculateScore(Udistance,eDistance){ // 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 // let enemyScore = Math.round(5000*Math.exp((-10*eDistance/14916.862))) // let damage = (userScore - enemyScore) * getMultiplier() // //console.log("distances:",Udistance, eDistance, "||", "scores:", userScore, enemyScore, "x:",getMultiplier(), "Damage:",damage) // return damage // } function getMultiplier(){ let obj = document.getElementsByClassName("round-icon_container__bNbtn")[0] if(!obj){return 1} let prop = obj[Object?.keys(document.getElementsByClassName("round-icon_container__bNbtn")[0])[0]]?.return?.memoizedProps return prop?.multiplier ?? 1 } function setInnerText(){ const text = ` Geoguessr Resolver Loaded Successfully IMPORTANT GEOGUESSR RESOLVER UPDATE INFORMATION: https://text.0x978.com/geoGuessr Please read the above update to GeoGuessr anticheat ` if(document.getElementsByClassName("header_logo__vV0HK")[0]){ document.getElementsByClassName("header_logo__vV0HK")[0].innerText = text } } GM_webRequest([ { selector: 'https://www.geoguessr.com/api/v4/trails', action: 'cancel' }, ]); let onKeyDown = (e) => { if (e.keyCode === 49) { placeMarker(true, false, undefined) } if (e.keyCode === 50) { placeMarker(false, false, undefined) } if (e.keyCode === 51) { displayLocationInfo() } if (e.keyCode === 52) { mapsFromCoords() } if (e.keyCode === 53) { displayBRGuesses() } } setInnerText() document.addEventListener("keydown", onKeyDown); let flag = false