GeoGuessr ChatGPT Assistant

Analyzes GeoGuessr scenes using OpenAI Vision API

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         GeoGuessr ChatGPT Assistant
// @namespace    http://tampermonkey.net/
// @version      2025-01-25
// @description  Analyzes GeoGuessr scenes using OpenAI Vision API
// @author       elmulinho
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @grant        GM_xmlhttpRequest
// @grant        window.navigator.mediaDevices
// @connect      api.openai.com
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // Configuration - Replace these with your values
    const CONFIG = {
        TIMEOUT: 5000,
        SAFE_MODE: false,
        OPENAI_API_KEY: 'YOUR-API-KEY',
        MODEL: 'gpt-4o',
        MAX_TOKENS: 2000,
        PROMPT: 'You are a Geoguessr bot. You will receive an image that is a round of Geoguessr. Your job is to provide the user with the correct answer to the given round. The possible countries: USA, Russia, Brazil, Indonesia, Australia, Mexico, Canada, Argentina, India, South Africa, Japan, Turkey, Peru, France, Spain, Chile, Colombia, Kazakhstan, Thailand, New Zealand, Philippines, Nigeria, Norway, Italy, Malaysia, United Kingdom, Kenya, Germany, Sweden, Ukraine, Romania',
        RESPONSE_FORMAT: {
            "type": "json_schema",
            "json_schema": {
                "name": "guess_response",
                "schema": {
                "type": "object",
                "properties": {
                    "country": {
                    "type": "string",
                    "description": "The country name based on the guess."
                    },
                    "province": {
                    "type": "string",
                    "description": "Province in the guessed country where you think the location is from."
                    },
                    "explanation": {
                    "type": "string",
                    "description": "Explanation detailing why this country was chosen and where in country could this location be."
                    },
                    "coordinates": {
                    "type": "object",
                    "properties": {
                        "latitude": {
                        "type": "number",
                        "description": "Latitude coordinate of the guessed country in the guessed province. They should be very precise, at least 5 decimal digits."
                        },
                        "longitude": {
                        "type": "number",
                        "description": "Longitude coordinate of the guessed country in the guessed province. They should be very precise, at least 5 decimal digits."
                        }
                    },
                    "required": [
                        "latitude",
                        "longitude"
                    ],
                    "additionalProperties": false
                    },
                    "confidence": {
                    "type": "number",
                    "description": "Confidence level of how sure you are in your guess expressed as a percentage."
                    }
                },
                "required": [
                    "country",
                    "province",
                    "explanation",
                    "coordinates",
                    "confidence"
                ],
                "additionalProperties": false
                },
                "strict": true
            }
        }
    };

    // Create info panel
    function createInfoPanel() {
        const panel = document.createElement('div');
        panel.style.cssText = `
            position: fixed;
            top: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 15px;
            border-radius: 8px;
            font-family: Arial, sans-serif;
            z-index: 10000;
            max-width: 300px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.3);
        `;
        return panel;
    }

    // Update info panel with analysis results
    function updateInfoPanel(analysis) {
        // Remove existing panel if present
        const existingPanel = document.querySelector('#gpt-info-panel');
        if (existingPanel) {
            existingPanel.remove();
        }

        const panel = createInfoPanel();
        panel.id = 'gpt-info-panel';
        document.body.appendChild(panel);

        // Set timeout to remove panel after 5 seconds
        setTimeout(() => {
            panel.remove();
        }, CONFIG.TIMEOUT);

        const confidenceColor = analysis.confidence >= 80 ? '#4CAF50' :
                              analysis.confidence >= 50 ? '#FFC107' : '#F44336';

        panel.innerHTML = `
            <div style="margin-bottom: 10px; font-size: 16px; font-weight: bold;">
                ${analysis.country}${analysis.province ? ` - ${analysis.province}` : ''}
            </div>
            <div style="margin-bottom: 10px; font-size: 14px;">
                ${analysis.explanation}
            </div>
            <div style="display: flex; align-items: center; gap: 10px;">
                <div style="flex-grow: 1; height: 20px; background: #444; border-radius: 10px; overflow: hidden;">
                    <div style="width: ${analysis.confidence}%; height: 100%; background: ${confidenceColor};"></div>
                </div>
                <div style="font-size: 14px; font-weight: bold;">
                    ${Math.round(analysis.confidence)}%
                </div>
            </div>
        `;
    }

    // Function to find Street View canvas
    async function findStreetViewIframe(timeout = 10000) {
        const startTime = Date.now();

        while (Date.now() - startTime < timeout) {
            const canvases = document.querySelectorAll('canvas');
            const mainCanvas = Array.from(canvases).find(canvas =>
                canvas.className.includes('mapsConsumerUiSceneCoreScene__canvas') &&
                !canvas.className.includes('impressCanvas')
            );

            if (mainCanvas) {
                return mainCanvas;
            }

            await new Promise(resolve => setTimeout(resolve, 100));
        }

        return null;
    }

    // Function to capture screenshot
    async function captureScreenshot() {
        try {
            const streetViewIframe = await findStreetViewIframe();
            if (!streetViewIframe) {
                throw new Error('Street View iframe not found');
            }

            const rect = streetViewIframe.getBoundingClientRect();

            // Request screen capture
            const stream = await navigator.mediaDevices.getDisplayMedia({
                preferCurrentTab: true,
                video: {
                    width: rect.width,
                    height: rect.height
                }
            });

            // Create video element to capture the stream
            const video = document.createElement('video');
            video.srcObject = stream;
            await new Promise(resolve => video.onloadedmetadata = resolve);
            await video.play();

            // Create canvas and capture frame
            const tempCanvas = document.createElement('canvas');
            tempCanvas.width = video.videoWidth;
            tempCanvas.height = video.videoHeight;

            const ctx = tempCanvas.getContext('2d');
            ctx.drawImage(video, 0, 0);

            // Stop the stream
            stream.getTracks().forEach(track => track.stop());

            const screenshot = tempCanvas;

            const dataUrl = screenshot.toDataURL('image/png');
            return dataUrl;
        } catch (error) {
            console.error('❌ Error capturing screenshot:', error);
            return null;
        }
    }

    // Function to place marker on the map
    async function placeMarker(coordinates, safeMode) {
        const { latitude: lat, longitude: lng } = coordinates;

        let finalLat = lat;
        let finalLng = lng;

        if (safeMode) { // applying random values to received coordinates
            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;
            finalLat = sway[0] ? lat + verticalAmount : lat - verticalAmount;
            finalLng = sway[1] ? lng + horizontalAmount : lng - horizontalAmount;
        }

        let element = document.querySelectorAll('[class^="guess-map_canvas__"]')[0];
        if (!element) {
            console.error('❌ Map canvas not found');
            return;
        }

        const latLngFns = {
            latLng: {
                lat: () => finalLat,
                lng: () => finalLng,
            }
        };

        try {
            // Fetching Map Element and Props to extract place function
            const reactKeys = Object.keys(element);
            const reactKey = reactKeys.find(key => key.startsWith("__reactFiber$"));
            const elementProps = element[reactKey];
            const mapElementClick = elementProps.return.return.memoizedProps.map.__e3_.click;
            const mapElementPropKey = Object.keys(mapElementClick)[0];
            const mapClickProps = mapElementClick[mapElementPropKey];
            const mapClickPropKeys = Object.keys(mapClickProps);

            for (let i = 0; i < mapClickPropKeys.length; i++) {
                if (typeof mapClickProps[mapClickPropKeys[i]] === "function") {
                    mapClickProps[mapClickPropKeys[i]](latLngFns);
                }
            }
        } catch (error) {
            console.error('❌ Error placing marker:', error);
        }
    }

    // Function to send image to OpenAI Vision API
    async function analyzeImage(imageData) {
        const base64Image = imageData.split(',')[1];

        const requestData = {
            model: CONFIG.MODEL,
            messages: [
                {
                    role: "user",
                    content: [
                        { type: "text", text: CONFIG.PROMPT },
                        { type: "image_url", image_url: { url: `data:image/jpeg;base64,${base64Image}` } }
                    ]
                }
            ],
            max_tokens: CONFIG.MAX_TOKENS,
            response_format: CONFIG.RESPONSE_FORMAT,
            store: true
        };

        try {
            const response = await fetch('https://api.openai.com/v1/chat/completions', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${CONFIG.OPENAI_API_KEY}`
                },
                body: JSON.stringify(requestData)
            });

            const data = await response.json();

            if (data.error) {
                console.error('❌ API Error:', data.error);
                return null;
            }

            return data.choices[0].message.content;
        } catch (error) {
            console.error('❌ Error analyzing image:', error);
            return null;
        }
    }

    // Function to wait for game elements to load
    async function waitForGame(timeout = 10000) {
        const startTime = Date.now();
        while (Date.now() - startTime < timeout) {
            const elements = document.querySelectorAll('iframe, div[class*="game"], div[class*="street"]');
            if (elements.length > 0) {
                console.log('✅ Game elements found');
                return true;
            }
            await new Promise(resolve => setTimeout(resolve, 100));
        }
        console.error('❌ Timeout waiting for game elements');
        return false;
    }

    // Event listener for key press
    document.addEventListener('keydown', async function(event) {
        if (event.key === '1') {
            console.log('🎮 Key "1" pressed - Starting process...');

            console.log('⏳ Waiting for game to load...');
            const gameLoaded = await waitForGame();
            if (!gameLoaded) {
                console.error('❌ Game not loaded');
                return;
            }
            // Debug log to show available elements
            console.log('Available elements:', {
                gameLayout: document.querySelector('.game-layout'),
                nextDiv: document.querySelector('#__next div[class*="game-layout"]'),
                streetView: document.querySelector('#street-view'),
                canvasContainer: document.querySelector('#canvas-container')
            });

            const screenshot = await captureScreenshot();
            if (screenshot) {
                console.log('✅ Screenshot captured successfully');
                const analysis = await analyzeImage(screenshot);
                if (analysis) {
                    try {
                        const parsedAnalysis = JSON.parse(analysis);
                        updateInfoPanel(parsedAnalysis);
                        console.log(`Coordinates: ${parsedAnalysis.coordinates.latitude}, ${parsedAnalysis.coordinates.longitude}`);
                        await placeMarker(parsedAnalysis.coordinates, CONFIG.SAFE_MODE);
                    } catch (e) {
                        console.error('❌ Error parsing JSON response:', e);
                    }
                } else {
                    console.error('❌ Analysis failed');
                }
            } else {
                console.error('❌ Screenshot capture failed');
            }
        }
    });
})();