// ==UserScript== // @name Geoguessrtips // @namespace https://www.leonbrandt.com // @version 2.2.0 // @description 这是一个指定地图提示plonkit的脚本 // @author Leon Brandt // @homepage https://greasyfork.org/zh-CN/users/1129612-yukejun // @match https://www.geoguessr.com/* // @grant GM_xmlhttpRequest // @run-at document-idle // @downloadURL none // ==/UserScript== let currentMapId = null; let lastUrl = window.location.href; // 记录最后一次检测的URL function checkAndUpdateMapId() { const urlPattern = /^https:\/\/www\.geoguessr\.com\/maps\//; const currentUrl = window.location.href; if (lastUrl !== currentUrl) { lastUrl = currentUrl; // 更新最后一次检测的URL console.log("Detected URL change. Current URL:", currentUrl); if (urlPattern.test(currentUrl)) { const match = currentUrl.match(/maps\/([^\/]+)/); if (match && match[1]) { const newMapId = match[1]; if (currentMapId !== newMapId) { currentMapId = newMapId; localStorage.setItem('currentMapId', currentMapId); // 更新LocalStorage console.log("Updated mapId:", currentMapId); } } else { console.error("Failed to extract mapId from URL:", currentUrl); } } else { console.log("The URL does not match the required pattern for map IDs. Attempting to retrieve ID from storage."); currentMapId = localStorage.getItem('currentMapId'); if (currentMapId) { console.log("Using stored mapId:", currentMapId); } else { console.error("No mapId available in LocalStorage."); } } } } // 设置定时器以定期检查URL变化 setInterval(checkAndUpdateMapId, 1000); // 每秒检查一次 // 在脚本开始时调用 checkAndUpdateMapId 函数确保ID是最新的 checkAndUpdateMapId(); // 添加模态窗口到页面中 function addModalToPage() { // 模态窗口的HTML内容,增加了关闭按钮并改进了样式 const modalHTML = ` `; // 找到目标元素或将模态窗口插入到中 const targetElement = document.body; targetElement.insertAdjacentHTML('beforeend', modalHTML); // 关闭按钮的事件监听器 const modalClose = document.getElementById('modalClose'); modalClose.addEventListener('click', function() { const modal = document.getElementById('customModal'); modal.style.opacity = '0'; modal.style.visibility = 'hidden'; }); const savedTop = localStorage.getItem('modalTop'); const savedLeft = localStorage.getItem('modalLeft'); // 如果localStorage中有位置信息,则使用这些信息设置模态窗口的位置 if (savedTop && savedLeft) { const modal = document.getElementById('customModal'); modal.style.top = savedTop; modal.style.left = savedLeft; // 由于我们设置了top和left,移除之前的transform modal.style.transform = 'none'; } // 确保模态已经被添加到页面 makeModalDraggable(); } // 初始化时调用 addModalToPage 函数以设置模态窗口的位置 addModalToPage(); // 拖动模态窗口的功能 function makeModalDraggable() { const modal = document.getElementById('customModal'); let isDragging = false; let offsetX = 0; let offsetY = 0; // 当在模态窗口上按下鼠标时 modal.addEventListener('mousedown', function(e) { isDragging = true; // 获取模态窗口的当前位置 const rect = modal.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; // 初始化模态窗口的位置,忽略transform属性 modal.style.left = rect.left + 'px'; modal.style.top = rect.top + 'px'; // 移除transform属性,以防止突然跳跃 modal.style.transform = 'none'; modal.style.cursor = 'move'; }); // 当在文档上移动鼠标时 document.addEventListener('mousemove', function(e) { if (isDragging) { modal.style.left = e.clientX - offsetX + 'px'; modal.style.top = e.clientY - offsetY + 'px'; } }); // 当在文档上释放鼠标时 document.addEventListener('mouseup', function() { if (isDragging) { isDragging = false; modal.style.cursor = 'default'; // 保存当前位置到localStorage localStorage.setItem('modalTop', modal.style.top); localStorage.setItem('modalLeft', modal.style.left); } }); } // 全局点击事件,用于检测当点击事件发生在模态窗口之外时 document.addEventListener('click', function(event) { const modal = document.getElementById('customModal'); // 检查模态窗口是否存在并且是可见的 if (modal && modal.style.opacity === '1') { // 现在只有当模态窗口可见时,才会检查点击是否发生在模态窗口之外 if (!modal.contains(event.target)) { modal.style.opacity = '0'; modal.style.visibility = 'hidden'; modal.style.display = 'none'; } } }); // 显示模态窗口并设置其内容 function showModal(content, serverText) { const modal = document.getElementById('customModal'); const modalContent = document.getElementById('modalContent'); // 将 serverText 作为普通文本添加到地址信息下方,而不是在 textarea 中 // 修改后的 textarea 样式 modalContent.innerHTML = content + '

' + serverText + '

