您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Pinpointing location finder (Geoguessr, Geotastic, GeoHub, etc.).
// ==UserScript== // @name Geoguessr Like Games - Location Finder // @namespace http://tampermonkey.net/ // @version 2.0.1 // @description Pinpointing location finder (Geoguessr, Geotastic, GeoHub, etc.). // @author Meffiu // @match https://geotastic.net/* // @match https://www.geoguessr.com/* // @match https://www.geohub.gg/* // @icon https://www.google.com/s2/favicons?sz=64&domain=geotastic.net // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (async function () { "use strict"; // Vars let alertOffset = 0; let geocodingApiKey = loadData("geocodingApiKey"); if (!geocodingApiKey) editGeocodingApiKey(); let flagIDs = await loadFlagIDs(); let defaultDescription = "Press search button to find location.<br>Press key button to edit Geocoding API key. (geocode.maps.co)<br>Press refresh button if searching is stuck on same location."; // Menu const style = document.createElement("style"); style.innerHTML = ` #dynamicMenu { position: fixed; bottom: -300px; left: 40px; width: 400px; background-color: #f4f4f4; box-shadow: 0 -2px 5px rgba(0,0,0,0.5); transition: bottom 0.3s ease; z-index: 10000; border-radius: 10px 10px 0 0; } #dynamicMenu.open { bottom: 0; } #menuTitle { background-color: #4CAF50; color: white; padding: 15px; font-size: 18px; text-align: left; border-radius: 10px 10px 0 0; cursor: pointer; } #menuDescription { padding: 15px; font-size: 14px; color: #333; text-align: left; } #toggleMenu { position: fixed; bottom: 10px; left: 150px; transform: translateX(-50%); background-color: #4CAF50; color: white; border: none; padding: 10px 15px; font-size: 16px; cursor: pointer; z-index: 10001; border-radius: 20px; } #toggleMenu:hover { background-color: #45a049; } #searchIcon, #keyIcon, #refreshIcon { position: absolute; top: 10px; width: 24px; height: 24px; cursor: pointer; } #searchIcon { right: 10px; } #keyIcon { right: 40px; } #refreshIcon { right: 70px; } `; document.head.appendChild(style); const toggleButton = document.createElement("button"); toggleButton.id = "toggleMenu"; toggleButton.textContent = `Location Finder v${GM_info.script.version}`; document.body.appendChild(toggleButton); const menu = document.createElement("div"); menu.id = "dynamicMenu"; const title = document.createElement("div"); title.id = "menuTitle"; title.textContent = `Location Finder v${GM_info.script.version}`; const searchIcon = document.createElement("img"); searchIcon.id = "searchIcon"; searchIcon.src = "https://img.icons8.com/FFFFFF/452/search--v1.png"; title.appendChild(searchIcon); const keyIcon = document.createElement("img"); keyIcon.id = "keyIcon"; keyIcon.src = "https://img.icons8.com/FFFFFF/452/key--v1.png"; title.appendChild(keyIcon); const refreshIcon = document.createElement("img"); refreshIcon.id = "refreshIcon"; refreshIcon.src = "https://img.icons8.com/win10/FFFFFF/452/refresh--v1.png"; title.appendChild(refreshIcon); searchIcon.addEventListener("click", (event) => { event.stopPropagation(); getLocation(); }); keyIcon.addEventListener("click", (event) => { event.stopPropagation(); editGeocodingApiKey(); }); refreshIcon.addEventListener("click", (event) => { event.stopPropagation(); performance.clearResourceTimings(); log("Performance resource timings cleared."); showAlert( "Refreshed", "Performance resource timings has been cleared.\nTry to move around and press search button.", "green", 2000 ); }); const description = document.createElement("div"); description.id = "menuDescription"; description.innerHTML = defaultDescription; menu.appendChild(title); menu.appendChild(description); document.body.appendChild(menu); toggleButton.addEventListener("click", () => { menu.classList.add("open"); toggleButton.style.display = "none"; }); title.addEventListener("click", () => { menu.classList.remove("open"); toggleButton.style.display = "block"; }); // Functions function log(message) { if (message.startsWith("Error:")) { console.error(`[Location Finder v${GM_info.script.version}]\n${message}`); } else { console.log(`[Location Finder v${GM_info.script.version}]\n${message}`); } } function saveData(key, data) { GM_setValue(key, JSON.stringify(data)); } function loadData(key) { const data = GM_getValue(key, null); return data ? JSON.parse(data) : null; } function editGeocodingApiKey() { const key = prompt("Please enter your Geocoding API key:"); if (key) { geocodingApiKey = key; saveData("geocodingApiKey", key); log(`API key set to ${key}`); } } function handleError(error) { log(`Error:\n${error.stack}`); if ( error.stack.includes("Failed to fetch") && error.stack.includes("at getLatestGeoPhotoService") ) { alert( "CORS is not unlocked!\nDownload an extension to unlock (eg. CORS Unlocker)\n(Or unlock it by any other method)" ); } } async function getLocation() { description.innerHTML = "Searching..."; if ( window.location.href.includes("geotastic.net") && document.querySelector(".flag-icon") ) { const flagID = document .querySelector(".flag-icon img") .getAttribute("src") .split("/")[4] .split(".")[0]; const country = flagIDs[flagID]; description.innerHTML = ""; const countryElement = document.createElement("h2"); countryElement.style.display = "flex"; countryElement.style.alignItems = "center"; const flagImg = document.createElement("img"); flagImg.src = `https://static.infra.geotastic.net/flags_rect/${flagID}.svg`; flagImg.alt = `${country} flag`; flagImg.style.marginLeft = "10px"; flagImg.style.width = "32px"; countryElement.textContent = country; countryElement.append(flagImg); description.appendChild(countryElement); return; } const geoBody = await getLatestGeoPhotoService(); if (!geoBody) return; log("Got GeoPhotoService response."); const location = lonlatExtraction(geoBody); log(`Lat & Lon: ${location}`); const address = await getAddress(location); if (address) { description.innerHTML = ""; if (address.country) { const countryElement = document.createElement("h2"); countryElement.style.display = "flex"; countryElement.style.alignItems = "center"; const flagImg = document.createElement("img"); flagImg.src = `https://flagsapi.com/${address.country_code.toUpperCase()}/flat/32.png`; flagImg.alt = `${address.country} flag`; flagImg.style.marginLeft = "10px"; countryElement.textContent = address.country; countryElement.append(flagImg); description.appendChild(countryElement); } const ul = document.createElement("ul"); for (let key in address) { if (key.includes("ISO") || key === "country_code" || key === "country") { continue; } const li = document.createElement("li"); li.textContent = `${key}: ${address[key]}`; ul.appendChild(li); } description.appendChild(ul); const button = document.createElement("button"); button.textContent = "Open in Google Maps"; button.style.display = "block"; button.style.margin = "10px 0"; button.style.padding = "5px 10px"; button.style.fontSize = "16px"; button.style.color = "#fff"; button.style.backgroundColor = "#4CAF50"; button.style.border = "none"; button.style.borderRadius = "5px"; button.style.cursor = "pointer"; button.style.transition = "background-color 0.3s ease"; button.addEventListener("mouseover", () => { button.style.backgroundColor = "#45a049"; }); button.addEventListener("mouseout", () => { button.style.backgroundColor = "#4CAF50"; }); button.addEventListener("click", () => { window.open(`https://maps.google.com/?q=${location[0]},${location[1]}`, "_blank"); }); description.appendChild(button); performance.clearResourceTimings(); } } async function getLatestGeoPhotoService() { const performanceEntries = await performance.getEntriesByType("resource"); let lastGeoPhotoServiceRequest = null; for (let i = performanceEntries.length - 1; i >= 0; i--) { if (performanceEntries[i].name.includes("GeoPhotoService.GetMetadata")) { lastGeoPhotoServiceRequest = performanceEntries[i]; break; } } if (lastGeoPhotoServiceRequest) { const geoBody = await fetch(lastGeoPhotoServiceRequest.name) .then((response) => response.text()) .catch((error) => handleError(error)); return geoBody; } else { log("No GeoPhotoService request found."); showAlert( "No GeoPhotoService request found", "1. Make sure you are in Street View game.\n2. Move around a few times before searching.", "red", 5000 ); description.innerHTML = defaultDescription; return null; } } function lonlatExtraction(body) { const regex = /-?\d+\.\d+/g; const matches = body.match(regex); const lonlat = matches.slice(0, 2); return lonlat; } async function getAddress([lat, lon]) { const url = `https://geocode.maps.co/reverse?lat=${lat}&lon=${lon}&api_key=${geocodingApiKey}`; const response = await fetch(url) .then((response) => response.json()) .catch((error) => handleError(error)); log(`Address:\n${JSON.stringify(response)}`); if (response) { return response.address; } else { log(`No response from Geocoding API.`); showAlert( "No response from Geocoding API", "Make sure your api key is correct.", "red", 5000 ); return null; } } function showAlert(title, description, color, duration) { const alertBox = document.createElement("div"); alertBox.style.position = "fixed"; alertBox.style.top = `${20 + alertOffset}px`; alertBox.style.right = "20px"; alertBox.style.padding = "15px"; alertBox.style.color = "white"; alertBox.style.backgroundColor = color; alertBox.style.maxWidth = "400px"; alertBox.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.2)"; alertBox.style.borderRadius = "10px"; alertBox.style.zIndex = "1000"; alertBox.style.opacity = "0"; alertBox.style.transition = "opacity 0.5s ease, transform 0.5s ease"; alertBox.style.transform = "translateY(-20px)"; const alertTitle = document.createElement("div"); alertTitle.textContent = title; alertTitle.style.fontWeight = "bold"; alertTitle.style.fontSize = "16px"; alertTitle.style.marginBottom = "5px"; const alertDescription = document.createElement("div"); alertDescription.innerHTML = description.replace(/\n/g, "<br>"); alertDescription.style.fontSize = "14px"; alertBox.appendChild(alertTitle); alertBox.appendChild(alertDescription); document.body.appendChild(alertBox); requestAnimationFrame(() => { alertBox.style.opacity = "1"; alertBox.style.transform = "translateY(0)"; }); setTimeout(() => { alertBox.style.opacity = "0"; alertBox.style.transform = "translateY(-20px)"; setTimeout(() => { document.body.removeChild(alertBox); alertOffset -= 90; }, 500); }, duration); alertOffset += 90; } async function loadFlagIDs() { const url = "https://gist.githubusercontent.com/Meff1u/1e596b84c8772355636326cc422a9fd0/raw/c6bf24bda96ce457b715688a692006c01807159c/flags.json"; const response = await fetch(url) .then((response) => response.json()) .catch((error) => handleError(error)); if (response) { return response; } else { return null; } } function antiAdblockSkip() { const skipButton = document.querySelector(".ad-blocker-info button"); if (skipButton) { skipButton.removeAttribute("disabled"); skipButton.classList.remove("v-btn-disabled"); skipButton.click(); } } // MutationObserver const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { if (mutation.type === "childList") { mutation.addedNodes.forEach((node) => { if ( node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === "div" && node.getAttribute("role") === "dialog" ) { antiAdblockSkip(); } }); } } }); observer.observe(document.body, { childList: true, subtree: true }); })();