// ==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);
}
}
}
});
})();