您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动记录国家/一级行政区连击次数
当前为
// ==UserScript== // @name 图寻连击计数器 // @namespace https://greasyfork.org/users/1179204 // @version 1.1.3 // @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 viewer,map,finalGuess,currentRound,gameState=false,roundPins={},gameMode,roundState,countsDiv,countsTitle,countsValue,mapsId,avgScore,avgValue_,previousWidth=0 let api_key=''//JSON.parse(localStorage.getItem('api_key')); 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') 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 getSVContainer(){ const streetViewContainer= document.getElementById('viewer') const keys = Object.keys(streetViewContainer) const key = keys.find(key => key.startsWith("__reactFiber")) const props = streetViewContainer[key] viewer=props.return.child.memoizedProps.children[1].props.googleMapInstance const gameData=props.return.return.return.return.return.memoizedState.next.next.memoizedState.current.gameData if(gameData){ if(gameData.status&&gameData.status==='ongoing'){ gameState=roundState=true mapsId=gameData.mapsId if (['challenge','infinity'].includes(gameData.type)) gameMode=gameData.type if (!streakCounts[mapsId]){ streakCounts[mapsId]={'country':0,'state':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 checkPano(viewer.pano) if(api_key) add=await queryGD(lat,lng) else add=await queryOSM(lat,lng) } else{ lat=viewer.location.latLng.lat() lng=viewer.location.latLng.lng() if(api_key) add=await queryGD(lat,lng) else add=await queryOSM(lat,lng)} 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 queryOSM(lat, lng) { const url =`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=jsonv2&accept-language=${Language}`; const response = await fetch(url); if (response.ok) { let data = await response.json(); if(data.address) return data.address } else { return null; } } async function streakCheck(){ if(!roundState) return const panoId=viewer.getPano() if(finalGuess){ var guess,answer if(panoId.length===27) { if(api_key) guess=await queryGD(finalGuess.lat,finalGuess.lng) else guess=await queryOSM(finalGuess.lat,finalGuess.lng) } else { if(api_key) guess=await queryGD(finalGuess.lat,finalGuess.lng) else guess=await queryOSM(finalGuess.lat,finalGuess.lng) } answer=roundPins[currentRound] var isStreak if(streakMode==='country'){ if(panoId.length!=27){ if(matchCountryCode(guess)===matchCountryCode(answer)){ isStreak=true }} else{ if(guess&&(guess.country_code in ['tw','cn','mo','hk'])) isStreak=true } } else if(streakMode==='state'){ if(matchState(guess)===matchState(answer)){ isStreak=true } else if (guess.country_code=='tw'&answer.country_code=='tw') 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(['Taiwan','HongKong','Macau','臺灣','台湾'].includes(item.country)) return Language=== 'zh' ? '中国' : 'China' else if(item.country_code==='xk') return Language=== 'zh' ? '塞尔维亚' : 'Serbia' else if(item['ISO3166-2-lvl4']==='IN-AR') return Language=== 'zh' ? '中国' : 'China' else if(item.country.length===0) return 'Undefined' 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['ISO3166-2-lvl4']==='IN-AR')return Language=== 'zh' ? '西藏自治区': 'Tibet' else if(item.country_code==='tw') return Language=== 'zh' ? '台湾省' : 'Taiwan Province' else if(item['ISO3166-2-lvl4']==='JP-13') return Language=== 'zh' ?'东京都': 'Tokyo' else if(item.country_code==='fk')return Language==='zh'?'福克兰群岛':'Falkland Islands' else if(item.country_code==='pn')return Language==='zh'?'皮特凯恩群岛':'Pitcairn Islands' else return matchState(item) } 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).split('/')[0]; message = `恭喜你选对 <span style="color: ${correctTextColor};">${answer}</span> , 连击次数: <span style="color: ${streakColor};">${streakCounts[mapsId][streakMode]}</span>`; } else if (streakMode === 'state') { answer = correctState(result).split('/')[0]; message = `恭喜你选对 <span style="color: ${correctTextColor};">${answer}</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).split('/')[0]; guess = correctCountry(pin).split('/')[0]; 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).split('/')[0]; guess = correctState(pin).split('/')[0]; 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>`; } } streakText.innerHTML = message; localStorage.setItem('streakCounts', JSON.stringify(streakCounts)); } const scoreBar=document.querySelector('.scoreReulst___qqkPH') const avgDiv=document.createElement('div') const scoresDiv=document.querySelectorAll('.scoreReulstValue___gFyI2')[3] if(scoresDiv.textContent) var total_scores=parseInt(scoresDiv.textContent.replace(',', '')) const roundsDiv=document.querySelectorAll('.scoreReulstValue___gFyI2')[0] if(roundsDiv.textContent) var rounds=parseInt(roundsDiv.textContent.split('/')[0]) if(total_scores&&rounds) avgScore=total_scores/rounds if(avgScore) avgScore=formatNumber(parseInt(avgScore)) const avgTitle=document.createElement('div') avgTitle.className='scoreReulstLabel___pgClU' avgTitle.textContent='平均分' const avgValue=document.createElement('div') avgValue.className='scoreReulstValue___gFyI2' avgValue.textContent=avgScore avgDiv.appendChild(avgTitle) avgDiv.appendChild(avgValue) scoreBar.appendChild(avgDiv) } async function queryGD(lat, lng) { const apiUrl = `https://restapi.amap.com/v3/geocode/regeo?output=json&location=${lng},${lat}&key=${api_key}&radius=50`; try { const response = await fetch(apiUrl); if (!response.ok) { throw new Error('Request failed with status: ' + response.status); } const data = await response.json(); if (data.status === '1' && data.regeocode) { return data.regeocode.addressComponent; } else { localStorage.removeItem('api_key'); Swal.fire('无效的API密钥', '请刷新页面并重新输入正确的高德地图API密钥', 'error'); throw new Error('Request failed: ' + data.info); } } catch (error) { console.error('Error fetching address:', error); throw error; } } function checkPano(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 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 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 matchState(t) { if(!t) return 'Undefined' if (t.state) { return t.state; }else if (t.province) { return t.province; } else if (t.territory) { return t.territory; } else if (t.state_district) { return t.state_district; } else if (t.county) { return t.county; } 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 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.key === 'p' || e.key === 'P') { e.stopImmediatePropagation(); if(streakMode!='state')streakMode='state' else streakMode='country' countsTitle.textContent = streakMode === 'country' ? '国家连击' : '一级行政区连击'; countsValue.textContent=streakCounts[mapsId][streakMode] localStorage.setItem('streakMode',JSON.stringify(streakMode)) Swal.fire({ title: '切换成功', text:`${streakMode === 'country' ? '国家连击' : '一级行政区连击'}连击计数器已就绪`, icon: 'success', timer: 1200, 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, showConfirmButton: false, }); } } } document.addEventListener("keydown", onKeyDown); })();