// ==UserScript== // @name 图寻复盘工具 // @namespace https://greasyfork.org/users/1179204 // @version 1.1.1 // @description 全方位提升复盘效果 // @match *://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 // @grant GM_xmlhttpRequest // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @require https://unpkg.com/leaflet@1.9.2/dist/leaflet.js // @copyright KaKa // @license BSD // @downloadURL none // ==/UserScript== (function() { 'use strict'; GM_addStyle(` @import url('https://unpkg.com/leaflet@1.9.2/dist/leaflet.css'); #panels { position: fixed; top: 100px; left: 10px; padding: 10px; border-radius: 20px !important; z-index: 1000; display: flex; flex-direction: column; width: 180px; } #panels 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; }; .link-button { background: none!important; border: none; padding: 0!important; color: #FFCC00 !important; text-decoration: underline; cursor: pointer; } .link-button:hover { color: #FFCC00 !important; } .custom-marker { background-color: red; /* 设置背景颜色 */ color: white; /* 设置文本颜色 */ border-radius: 50%; /* 圆形样式 */ width: 20px; /* 宽度 */ height: 20px; /* 高度 */ text-align: center; /* 文本居中 */ line-height: 20px; /* 行高与高度一致 */ } `); let guideMap,map,marker,previousPin,isMapDisplay=true let api_key=JSON.parse(localStorage.getItem('api_key')); let address_source=JSON.parse(localStorage.getItem('address_source')); if (!address_source) { Swal.fire({ title: '请选择获取地址信息的来源', icon: 'question', text: 'OSM具有更详细的地址信息,高德地图的获取速度更快且带有电话区号信息(需要自行注册API密钥)', showCancelButton: true, allowOutsideClick: false, confirmButtonColor: '#3085d6', confirmButtonText: 'OSM', cancelButtonText: '高德地图', }).then((result) => { if (result.isConfirmed) { localStorage.setItem('address_source', JSON.stringify('OSM')); address_source='OSM' } else if (result.dismiss === Swal.DismissReason.cancel) { localStorage.setItem('address_source', JSON.stringify('GD')); address_source=JSON.parse(localStorage.getItem('address_source')) console.log(address_source) Swal.fire({ title: '请输入您的高德地图 API 密钥', input: 'text', inputPlaceholder: '', showCancelButton: true, confirmButtonText: '保存', cancelButtonText: '取消', preConfirm: (inputValue) => { if (inputValue.length===32){ return inputValue; } else{ Swal.showValidationMessage('请输入有效的高德地图API密钥!') } } }).then((result) => { if (result.isConfirmed) { if(result.value){ localStorage.setItem('api_key', JSON.stringify(result.value)); Swal.fire('保存成功!', '您的API密钥已保存,请刷新页面。', 'success');} else{ localStorage.removeItem('address_source') } } }); } }); } if(!api_key&&address_source==='GD'){ Swal.fire({ title: '请输入您的高德地图 API 密钥', input: 'text', inputPlaceholder: '', showCancelButton: true, confirmButtonText: '保存', cancelButtonText: '取消', preConfirm: (inputValue) => { if (inputValue.length===32){ return inputValue; } else{ Swal.showValidationMessage('请输入有效的高德地图API密钥!') } } }).then((result) => { if (result.isConfirmed) { if(result.value){ api_key=JSON.parse(localStorage.getItem('api_key')); Swal.fire('保存成功!', '您的API密钥已保存,请刷新页面。', 'success');} } else{ localStorage.removeItem('address_source') } }); } const container = document.createElement('div'); container.id = 'panels'; document.body.appendChild(container); const openButton = document.createElement('button'); openButton.textContent = '在地图中打开'; container.appendChild(openButton); const mapButton = document.createElement('button'); mapButton.textContent = '关闭小地图'; container.appendChild(mapButton); const copyButton = document.createElement('button'); copyButton.textContent = '复制街景链接'; container.appendChild(copyButton); let currentLink = ''; let globalPanoId=null openButton.onclick = () => window.open(currentLink, '_blank'); copyButton.onclick = () => { GM_setClipboard(currentLink, 'text'); setTimeout(function() { copyButton.textContent='复制成功!' }, 100) setTimeout(function() { copyButton.textContent='复制街景链接' }, 1600) }; mapButton.onclick = () => { if (isMapDisplay){ guideMap.style.display='none' mapButton.textContent='显示小地图' isMapDisplay=false } else{ guideMap.style.display='block' mapButton.textContent='关闭小地图' isMapDisplay=true } }; 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); const panoIdButton = document.createElement('button'); panoIdButton.textContent = 'PanoId'; container.appendChild(panoIdButton); let globalTimeInfo = null; let globalAreaInfo = null; let globalStreetInfo = null; function updateButtonContent() { areaButton.textContent = globalAreaInfo ? `${globalAreaInfo}` : '地址'; streetButton.textContent = globalStreetInfo ? `${globalStreetInfo}` : '路名'; timeButton.textContent = globalTimeInfo ? `${globalTimeInfo}` : '时间'; panoIdButton.textContent=globalPanoId ? `${globalPanoId.substring(6,10)}, ${globalPanoId.substring(25,27)}` : '未知' panoIdButton.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 (!map) createMap("google") if (previousPin){ drawPolyline(previousPin,[latitude,longitude]) } else{ addMarker(latitude,longitude) } previousPin=[latitude,longitude] 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,startPoint const responseText = this.responseText; if (responseText) responseData=JSON.parse(responseText) if(responseData){ const latitude = responseData.data.lat const longitude =responseData.data.lng if (!map) createMap("baidu") if (previousPin&&[latitude,longitude]!=startPoint){ drawPolyline(previousPin,[latitude,longitude]) } else{ startPoint=[latitude,longitude] addMarker(latitude,longitude) } previousPin=[latitude,longitude] globalPanoId=responseData.data.pano if (globalPanoId){ const year=parseInt(globalPanoId.substring(10,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)-90 if (latitude && longitude) { currentLink = `https://map.baidu.com/@13057562,4799985#panoid=${globalPanoId}&panotype=street&heading=${heading}&pitch=0&l=21&tn=B_NORMAL_MAP&sc=0&newmap=1&shareurl=1&pid=${globalPanoId}`; } if (api_key){ getAddressFromGD(latitude,longitude) .then(address => { if (address) { globalAreaInfo=address } }) .catch(error => { console.error('获取地址时发生错误:', error); }); } else{ getAddressFromOSM(latitude,longitude) .then(address => { var province,city,region,suburb,street if (address) { globalAreaInfo=processAddress(address) } }) .catch(error => { console.error('获取地址时发生错误:', error); }); } if (globalPanoId){ getRoadName(globalPanoId) .then(roadName => { if (roadName) { globalStreetInfo=roadName } }) .catch(error => { console.error('获取路名时发生错误:', error); }); } } } }, false); realSend.call(this, value); function getAddressFromGD(latitude, longitude) { return new Promise((resolve, reject) => { const apiUrl = `https://restapi.amap.com/v3/geocode/regeo?output=json&location=${longitude},${latitude}&key=${api_key}&radius=20`; GM_xmlhttpRequest({ method: "GET", url: apiUrl, onload: function(response) { if (response.status === 200) { const data = JSON.parse(response.responseText); if (data.status === '1' && data.regeocode) { const province=data.regeocode.addressComponent.province const city=data.regeocode.addressComponent.city const district=data.regeocode.addressComponent.district const township=data.regeocode.addressComponent.township const cityCode=data.regeocode.addressComponent.citycode const addressInfo={province,city,district,township,cityCode} var formatted_address= '中国' for (const key in addressInfo) { if (addressInfo[key]) { if (addressInfo[key]!='') { formatted_address+=`, ${addressInfo[key]} `} } } resolve(formatted_address); } else { localStorage.removeItem('api_key') Swal.fire('无效的API密钥','请刷新页面并重新输入正确的高德地图API密钥','error'); reject(new Error('Request failed: ' + data.info)); } } else { reject(new Error('Request failed with status: ' + response.status)); } }, onerror: function(error) { console.error('Error fetching address:', error); reject(error); } }); });} function getAddressFromOSM(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 => { if (data.display_name) resolve(data.display_name); else resolve('未知') }) .catch(error => { console.error('Error fetching address:', error); reject(error); }); }); } function getRoadName(id){ return new Promise((resolve, reject) => { const url = `https://mapsv0.bdimg.com/?qt=sdata&sid=${id}`; fetch(url) .then(response => response.json()) .then(data => { try{ if(data.content[0].Rname&&data.content[0].Rname!=""){ resolve(data.content[0].Rname);} else{ resolve('未知道路') } } catch (error){ resolve('未知道路')} }) .catch(error => { console.error('Error fetching road name:', error); reject(error); }); }); } function processAddress(text) { const items = text.split(',').map(item => item.trim()); const filteredItems = items.filter(item => isNaN(item)); const reversedItems = filteredItems.reverse(); const result = reversedItems.join(', '); return result; } } function addMarker(lat, lng) { if (lat && lng) { if (marker) { marker.off('click'); map.removeLayer(marker); } marker = L.marker([lat, lng]).addTo(map); if (!previousPin){ map.setView([lat, lng], 5)}; } } function drawPolyline(previous,current){ L.polyline([previous,current], { color: 'red' ,weight:2}).addTo(map) } function createMap(source){ guideMap=document.createElement('div') guideMap.style.position = 'absolute'; guideMap.style.right='20px' guideMap.id='guide-map' guideMap.style.bottom='20px' guideMap.style.width='400px' guideMap.style.height='300px' guideMap.style.zIndex='999' document.body.appendChild(guideMap) const hwLayer=L.tileLayer("https://maprastertile-drcn.dbankcdn.cn/display-service/v1/online-render/getTile/24.07.06.10/{z}/{x}/{y}/?language=zh&p=46&scale=2&mapType=ROADMAP&presetStyleId=standard&pattern=JPG&key=DAEDANitav6P7Q0lWzCzKkLErbrJG4kS1u%2FCpEe5ZyxW5u0nSkb40bJ%2BYAugRN03fhf0BszLS1rCrzAogRHDZkxaMrloaHPQGO6LNg==") map = L.map("guide-map", {zoomControl: false, attributionControl: false, doubleClickZoom: false,preferCanvas: true}) hwLayer.addTo(map).bringToFront() guideMap.addEventListener('mouseenter', function() { guideMap.style.width = '900px'; guideMap.style.height = '600px'; map.invalidateSize() }); guideMap.addEventListener('mouseleave', function() { setTimeout(function() { guideMap.style.width='400px' guideMap.style.height='300px' map.invalidateSize() }, 200) }); } 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(); } }); let onKeyDown = (e) => { if (e.key === 'r' || e.key === 'R') { e.stopImmediatePropagation(); localStorage.removeItem('address_source') localStorage.removeItem('api_key') Swal.fire('清除成功','获取地址信息的来源已重置,您的API密钥已从缓存中清除,请刷新页面后重新选择。','success'); } } document.addEventListener("keydown", onKeyDown); })();