GeoGuessr ChatGPT Assistant

Analyzes GeoGuessr scenes using OpenAI Vision API

  1. // ==UserScript==
  2. // @name GeoGuessr ChatGPT Assistant
  3. // @namespace http://tampermonkey.net/
  4. // @version 2025-01-25
  5. // @description Analyzes GeoGuessr scenes using OpenAI Vision API
  6. // @author elmulinho
  7. // @match https://www.geoguessr.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
  9. // @grant GM_xmlhttpRequest
  10. // @grant window.navigator.mediaDevices
  11. // @connect api.openai.com
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // Configuration - Replace these with your values
  19. const CONFIG = {
  20. TIMEOUT: 5000,
  21. SAFE_MODE: false,
  22. OPENAI_API_KEY: 'YOUR-API-KEY',
  23. MODEL: 'gpt-4o',
  24. MAX_TOKENS: 2000,
  25. 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',
  26. RESPONSE_FORMAT: {
  27. "type": "json_schema",
  28. "json_schema": {
  29. "name": "guess_response",
  30. "schema": {
  31. "type": "object",
  32. "properties": {
  33. "country": {
  34. "type": "string",
  35. "description": "The country name based on the guess."
  36. },
  37. "province": {
  38. "type": "string",
  39. "description": "Province in the guessed country where you think the location is from."
  40. },
  41. "explanation": {
  42. "type": "string",
  43. "description": "Explanation detailing why this country was chosen and where in country could this location be."
  44. },
  45. "coordinates": {
  46. "type": "object",
  47. "properties": {
  48. "latitude": {
  49. "type": "number",
  50. "description": "Latitude coordinate of the guessed country in the guessed province. They should be very precise, at least 5 decimal digits."
  51. },
  52. "longitude": {
  53. "type": "number",
  54. "description": "Longitude coordinate of the guessed country in the guessed province. They should be very precise, at least 5 decimal digits."
  55. }
  56. },
  57. "required": [
  58. "latitude",
  59. "longitude"
  60. ],
  61. "additionalProperties": false
  62. },
  63. "confidence": {
  64. "type": "number",
  65. "description": "Confidence level of how sure you are in your guess expressed as a percentage."
  66. }
  67. },
  68. "required": [
  69. "country",
  70. "province",
  71. "explanation",
  72. "coordinates",
  73. "confidence"
  74. ],
  75. "additionalProperties": false
  76. },
  77. "strict": true
  78. }
  79. }
  80. };
  81.  
  82. // Create info panel
  83. function createInfoPanel() {
  84. const panel = document.createElement('div');
  85. panel.style.cssText = `
  86. position: fixed;
  87. top: 10px;
  88. right: 10px;
  89. background: rgba(0, 0, 0, 0.8);
  90. color: white;
  91. padding: 15px;
  92. border-radius: 8px;
  93. font-family: Arial, sans-serif;
  94. z-index: 10000;
  95. max-width: 300px;
  96. box-shadow: 0 2px 10px rgba(0,0,0,0.3);
  97. `;
  98. return panel;
  99. }
  100.  
  101. // Update info panel with analysis results
  102. function updateInfoPanel(analysis) {
  103. // Remove existing panel if present
  104. const existingPanel = document.querySelector('#gpt-info-panel');
  105. if (existingPanel) {
  106. existingPanel.remove();
  107. }
  108.  
  109. const panel = createInfoPanel();
  110. panel.id = 'gpt-info-panel';
  111. document.body.appendChild(panel);
  112.  
  113. // Set timeout to remove panel after 5 seconds
  114. setTimeout(() => {
  115. panel.remove();
  116. }, CONFIG.TIMEOUT);
  117.  
  118. const confidenceColor = analysis.confidence >= 80 ? '#4CAF50' :
  119. analysis.confidence >= 50 ? '#FFC107' : '#F44336';
  120.  
  121. panel.innerHTML = `
  122. <div style="margin-bottom: 10px; font-size: 16px; font-weight: bold;">
  123. ${analysis.country}${analysis.province ? ` - ${analysis.province}` : ''}
  124. </div>
  125. <div style="margin-bottom: 10px; font-size: 14px;">
  126. ${analysis.explanation}
  127. </div>
  128. <div style="display: flex; align-items: center; gap: 10px;">
  129. <div style="flex-grow: 1; height: 20px; background: #444; border-radius: 10px; overflow: hidden;">
  130. <div style="width: ${analysis.confidence}%; height: 100%; background: ${confidenceColor};"></div>
  131. </div>
  132. <div style="font-size: 14px; font-weight: bold;">
  133. ${Math.round(analysis.confidence)}%
  134. </div>
  135. </div>
  136. `;
  137. }
  138.  
  139. // Function to find Street View canvas
  140. async function findStreetViewIframe(timeout = 10000) {
  141. const startTime = Date.now();
  142.  
  143. while (Date.now() - startTime < timeout) {
  144. const canvases = document.querySelectorAll('canvas');
  145. const mainCanvas = Array.from(canvases).find(canvas =>
  146. canvas.className.includes('mapsConsumerUiSceneCoreScene__canvas') &&
  147. !canvas.className.includes('impressCanvas')
  148. );
  149.  
  150. if (mainCanvas) {
  151. return mainCanvas;
  152. }
  153.  
  154. await new Promise(resolve => setTimeout(resolve, 100));
  155. }
  156.  
  157. return null;
  158. }
  159.  
  160. // Function to capture screenshot
  161. async function captureScreenshot() {
  162. try {
  163. const streetViewIframe = await findStreetViewIframe();
  164. if (!streetViewIframe) {
  165. throw new Error('Street View iframe not found');
  166. }
  167.  
  168. const rect = streetViewIframe.getBoundingClientRect();
  169.  
  170. // Request screen capture
  171. const stream = await navigator.mediaDevices.getDisplayMedia({
  172. preferCurrentTab: true,
  173. video: {
  174. width: rect.width,
  175. height: rect.height
  176. }
  177. });
  178.  
  179. // Create video element to capture the stream
  180. const video = document.createElement('video');
  181. video.srcObject = stream;
  182. await new Promise(resolve => video.onloadedmetadata = resolve);
  183. await video.play();
  184.  
  185. // Create canvas and capture frame
  186. const tempCanvas = document.createElement('canvas');
  187. tempCanvas.width = video.videoWidth;
  188. tempCanvas.height = video.videoHeight;
  189.  
  190. const ctx = tempCanvas.getContext('2d');
  191. ctx.drawImage(video, 0, 0);
  192.  
  193. // Stop the stream
  194. stream.getTracks().forEach(track => track.stop());
  195.  
  196. const screenshot = tempCanvas;
  197.  
  198. const dataUrl = screenshot.toDataURL('image/png');
  199. return dataUrl;
  200. } catch (error) {
  201. console.error('❌ Error capturing screenshot:', error);
  202. return null;
  203. }
  204. }
  205.  
  206. // Function to place marker on the map
  207. async function placeMarker(coordinates, safeMode) {
  208. const { latitude: lat, longitude: lng } = coordinates;
  209.  
  210. let finalLat = lat;
  211. let finalLng = lng;
  212.  
  213. if (safeMode) { // applying random values to received coordinates
  214. const sway = [Math.random() > 0.5, Math.random() > 0.5];
  215. const multiplier = Math.random() * 4;
  216. const horizontalAmount = Math.random() * multiplier;
  217. const verticalAmount = Math.random() * multiplier;
  218. finalLat = sway[0] ? lat + verticalAmount : lat - verticalAmount;
  219. finalLng = sway[1] ? lng + horizontalAmount : lng - horizontalAmount;
  220. }
  221.  
  222. let element = document.querySelectorAll('[class^="guess-map_canvas__"]')[0];
  223. if (!element) {
  224. console.error('❌ Map canvas not found');
  225. return;
  226. }
  227.  
  228. const latLngFns = {
  229. latLng: {
  230. lat: () => finalLat,
  231. lng: () => finalLng,
  232. }
  233. };
  234.  
  235. try {
  236. // Fetching Map Element and Props to extract place function
  237. const reactKeys = Object.keys(element);
  238. const reactKey = reactKeys.find(key => key.startsWith("__reactFiber$"));
  239. const elementProps = element[reactKey];
  240. const mapElementClick = elementProps.return.return.memoizedProps.map.__e3_.click;
  241. const mapElementPropKey = Object.keys(mapElementClick)[0];
  242. const mapClickProps = mapElementClick[mapElementPropKey];
  243. const mapClickPropKeys = Object.keys(mapClickProps);
  244.  
  245. for (let i = 0; i < mapClickPropKeys.length; i++) {
  246. if (typeof mapClickProps[mapClickPropKeys[i]] === "function") {
  247. mapClickProps[mapClickPropKeys[i]](latLngFns);
  248. }
  249. }
  250. } catch (error) {
  251. console.error('❌ Error placing marker:', error);
  252. }
  253. }
  254.  
  255. // Function to send image to OpenAI Vision API
  256. async function analyzeImage(imageData) {
  257. const base64Image = imageData.split(',')[1];
  258.  
  259. const requestData = {
  260. model: CONFIG.MODEL,
  261. messages: [
  262. {
  263. role: "user",
  264. content: [
  265. { type: "text", text: CONFIG.PROMPT },
  266. { type: "image_url", image_url: { url: `data:image/jpeg;base64,${base64Image}` } }
  267. ]
  268. }
  269. ],
  270. max_tokens: CONFIG.MAX_TOKENS,
  271. response_format: CONFIG.RESPONSE_FORMAT,
  272. store: true
  273. };
  274.  
  275. try {
  276. const response = await fetch('https://api.openai.com/v1/chat/completions', {
  277. method: 'POST',
  278. headers: {
  279. 'Content-Type': 'application/json',
  280. 'Authorization': `Bearer ${CONFIG.OPENAI_API_KEY}`
  281. },
  282. body: JSON.stringify(requestData)
  283. });
  284.  
  285. const data = await response.json();
  286.  
  287. if (data.error) {
  288. console.error('❌ API Error:', data.error);
  289. return null;
  290. }
  291.  
  292. return data.choices[0].message.content;
  293. } catch (error) {
  294. console.error('❌ Error analyzing image:', error);
  295. return null;
  296. }
  297. }
  298.  
  299. // Function to wait for game elements to load
  300. async function waitForGame(timeout = 10000) {
  301. const startTime = Date.now();
  302. while (Date.now() - startTime < timeout) {
  303. const elements = document.querySelectorAll('iframe, div[class*="game"], div[class*="street"]');
  304. if (elements.length > 0) {
  305. console.log('✅ Game elements found');
  306. return true;
  307. }
  308. await new Promise(resolve => setTimeout(resolve, 100));
  309. }
  310. console.error('❌ Timeout waiting for game elements');
  311. return false;
  312. }
  313.  
  314. // Event listener for key press
  315. document.addEventListener('keydown', async function(event) {
  316. if (event.key === '1') {
  317. console.log('🎮 Key "1" pressed - Starting process...');
  318.  
  319. console.log('⏳ Waiting for game to load...');
  320. const gameLoaded = await waitForGame();
  321. if (!gameLoaded) {
  322. console.error('❌ Game not loaded');
  323. return;
  324. }
  325. // Debug log to show available elements
  326. console.log('Available elements:', {
  327. gameLayout: document.querySelector('.game-layout'),
  328. nextDiv: document.querySelector('#__next div[class*="game-layout"]'),
  329. streetView: document.querySelector('#street-view'),
  330. canvasContainer: document.querySelector('#canvas-container')
  331. });
  332.  
  333. const screenshot = await captureScreenshot();
  334. if (screenshot) {
  335. console.log('✅ Screenshot captured successfully');
  336. const analysis = await analyzeImage(screenshot);
  337. if (analysis) {
  338. try {
  339. const parsedAnalysis = JSON.parse(analysis);
  340. updateInfoPanel(parsedAnalysis);
  341. console.log(`Coordinates: ${parsedAnalysis.coordinates.latitude}, ${parsedAnalysis.coordinates.longitude}`);
  342. await placeMarker(parsedAnalysis.coordinates, CONFIG.SAFE_MODE);
  343. } catch (e) {
  344. console.error('❌ Error parsing JSON response:', e);
  345. }
  346. } else {
  347. console.error('❌ Analysis failed');
  348. }
  349. } else {
  350. console.error('❌ Screenshot capture failed');
  351. }
  352. }
  353. });
  354. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址