图寻连击计数器

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

目前為 2024-09-01 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
})();