GeoGuessr - Right-Click to Remove Pin

Right-click on the map removes your pin.

当前为 2025-09-29 提交的版本,查看 最新版本

// ==UserScript==
// @name         GeoGuessr - Right-Click to Remove Pin
// @namespace    https://greasyfork.org/en/users/your-name
// @version      1.0.0
// @description  Right-click on the map removes your pin.
// @author       Your Name
// @license      MIT
// @match        https://www.geoguessr.com/*
// @run-at       document-end
// @grant        none
// @noframes
// ==/UserScript==

(function () {
  'use strict';

  // Selector that matches the small guess map (hashed class names)
  var MAP_SELECTOR = '[class^="game_guessMap__"], [class*=" game_guessMap__"]';

  function isInsideGuessMap(el) {
    while (el && el !== document && el !== window) {
      if (el.matches && el.matches(MAP_SELECTOR)) return true;
      el = el.parentNode || el.host;
    }
    return false;
  }

  function fireEvent(type, target, baseEvt) {
    var opts = {
      bubbles: true,
      cancelable: true,
      composed: true,
      clientX: baseEvt.clientX,
      clientY: baseEvt.clientY,
      screenX: baseEvt.screenX,
      screenY: baseEvt.screenY,
      button: 0,      // left button
      buttons: 1,     // left pressed
      shiftKey: true, // emulate holding Shift
      ctrlKey: false,
      altKey: false,
      metaKey: false,
      view: window
    };

    var ev;
    try {
      if (window.PointerEvent && (type.indexOf('pointer') === 0)) {
        ev = new PointerEvent(type, Object.assign({ pointerType: 'mouse', pointerId: 1 }, opts));
      } else {
        ev = new MouseEvent(type, opts);
      }
    } catch (e) {
      ev = document.createEvent('MouseEvents');
      ev.initMouseEvent(
        type, true, true, window,
        0, opts.screenX, opts.screenY, opts.clientX, opts.clientY,
        opts.ctrlKey, opts.altKey, opts.shiftKey, opts.metaKey,
        opts.button, null
      );
    }
    target.dispatchEvent(ev);
  }

  function emulateShiftLeftClickAt(clientX, clientY, baseEvt) {
    // Use the element actually under the cursor (map libraries listen on inner nodes/canvas)
    var target = document.elementFromPoint(clientX, clientY) || baseEvt.target;
    if (!target || !isInsideGuessMap(target)) return;

    // Dispatch a typical mouse sequence with Shift held
    // (Pointer events first if supported, then mouse events)
    if (window.PointerEvent) {
      fireEvent('pointerdown', target, baseEvt);
    }
    fireEvent('mousedown', target, baseEvt);

    if (window.PointerEvent) {
      fireEvent('pointerup', target, baseEvt);
    }
    fireEvent('mouseup', target, baseEvt);
    fireEvent('click', target, baseEvt);
  }

  function attachHandlers() {
    var mapEls = document.querySelectorAll(MAP_SELECTOR);
    for (var i = 0; i < mapEls.length; i++) {
      var el = mapEls[i];
      if (el.dataset.ggRightToShift === '1') continue;
      el.dataset.ggRightToShift = '1';

      // Primary hook: contextmenu (fires on right-click)
      el.addEventListener('contextmenu', function (e) {
        // Only act for right-clicks actually over the guess map
        if (!isInsideGuessMap(e.target)) return;
        e.preventDefault();
        e.stopImmediatePropagation();
        emulateShiftLeftClickAt(e.clientX, e.clientY, e);
        return false;
      }, { capture: true, passive: false });

      // Fallback: some browsers/apps use auxclick/mousedown with button==2
      el.addEventListener('auxclick', function (e) {
        if (e.button !== 2) return;
        if (!isInsideGuessMap(e.target)) return;
        e.preventDefault();
        e.stopImmediatePropagation();
        emulateShiftLeftClickAt(e.clientX, e.clientY, e);
      }, { capture: true, passive: false });

      el.addEventListener('mousedown', function (e) {
        if (e.button !== 2) return;
        if (!isInsideGuessMap(e.target)) return;
        // Prevent map from starting a right-drag action
        e.preventDefault();
        e.stopImmediatePropagation();
      }, { capture: true, passive: false });
    }
  }

  // Initial attach
  attachHandlers();

  // Re-attach when the SPA re-renders between rounds
  if (window.MutationObserver) {
    var obs = new MutationObserver(function () { attachHandlers(); });
    obs.observe(document.documentElement, { childList: true, subtree: true });
  }

  // Also catch history navigation changes
  function wrap(fn) {
    return function () {
      var r = fn.apply(this, arguments);
      setTimeout(attachHandlers, 0);
      setTimeout(attachHandlers, 200);
      return r;
    };
  }
  try {
    if (!history.__ggRightToShiftWrapped) {
      history.pushState = wrap(history.pushState);
      history.replaceState = wrap(history.replaceState);
      window.addEventListener('popstate', function () { setTimeout(attachHandlers, 0); }, false);
      history.__ggRightToShiftWrapped = true;
    }
  } catch (e) {}
})();