Greasy Fork 支持简体中文。

GeoTracker

Get the exact location, even the exact coordinates of a location in GeoGuessr through Discord

// ==UserScript==
// @name         GeoTracker
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Get the exact location, even the exact coordinates of a location in GeoGuessr through Discord
// @author       ottersek & 19costa & kruzzi steam weedcocainespeed
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @grant        GM_webRequest
// @license      MIT
// ==/UserScript==

let discordWebhookUrl = localStorage.getItem('discordWebhookUrl');

if (!discordWebhookUrl) {
    discordWebhookUrl = prompt('Discord Webhook:', 'https://discord.com/api/webhooks/xxxxxx/xxxxxx');
    if (discordWebhookUrl) {
        localStorage.setItem('discordWebhookUrl', discordWebhookUrl);
    } else {
        alert('You must provide a Discord Webhook URL to continue.');
    }
}

window.alert = function (message) {
    nativeAlert(message);
};

const originalFetch = window.fetch;
window.fetch = function (url, options) {
    if (url === "https://www.geoguessr.com/api/v4/cd0d1298-a3aa-4bd0-be09-ccf513ad14b1") {
        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 = coordinateClimber();
    getAddress(coordinates[0], coordinates[1]).then(data => {
        const embed = createEmbed(data);

        sendToDiscord(embed);
    });
}

function createEmbed(data) {
    const coordinates = coordinateClimber();
    const lat = coordinates[0];
    const lon = coordinates[1];
    const googleMapsUrl = `https://www.google.com/maps/place/${lat},${lon}`;

    return {
        title: "<:genesis_tick:1153936410651414579> Location Tracked",
        description: `Postal Address: \`${data.address.road}\`\nPost Code: \`${data.address.postcode}\`\nMaps: [Click to open the exact location in Google Maps](${googleMapsUrl})`,
        color: 6225664,
        fields: [
            {
                name: "Country 🌍",
                value: `\`${data.address.country}\``,
                inline: true
            },
            {
                name: "County 🏡",
                value: `\`${data.address.county}\``,
                inline: true
            },
            {
                name: "City 🏙️",
                value: `\`${data.address.city}\``,
                inline: true
            },
            {
                name: "Road 🛣️",
                value: `\`${data.address.road}\``,
                inline: true
            },
            {
                name: "State 🏞️",
                value: `\`${data.address.state}\``,
                inline: true
            },
            {
                name: "Village/Suburb 🏘️",
                value: `\`${data.address.village || data.address.suburb}\``,
                inline: true
            }
        ],
        author: {
            name: "github.com/ottersek/geotracker",
            url: "https://github.com/ottersek/geotracker",
            icon_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png"
        },
        footer: {
            text: "github.com/ottersek",
            icon_url: "https://avatars.githubusercontent.com/u/121310374?v=4"
        }
    };
}



function sendToDiscord(embed) {
    const payload = JSON.stringify({
        embeds: [embed],
        username: "GeoTracker",
        avatar_url: "https://play-lh.googleusercontent.com/DboQuoFNkqgfcl5NiLeXsSgUOLo1F_BMe0g9ZBQBFzq5GpX5M1o7LbJeMgocXmbfy8Y"
    });

    fetch(discordWebhookUrl, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: payload
    })
        .then(response => {
            if (response.status === 204) {
                console.log("Mensaje enviado a Discord con éxito.");
            } else {
                console.error("Error al enviar el mensaje a Discord.");
            }
        })
        .catch(error => {
            console.error("Error en la solicitud fetch: " + error);
        });
}



function placeMarker(safeMode, skipGet, coords) {
    const isPanic = document.getElementsByClassName("coordinate-map_canvasContainer__7d8Yw")[0]
    if(isPanic){panicPlaceMarker(isPanic); return;}
    const isStreaks = document.getElementsByClassName("guess-map__canvas-container")[0] === undefined
    let location = skipGet ? coords : coordinateClimber(isStreaks)
    if (isStreaks) {
        placeMarkerStreaksMode(location)
        return;
    }
    let [lat, lng] = location

    if (safeMode) {
        const sway = [Math.random() > 0.5,Math.random() > 0.5]
        const multiplier = Math.random() * 4
        const horizontalAmount = Math.random() * multiplier
        const verticalAmount = Math.random() * multiplier
        sway[0] ? lat += verticalAmount : lat -= verticalAmount
        sway[1] ? lng += horizontalAmount : lat -= horizontalAmount
    }

    const element = document.getElementsByClassName("guess-map__canvas-container")[0]
    const keys = Object.keys(element) // all keys
    const key = keys.find(key => key.startsWith("__reactFiber$"))
    const place = element[key].return.memoizedProps.onMarkerLocationChanged

    flag = false;
    place({lat: lat, lng: lng})
    toggleClick(({lat: lat, lng: lng}))
    displayDistanceFromCorrect({lat: lat, lng: lng})
    injectOverride()
}

function placeMarkerStreaksMode(code) {
    let element = document.getElementsByClassName("region-map_map__5e4h8")[0]
    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

    if(typeof code !== "string"){
        let [lat,lng] = code
        getAddress(lat, lng).then(data => {
            const countryCode = data.address.country_code
            placeMarkerFunction(countryCode)
        })
        return
    }

    placeMarkerFunction(code)
}

