GeoGuessr Location Helper

Advanced geolocation utility with AI-powered hints, coordinate detection, and map analysis tools for GeoGuessr

Size

19.4 KB

Version

1.0.1

Created

Jan 28, 2026

Updated

7 days ago

1// ==UserScript==
2// @name		GeoGuessr Location Helper
3// @description		Advanced geolocation utility with AI-powered hints, coordinate detection, and map analysis tools for GeoGuessr
4// @version		1.0.1
5// @match		https://*.geoguessr.com/*
6// @icon		https://www.geoguessr.com/_next/static/media/favicon.bffdd9d3.png
7// @grant		GM.xmlhttpRequest
8// @grant		GM.getValue
9// @grant		GM.setValue
10// ==/UserScript==
11(function() {
12    'use strict';
13
14    console.log('GeoGuessr Location Helper initialized');
15
16    // Utility panel state
17    let utilityPanel = null;
18    let isPanelVisible = false;
19    let currentCoordinates = null;
20    let isAnalyzing = false;
21
22    // Debounce function for performance
23    function debounce(func, wait) {
24        let timeout;
25        return function executedFunction(...args) {
26            const later = () => {
27                clearTimeout(timeout);
28                func(...args);
29            };
30            clearTimeout(timeout);
31            timeout = setTimeout(later, wait);
32        };
33    }
34
35    // Create the utility panel UI
36    function createUtilityPanel() {
37        const panel = document.createElement('div');
38        panel.id = 'geoguessr-utility-panel';
39        panel.style.cssText = `
40            position: fixed;
41            top: 80px;
42            right: 20px;
43            width: 320px;
44            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
45            border-radius: 12px;
46            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
47            z-index: 10000;
48            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
49            color: white;
50            display: ${isPanelVisible ? 'block' : 'none'};
51            backdrop-filter: blur(10px);
52        `;
53
54        panel.innerHTML = `
55            <div style="padding: 16px; border-bottom: 1px solid rgba(255, 255, 255, 0.2);">
56                <div style="display: flex; justify-content: space-between; align-items: center;">
57                    <h3 style="margin: 0; font-size: 18px; font-weight: 600;">🌍 Location Helper</h3>
58                    <button id="close-utility-panel" style="background: rgba(255, 255, 255, 0.2); border: none; color: white; width: 28px; height: 28px; border-radius: 50%; cursor: pointer; font-size: 18px; display: flex; align-items: center; justify-content: center;">Γ—</button>
59                </div>
60            </div>
61            
62            <div style="padding: 16px;">
63                <!-- Coordinates Section -->
64                <div style="background: rgba(255, 255, 255, 0.15); border-radius: 8px; padding: 12px; margin-bottom: 12px;">
65                    <div style="font-size: 12px; opacity: 0.9; margin-bottom: 6px; font-weight: 500;">πŸ“ Current Coordinates</div>
66                    <div id="coordinates-display" style="font-size: 14px; font-family: monospace; word-break: break-all;">
67                        Detecting location...
68                    </div>
69                    <button id="copy-coordinates" style="margin-top: 8px; background: rgba(255, 255, 255, 0.25); border: none; color: white; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 12px; width: 100%; font-weight: 500;">
70                        πŸ“‹ Copy Coordinates
71                    </button>
72                </div>
73
74                <!-- Map Analysis Section -->
75                <div style="background: rgba(255, 255, 255, 0.15); border-radius: 8px; padding: 12px; margin-bottom: 12px;">
76                    <div style="font-size: 12px; opacity: 0.9; margin-bottom: 6px; font-weight: 500;">πŸ—ΊοΈ Map Analysis</div>
77                    <div id="map-info" style="font-size: 13px; line-height: 1.6;">
78                        <div style="margin-bottom: 4px;">β€’ <span id="heading-info">Heading: N/A</span></div>
79                        <div style="margin-bottom: 4px;">β€’ <span id="zoom-info">Zoom: N/A</span></div>
80                        <div>β€’ <span id="pov-info">POV: N/A</span></div>
81                    </div>
82                </div>
83
84                <!-- AI Hints Section -->
85                <div style="background: rgba(255, 255, 255, 0.15); border-radius: 8px; padding: 12px; margin-bottom: 12px;">
86                    <div style="font-size: 12px; opacity: 0.9; margin-bottom: 8px; font-weight: 500;">πŸ€– AI Location Hints</div>
87                    <button id="get-ai-hints" style="background: rgba(255, 255, 255, 0.25); border: none; color: white; padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; width: 100%; font-weight: 500; margin-bottom: 8px;">
88                        ✨ Get AI Hints
89                    </button>
90                    <div id="ai-hints-display" style="font-size: 12px; line-height: 1.6; max-height: 200px; overflow-y: auto;">
91                        Click the button to get AI-powered location hints
92                    </div>
93                </div>
94
95                <!-- Quick Actions -->
96                <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
97                    <button id="refresh-data" style="background: rgba(255, 255, 255, 0.25); border: none; color: white; padding: 8px; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 500;">
98                        πŸ”„ Refresh
99                    </button>
100                    <button id="open-maps" style="background: rgba(255, 255, 255, 0.25); border: none; color: white; padding: 8px; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 500;">
101                        πŸ—ΊοΈ Open Maps
102                    </button>
103                </div>
104            </div>
105        `;
106
107        document.body.appendChild(panel);
108        console.log('Utility panel created');
109
110        // Add event listeners
111        document.getElementById('close-utility-panel').addEventListener('click', togglePanel);
112        document.getElementById('copy-coordinates').addEventListener('click', copyCoordinates);
113        document.getElementById('get-ai-hints').addEventListener('click', getAIHints);
114        document.getElementById('refresh-data').addEventListener('click', refreshData);
115        document.getElementById('open-maps').addEventListener('click', openInGoogleMaps);
116
117        return panel;
118    }
119
120    // Create toggle button
121    function createToggleButton() {
122        const button = document.createElement('button');
123        button.id = 'geoguessr-utility-toggle';
124        button.innerHTML = '🌍';
125        button.title = 'Toggle Location Helper';
126        button.style.cssText = `
127            position: fixed;
128            top: 20px;
129            right: 20px;
130            width: 50px;
131            height: 50px;
132            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
133            border: none;
134            border-radius: 50%;
135            color: white;
136            font-size: 24px;
137            cursor: pointer;
138            z-index: 10001;
139            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
140            transition: transform 0.2s, box-shadow 0.2s;
141        `;
142
143        button.addEventListener('mouseenter', () => {
144            button.style.transform = 'scale(1.1)';
145            button.style.boxShadow = '0 6px 20px rgba(0, 0, 0, 0.4)';
146        });
147
148        button.addEventListener('mouseleave', () => {
149            button.style.transform = 'scale(1)';
150            button.style.boxShadow = '0 4px 16px rgba(0, 0, 0, 0.3)';
151        });
152
153        button.addEventListener('click', togglePanel);
154        document.body.appendChild(button);
155        console.log('Toggle button created');
156    }
157
158    // Toggle panel visibility
159    function togglePanel() {
160        isPanelVisible = !isPanelVisible;
161        if (utilityPanel) {
162            utilityPanel.style.display = isPanelVisible ? 'block' : 'none';
163            if (isPanelVisible) {
164                refreshData();
165            }
166        }
167        console.log('Panel toggled:', isPanelVisible);
168    }
169
170    // Extract coordinates from Google Street View
171    function extractCoordinates() {
172        try {
173            // Try to get coordinates from the URL
174            const urlParams = new URLSearchParams(window.location.search);
175            
176            // Check for Google Maps iframe or embedded content
177            const panoramaContainer = document.querySelector('#panorama-container');
178            if (panoramaContainer) {
179                // Try to extract from Google Maps API
180                const canvas = document.querySelector('.widget-scene-canvas');
181                if (canvas) {
182                    // Look for coordinate data in the page
183                    const scripts = document.querySelectorAll('script');
184                    for (const script of scripts) {
185                        const content = script.textContent;
186                        // Look for coordinate patterns
187                        const latLngMatch = content.match(/[-+]?\d{1,3}\.\d+,\s*[-+]?\d{1,3}\.\d+/);
188                        if (latLngMatch) {
189                            const [lat, lng] = latLngMatch[0].split(',').map(s => parseFloat(s.trim()));
190                            if (lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
191                                currentCoordinates = { lat, lng };
192                                console.log('Coordinates extracted:', currentCoordinates);
193                                return currentCoordinates;
194                            }
195                        }
196                    }
197                }
198            }
199
200            // Try to get from window object
201            if (window.google && window.google.maps) {
202                console.log('Google Maps API detected');
203            }
204
205            console.log('Could not extract coordinates automatically');
206            return null;
207        } catch (error) {
208            console.error('Error extracting coordinates:', error);
209            return null;
210        }
211    }
212
213    // Update coordinates display
214    function updateCoordinatesDisplay() {
215        const display = document.getElementById('coordinates-display');
216        if (!display) return;
217
218        const coords = extractCoordinates();
219        if (coords) {
220            display.innerHTML = `
221                <div style="margin-bottom: 4px;">Lat: ${coords.lat.toFixed(6)}</div>
222                <div>Lng: ${coords.lng.toFixed(6)}</div>
223            `;
224        } else {
225            display.textContent = 'Coordinates not available in this view';
226        }
227    }
228
229    // Update map analysis info
230    function updateMapAnalysis() {
231        const headingInfo = document.getElementById('heading-info');
232        const zoomInfo = document.getElementById('zoom-info');
233        const povInfo = document.getElementById('pov-info');
234
235        if (!headingInfo || !zoomInfo || !povInfo) return;
236
237        try {
238            // Try to detect Street View orientation
239            const canvas = document.querySelector('.widget-scene-canvas');
240            if (canvas) {
241                headingInfo.textContent = 'Heading: Street View Active';
242                zoomInfo.textContent = 'Zoom: Interactive';
243                povInfo.textContent = 'POV: 360Β° Available';
244            } else {
245                headingInfo.textContent = 'Heading: N/A';
246                zoomInfo.textContent = 'Zoom: N/A';
247                povInfo.textContent = 'POV: N/A';
248            }
249        } catch (error) {
250            console.error('Error updating map analysis:', error);
251        }
252    }
253
254    // Copy coordinates to clipboard
255    async function copyCoordinates() {
256        const coords = currentCoordinates || extractCoordinates();
257        if (coords) {
258            const text = `${coords.lat}, ${coords.lng}`;
259            try {
260                await GM.setClipboard(text);
261                showNotification('βœ… Coordinates copied to clipboard!');
262                console.log('Coordinates copied:', text);
263            } catch (error) {
264                console.error('Error copying coordinates:', error);
265                showNotification('❌ Failed to copy coordinates');
266            }
267        } else {
268            showNotification('⚠️ No coordinates available');
269        }
270    }
271
272    // Get AI-powered location hints
273    async function getAIHints() {
274        const button = document.getElementById('get-ai-hints');
275        const display = document.getElementById('ai-hints-display');
276        
277        if (!button || !display || isAnalyzing) return;
278
279        isAnalyzing = true;
280        button.disabled = true;
281        button.textContent = '⏳ Analyzing...';
282        display.innerHTML = '<div style="text-align: center; padding: 12px;">πŸ” AI is analyzing the scene...</div>';
283
284        try {
285            // Get visible elements and context from the page
286            const visibleText = document.body.innerText.substring(0, 2000);
287            const coords = currentCoordinates || extractCoordinates();
288            
289            let prompt = 'You are a GeoGuessr expert. Analyze this game context and provide helpful location hints:\n\n';
290            
291            if (coords) {
292                prompt += `Coordinates detected: ${coords.lat}, ${coords.lng}\n`;
293            }
294            
295            prompt += `\nProvide 3-5 specific, actionable hints that would help identify the location. Focus on:\n`;
296            prompt += `- Geographic region indicators\n`;
297            prompt += `- Climate and vegetation clues\n`;
298            prompt += `- Architecture and infrastructure\n`;
299            prompt += `- Cultural markers\n`;
300            prompt += `- Language and signage\n\n`;
301            prompt += `Format as a JSON object with a "hints" array of strings.`;
302
303            console.log('Requesting AI hints...');
304            
305            const response = await RM.aiCall(prompt, {
306                type: "json_schema",
307                json_schema: {
308                    name: "location_hints",
309                    schema: {
310                        type: "object",
311                        properties: {
312                            hints: {
313                                type: "array",
314                                items: { type: "string" },
315                                minItems: 3,
316                                maxItems: 5
317                            },
318                            confidence: {
319                                type: "string",
320                                enum: ["high", "medium", "low"]
321                            }
322                        },
323                        required: ["hints"]
324                    }
325                }
326            });
327
328            console.log('AI response received:', response);
329
330            // Display hints
331            let hintsHTML = '<div style="line-height: 1.8;">';
332            response.hints.forEach((hint, index) => {
333                hintsHTML += `<div style="margin-bottom: 8px;">πŸ’‘ ${hint}</div>`;
334            });
335            hintsHTML += '</div>';
336            
337            if (response.confidence) {
338                hintsHTML += `<div style="margin-top: 12px; padding-top: 8px; border-top: 1px solid rgba(255,255,255,0.2); font-size: 11px; opacity: 0.8;">Confidence: ${response.confidence}</div>`;
339            }
340
341            display.innerHTML = hintsHTML;
342            showNotification('✨ AI hints generated!');
343
344        } catch (error) {
345            console.error('Error getting AI hints:', error);
346            display.innerHTML = '<div style="color: #ffcccc;">❌ Failed to generate hints. Please try again.</div>';
347            showNotification('❌ Failed to get AI hints');
348        } finally {
349            isAnalyzing = false;
350            button.disabled = false;
351            button.textContent = '✨ Get AI Hints';
352        }
353    }
354
355    // Refresh all data
356    function refreshData() {
357        console.log('Refreshing data...');
358        updateCoordinatesDisplay();
359        updateMapAnalysis();
360        showNotification('πŸ”„ Data refreshed');
361    }
362
363    // Open current location in Google Maps
364    async function openInGoogleMaps() {
365        const coords = currentCoordinates || extractCoordinates();
366        if (coords) {
367            const url = `https://www.google.com/maps?q=${coords.lat},${coords.lng}`;
368            await GM.openInTab(url, true);
369            showNotification('πŸ—ΊοΈ Opened in Google Maps');
370            console.log('Opened in Google Maps:', url);
371        } else {
372            showNotification('⚠️ No coordinates available');
373        }
374    }
375
376    // Show notification
377    function showNotification(message) {
378        const notification = document.createElement('div');
379        notification.style.cssText = `
380            position: fixed;
381            top: 20px;
382            left: 50%;
383            transform: translateX(-50%);
384            background: rgba(0, 0, 0, 0.9);
385            color: white;
386            padding: 12px 24px;
387            border-radius: 8px;
388            z-index: 10002;
389            font-size: 14px;
390            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
391            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
392            animation: slideDown 0.3s ease-out;
393        `;
394        notification.textContent = message;
395        document.body.appendChild(notification);
396
397        setTimeout(() => {
398            notification.style.animation = 'slideUp 0.3s ease-out';
399            setTimeout(() => notification.remove(), 300);
400        }, 2500);
401    }
402
403    // Add CSS animations
404    const style = document.createElement('style');
405    style.textContent = `
406        @keyframes slideDown {
407            from {
408                transform: translateX(-50%) translateY(-20px);
409                opacity: 0;
410            }
411            to {
412                transform: translateX(-50%) translateY(0);
413                opacity: 1;
414            }
415        }
416        @keyframes slideUp {
417            from {
418                transform: translateX(-50%) translateY(0);
419                opacity: 1;
420            }
421            to {
422                transform: translateX(-50%) translateY(-20px);
423                opacity: 0;
424            }
425        }
426        #geoguessr-utility-panel::-webkit-scrollbar {
427            width: 6px;
428        }
429        #geoguessr-utility-panel::-webkit-scrollbar-track {
430            background: rgba(255, 255, 255, 0.1);
431            border-radius: 3px;
432        }
433        #geoguessr-utility-panel::-webkit-scrollbar-thumb {
434            background: rgba(255, 255, 255, 0.3);
435            border-radius: 3px;
436        }
437        #geoguessr-utility-panel::-webkit-scrollbar-thumb:hover {
438            background: rgba(255, 255, 255, 0.5);
439        }
440    `;
441    document.head.appendChild(style);
442
443    // Initialize the extension
444    function init() {
445        console.log('Initializing GeoGuessr Location Helper...');
446        
447        // Wait for the game to load
448        const checkGameLoaded = setInterval(() => {
449            const panoramaContainer = document.querySelector('#panorama-container');
450            if (panoramaContainer) {
451                clearInterval(checkGameLoaded);
452                console.log('Game loaded, creating UI...');
453                
454                createToggleButton();
455                utilityPanel = createUtilityPanel();
456                
457                // Set up periodic updates
458                setInterval(() => {
459                    if (isPanelVisible) {
460                        updateCoordinatesDisplay();
461                        updateMapAnalysis();
462                    }
463                }, 3000);
464                
465                console.log('GeoGuessr Location Helper ready!');
466            }
467        }, 1000);
468
469        // Clear interval after 30 seconds if game doesn't load
470        setTimeout(() => {
471            clearInterval(checkGameLoaded);
472        }, 30000);
473    }
474
475    // Start the extension when DOM is ready
476    if (document.readyState === 'loading') {
477        document.addEventListener('DOMContentLoaded', init);
478    } else {
479        init();
480    }
481
482})();
GeoGuessr Location Helper | Robomonkey