// ==UserScript==
// @name 图寻复盘工具
// @namespace https://greasyfork.org/users/1179204
// @version 1.0.1
// @description 多功能转换图寻复盘链接成街景链接,显示地点位置信息和拍摄时间以及百度街景的panoId
// @match https://tuxun.fun/replay-pano?gameId=*&round=*
// @icon data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDggNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0iIzAwMDAwMCI+PGcgaWQ9IlNWR1JlcG9fYmdDYXJyaWVyIiBzdHJva2Utd2lkdGg9IjAiPjwvZz48ZyBpZD0iU1ZHUmVwb190cmFjZXJDYXJyaWVyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjwvZz48ZyBpZD0iU1ZHUmVwb19pY29uQ2FycmllciI+PHRpdGxlPjcwIEJhc2ljIGljb25zIGJ5IFhpY29ucy5jbzwvdGl0bGU+PHBhdGggZD0iTTI0LDEuMzJjLTkuOTIsMC0xOCw3LjgtMTgsMTcuMzhBMTYuODMsMTYuODMsMCwwLDAsOS41NywyOS4wOWwxMi44NCwxNi44YTIsMiwwLDAsMCwzLjE4LDBsMTIuODQtMTYuOEExNi44NCwxNi44NCwwLDAsMCw0MiwxOC43QzQyLDkuMTIsMzMuOTIsMS4zMiwyNCwxLjMyWiIgZmlsbD0iI2ZmOTQyNyI+PC9wYXRoPjxwYXRoIGQ9Ik0yNS4zNywxMi4xM2E3LDcsMCwxLDAsNS41LDUuNUE3LDcsMCwwLDAsMjUuMzcsMTIuMTNaIiBmaWxsPSIjZmZmZmZmIj48L3BhdGg+PC9nPjwvc3ZnPg==
// @author KaKa
// @grant GM_setClipboard
// @grant GM_addStyle
// @copyright KaKa
// @license MIT
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
#coordinates-container {
position: fixed;
top: 100px;
left: 10px;
padding: 10px;
border-radius: 20px !important;
z-index: 1000;
display: flex;
flex-direction: column;
width: 180px;
}
#coordinates-container button {
cursor: pointer;
width: 100% !important;
font-weight: bold !important;
border: 8px solid #000000 !important;
text-align: left !important;
padding-left: 8px !important;
padding-right: 8px !important;
backdrop-filter: blur(10px);
margin-bottom: 5px;
border-radius: 4px;
background-color: #000000 !important;
color: #A0A0A0 !important;
};
#conversion-mode {
font-family: Arial, sans-serif !important;
color: #000000 !important;
text-shadow: -1px -1px 0 #A0A0A0, 1px -1px 0 #A0A0A0, -1px 1px 0 #A0A0A0, 1px 1px 0 #A0A0A0 !important;
margin-bottom: 5px !important;
}
.link-button {
background: none!important;
border: none;
padding: 0!important;
color: #FFCC00 !important;
text-decoration: underline;
cursor: pointer;
}
.link-button:hover {
color: #FFCC00 !important;
}
`);
const container = document.createElement('div');
container.id = 'coordinates-container';
document.body.appendChild(container);
const conversionModeLabel = document.createElement('div');
conversionModeLabel.id = 'conversion-mode';
conversionModeLabel.style.display='none'
container.appendChild(conversionModeLabel);
const openButton = document.createElement('button');
openButton.textContent = 'Open in Map';
container.appendChild(openButton);
const copyButton = document.createElement('button');
copyButton.textContent = 'Copy to Clipboard';
container.appendChild(copyButton);
let currentLink = '';
let globalPanoId=null
openButton.onclick = () => window.open(currentLink, '_blank');
copyButton.onclick = () => {
if (globalPanoId)currentLink=globalPanoId
GM_setClipboard(currentLink, 'text');
alert('Link copied to clipboard');
};
const areaButton = document.createElement('button');
areaButton.textContent = 'Area';
container.appendChild(areaButton);
const streetButton = document.createElement('button');
streetButton.textContent = 'Street';
container.appendChild(streetButton);
const timeButton = document.createElement('button');
timeButton.textContent = 'Time';
container.appendChild(timeButton);
let globalTimeInfo = null;
let globalAreaInfo = null;
let globalStreetInfo = null;
function updateButtonContent() {
areaButton.textContent = globalAreaInfo ? `${globalAreaInfo}` : 'Area';
streetButton.textContent = globalStreetInfo ? `${globalStreetInfo}` : 'Street';
timeButton.textContent = globalTimeInfo ? `${globalTimeInfo}` : 'Time';
copyButton.textContent=globalPanoId ? `${globalPanoId.substring(6,10)}, ${globalPanoId.substring(25,27)}` : 'Copy to clipboard'
copyButton.style.fontSize='15px'
}
setInterval(updateButtonContent, 1000);
var realSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(value) {
this.addEventListener('load', function() {
if (this._url && this._url.includes('https://tuxun.fun/api/v0/tuxun/mapProxy/getGooglePanoInfoPost')) {
const responseText = this.responseText;
const coordinatePattern = /\[\[null,null,(-?\d+\.\d+),(-?\d+\.\d+)\],\[\d+\.\d+\],\[\d+\.\d+,\d+\.\d+,\d+\.\d+\]\]|\[\s*null,\s*null,\s*(-?\d+\.\d+),\s*(-?\d+\.\d+)\s*\]/;
const coordinateMatches = coordinatePattern.exec(responseText);
if (coordinateMatches) {
const latitude = coordinateMatches[1] || coordinateMatches[3];
const longitude = coordinateMatches[2] || coordinateMatches[4];
if (latitude && longitude) {
currentLink = `https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=${latitude},${longitude}`;
}
}
const countryPattern = /,\s*"([A-Z]{2})"\s*\],null,\[/;
const countryMatches = countryPattern.exec(responseText);
let countryCode = countryMatches ? countryMatches[1] : '未知国家';
const areaPattern = /\[\[\s*"([^"]+)",\s*"[a-z]{2}"\s*\],\s*\["([^"]+)",\s*"zh"\s*\]\]/;
const areaMatches = areaPattern.exec(responseText);
if (areaMatches && areaMatches.length >= 3) {
globalAreaInfo = `${countryCode}, ${areaMatches[2]}`;
}
const fullAddressPattern = /\[\s*null,\s*null,\s*\[\s*\["([^"]+)",\s*"[a-z]{2}"\s*\]\]/;
const addressMatches = fullAddressPattern.exec(responseText);
if (addressMatches && addressMatches.length > 1) {
globalStreetInfo = addressMatches[1];
} else {
globalStreetInfo = '未知地址';
}
const timePattern = /\[\d+,\d+,\d+,null,null,\[null,null,"launch",\[\d+\]\],null,\[(\d{4}),(\d{1,2})\]\]/;
const timeMatches = timePattern.exec(responseText);
if (timeMatches) {
globalTimeInfo = `${timeMatches[1]}年${timeMatches[2]}月`;
} else {
globalTimeInfo = '未知时间';
}
}
if (this._url && this._url.includes('mapProxy/getPanoInfo')) {
var responseData
const responseText = this.responseText;
if (responseText) responseData=JSON.parse(responseText)
if(responseData){
const latitude = responseData.data.lat
const longitude =responseData.data.lng
globalPanoId=responseData.data.pano
if (globalPanoId){
const year=parseInt(globalPanoId.substring(9,12))
const month=parseInt(globalPanoId.substring(12,14))
const day=parseInt(globalPanoId.substring(14,16))
globalTimeInfo = `20${year}年${month}月${day}日`;
}
else{
globalTimeInfo = '未知时间';
}
const heading=responseData.data.centerHeading
if (latitude && longitude) {
currentLink = `https://map.baidu.com/@12707848.16,2573405.14,21z,87t,-139.74h#panoid=${globalPanoId}&panotype=street&heading=${heading}&pitch=0&l=21&tn=B_NORMAL_MAP&sc=0&newmap=1&shareurl=1&pid=${globalPanoId}`;
}
getAddressFromApi(latitude,longitude) .then(address => {
var province,city,suburb,street
globalAreaInfo=''
if (address) {
if (address.province) {
province = address.province;
globalAreaInfo += `中国, ${province}`
}
if (address.city) {
city = address.city;
globalAreaInfo +=`, ${city}`
}
if (address.suburb) {
suburb = address.suburb;
globalAreaInfo +=`, ${suburb}`
}
if (address.street) {
globalStreetInfo = address.street;
}
}
})
.catch(error => {
console.error('获取地址时发生错误:', error);
});
}
}
}, false);
realSend.call(this, value);
function getAddressFromApi(latitude, longitude) {
return new Promise((resolve, reject) => {
const apiUrl = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&addressdetails=1&accept-language=cn`;
fetch(apiUrl)
.then(response => response.json())
.then(data => {
const addressInfo = {
province: data?.address?.state || '',
city: data?.address?.city || '',
suburb: data?.address?.suburb || '',
street: data?.address?.road || '',
};
resolve(addressInfo);
})
.catch(error => {
console.error('Error fetching address:', error);
reject(error);
});
});
}
};
XMLHttpRequest.prototype.realOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
this._url = url;
this.realOpen(method, url, async, user, pass);
};
window.addEventListener('popstate', function(event) {
const container = document.getElementById('coordinates-container');
if (container) {
container.remove();
}
});
})();