Greasy Fork

Guess Peek (Geoguessr)

See where your guess was after each round!

目前为 2024-01-13 提交的版本。查看 最新版本

// ==UserScript==
// @name         Guess Peek (Geoguessr)
// @namespace    alienperfect
// @version      1.0.8
// @description  See where your guess was after each round!
// @author       Alien Perfect
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=32&domain=geoguessr.com
// @run-at       document-start
// @grant        GM_addStyle
// @grant        GM_info
// @grant        unsafeWindow
// ==/UserScript==

"use strict";

const searchRadius = 250000;
const scriptName = GM_info.script.name;
const GAMES_API = "https://www.geoguessr.com/api/v3/games/";

let streetViewService;

function main() {
  console.log(`${scriptName} is running!`);

  GM_addStyle(`
    .peek-tooltip {
      display: none;
      position: absolute;
      width: 120px;
      background: #323232;
      border-radius: 4px;
      text-align: center;
      padding: 0.5rem;
      font-size: 0.9rem;
      right: 50%;
      bottom: 220%;
      margin-right: -60px;
      opacity: 90%;
      z-index: 4;
    }

    .peek-tooltip:after {
      content: "";
      position: absolute;
      top: 100%;
      left: 50%;
      margin-left: -5px;
      border-width: 5px;
      border-style: solid;
      border-color: #323232 transparent transparent transparent;
    }

    [data-pano]:hover .peek-tooltip,
    [data-no-pano]:hover .peek-tooltip {
      display: block;
    }

    [data-pano] > :first-child {
      cursor: pointer;
      --border-color: #E91E63 !important;
      --border-size-factor: 2 !important;
    }

    [data-no-pano] > :first-child {
      cursor: initial;
      --border-color: #323232 !important;
      --border-size-factor: 1.5 !important;
    }
  `);

  const _fetch = unsafeWindow.fetch;

  unsafeWindow.fetch = async (resource, options) => {
    const response = await _fetch(resource, options);
    const url = typeof resource === "string" ? resource : resource.url;

    if (url.includes(GAMES_API) && options.method === "POST") {
      try {
        if (!streetViewService) initStreetViewService();

        const resp = await response.clone().json();
        const guess = resp.player.guesses.pop();
        const guessCoords = { lat: guess.lat, lng: guess.lng };

        let pano;
        let oldRadius;
        let radius = searchRadius;
        let proceed = true;

        while (proceed) {
          try {
            pano = await streetViewService.getPanorama({
              location: guessCoords,
              radius: radius,
              source: "outdoor",
              preference: "nearest",
            });

            radius =
              unsafeWindow.google.maps.geometry.spherical.computeDistanceBetween(
                guessCoords,
                pano.data.location.latLng,
              );

            // Avoid infinite loop!
            if (oldRadius && radius >= oldRadius) break;
            oldRadius = radius;
          } catch (e) {
            proceed = false;
          }
        }

        if (pano) {
          const url = getPanoUrl(pano.data.location.pano);
          updateGuessMarker(url, radius);
        } else {
          updateGuessMarker();
        }
      } catch (e) {
        console.error(`${scriptName} error: ${e}`);
      }
    }

    return response;
  };
}

function updateGuessMarker(url, radius) {
  new MutationObserver(function () {
    const flag = document.querySelector("[data-qa='correct-location-marker']");
    const marker = document.querySelector("[data-qa='guess-marker']");

    if (flag && marker) {
      this.disconnect();

      const [distance, units] = humanizeDistance(searchRadius);
      const tooltip = document.createElement("div");
      tooltip.className = "peek-tooltip";
      tooltip.textContent = `No location was found within ${distance} ${units}!`;

      if (url) {
        const [distance, units] = humanizeDistance(radius);
        tooltip.textContent = `Click to see the nearest location! [${distance} ${units}]`;

        marker.setAttribute("data-pano", "true");
        marker.addEventListener("click", () => {
          window.open(url, "_blank");
        });
      } else {
        marker.setAttribute("data-no-pano", "true");
      }

      marker.append(tooltip);
    }
  }).observe(document.body, {
    childList: true,
    subtree: true,
  });
}

function initStreetViewService() {
  streetViewService = new unsafeWindow.google.maps.StreetViewService();
}

function getPanoUrl(pano) {
  return `https://www.google.com/maps/@?api=1&map_action=pano&pano=${pano}`;
}

function humanizeDistance(distance) {
  if (distance >= 1000) return [(distance / 1000).toFixed(1), "km"];
  return [distance.toFixed(1), "m"];
}

main();