function panicPlaceMarker(element){
    const keys = Object.keys(element)
    const key = keys.find(key => key.startsWith("__reactFiber$"))
    const props = element[key]

    const clickProperty = props.return.memoizedProps.map.__e3_.click
    const clickFunction = clickProperty[getDynamicIndex(Object.keys(clickProperty),clickProperty)].xe
    console.log(clickFunction)
    let [lat,lng] = coordinateClimber()

    lat += 0.1
    lng += 0.1

    let y = {
        "latLng": {
            "lat": () => lat,
            "lng": () =>  lng,
        }
    }
    clickFunction(y)
}

function getDynamicIndex(indexArray,clickProperty){
    for(let i = 0; i < indexArray.length;i++){
        if(clickProperty[indexArray[i]]?.xe.toString().slice(0,20) === "l=>{let e={lat:l.lat"){
            return indexArray[i]
        }
    }
    alert("Maprunner Placer failed. \n Please report this on GitHub or Greasyfork.")
}

function coordinateClimber(isStreaks){
    let timeout = 10
    let path = document.querySelector('div[data-qa="panorama"]');
    while (timeout > 0){
        const props = path[Object.keys(path).find(key => key.startsWith("__reactFiber$"))]
        const checkReturns = iterateReturns(props,isStreaks)
        if(checkReturns){
            return checkReturns
        }
        path = path.parentNode
        timeout--
    }
    alert("Failed to find co-ordinates. Please make an issue on GitHub or GreasyFork. " +
        "Please make sure you mention the game mode in your report.")
}

function iterateReturns(element,isStreaks){
    let timeout = 10
    let path = element
    while(timeout > 0){
        if(path){
            const coords = checkProps(path.memoizedProps,isStreaks)
            if(coords){
                return coords
            }
        }
        if(!path["return"]){
            return
        }
        path = path["return"]
        timeout--
    }
}

function checkProps(props,isStreaks){
    if(props?.panoramaRef){
        const found = props.panoramaRef.current.location.latLng
        return [found.lat(),found.lng()]
    }
    if(props.streakLocationCode && isStreaks){
        return props.streakLocationCode
    }
    if(props.gameState){
        const x = props.gameState[props.gameState.rounds.length-1]
        return [x.lat,x.lng]
    }
    if(props.lat){
        return [props.lat,props.lng]
    }
}

function mapsFromCoords() {
    const [lat, lon] = coordinateClimber()
    if (!lat || !lon) {
        return;
    }
    window.open(`https://www.google.com/maps/place/${lat},${lon}`);
}

function getGuessDistance(manual) {
    const coords = coordinateClimber()
    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)
    let distance = Math.round(unRoundedDistance)
    if (distance === null) {
        return
    }
    let text = `${distance} km (${Math.round(distance * 0.621371)} miles)`
    setGuessButtonText(text)
}

function setGuessButtonText(text) {
    let x = document.querySelector('button[data-qa="perform-guess"]');
    if(!x){
        return null}
    x.innerText = text
}

function toggleClick(coords) {
    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 = (() => {
        displayDistanceFromCorrect()
    })
}

function getBRCoordinateGuesses() {
    const gameRoot = document.getElementsByClassName("game_root__2vV1H")[0]
    const props = gameRoot[Object.keys(document.getElementsByClassName("game_root__2vV1H")[0])[0]]
    const gameProps = props.return.return.memoizedProps.value.gameState
    const roundNumber = gameProps.currentRoundNumber
    const playerArray = gameProps.players

    let bestGuessDistance = Number.MAX_SAFE_INTEGER

    playerArray.forEach((player) => {
        const guesses = player.coordinateGuesses
        if(guesses){
            const guess = guesses[guesses.length - 1]
            if(guess && guess.roundNumber === roundNumber){
                if(guess.distance < bestGuessDistance){
                    bestGuessDistance = guess.distance
                }
            }
        }
    })

    if (bestGuessDistance === Number.MAX_SAFE_INTEGER) {
        return null;
    }
    return Math.round(bestGuessDistance / 1000)
}

function displayBRGuesses(){
    const distance = getBRCoordinateGuesses()
    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. (This may include your guess)`)
}

function setInnerText(){
    const text = `
                GeoTracker loaded succesfully, if you got any errors, report the issue in github/greasyfork, thanks!
                `
    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) {
        e.stopImmediatePropagation();
        placeMarker(true, false, undefined)
    }
    if (e.keyCode === 50) {
        e.stopImmediatePropagation();
        placeMarker(false, false, undefined)
    }
    if (e.keyCode === 51) {
        e.stopImmediatePropagation();
        displayLocationInfo()
    }
    if (e.keyCode === 52) {
        e.stopImmediatePropagation();
        mapsFromCoords()
    }
    if (e.keyCode === 53) {
        e.stopImmediatePropagation();
        displayBRGuesses()
    }
}
setInnerText()
document.addEventListener("keydown", onKeyDown);
let flag = false