' + '' + '
' + '' + ''; // 设置模态窗口的样式为可见 modal.style.opacity = '1'; modal.style.visibility = 'visible'; modal.style.display = 'block'; const saveBtn = document.getElementById('saveBtn'); // 添加新的事件监听器 saveBtn.addEventListener("click", saveTextFunction); } // 提取保存文本的功能,以便可以从多个地方引用 async function saveTextFunction() { const modalContent = document.getElementById('modalContent'); const updatedText = modalContent.querySelector('textarea').value; const coords = getCoordinates(); const dataToSend = { lat: coords.lat, lng: coords.lng, text: updatedText, mapId: currentMapId // 添加这一行 }; const response = await fetch("http://localhost:8000/update_text", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(dataToSend) }); const result = await response.json(); const saveStatusDiv = document.getElementById('saveStatus'); if (result.success) { saveStatusDiv.innerText = "文本已保存!"; } else { saveStatusDiv.innerText = "保存失败!"; } // 显示保存状态 saveStatusDiv.style.display = 'block'; // 在1秒后开始使其透明度降为0 setTimeout(() => { saveStatusDiv.style.opacity = '0'; }, 1000); // 重新预加载数据 fetchDataAndShowModal(); } // 下面是一系列获取当前游戏坐标的函数 function coordinateClimber(isStreaks){ let timeout = 10 let path = document.querySelector('div[data-qa="panorama"]'); while (timeout > 0){ const props = path[Object.keys(path).find(key => key.startsWith("__reactFiber$"))]; const checkReturns = iterateReturns(props,isStreaks) if(checkReturns){ return checkReturns } path = path.parentNode timeout-- } alert("Failed to find co-ordinates. Please make an issue on GitHub or GreasyFork. " + "Please make sure you mention the game mode in your report.") } function iterateReturns(element,isStreaks){ let timeout = 10 let path = element while(timeout > 0){ if(path){ const coords = checkProps(path.memoizedProps,isStreaks) if(coords){ return coords } } if(!path["return"]){ return } path = path["return"] timeout-- } } function checkProps(props,isStreaks){ if(props?.panoramaRef){ const found = props.panoramaRef.current.location.latLng return [found.lat(),found.lng()] } if(props.streakLocationCode && isStreaks){ return props.streakLocationCode } if(props.gameState){ const x = props.gameState[props.gameState.rounds.length-1] return [x.lat,x.lng] } if(props.lat){ return [props.lat,props.lng] } } function getCoordinates() { const coords = coordinateClimber(); return { lat: coords[0], lng: coords[1] }; } // 使用OpenStreetMap API获取坐标对应的地址 function getAddress(lat, lon) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json`, onload: function(response) { if (response.status === 200) { resolve(JSON.parse(response.responseText)); } else { reject('Failed to fetch address'); } } }); }); } let lastFetchedData = null; // 用于存储最后一次获取的地址信息 // 获取数据并显示模态窗口 async function fetchDataAndShowModal() { const coords = getCoordinates(); currentMapId = localStorage.getItem('currentMapId'); // 确保重新获取 localStorage 中的 currentMapId if (!currentMapId) { console.error("No mapId available to send to the server."); return; } // 获取地址信息 try { const addressData = await getAddress(coords.lat, coords.lng); const addressInfo = ` Country: ${addressData.address.country}
`; /*在addressInfo添加以下信息可显示更详细的地址信息: Country: ${addressData.address.country}
State: ${addressData.address.state}
County: ${addressData.address.county}
City: ${addressData.address.city}
Road: ${addressData.address.road}
Postcode: ${addressData.address.postcode}
Village/Suburb: ${(addressData.address.village || addressData.address.suburb)}
Postal Address: ${addressData.display_name}
*/ const dataToSend = { lat: coords.lat, lng: coords.lng, mapId: currentMapId }; GM_xmlhttpRequest({ method: "POST", url: "http://localhost:8000/compare_coordinates", data: JSON.stringify(dataToSend), headers: { "Content-Type": "application/json" }, onload: function(response) { const result = JSON.parse(response.responseText); let serverResponse = result.match ? result.text : "Coordinates do not match."; lastFetchedData = { address: addressInfo, serverText: serverResponse }; } }); } catch (error) { console.error("Error fetching address:", error); } } // 在主函数中 (function() { 'use strict'; addModalToPage(); // 在页面加载后3秒执行 fetchDataAndShowModal 函数 setTimeout(fetchDataAndShowModal, 3000); const selectors = [ 'button[data-qa="close-round-result"]', 'button[data-qa="play-again-button"]', 'button[data-qa="start-game-button"]', '#saveBtn' ]; function addEventToButton() { selectors.forEach(selector => { const btn = document.querySelector(selector); if (btn) { btn.removeEventListener("click", fetchDataAndShowModalDelayed); // 防止重复添加 btn.addEventListener("click", fetchDataAndShowModalDelayed); } }); } function fetchDataAndShowModalDelayed() { setTimeout(fetchDataAndShowModal, 3000); // 延迟3秒执行 } // 使用 MutationObserver 监听页面元素变化 const observer = new MutationObserver(addEventToButton); observer.observe(document.body, { childList: true, subtree: true }); addEventToButton(); // 初始化时尝试添加 // 使用 addEventListener 替代 onkeydown document.addEventListener("keydown", function(evt) { const targetTagName = (evt.target || evt.srcElement).tagName; // 检查按下的键是否是“1” if (evt.keyCode == 49 && targetTagName != 'INPUT' && targetTagName != 'TEXTAREA') { const modal = document.getElementById('customModal'); // 检查模态窗口是否已经可见 if (modal.style.opacity === '1') { // 模态窗口已经显示,将其隐藏 modal.style.opacity = '0'; modal.style.visibility = 'hidden'; modal.style.display = 'none'; } else { // 模态窗口未显示,将其显示 if (lastFetchedData) { // 确保有数据显示 showModal(lastFetchedData.address, lastFetchedData.serverText); } } } }); })();