// ==UserScript==
// @name 图寻连击计数器
// @namespace https://gf.qytechs.cn/users/1179204
// @version 1.0.0
// @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() {
let viewer, map, finalGuess, currentRound, gameState = false, roundPins = {}, roundState, countsDiv, countsValue;
let streakMode = 'country';
// 初始化 streakCounts
let streakCounts = JSON.parse(localStorage.getItem('streakCounts')) || { country: 0, state: 0 };
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"
};
// 轮询获取必要的 DOM 元素
let intervalId = setInterval(() => {
const streetViewContainer = document.getElementById('viewer');
if (streetViewContainer) {
getSVContainer();
getMap();
if (map && viewer && viewer.location) {
mapListener();
clearInterval(intervalId);
}
}
}, 500);
function getMap() {
const mapContainer = document.getElementById('map');
const key = Object.keys(mapContainer).find(k => k.startsWith("__reactFiber$"));
const props = mapContainer[key];
map = props.child.memoizedProps.value.map.getMap();
}
function getSVContainer() {
const streetViewContainer = document.getElementById('viewer');
const key = Object.keys(streetViewContainer).find(k => k.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?.status === 'ongoing') {
gameState = roundState = true;
currentRound = gameData.rounds.length;
}
}
function mapListener() {
setMapObserver();
setSVObserver();
if (!roundPins[currentRound]) {
getRoundPin();
updatePanel();
}
const mapContainer = document.querySelector('.maplibregl-canvas');
const observer = new MutationObserver(mutationsList => {
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() {
const { lat, lng } = viewer.location.latLng;
roundPins[currentRound] = await queryOSM(lat(), lng(), 'en');
}
function handleSizeChange(target) {
const { width } = target.getBoundingClientRect();
const widthRatio = (width / window.innerWidth) * 100;
if (widthRatio >= 90) {
streakCheck();
roundState = false;
} else {
roundState = true;
updatePanel();
}
}
async function queryOSM(lat, lng, language) {
const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=jsonv2&accept-language=${language}`;
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
return data.address || null;
}
return null;
}
async function streakCheck() {
if (!roundState || !finalGuess) return;
const guess = await queryOSM(finalGuess.lat, finalGuess.lng, 'en');
if (guess?.country === 'India' && guess.state === 'Arunachal Pradesh') {
guess.country = 'China';
guess.state = 'Tibet';
}
const isStreak = streakMode === 'country'
? matchCountryCode(guess) === matchCountryCode(roundPins[currentRound])
: streakMode === 'state'
? matchState(guess) === matchState(roundPins[currentRound])
: false;
updateBar(isStreak, guess || { country: 'Undefined' }, roundPins[currentRound], streakMode);
currentRound += 1;
}
function correctAddress(item) {
return ['Taiwan', 'HongKong', 'Macau'].includes(item) ? 'China' : item;
}
function updateBar(status, guess, pin, mode) {
const infoBar = document.querySelector('.controls___yY74y');
if (!infoBar) return;
const streakText = infoBar.querySelector('p')
streakText.style.cssText = 'font-size: 24px; color: #fff; font-family: Baloo Bhaina;';
infoBar.appendChild(streakText);
if (status) {
streakCounts[mode] += 1;
streakText.textContent = `恭喜你选中 ${correctAddress(guess.country)}, 连击次数: ${streakCounts[mode]}`;
} else {
const endCount = streakCounts[mode];
streakCounts[mode] = 0;
streakText.textContent = `答案是 ${correctAddress(guess.country)}, 你选了 ${correctAddress(pin.country)}, 连击次数: ${streakCounts[mode]}, 本轮达成连击: ${endCount}`;
}
localStorage.setItem('streakCounts', JSON.stringify(streakCounts));
}
function updatePanel() {
const panelContainer = document.querySelector('.roundWrapper___eTnOj');
if (!panelContainer) return;
if (!countsDiv) {
countsDiv = document.createElement('div');
countsDiv.className = 'roundInfoBox___ikizG';
const countsTitle = document.createElement('div');
countsTitle.className = 'roundInfoTitle___VOdv2';
countsTitle.textContent = streakMode === 'country' ? '国家连击' : '一级行政区连击';
countsValue = document.createElement('div');
countsValue.className = 'roundInfoValue___zV6GS';
countsDiv.append(countsTitle, countsValue);
const divider = document.createElement('div');
divider.className = 'ant-divider css-i874aq ant-divider-vertical';
divider.setAttribute('role', 'separator');
panelContainer.append(divider, countsDiv);
}
countsValue.textContent = streakCounts[streakMode];
}
function matchCountryCode(t) {
const code = t?.country_code?.toUpperCase();
return CC_DICT[code] || code || 'Undefined';
}
function matchState(t) {
return t?.state || t?.province || t?.county || 'Undefined';
}
document.addEventListener("keydown", (e) => {
if (e.key.toLowerCase() === 'p') {
e.stopImmediatePropagation();
streakMode = streakMode === 'state' ? 'country' : 'state';
Swal.fire({
title: '切换成功',
text: `${streakMode} 连击计数器已就绪!`,
icon: 'success',
timer: 1000,
showConfirmButton: false,
});
}
});
})();