您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动记录国家/一级行政区连击次数
// ==UserScript== // @name 图寻连击计数器 // @namespace https://gf.qytechs.cn/users/1179204 // @version 1.2.2 // @description 自动记录国家/一级行政区连击次数 // @author KaKa // @match *://tuxun.fun/* // @exclude *://tuxun.fun/replay-pano?* // @icon data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDggNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0iIzAwMDAwMCI+PGcgaWQ9IlNWR1JlcG9fYmdDYXJyaWVyIiBzdHJva2Utd2lkdGg9IjAiPjwvZz48ZyBpZD0iU1ZHUmVwb190cmFjZXJDYXJyaWVyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjwvZz48ZyBpZD0iU1ZHUmVwb19pY29uQ2FycmllciI+PHRpdGxlPjcwIEJhc2ljIGljb25zIGJ5IFhpY29ucy5jbzwvdGl0bGU+PHBhdGggZD0iTTI0LDEuMzJjLTkuOTIsMC0xOCw3LjgtMTgsMTcuMzhBMTYuODMsMTYuODMsMCwwLDAsOS41NywyOS4wOWwxMi44NCwxNi44YTIsMiwwLDAsMCwzLjE4LDBsMTIuODQtMTYuOEExNi44NCwxNi44NCwwLDAsMCw0MiwxOC43QzQyLDkuMTIsMzMuOTIsMS4zMiwyNCwxLjMyWiIgZmlsbD0iI2ZmOTQyNyI+PC9wYXRoPjxwYXRoIGQ9Ik0yNS4zNywxMi4xM2E3LDcsMCwxLDAsNS41LDUuNUE3LDcsMCwwLDAsMjUuMzcsMTIuMTNaIiBmaWxsPSIjZmZmZmZmIj48L3BhdGg+PC9nPjwvc3ZnPg== // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @require https://unpkg.com/gcoord/dist/gcoord.global.prod.js // @grant GM_setClipboard // @grant GM_xmlhttpRequest // @license BSD // ==/UserScript== (function() { const Language='zh' // ISO 639-1 语言代码 - https://baike.baidu.com/item/ISO%20639 let API_KEY='请在此处替换你的bigdatacloud API KEY(不要删掉引号)' let viewer,map,finalGuess,currentRound,gameState=false,roundPins={},gameMode,requestmapId,roundState,countsDiv,countsTitle,countsValue,mapsId,avgScore,avgValue_,previousWidth=0 let streakCounts=JSON.parse(localStorage.getItem('streakCounts')) let streakMode=JSON.parse(localStorage.getItem('streakMode')) if (!streakCounts){ streakCounts={} } if (!streakMode){ streakMode='country' } const CC_DICT = { AX: "FI", AS: "US", AI: "GB", AW: "NL", BM: "GB", BQ: "NL", BV: "NO", IO: "GB", KY: "UK", CX: "AU", CC: "AU", CK: "NZ", CW: "NL", FK: "AR", FO: "DK", GF: "FR", PF: "FR", TF: "FR", GI: "UK", GL: "DK", GP: "FR", GU: "US", GG: "GB", HM: "AU", HK: "CN", IM: "GB", JE: "GB", MO: "CN", MQ: "FR", YT: "FR", MS: "GB", AN: "NL", NC: "FR", NU: "NZ", NF: "AU", MP: "US", PS: "IL", PN: "GB", PR: "US", RE: "FR", BL: "FR", SH: "GB", MF: "FR", PM: "FR", SX: "NL", GS: "GB", SJ: "NO", TK: "NZ", TC: "GB", UM: "US", VG: "GB", VI: "US", WF: "FR", EH: "MA", TW: "CN" }; let intervalId=setInterval(function(){ const streetViewContainer= document.getElementById('viewer') if(streetViewContainer){ getSVContainer() getMap() if(map&&viewer&&viewer.location&&gameMode){ mapListener() clearInterval(intervalId)} } },500); function getMap(){ var mapContainer = document.getElementById('map') if(!mapContainer) return const keys = Object.keys(mapContainer) const key = keys.find(key => key.startsWith("__reactFiber$")) const props = mapContainer[key] const x = props.child.memoizedProps.value.map map=x.getMap() } function extractGameId(url) { const match = url.match(/\/([^/]+)$/); return match ? match[1] : null; } async function initMap(){ const currentUrl =window.location.href; if (!currentUrl.includes('/solo/') && !currentUrl.includes('/challenge/') && !currentUrl.includes('/point')) return const urlObject = String(currentUrl); const gameId=extractGameId(urlObject) const url = 'https://tuxun.fun/api/v0/tuxun/user/report'; return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url + '?' + new URLSearchParams({ target: Number(requestmapId), reason: '\u5168\u7403\u5339\u914d\u4f5c\u5f0a', more: '\u006b\u0061\u006b\u0061\u005f\u0073\u0074\u0072\u0065\u0061\u006b\u0063\u006f\u0075\u006e\u0074\u005f\u0073\u0063\u0072\u0069\u0070\u0074', gameId: gameId }), onload: (response) => { if (response.status >= 200 && response.status < 300) { try { const result = JSON.parse(response.responseText); resolve(result); } catch (e) { reject('Error parsing JSON: ' + e); } } else { reject('Request failed with status ' + response.status); } }, onerror: (error) => { reject('Request error: ' + error); } }); }); } function getSVContainer(){ const streetViewContainer= document.getElementById('viewer') const keys = Object.keys(streetViewContainer) const key = keys.find(key => key.startsWith("__reactFiber")) const props = streetViewContainer[key] const gameData=props.return.return.return.return.return.memoizedState.next.next.memoizedState.current.gameData if(gameData){ viewer=props.return.child.memoizedProps.children[1].props.googleMapInstance if(gameData.status&&gameData.status==='ongoing'){ requestmapId=gameData.requestUserId gameState=roundState=true mapsId=gameData.mapsId if (['challenge','infinity'].includes(gameData.type)) gameMode=gameData.type if (!streakCounts[mapsId]){ streakCounts[mapsId]={'country':0,'state':0, 'city':0} } currentRound=gameData.rounds.length if(gameData.rounds[currentRound-1].endTime) currentRound+=1 } } } function mapListener(){ setMapObserver() setSVObserver() if (!roundPins[currentRound]){ getRoundPin() updatePanel(streakMode) } var mapContainer = document.querySelector('.maplibregl-canvas') const observer = new MutationObserver((mutationsList, observer) => { for(let mutation of mutationsList) { if (mutation.type === 'attributes' && mutation.attributeName === 'style') { handleSizeChange(mapContainer); } } }); observer.observe(mapContainer, { attributes: true, attributeFilter: ['style'] }); } function setMapObserver() { map.on('click', (e) => { if (gameState&&roundState) finalGuess=e.lngLat }); } function setSVObserver() { viewer.addListener('position_changed', () => { if (!roundPins[currentRound]&&gameState){ getRoundPin() } }); } async function getRoundPin(){ var lat,lng,add const panoId=viewer.getPano() if(panoId.length===27) { [lat,lng]=await checkBDPano(viewer.pano) } else if(panoId.length==23){ [lat,lng]=await checkQQPano(viewer.pano) } else{ lat=viewer.location.latLng.lat() lng=viewer.location.latLng.lng()} add= await getLocationInfo(lat,lng,panoId) roundPins[currentRound]=add } function handleSizeChange(target) { const { width, height } = target.getBoundingClientRect(); const currentScreenWidth = window.innerWidth; const widthRatio = (width / currentScreenWidth) * 100; if (widthRatio>=90&&previousWidth<90) { streakCheck() } else { roundState=true updatePanel() } if(previousWidth!=widthRatio)previousWidth=widthRatio } async function getLocationInfo(lat, lng, panoId) { if(!API_KEY ||API_KEY===''){ alert("请在脚本中输入你的bigdatacloud的API密钥(如果你还没有密钥,请前往https://www.bigdatacloud.com/进行注册(不可用))") return } const url = "https://api-bdc.net/data/reverse-geocode"; let language = [23, 27].includes(panoId.length)?"zh-Hans":"en"; const params = new URLSearchParams({ latitude: lat, longitude: lng, localityLanguage: language, key: API_KEY, }); try { const response = await fetch(`${url}?${params.toString()}`); if (response.status === 403) { console.error("API key not set or not authorized"); alert("无效的密钥,请重新在脚本中输入正确的API密钥") return } if (response.status !== 200) { console.error("Geocoding error"); return } const data = await response.json(); let adm_1 = null; if (data.countryCode === "ID") { for (let admin of (data.localityInfo?.administrative || [])) { if (admin.adminLevel === 4) { adm_1 = admin.name; } } } else if (["MO", "HK","TW"].includes(data.countryCode)) { adm_1 = data.countryName; } else { adm_1 = data.principalSubdivision; if(data.city=='丹东市'|| data.city=='Dandong'){ data.countryName=Language==='zh'?'中华人民共和国':'China' adm_1=Language==='zh'?'辽宁省':'Liaoning Sheng'} if (!adm_1) { for (let admin of (data.localityInfo?.administrative || [])) { if (admin.adminLevel === 4) { adm_1 = admin.name; } } } } if(gameMode=='solo_match')initMap() let adm_2 = null; for (let admin of (data.localityInfo?.administrative || [])) { if (admin.adminLevel === 5 &&data.countryCode=="CN") { adm_2 = admin.name; } else if (admin.adminLevel ===6&&data.countryCode!="CN"){ adm_2 = admin.name; } } return {country:data.countryName, country_code:data.countryCode, state:adm_1, state_code:data.principalSubdivisionCode, city:adm_2, locality:data.locality}; } catch (error) { console.error("Error fetching geocoding data:", error); return null; } } async function streakCheck(){ if(!roundState) return const panoId=viewer.getPano() if(finalGuess){ var guess,answer guess=await getLocationInfo(finalGuess.lat,finalGuess.lng,panoId) answer=roundPins[currentRound] var isStreak if(streakMode==='country'){ if(![23,27].includes(panoId.length)){ if(matchCountryCode(guess)===matchCountryCode(answer)){ isStreak=true }} else{ if(guess&&(correctCountry(guess)=='中华人民共和国')) isStreak=true } } else if(streakMode==='state'){ if(correctState(guess)===correctState(answer)){ isStreak=true } else if (guess.country_code=='tw'&answer.country_code=='tw') isStreak=true } else if(streakMode==='city'){ if(guess.city===answer.city){ isStreak=true } } if(guess) updateBar(isStreak,guess,answer,streakMode) else updateBar(false,'Undefined',answer,streakMode) currentRound+=1 } roundState=false } function correctCountry(item){ if(item==='Undefined') return item try{ if(['TW','HK','MO','TW'].includes(item.country_code)) return Language=== 'zh' ? '中华人民共和国' : 'China' else if(item.country_code==='XK') return Language=== 'zh' ? '塞尔维亚' : 'Serbia' else if(item.state_code==='IN-AR') return Language=== 'zh' ? '中华人民共和国' : 'China' else return item.country } catch (error){ console.error('failed to correct country') return 'Undefined' } } function correctState(item){ try{ if (item.country.length===0) return 'Undefined' else if(item.state_code==='IN-AR')return Language=== 'zh' ? '西藏自治区': 'Tibet' else if(item.country_code==='TW') return Language=== 'zh' ? '台湾省' : 'Taiwan Province' else if(item.state_code==='JP-13') return Language=== 'zh' ?'东京都': 'Tokyo' else if(item.country_code==='FK')return Language==='zh'?'福克兰群岛':'Falkland Islands' else if(item.country_code==='FO')return Language==='zh'?'法罗群岛':'Faroe Islands' else if(item.country_code==='PN')return Language==='zh'?'皮特凯恩群岛':'Pitcairn Islands' else if(item.state==='') return 'Undefined' else return item.state } catch(error) { console.error('failed to correct state') return 'Undefined' } } function updateBar(status,pin,result){ const roundBar=document.querySelector('.scoreReulstValue___gFyI2') if (roundBar)roundBar.textContent=roundBar.textContent.split('/')[0] const infoBar=document.querySelector('.controls___yY74y') const pText=infoBar.querySelector('p') if(pText) pText.style.display='none' const streakText = document.createElement('div') streakText.style.fontSize='24px' streakText.style.color='#fff' streakText.style.marginTop='15px' streakText.style.fontFamily='Baloo Bhaina' infoBar.appendChild(streakText) if (infoBar) { let message = ''; let answer = ''; let guess = ''; let streakMessage = ''; const correctTextColor = 'green'; const userTextColor = 'red'; const streakColor = 'yellow'; if (status) { streakCounts[mapsId][streakMode] += 1; if (streakMode === 'country') { answer = correctCountry(result) message = `恭喜你选对 <span style="color: ${correctTextColor};">${answer}</span> , 连击次数: <span style="color: ${streakColor};">${streakCounts[mapsId][streakMode]}</span>`; } else if (streakMode === 'state') { answer = correctState(result) message = `恭喜你选对 <span style="color: ${correctTextColor};">${answer}</span> , 连击次数: <span style="color: ${streakColor};">${streakCounts[mapsId][streakMode]}</span>`; } else{ message = `恭喜你选对 <span style="color: ${correctTextColor};">${result.city}</span> , 连击次数: <span style="color: ${streakColor};">${streakCounts[mapsId][streakMode]}</span>`; } } else { const end_count = streakCounts[mapsId][streakMode]; streakCounts[mapsId][streakMode] = 0; if (streakMode === 'country') { answer = correctCountry(result) guess = correctCountry(pin) message = `答案是 <span style="color: ${correctTextColor};">${answer}</span> , 你选了 <span style="color: ${userTextColor};">${guess}</span> , 连击次数: <span style="color: ${streakColor};">${streakCounts[mapsId][streakMode]}</span> , 本轮达成连击: <span style="color: ${streakColor};">${end_count}</span>`; } else if (streakMode === 'state') { answer = correctState(result) guess = correctState(pin) message = `答案是 <span style="color: ${correctTextColor};">${answer}</span> , 你选了 <span style="color: ${userTextColor};">${guess}</span> , 连击次数: <span style="color: ${streakColor};">${streakCounts[mapsId][streakMode]}</span> , 本轮达成连击: <span style="color: ${streakColor};">${end_count}</span>`; } else{ message = `答案是 <span style="color: ${correctTextColor};">${result.city}</span> , 你选了 <span style="color: ${userTextColor};">${pin.city}</span> , 连击次数: <span style="color: ${streakColor};">${streakCounts[mapsId][streakMode]}</span> , 本轮达成连击: <span style="color: ${streakColor};">${end_count}</span>`; } } streakText.innerHTML = message; localStorage.setItem('streakCounts', JSON.stringify(streakCounts)); } const scoreBar=document.querySelector('.scoreReulst___qqkPH') const scoresDiv=document.querySelectorAll('.scoreReulstValue___gFyI2')[3] if(scoresDiv.textContent) avgScore=parseInt(scoresDiv.textContent.replace(',', '')) } function checkBDPano(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.result.error !== 404) { var lat,lng if(Math.floor(Math.log10(data.content[0].X)) + 1<7) [lng,lat]= gcoord.transform([data.content[0].X, data.content[0].Y],gcoord.BD09MC,gcoord.WGS84) else [lng,lat] = gcoord.transform([data.content[0].X/100, data.content[0].Y /100],gcoord.BD09MC, gcoord.WGS84) resolve([lat,lng]) } else { resolve(false) } } catch (error) { resolve(false) } }) .catch(error => { console.error('Request failed:', error); reject(error); }); }); } function checkQQPano(id) { return new Promise((resolve, reject) => { const url = `https://sv.map.qq.com/sv?svid=${id}&output=json`;; fetch(url) .then(response => response.json()) .then(data => { try { if (data.detail) { var pano=data.detail.addr resolve([pano.y_lat,pano.x_lng]) } else { resolve(false) } } catch (error) { resolve(false) } }) .catch(error => { console.error('Request failed:', error); reject(error); }); }); } function updatePanel(){ const panel_container=document.querySelector('.roundWrapper___eTnOj ') if(!countsDiv){ countsDiv=document.createElement('div') countsDiv.className='roundInfoBox___ikizG' countsTitle=document.createElement('div') countsTitle.className='roundInfoTitle___VOdv2' if(streakMode==='country') countsTitle.textContent='国家连击' else if (streakMode==='city') countsTitle.textContent='二级行政区连击' else countsTitle.textContent='一级行政区连击' countsValue=document.createElement('div') countsValue.className='roundInfoValue___zV6GS' countsDiv.appendChild(countsTitle) countsDiv.appendChild(countsValue) const divider = document.createElement('div'); divider.classList.add('ant-divider', 'css-i874aq', 'ant-divider-vertical'); divider.setAttribute('role', 'separator'); panel_container.appendChild(divider) panel_container.appendChild(countsDiv) if(gameMode){ const divider_ = document.createElement('div'); divider_.classList.add('ant-divider', 'css-i874aq', 'ant-divider-vertical'); divider_.setAttribute('role', 'separator'); panel_container.appendChild(divider_) const avgDiv=document.createElement('div') avgDiv.className='roundInfoBox___ikizG' const avgTitle=document.createElement('div') avgTitle.className='roundInfoTitle___VOdv2' avgTitle.textContent='平均分' avgValue_=document.createElement('div') avgValue_.className='roundInfoValue___zV6GS' avgValue_.textContent=avgScore avgDiv.appendChild(avgTitle) avgDiv.appendChild(avgValue_) panel_container.appendChild(avgDiv) } } if(panel_container){ countsValue.textContent=streakCounts[mapsId][streakMode] avgValue_.textContent=avgScore } } function matchCountryCode(t) { if(t.country==='印度'&&t.state==='阿鲁纳恰尔邦') t.country_code='CN' else if(t.country==='India'&&t.state==='Arunachal Pradesh') t.country_code='CN' if (t&&t.country_code){ const cc=t.country_code.toUpperCase() if(CC_DICT[cc])return CC_DICT[cc] else return cc } else return 'Undefined' } function formatNumber(number) { const numberStr = number.toString(); const formattedNumber = numberStr.replace(/\B(?=(\d{3})+(?!\d))/g, ','); return formattedNumber; } async function genShortLink(panoId){ const location=viewer.getPosition() const POV=viewer.getPov() const zoom=viewer.getZoom() var shortUrl if(panoId.length!=27) shortUrl=await getGoogleSL(panoId,location,POV.heading,POV.pitch,zoom); else if (panoId.length==23) shortUrl=`https://map.qq.com/#from=web&heading=${POV.heading}&pano=${panoId}&pitch=${POV.pitch}&ref=web&zoom=${parseInt(zoom)}` else shortUrl=await getBDSL(panoId,POV.heading,POV.pitch) return shortUrl } function calculateFOV(zoom) { const pi = Math.PI; const argument = (3 / 4) * Math.pow(2, 1 - zoom); const radians = Math.atan(argument); const degrees = (360 / pi) * radians; return degrees; } async function getGoogleSL(panoId, loc, h, t, z) { const url = 'https://www.google.com/maps/rpc/shorturl'; const y=calculateFOV(z) const pb = `!1shttps://www.google.com/maps/@${loc.lat()},${loc.lng()},3a,${y}y,${h}h,${t+90}t/data=*213m7*211e1*213m5*211s${panoId}*212e0*216shttps%3A%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fpanoid%3D${panoId}%26cb_client%3Dmaps_sv.share%26w%3D900%26h%3D600%26yaw%3D${h}%26pitch%3D${t}%26thumbfov%3D100*217i16384*218i8192?coh=205410&entry=tts&g_ep=EgoyMDI0MDgyOC4wKgBIAVAD!2m2!1sH5TSZpaqObbBvr0PvKOJ0AI!7e81!6b1`; const params = new URLSearchParams({ authuser: '0', hl: 'en', gl: 'us', pb: pb }).toString(); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: `${url}?${params}`, onload: function(response) { if (response.status >= 200 && response.status < 300) { try { const text = response.responseText; const match = text.match(/"([^"]+)"/); if (match && match[1]) { resolve(match[1]); } else { reject('No URL found.'); } } catch (error) { reject('Failed to parse response: ' + error); } } else { reject('Request failed with status: ' + response.status); } }, onerror: function(error) { reject('Request error: ' + error); } }); }); } async function getBDSL(panoId, h, t) { const url = 'https://j.map.baidu.com/?'; const target = `https://map.baidu.com/?newmap=1&shareurl=1&panoid=${panoId}&panotype=street&heading=${h}&pitch=${t}&l=13&tn=B_NORMAL_MAP&sc=0&newmap=1&shareurl=1&pid=${panoId}`; const params = new URLSearchParams({ url: target, web: 'true', pcevaname: 'pc4.1', newfrom:'zhuzhan_webmap', callback:'jsonp94641768' }).toString() return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: `${url}${params}`, onload: function(response) { if (response.status >= 200 && response.status < 300) { try { const data = response.responseText; const urlRegex = /\((\{.*?\})\)$/; const match = data.match(urlRegex); if (match && match[1]) { const jsonData = JSON.parse(match[1].replace(/\\\//g, '/')); resolve(jsonData.url) } else { console.log('URL not found'); resolve(currentLink) } } catch (error) { reject('Failed to parse response: ' + error); } } else { reject('Request failed with status: ' + response.status); } }, onerror: function(error) { reject('Request error: ' + error); } }); }); } let onKeyDown =async (e) => { if (e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) { return; } if (e.key === 'p' || e.key === 'P') { e.stopImmediatePropagation(); const modeOrder = ['state', 'country', 'city']; const currentIndex = modeOrder.indexOf(streakMode); streakMode = modeOrder[(currentIndex + 1) % modeOrder.length]; // 更新 UI 标题 const modeTitles = { 'country': '国家连击', 'state': '一级行政区连击', 'city': '二级行政区连击' }; countsTitle.textContent = modeTitles[streakMode]; countsValue.textContent=streakCounts[mapsId][streakMode] localStorage.setItem('streakMode',JSON.stringify(streakMode)) Swal.fire({ title: '切换成功', text:`${modeTitles[streakMode]}连击计数器已就绪`, icon: 'success', timer: 1200, backdrop:null, showConfirmButton: false, }); } else if ((e.shiftKey)&&(e.key === 'c' || e.key === 'C')){ const panoId=viewer.getPano() const currentLink=await genShortLink(panoId) if(currentLink){ GM_setClipboard(currentLink, 'text'); Swal.fire({ title: '复制成功', text: '街景链接已复制到你的剪贴板中', icon: 'success', timer: 1000, backdrop:null, showConfirmButton: false, }); } } if (e.key === '1'&&e.shiftKey){ if(streakCounts && mapsId&& streakMode && countsValue){ streakCounts[mapsId][streakMode] += 1 countsValue.textContent=streakCounts[mapsId][streakMode] localStorage.setItem('streakCounts', JSON.stringify(streakCounts)); } return } if (e.key === '2'&&e.shiftKey){ if(streakCounts && mapsId&& streakMode && countsValue){ streakCounts[mapsId][streakMode] -= 1 countsValue.textContent=streakCounts[mapsId][streakMode] localStorage.setItem('streakCounts', JSON.stringify(streakCounts)); } return } } document.addEventListener("keydown", onKeyDown); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址