Geoguessr Location Finder

Displays GeoGuessr location in a dragable infobox. Press '1' to toggle the info, '2' to switch views. For educational use.

// ==UserScript==
// @name         Geoguessr Location Finder
// @namespace    http://tampermonkey.net/
// @version      2.11
// @description  Displays GeoGuessr location in a dragable infobox. Press '1' to toggle the info, '2' to switch views. For educational use.
// @author       WannabeLynx
// @match        https://www.geoguessr.com/*
// @grant        none
// @run-at       document-start
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const SHOW_ON_START = true;
    let currentCoordinates = { lat: null, lng: null };

    let countryInfo = { name: null, code: null };
    let displayMode = 'coords';

    const style = `
        #location-finder-container {
            position: fixed;
            top: 10px;
            left: 10px;
            background-color: rgba(0, 0, 0, 0.85);
            color: white;
            padding: 15px;
            border-radius: 10px;
            z-index: 9999;
            font-family: 'Inter', sans-serif;
            font-size: 14px;
            display: none; /* Initially hidden */
            cursor: move;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
            border: 1px solid #444;
            min-width: 250px;
        }
        #location-finder-container h3 {
            margin: 0 0 10px 0;
            font-size: 16px;
            text-align: center;
            border-bottom: 1px solid #555;
            padding-bottom: 8px;
        }
        #location-finder-container p {
            margin: 8px 0;
            min-height: 22px; /* Prevent layout shift while loading country */
        }
        #location-finder-container img {
             height: 20px;
             border: 1px solid #555;
             border-radius: 3px;
        }
        #location-finder-container a {
            color: #4da6ff;
            text-decoration: none;
            display: block;
            text-align: center;
            margin-top: 10px;
            background-color: #333;
            padding: 8px 12px;
            border-radius: 5px;
            transition: background-color 0.3s;
        }
        #location-finder-container a:hover {
            background-color: #555;
        }
    `;

    let infoContainer;

    function createUI() {
        if (document.getElementById('location-finder-container')) return;

        const styleSheet = document.createElement("style");
        styleSheet.innerText = style;
        document.head.appendChild(styleSheet);

        infoContainer = document.createElement('div');
        infoContainer.id = 'location-finder-container';
        infoContainer.innerHTML = '<h3>Location Finder</h3><p>Waiting for a new round...</p>';
        document.body.appendChild(infoContainer);

        makeDraggable(infoContainer);
    }

    async function fetchCountryInfo(lat, lng) {
        if (!lat || !lng) return;
        try {
            const response = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=en`);
            const data = await response.json();
            if (data && data.address) {
                countryInfo = {
                    name: data.address.country,
                    code: data.address.country_code
                };
                console.log('Location Finder: Found country info', countryInfo);
                if (displayMode === 'country') {
                    updateInfoBox();
                }
            } else {
                 throw new Error("No address data in response.");
            }
        } catch (error) {
            console.error('Location Finder: Error fetching country info:', error);
            countryInfo = { name: 'Could not retrieve country', code: null };
        }
    }

    function updateInfoBox() {
        if (!infoContainer || !currentCoordinates.lat) return;

        const { lat, lng } = currentCoordinates;
        const mapsLink = `https://www.google.com/maps/@${lat},${lng},4z?entry=ttu`;
        let contentHTML = '';

        if (displayMode === 'coords') {
            contentHTML = `
                <p><strong>Latitude:</strong> ${lat.toFixed(6)}</p>
                <p><strong>Longitude:</strong> ${lng.toFixed(6)}</p>
            `;
        } else {
            if (countryInfo.name && countryInfo.code) {
                const flagUrl = `https://flagcdn.com/w40/${countryInfo.code}.png`;
                contentHTML = `
                    <p style="display: flex; align-items: center; gap: 10px;">
                        <img src="${flagUrl}" alt="${countryInfo.name} Flag">
                        <strong>${countryInfo.name}</strong>
                    </p>
                `;
            } else {
                contentHTML = `<p>${countryInfo.name || 'Loading country...'}</p>`;
            }
        }

        infoContainer.innerHTML = `
            <h3>Location Finder</h3>
            ${contentHTML}
            <a href="${mapsLink}" target="_blank">Open in Google Maps</a>
        `;
    }

    function handleCompetitiveMode() {
        if (!infoContainer) createUI();
        infoContainer.innerHTML = `
            <h3>Location Finder</h3>
            <p style="color: #ff6b6b; font-weight: bold;">Competitive mode detected!</p>
            <p>Script is disabled to prevent cheating. Play fair.</p>
        `;
        infoContainer.style.display = 'block';
    }

    function isCompetitiveMode() {
        const url = window.location.href;
        const competitivePaths = ['/duels', '/battle-royale', '/competitive', '/multiplayer'];
        return competitivePaths.some(path => url.includes(path));
    }

    function toggleInfoBox() {
        if (!infoContainer) return;
        infoContainer.style.display = (infoContainer.style.display === 'none') ? 'block' : 'none';
    }

    function makeDraggable(element) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        element.onmousedown = dragMouseDown;

        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            element.style.top = (element.offsetTop - pos2) + "px";
            element.style.left = (element.offsetLeft - pos1) + "px";
        }

        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
        }
    }

    function parseGameData(data) {
        if (!data) return null;
        let roundData = null;
        if (data.rounds && (data.round || data.roundNumber)) {
            roundData = data.rounds[(data.round || data.roundNumber) - 1];
        } else if (data.player && data.player.currentRound) {
            roundData = data.player.currentRound;
        }
        if (roundData) {
            if (roundData.lat && roundData.lng) return { lat: roundData.lat, lng: roundData.lng };
            if (roundData.panorama?.lat && roundData.panorama?.lng) return { lat: roundData.panorama.lat, lng: roundData.panorama.lng };
        }
        return null;
    }

    function setupKeyListeners() {
        document.addEventListener('keydown', (e) => {
            if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;

            if (e.key === '1') {
                e.stopImmediatePropagation();
                toggleInfoBox();
            } else if (e.key === '2') {
                if (currentCoordinates.lat !== null && !isCompetitiveMode()) {
                    e.stopImmediatePropagation();
                    displayMode = displayMode === 'coords' ? 'country' : 'coords';
                    updateInfoBox();
                }
            }
        }, true);
    }

    function interceptFetch() {
        const originalFetch = window.fetch;
        window.fetch = function(input, init) {
            const url = typeof input === 'string' ? input : input.url;

            if (typeof url === 'string' && url.includes('/api/v3/games/')) {
                return originalFetch.apply(this, arguments).then(response => {
                    const clonedResponse = response.clone();
                    clonedResponse.json().then(data => {
                        if (isCompetitiveMode()) {
                            handleCompetitiveMode();
                            return;
                        }
                        const coords = parseGameData(data);
                        if (coords && (coords.lat !== currentCoordinates.lat || coords.lng !== currentCoordinates.lng)) {
                            console.log('Location Finder (fetch): Found new coordinates', coords);
                            currentCoordinates = coords;
                            countryInfo = { name: null, code: null };
                            displayMode = 'coords';

                            updateInfoBox();
                            fetchCountryInfo(coords.lat, coords.lng);

                            if (SHOW_ON_START) {
                                infoContainer.style.display = 'block';
                            }
                        }
                    }).catch(err => {
                        // ignore
                    });
                    return response;
                });
            }
            return originalFetch.apply(this, arguments);
        };
    }

    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', createUI);
    } else {
        createUI();
    }

    setupKeyListeners();
    interceptFetch();

})();