图寻连击计数器

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

当前为 2024-09-01 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
})();