// ==UserScript==
// @name Geoguessr Resolver Release
// @namespace http://tampermonkey.net/
// @version 9.8.2
// @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
// ==/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]) {
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.
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){
if(document.getElementsByClassName("game-layout__panorama-canvas")[0]){
return backupGetUserCoordinates()
}
else{
let y = document.getElementsByClassName("gm-style")[0].parentNode.parentNode
const keys = Object.keys(y)
const key = keys.find(key => key.startsWith("__reactFiber$"))
const props = y[key]
const found = props.return.memoizedProps
return [found.lat,found.lng]
}
}
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.getElementsByClassName("game-layout__panorama-canvas")[0]
const keys = Object.keys(x)
const key = keys.find(key => key.startsWith("__reactFiber$"))
const props = x[key]
const found = props.memoizedProps.children.props
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)` // this got pretty hard to read as I added more features
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){
console.log("ERROR: Failed to calculate distance, unable to locate button.")
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