Preview your GeoGuessr guess before placing it!
当前为
// ==UserScript==
// @name Guess Preview (GeoGuessr)
// @namespace rawblocky
// @version 2025.06.08
// @description Preview your GeoGuessr guess before placing it!
// @author Rawblocky
// @match *://*.geoguessr.com/*
// @run-at document-start
// @grant GM_addStyle
// @grant unsafeWindow
// @grant window.onurlchange
// @license MIT
// ==/UserScript==
// Credit to Alien Perfect's original Guess Peek
const SEARCH_RADIUS = 50000;
const PREVIEW_SIZE_WIDTH = "30%";
GM_addStyle(`
.guess-preview-button {
position: absolute;
bottom: 0;
left: 0;
width: ${PREVIEW_SIZE_WIDTH};
height: auto;
z-index: 10;
user-select: none;
}
`);
let svs;
function initSVS() {
svs = new unsafeWindow.google.maps.StreetViewService();
}
function convertDistance(distance) {
if (distance >= 1000) return (distance / 1000).toFixed(1) + " km";
return distance.toFixed(1) + " m";
}
function computeDistanceBetween(coords1, coords2) {
return unsafeWindow.google.maps.geometry.spherical.computeDistanceBetween(
coords1,
coords2
);
}
function getStreetViewUrl(panoId) {
return `https://www.google.com/maps/@?api=1&map_action=pano&pano=${panoId}`;
}
async function getNearestPano(coords) {
let pano = {};
let panorama, oldRadius;
let radius = SEARCH_RADIUS;
if (!svs) initSVS();
// eslint-disable-next-line no-constant-condition
while (true) {
try {
panorama = await svs.getPanorama({
location: coords,
radius: radius,
source: "outdoor",
preference: "nearest",
});
let roadHeading = 0;
if (panorama.data.tiles && panorama.data.tiles.centerHeading) {
roadHeading = panorama.data.tiles.centerHeading;
}
radius = computeDistanceBetween(coords, panorama.data.location.latLng);
pano.radius = radius;
pano.url =
getStreetViewUrl(panorama.data.location.pano) +
`&heading=${roadHeading}`;
pano.image = `https://streetviewpixels-pa.googleapis.com/v1/thumbnail?w=640&h=360&panoid=${panorama.data.location.pano}&yaw=${roadHeading}&cb_client=maps_sv.share`;
if (oldRadius && radius >= oldRadius) break;
oldRadius = radius;
} catch (e) {
break;
}
}
return pano;
}
function removeImage() {
const container = document.querySelector(
'[class^="guess-map_canvasContainer__"]'
);
if (container) {
const button = container.querySelector(".guess-preview-button");
if (button) {
container.removeChild(button);
}
}
}
function getIsClassicGame() {
const currentUrl = window.location.href;
return (
currentUrl.includes("geoguessr.com/game/") ||
currentUrl.includes("geoguessr.com/challenge/")
);
}
function getImage() {
if (!getIsClassicGame) {
return removeImage();
}
const container = document.querySelector(
'[class^="guess-map_canvasContainer__"]'
);
if (container) {
let button = container.querySelector(".guess-preview-button");
if (!button) {
button = document.createElement("a");
button.className = "guess-preview-button";
button.target = "_blank";
button.style.zIndex = 10;
let img = document.createElement("img");
img.className = "guess-preview";
img.style.width = "100%";
img.style.height = "100%";
img.style.zIndex = 10;
button.appendChild(img);
container.appendChild(button);
}
return [button.querySelector(".guess-preview"), button];
} else {
return null;
}
}
const originalFetch = unsafeWindow.fetch;
let lastRanEpoch = 0;
async function onFetch(args) {
if (!getIsClassicGame) {
removeImage();
return;
}
// Cooldown
const currentEpoch = Date.now();
const previousEpoch = lastRanEpoch;
lastRanEpoch = currentEpoch;
if (currentEpoch - previousEpoch < 1000) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
if (currentEpoch !== lastRanEpoch) {
return;
}
// Whenever the terrain api gets called, it'll send the coords with it (probably used by Geo to decide to either play the water/plonk SFX)
// We'll use that to display the current location
if (
args[0] === "https://www.geoguessr.com/api/v4/geo-coding/terrain" &&
args[1]?.method === "POST"
) {
const requestBody = args[1]?.body;
if (requestBody) {
try {
let imgInfo = getImage();
if (!imgInfo || !imgInfo[0] || !imgInfo[1]) {
return;
}
let img = imgInfo[0];
let button = imgInfo[1];
const jsonBody = JSON.parse(requestBody);
let locationInfo = await getNearestPano(jsonBody);
if (!locationInfo || !locationInfo.image) {
button.style.display = "none";
return;
}
button.style.display = "block";
img.src = locationInfo.image;
button.href = locationInfo.url;
} catch (e) {
console.error("Failed to parse JSON body:", e);
}
}
}
}
unsafeWindow.fetch = async function (...args) {
Promise.resolve().then(() => onFetch(args));
const response = await originalFetch.apply(this, args);
return response;
};