- // ==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');
- }
- }
- });
- })();