Quickly resets the game by navigating to the last visited map and starting a new game.
目前為
// ==UserScript==
// @name GeoGuessr Retry Hotkey
// @namespace https://www.geoguessr.com/
// @version 1.4
// @description Quickly resets the game by navigating to the last visited map and starting a new game.
// @author Shukaaa (mr aduchi)
// @match https://www.geoguessr.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant window.focus
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// Constants for script metadata, storage keys
const SCRIPT_NAME = "GeoGuessr Retry Hotkey";
const LAST_VISITED_MAP_KEY = "lastVisitedMap";
const PLAY_TRIGGERED_KEY = "playTriggered";
const RESET_KEY_STORAGE = "resetKey";
const DELAY_STORAGE = "rh-delay";
// Retry Hotkey
let RESET_KEY = localStorage.getItem(RESET_KEY_STORAGE) || "p";
let DELAY = localStorage.getItem(DELAY_STORAGE) || 50;
// Helper: Logging function for consistent console outputs
const log = (message, level = "log") => {
const levels = {
log: console.log,
warn: console.warn
};
const logFunction = levels[level] || console.log;
logFunction(`[${SCRIPT_NAME}] ${message}`);
};
// Helper: Save to GM storage
const saveToStorage = (key, value) => GM_setValue(key, value);
// Helper: Load from GM storage
const loadFromStorage = (key, defaultValue = null) => GM_getValue(key, defaultValue);
// Handle keydown for resetting the game
const handleKeyDown = (event) => {
const currentURL = window.location.href;
// Trigger reset
if (currentURL.includes("/game/") && event.key === RESET_KEY) {
const lastVisitedMap = loadFromStorage(LAST_VISITED_MAP_KEY);
if (lastVisitedMap) {
log(`'${RESET_KEY.toUpperCase()}' key pressed. Navigating to: ${lastVisitedMap}`);
saveToStorage(PLAY_TRIGGERED_KEY, true); // Set playTriggered to true
window.location.href = lastVisitedMap; // Redirect to the map
} else {
log("No last map URL found. Reset aborted.", "warn");
}
}
if (!currentURL.includes("/game/")) {
log("Reset aborted: Not on a game page.", "warn");
}
};
// Automatically click the play button on the map page
const attemptPlay = () => {
const currentURL = window.location.href;
const playTriggered = loadFromStorage(PLAY_TRIGGERED_KEY, false);
if (currentURL.includes("/maps/") && playTriggered) {
log("Map page detected with playTriggered=true. Attempting to start a new game...");
// Try finding the play button container
const playButtonContainer = document.querySelector("div[class*='map-selector_playButtons']");
if (playButtonContainer) {
const playButton = playButtonContainer.querySelector("button");
if (playButton) {
saveToStorage(PLAY_TRIGGERED_KEY, false);
playButton.focus();
setTimeout(() => {
playButton.click();
}, DELAY);
} else {
log("'Play' button not found inside container.", "warn");
}
} else {
log("Play button container not found.", "warn");
}
}
};
// Save the current map URL if on a map page
const saveCurrentMap = () => {
const currentURL = window.location.href;
if (currentURL.includes("/maps/")) {
log(`Saving current map URL: ${currentURL}`);
saveToStorage(LAST_VISITED_MAP_KEY, currentURL);
}
};
// Check for URL Changes
const observeUrlChanges = () => {
let lastUrl = window.location.href;
const observer = new MutationObserver(() => {
const currentUrl = window.location.href;
if (currentUrl !== lastUrl) {
log(`URL changed: ${currentUrl}`);
lastUrl = currentUrl;
// Save the map URL if it's a map page
saveCurrentMap();
}
});
observer.observe(document.body, { childList: true, subtree: true });
};
const addConfigurationsToMenuOverlay = () => {
const SETTINGS_INITIALIZED_ATTR = "data-retry-hotkey-settings-initialized";
// Utility functions for creating reusable components made by geoguessr
const cloneComponent = (selector, modifier = (clone) => clone) => {
const component = document.querySelector(selector);
if (!component) return null;
const clone = component.cloneNode(true);
return modifier(clone);
};
const createTitle = (title) =>
cloneComponent("div[class*='game-menu_headerContainer']", (clone) => {
clone.childNodes[0].innerHTML = title;
return clone;
});
const createDivider = () => cloneComponent("div[class*='game-menu_divider']");
const createOptionContainer = (optionName) =>
cloneComponent("div[class*='game-menu_volumeContainer']", (clone) => {
clone.removeChild(clone.lastChild);
clone.childNodes[0].innerHTML = optionName;
return clone;
});
const createButton = (text, onClick) => {
const button = document.createElement("button");
button.style.cursor = "pointer";
button.style.color = "#fff";
button.style.border = ".0625rem solid var(--ds-color-white-80)";
button.style.padding = "0.75rem 1.5rem";
button.style.borderRadius = "3.75rem";
button.style.marginTop = "1em";
button.innerHTML = text;
if (onClick) button.onclick = onClick;
return button;
};
const createBlockquote = (text) => {
const blockquote = document.createElement("blockquote");
blockquote.style.borderLeft = ".333rem solid #ccc";
blockquote.style.color = "#ccc";
blockquote.style.marginLeft = "0";
blockquote.style.paddingLeft = "0.5em";
blockquote.innerHTML = text
return blockquote
}
const initializeSettingsMenu = () => {
const settingsContainer = document.querySelector("div[class*='game-menu_settingsContainer']");
if (!settingsContainer || settingsContainer.hasAttribute(SETTINGS_INITIALIZED_ATTR)) return;
settingsContainer.appendChild(createDivider());
settingsContainer.appendChild(createTitle("Retry Hotkey Settings"));
const switchHotkeySetting = createOptionContainer("Hotkey");
const updateHotkeyButton = createButton("Set new hotkey (Current Hotkey: " + RESET_KEY.toUpperCase() + ")", () => {
updateHotkeyButton.innerHTML = "Press a new key to set as the hotkey";
updateHotkeyButton.style.color = "#ccc";
updateHotkeyButton.disabled = true;
const handleNewKey = (event) => {
RESET_KEY = event.key;
localStorage.setItem(RESET_KEY_STORAGE, RESET_KEY);
log(`New hotkey set: ${RESET_KEY}`);
alert(`New hotkey set to: ${RESET_KEY.toUpperCase()}`);
updateHotkeyButton.disabled = false;
updateHotkeyButton.style.color = "#fff";
updateHotkeyButton.innerHTML = "Set new hotkey (Current Hotkey: " + RESET_KEY.toUpperCase() + ")";
document.removeEventListener("keydown", handleNewKey);
};
document.addEventListener("keydown", handleNewKey);
});
switchHotkeySetting.appendChild(updateHotkeyButton);
const changeDelaySetting = createOptionContainer("Delay");
const changeDelayInfoText = createBlockquote("When dealing with bad internet connection or bugs that the match won't automatically start, it can be helpful to increase the delay")
const changeDelayButton = createButton("Change Delay (Current Delay: " + DELAY + "ms)", () => {
const newDelay = prompt("Enter new delay in ms")
if (isNaN(newDelay)) {
alert("Input is not a number")
return
}
DELAY = Number(newDelay)
localStorage.setItem(DELAY_STORAGE, DELAY);
log(`New delay set: ${DELAY}`);
alert(`New delay set to: ${DELAY}`);
changeDelayButton.innerHTML = "Change Delay (Current Delay: " + DELAY + "ms)"
});
changeDelaySetting.appendChild(changeDelayInfoText);
changeDelaySetting.appendChild(changeDelayButton);
settingsContainer.appendChild(switchHotkeySetting);
settingsContainer.appendChild(changeDelaySetting);
settingsContainer.setAttribute(SETTINGS_INITIALIZED_ATTR, true);
};
initializeSettingsMenu();
};
const observeSettingsView = () => {
const observer = new MutationObserver(() => {
const settingsView = document.querySelector("div[class*='game-menu_inGameMenuOverlay']");
if (settingsView) {
log("Settings view loaded.");
addConfigurationsToMenuOverlay()
}
});
observer.observe(document.body, { childList: true, subtree: true });
};
// Initialize script
const initialize = () => {
document.addEventListener("keydown", handleKeyDown); // Add keydown listener
attemptPlay(); // Check if play needs to be triggered
saveCurrentMap(); // Save the map URL if relevant
observeUrlChanges(); // Start observing DOM and URL changes
observeSettingsView(); // Start observing the menu overlay view to add own settings
};
// Run the script
initialize();
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址