Guess Incorrector

Adds a percentage of incorrectness to your GeoGuessr guesses. For example, if set to 10%, and your guess would have been 10km away, your guess will be moved 11km away.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Guess Incorrector
// @description  Adds a percentage of incorrectness to your GeoGuessr guesses. For example, if set to 10%, and your guess would have been 10km away, your guess will be moved 11km away.
// @license      MIT
// @namespace    http://tampermonkey.net/
// @version      2026-03-12
// @match        *://*.geoguessr.com/*
// @run-at       document-start
// @grant        unsafeWindow
// @grant        GM_registerMenuCommand
// @require      https://cdn.jsdelivr.net/npm/[email protected]/lib/index.js
// ==/UserScript==

let percentage = parseInt( localStorage.getItem( 'incorrectorpercentage' ), 10 ) || 0;
let gcoords = [ 0, 0 ];

const showOptions = () => {
	let optionsWindow = document.querySelector('#incorrectorOptions');
	if ( ! optionsWindow ) {
		optionsWindow = document.createElement( 'div' );
		optionsWindow.id = 'incorrectorOptions';
		optionsWindow.style.borderRadius = '16px';
		optionsWindow.style.textAlign = 'center';
		optionsWindow.style.padding = '14px';
		optionsWindow.style.position = 'absolute';
		optionsWindow.style.width = '600px';
		optionsWindow.style.zIndex = '999999';
		optionsWindow.style.top = '20%';
  		optionsWindow.style.left = 'calc( 50vw - 300px )';
		optionsWindow.style.backgroundColor = 'rgba(255,255,255,.94)';
		const h1 = document.createElement( 'h1' );
		h1.innerText = 'Guess Incorrector Settings';
		const h2 = document.createElement( 'h2' );
		h2.innerText = `Add ${ percentage }% incorrectness`;
		const h3 = document.createElement( 'h3' );
		h3.innerText = `A guess that is 100km away would change to ${ Math.round( 100 * ( 1 + percentage / 100 ) ) }km away`;
		h3.style.marginBottom = '22px';
		const pInput = document.createElement( 'input' );
		pInput.type='range';
		pInput.min='0';
		pInput.max='180';
		pInput.step='1';
		pInput.value=percentage <= 100 ? percentage : 100 + ( percentage - 100 ) / 10;
		const update = () => {
			const value = parseInt( pInput.value, 10 )
			percentage = value <= 100 ? value : 100 + ( value - 100 ) * 10;
			h2.innerText = `Add ${ percentage }% incorrectness`;
			h3.innerText = `A guess that is 100km away would change to ${ Math.round( 100 * ( 1 + percentage / 100 ) ) }km away`;
			localStorage.setItem( 'incorrectorpercentage', percentage );
		};
		pInput.oninput = update;
		pInput.onchange = update;
		const close = document.createElement( 'button' );
		close.innerText = 'Close Settings';
		close.onclick = () => optionsWindow.style.display = 'none';
		close.style.margin='12px';
		close.style.padding='8px';
		close.style.borderRadius='6px';
		close.style.width='60%';
		close.style.backgroundColor='#23c921';
		const note = document.createElement( 'div' );
		note.innerText = "If you've already placed a marker, setting won't take effect until next marker placement.";
		note.style.fontSize = '0.85em';
		note.style.color = '#777';
		optionsWindow.appendChild( h1 );
		optionsWindow.appendChild( h2 );
		optionsWindow.appendChild( pInput );
		optionsWindow.appendChild( h3 );
		optionsWindow.appendChild( close );
		optionsWindow.appendChild( note );

		document.body.appendChild( optionsWindow );
		optionsWindow.style.display = 'block';
	} else if ( optionsWindow.style.display === 'block' ){
		optionsWindow.style.display = 'none';
	} else {
		optionsWindow.style.display = 'block';
	}
};

GM_registerMenuCommand( "Configure", showOptions );

function calculateIncorrectedLatLong( latitude, longitude ) {
	const distance = window.geolib.getPreciseDistance(
		{ latitude: gcoords[0], longitude: gcoords[1] },
		{ latitude, longitude }
	);

	const bearing = window.geolib.getGreatCircleBearing(
		{ latitude: gcoords[0], longitude: gcoords[1] },
		{ latitude, longitude }
	);

	const incorrected = window.geolib.computeDestinationPoint(
		{ latitude: gcoords[0], longitude: gcoords[1] },
		// 20000000 meters is roughly half the earth's circumference
		// If we move further than that, we actually may be making the guess closer
		Math.min( distance * ( 1 + percentage / 100 ), 20000000 ),
		bearing
	);
	return incorrected;
}

const originalFetch = unsafeWindow.fetch;

const updateButton = () => {
	const button = document.querySelector('button[data-qa=perform-guess]');
	if ( ! button || button.disabled ) return;

	button.style.background = percentage > 0 ? '#cb0561' : '';
	button.innerText = percentage > 0 ? `GUESS (+${percentage}% incorrect)` : 'GUESS';
};
setInterval( updateButton, 250 );

unsafeWindow.fetch = new Proxy( originalFetch, {
	apply: function (target, that, args) {

		let [resource, config] = args;

		if ( percentage > 0 ) {

			if (
				resource.match && (
					resource?.match( /^https:\/\/www\.geoguessr\.com\/api\/v\d\/games\// ) ||
					resource?.match( /^https:\/\/[^\.]+\.geoguessr\.com\/.*?\/(guess|pin)$/ )
				)
			) {
				if ( config?.body ) {
					const orig = JSON.parse( config.body );
					if ( orig.lat && orig.lng ) {
						const incorrected = calculateIncorrectedLatLong( orig.lat, orig.lng );
						if ( incorrected.latitude && incorrected.longitude ) {
							config.body = JSON.stringify( {
								...orig,
								lat: incorrected.latitude,
								lng: incorrected.longitude,
							} );
						}
					}
				}
			}
		}

		const promise = Reflect.apply(target, that, args);

		return promise;
	}
} );


const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
	if (method.toUpperCase() === 'POST' &&
		(url.startsWith('https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/GetMetadata') ||
			url.startsWith('https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/SingleImageSearch'))) {

		this.addEventListener('load', function () {
			const pattern = /-?\d+\.\d+,-?\d+\.\d+/g;
			const match = this.responseText.match(pattern);
			if (match && match[0]) {
				const [lat, lng] = match[0].split(",").map(Number);
				gcoords = [ lat, lng ];
			}
		});
	}
	return originalOpen.apply(this, arguments);
};