图寻连击计数器

自动记录国家/一级行政区连击次数

目前为 2024-09-01 提交的版本。查看 最新版本

// ==UserScript==
// @name         图寻连击计数器
// @namespace    https://greasyfork.org/users/1179204
// @version      1.0.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
// @copyright    KaKa
// @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={},roundState,countsDiv,countsTitle,countsValue,mapsId

    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: "GB", 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){
                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 (!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
        if(viewer.pano.length===27) [lat,lng]=await checkPano(viewer.pano)
        else{
            lat=viewer.location.latLng.lat()
            lng=viewer.location.latLng.lng()}
        const 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) {
            streakCheck()
            roundState=false
        }
        else {
            roundState=true
            updatePanel()
        }
    }


    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

        if(finalGuess){
            const guess=await queryOSM(finalGuess.lat,finalGuess.lng)
            try{
                if(guess.country==='India'&&guess.state==='Arunachal Pradesh'){
                    guess.country='China'
                    guess.state='Tibet'}
            }
            catch(error) {
            }

            var isStreak
            if(streakMode==='country'){
                if(matchCountryCode(guess)===matchCountryCode(roundPins[currentRound])){
                    isStreak=true
                }
            }
            else if(streakMode==='state'){
                if(matchState(guess)===matchState(roundPins[currentRound])){
                    isStreak=true
                }
            }
            if(guess) updateBar(isStreak,guess,roundPins[currentRound],streakMode)
            else updateBar(false,'Undefined',roundPins[currentRound],streakMode)
            currentRound+=1
        }

    }

    function correctAddress(item){
        if(['Taiwan','HongKong','Macau'].includes(item)) return 'China'
        else return item
    }

    function updateBar(status,pin,result){
        const infoBar=document.querySelector('.controls___yY74y')
        const streakText = infoBar.querySelector('p')
        streakText.style.fontSize='24px'
        streakText.style.color='#fff'
        streakText.style.fontFamily='Baloo Bhaina'
        infoBar.appendChild(streakText)
        if (infoBar){
            if(status){
                streakCounts[mapsId][streakMode]+=1
                if(streakMode==='country') streakText.textContent = `恭喜你选对 ${correctAddress(result.country).split('/')[0]}, 连击次数: ${ streakCounts[mapsId][streakMode]}`
                else if(streakMode='state') streakText.textContent = `恭喜你选对 ${matchState(result)}, 连击次数: ${ streakCounts[mapsId][streakMode]}`

            }

            else{
                const end_count=streakCounts[mapsId][streakMode]
                streakCounts[mapsId][streakMode]=0
                if(streakMode==='country') streakText.textContent = `答案是 ${correctAddress(result.country).split('/')[0]}, 你选了${correctAddress(pin.country).split('/')[0]}, 连击次数: ${ streakCounts[mapsId][streakMode]}, 本轮达成连击:${end_count}`
                else if(streakMode='state')streakText.textContent = `答案是 ${matchState(result)}, 你选了${matchState(pin)}, 连击次数: ${ streakCounts[mapsId][streakMode]}, 本轮达成连击:${end_count}`
            }
            localStorage.setItem('streakCounts',JSON.stringify(streakCounts))
        }

    }

    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) {
                        const [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(panel_container){
            countsValue.textContent=streakCounts[mapsId][streakMode]}

    }

    function matchCountryCode(t) {
        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.country_code==='tw') return Language=== 'zh' ? '台湾省' : 'Taiwan Province'
        if (t.state) {
            return t.state;
        }else if (t.province) {
            return t.province;
        } else if (t.county) {
            return t.county;
        } else {
            return 'Undefined';
        }
    }
    let onKeyDown = (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,
            });
        }
    }
    document.addEventListener("keydown", onKeyDown);
})();