// ==UserScript== // @name Geoguessr百度街景脚本 // @description Geoguessr Unity Script的简化版, 国内可直接玩 // @version 0.1.0 // @include https://www.geoguessr.com/* // @run-at document-start // @license MIT // @namespace https://greasyfork.org/users/838374 // @downloadURL none // ==/UserScript== myLog("Geoguessr百度街景脚本"); // Store each player instance let BAIDU_INJECTED = false; // Game mode detection let isBattleRoyale = false; let isDuel = false; // Player detection and coordinate conversion let nextPlayer = "Google"; let global_lat = 0; let global_lng = 0; let global_panoID = null; let global_BDID, global_BDAh, global_BDBh; let global_heading = null; let global_pitch = null; let global_radi = 100 // Callback variables let playerLoaded = false; let defaultPanoIdChange = true; // Round check let ROUND = 0; let CURRENT_ROUND_DATA = null; let switch_call = true; let one_reset = false; // let cnt = 0; let cn_tips = false; var isFirefox = typeof InstallTrigger !== 'undefined'; /** * Helper Functions */ // Pretty print function myLog(...args) { console.log(...args); } function myHighlight(...args) { console.log(`%c${[...args]}`, "color: dodgerblue; font-size: 24px;"); } // Hex to number conversion for Baidu coordinate conversion function hex2a(hexx) { var hex = hexx.toString(); var str = ''; for (var i = 0; i < hex.length; i += 2) str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); return str; } // Coordinate computation given heading, distance and current coordinates for teleport function FindPointAtDistanceFrom(lat, lng, initialBearingRadians, distanceKilometres) { const radiusEarthKilometres = 6371.01; var distRatio = distanceKilometres / radiusEarthKilometres; var distRatioSine = Math.sin(distRatio); var distRatioCosine = Math.cos(distRatio); var startLatRad = DegreesToRadians(lat); var startLonRad = DegreesToRadians(lng); var startLatCos = Math.cos(startLatRad); var startLatSin = Math.sin(startLatRad); var endLatRads = Math.asin((startLatSin * distRatioCosine) + (startLatCos * distRatioSine * Math.cos(initialBearingRadians))); var endLonRads = startLonRad + Math.atan2( Math.sin(initialBearingRadians) * distRatioSine * startLatCos, distRatioCosine - startLatSin * Math.sin(endLatRads)); return { lat: RadiansToDegrees(endLatRads), lng: RadiansToDegrees(endLonRads) }; } function DegreesToRadians(degrees) { const degToRadFactor = Math.PI / 180; return degrees * degToRadFactor; } function RadiansToDegrees(radians) { const radToDegFactor = 180 / Math.PI; return radians * radToDegFactor; } // Check if two floating point numbers are really really really really close to each other (to 10 decimal points) function almostEqual (a, b) { return a.toFixed(10) === b.toFixed(10) } function almostEqual2 (a, b) { return a.toFixed(3) === b.toFixed(3) } // Script injection, extracted from extenssr: // https://gitlab.com/nonreviad/extenssr/-/blob/main/src/injected_scripts/maps_api_injecter.ts function overrideOnLoad(googleScript, observer, overrider) { const oldOnload = googleScript.onload googleScript.onload = (event) => { const google = unsafeWindow.google if (google) { observer.disconnect() overrider(google) } if (oldOnload) { oldOnload.call(googleScript, event) } } } function grabGoogleScript(mutations) { for (const mutation of mutations) { for (const newNode of mutation.addedNodes) { const asScript = newNode if (asScript && asScript.src && asScript.src.startsWith('https://maps.googleapis.com/')) { return asScript } } } return null } function injecter(overrider) { new MutationObserver((mutations, observer) => { const googleScript = grabGoogleScript(mutations) if (googleScript) { overrideOnLoad(googleScript, observer, overrider) } }).observe(document.documentElement, { childList: true, subtree: true }) } /** * This observer stays alive while the script is running */ function launchObserver() { myHighlight("Main Observer"); const OBSERVER = new MutationObserver((mutations, observer) => { detectGamePage(); }); OBSERVER.observe(document.head, { attributes: true, childList: true, subtree: true }); } /** * Once the Google Maps API was loaded we can do more stuff */ injecter(() => { launchObserver(); }) /** * Check whether the current page is a game, if so which game mode */ function detectGamePage() { if (document.querySelector(".game-layout__panorama-message") !== null && !one_reset) { one_reset = true; myLog("Hide fail to load panorama canvas"); document.querySelector(".game-layout__panorama-message").style.visibility = "hidden"; } let toLoad = !playerLoaded const PATHNAME = window.location.pathname; if (PATHNAME.startsWith("/game/") || PATHNAME.startsWith("/challenge/")) { // myLog("Game page"); isBattleRoyale = false; isDuel = false; if (toLoad) { loadPlayers(); } waitLoad(); } else if (PATHNAME.startsWith("/battle-royale/")) { if (document.querySelector(".br-game-layout") == null) { // myLog("Battle Royale Lobby"); rstValues(); } else { // myLog("Battle Royale"); isBattleRoyale = true; isDuel = false; if (toLoad) { loadPlayers(); } waitLoad(); } } else if (PATHNAME.startsWith("/duels/") || PATHNAME.startsWith("/team-duels/")) { if (document.querySelector(".game_layout__TO_jf") == null) { // myLog("Battle Royale Lobby"); rstValues(); } else { // myLog("Duels"); isBattleRoyale = true; isDuel = true; if (toLoad) { loadPlayers(); } waitLoad(); } } else { rstValues(); //myLog("Not a Game page"); } } function rstValues() { ROUND = 0; BAIDU_INJECTED = false; nextPlayer = "Google" global_lat = 0; global_lng = 0; global_panoID = null; global_BDAh = null; global_BDBh = null; global_BDID = null; playerLoaded = false; one_reset = false; CURRENT_ROUND_DATA = null; } /** * Wait for various players to load */ function waitLoad() { checkRound(); } /** * Checks for round changes */ function checkRound() { if (!isBattleRoyale) { let currentRound = getRoundFromPage(); if (ROUND != currentRound) { myHighlight("New round"); ROUND = currentRound; one_reset = false; getMapData(); } } else { getMapData(); } } function loadPlayers() { playerLoaded = true; if (!isBattleRoyale) { getSeed().then((data) => { if (!data.mapName.includes("China Tips for each province")) { cn_tips = false; } else { cn_tips = true; guaranteeUI(); } }).catch((error) => { myLog(error); }); } initializeCanvas(); } function guaranteeUI() { // myLog("UI") if (document.getElementById("GH-ui") !== null) { document.getElementById("GH-ui").style.display = "block"; } else { setTimeout(guaranteeUI, 500); } } /** * Handles Return to start and undo */ function handleReturnToStart() { let rtsButton = document.querySelector("button[data-qa='return-to-start']"); if (rtsButton != null) { myLog("handleReturnToStart listener attached"); rtsButton.addEventListener("click", (e) => { if (nextPlayer != "Baidu") { goToLocation(); } else { document.getElementById("PanoramaMap").src = "https://map.baidu.com/?panotype=street&pid=" + global_BDID + "&panoid=" + global_BDID + "&from=api"; } const elementClicked = e.target; elementClicked.setAttribute('listener', 'true'); myLog("Return to start"); }); } else { setTimeout(handleReturnToStart, 500); } } /** * Load game information */ function getMapData() { // myHighlight("Seed data"); getSeed().then((data) => { // myHighlight("Seed data"); // myLog(data); if (isBattleRoyale) { if ((document.querySelector(".br-game-layout") == null && document.querySelector(".version3-in-game_layout__Hi_Iw") == null) || typeof data.gameId == typeof undefined) { // myLog("Battle Royale Lobby"); } else { let origin = false; if (!CURRENT_ROUND_DATA) { CURRENT_ROUND_DATA = data origin = true; } if (origin || !(data.currentRoundNumber === CURRENT_ROUND_DATA.currentRoundNumber)) { // myHighlight("Battle Royale New round"); // NEW_ROUND_LOADED = true; one_reset = false; if (!origin) { CURRENT_ROUND_DATA = data; } locationCheck(data); // myLog(data); goToLocation(); handleReturnToStart(); if (isDuel) { hideButtons(); } } } } else { locationCheck(data); goToLocation(); handleReturnToStart(); hideButtons(); } }).catch((error) => { myLog(error); }); } /** * Hide unnecessary buttons for non-Google coverages */ function hideButtons() { let CHECKPOINT = document.querySelector("button[data-qa='set-checkpoint']"); let ZOOM_IN = document.querySelector("button[data-qa='pano-zoom-in']"); let ZOOM_OUT = document.querySelector("button[data-qa='pano-zoom-out']"); if (CHECKPOINT != null) { if (nextPlayer === "Google") { CHECKPOINT.style.visibility = ""; ZOOM_IN.style.visibility = ""; ZOOM_OUT.style.visibility = ""; myLog("Buttons Unhidden"); } else { CHECKPOINT.style.visibility = "hidden"; ZOOM_IN.style.visibility = "hidden"; ZOOM_OUT.style.visibility = "hidden"; myLog("Buttons Hidden"); } } else { setTimeout(hideButtons, 250); } } /** * Check which player to use for the next location */ function locationCheck(data) { // console.log(data); let round; if (isBattleRoyale) { if (isDuel) { round = data.rounds[data.currentRoundNumber - 1].panorama; } else { round = data.rounds[data.currentRoundNumber - 1]; } } else { round = data.rounds[data.round - 1]; } global_lat = round.lat; global_lng = round.lng; global_panoID = round.panoId; global_heading = round.heading; global_pitch = round.pitch; nextPlayer = "Google"; if (global_panoID) { let locInfo = hex2a(global_panoID); // myLog(locInfo) let mapType = locInfo.substring(0, 5); if (mapType === "BDMAP") { nextPlayer = "Baidu"; let coord = locInfo.substring(5); if(coord.includes('BDAh')) { global_BDID = coord.split('BDAh')[0].replace("panoId",""); let tem = coord.split('BDAh')[1]; global_BDAh = tem.split('BDBh')[0]; global_BDBh = tem.split('BDBh')[1]; } else { global_BDID = coord.replace("panoId",""); } } } myLog(nextPlayer); injectCanvas(); } /** * setID for canvas */ function initializeCanvas() { let GAME_CANVAS = ""; let DUEL_CANVAS = ""; //myLog("Is duels"); //myLog(duels); if (isBattleRoyale) { if (isDuel) { GAME_CANVAS = document.querySelector(".game-panorama_panorama__rdhFg"); DUEL_CANVAS = document.querySelector(".game-panorama_panoramaCanvas__PNKve"); } else { GAME_CANVAS = document.querySelector(".br-game-layout__panorama-wrapper"); DUEL_CANVAS = "dummy"; } } else { GAME_CANVAS = document.querySelector(".game-layout__canvas"); DUEL_CANVAS = "dummy"; } if (GAME_CANVAS && DUEL_CANVAS) { myLog("Canvas injected"); GAME_CANVAS.id = "player"; if (isDuel) { DUEL_CANVAS.id = "default_player"; } injectBaiduScript(); } else { setTimeout(initializeCanvas, 250); } } /** * Hide or show players based on where the next location is */ function injectCanvas() { if (isDuel) { canvasSwitch(); } else { Google(); Baidu(); } } // for duels (class ID change) function canvasSwitch() { if (document.querySelector(".compass") !== null && document.querySelector("button[data-qa='undo-move']") !== null) { let GOOGLE_MAPS_CANVAS = document.querySelector(".game-panorama_panoramaCanvas__PNKve"); if (nextPlayer === "Google") { document.getElementById("default_player").className = "game-panorama_panoramaCanvas__PNKve"; document.getElementById("PanoramaMap").className = "inactive"; document.getElementById("default_player").style.position = "absolute"; document.querySelector(".compass").style.visibility = ""; document.querySelector("button[data-qa='undo-move']").visibility = ""; myLog("Google Duel Canvas loaded"); } else if (nextPlayer === "Baidu") { document.getElementById("default_player").className = "inactive"; document.getElementById("PanoramaMap").className = "game-panorama_panorama__rdhFg"; document.getElementById("PanoramaMap").style.position = "absolute"; document.querySelector(".compass").style.visibility = "hidden"; document.querySelector("button[data-qa='undo-move']").visibility = "hidden"; myLog("Baidu Duel Canvas loaded"); } } else { setTimeout(canvasSwitch, 250); } } // for Battle Royale and classic (change visibility) function Google() { let GOOGLE_MAPS_CANVAS = "" if (isBattleRoyale) { GOOGLE_MAPS_CANVAS = document.querySelector(".br-game-layout__panorama-canvas"); } else { GOOGLE_MAPS_CANVAS = document.querySelector(".game-layout__panorama-canvas"); } if (nextPlayer === "Google") { GOOGLE_MAPS_CANVAS.style.visibility = ""; myLog("Google Canvas loaded"); } else { GOOGLE_MAPS_CANVAS.style.visibility = "hidden"; myLog("Google Canvas hidden"); } } function Baidu() { let BAIDU_MAPS_CANVAS = document.getElementById("PanoramaMap"); // myLog("Baidu canvas"); document.getElementById("PanoramaMap").style.position = "absolute"; if (BAIDU_MAPS_CANVAS !== null && document.querySelector(".compass") !== null && document.querySelector("button[data-qa='undo-move']") !== null) { if (nextPlayer === "Baidu") { BAIDU_MAPS_CANVAS.style.visibility = ""; document.querySelector(".compass").style.visibility = "hidden"; document.querySelector("button[data-qa='undo-move']").style.visibility = "hidden"; myLog("Baidu Canvas loaded"); } else { document.querySelector(".compass").style.visibility = ""; document.querySelector("button[data-qa='undo-move']").style.visibility = ""; BAIDU_MAPS_CANVAS.style.visibility = "hidden"; myLog("Baidu Canvas hidden"); } } else { setTimeout(Baidu, 250); } } /** * Open next location in streetview player given next player and next coordinate */ function goToLocation() { myLog("Going to location"); if (nextPlayer === "Baidu") { if (document.getElementById("PanoramaMap") !== null) { let urlStr2 = "https://map.baidu.com/?panotype=street&pid=" + global_BDID + "&panoid=" + global_BDID + "&from=api"; let urlStr = "https://map.baidu.com/@" + global_BDAh + "," + global_BDBh + "#panoid=" + global_BDID + "&panotype=street&l=12&tn=B_NORMAL_MAP&sc=0&newmap=1&shareurl=1&pid=" + global_BDID; if (global_BDAh != null) { document.getElementById("PanoramaMap").src = urlStr; } else { document.getElementById("PanoramaMap").src = urlStr2; } } else { setTimeout(goToLocation, 250); } } } /** * Gets the seed data for the current game * * @returns Promise with seed data as object */ function getSeed() { // myLog("getSeed called"); return new Promise((resolve, reject) => { let token = getToken(); let URL; let cred = "" const PATHNAME = window.location.pathname; if (PATHNAME.startsWith("/game/")) { URL = `https://www.geoguessr.com/api/v3/games/${token}`; } else if (PATHNAME.startsWith("/challenge/")) { URL = `https://www.geoguessr.com/api/v3/challenges/${token}/game`; } else if (PATHNAME.startsWith("/battle-royale/")) { URL = `https://game-server.geoguessr.com/api/battle-royale/${token}`; } else if (PATHNAME.startsWith("/duels/") || PATHNAME.startsWith("/team-duels/")) { URL = `https://game-server.geoguessr.com/api/duels/${token}`; } if (isBattleRoyale) { fetch(URL, { // Include credentials to GET from the endpoint credentials: 'include' }) .then((response) => response.json()) .then((data) => { resolve(data); }) .catch((error) => { reject(error); }); } else { fetch(URL) .then((response) => response.json()) .then((data) => { resolve(data); }) .catch((error) => { reject(error); }); } }); } /** * Gets the token from the current URL * * @returns token */ function getToken() { const PATHNAME = window.location.pathname; if (PATHNAME.startsWith("/game/")) { return PATHNAME.replace("/game/", ""); } else if (PATHNAME.startsWith("/challenge/")) { return PATHNAME.replace("/challenge/", ""); } else if (PATHNAME.startsWith("/battle-royale/")) { return PATHNAME.replace("/battle-royale/", ""); } else if (PATHNAME.startsWith("/duels/")) { return PATHNAME.replace("/duels/", ""); } else if (PATHNAME.startsWith("/team-duels/")) { return PATHNAME.replace("/team-duels/", ""); } } /** * Gets the round number from the ongoing game from the page itself * * @returns Round number */ function getRoundFromPage() { const roundData = document.querySelector("div[data-qa='round-number']"); if (roundData) { let roundElement = roundData.querySelector("div:last-child"); if (roundElement) { let round = parseInt(roundElement.innerText.charAt(0)); if (!isNaN(round) && round >= 1 && round <= 5) { return round; } } } else { return ROUND; } } /** * Injects Baidu script */ function reportWindowSize() { let iframeC = document.getElementById("PanoramaMap"); iframeC.style.top = '-60px'; iframeC.style.height = (window.innerHeight + 200) + 'px'; iframeC.style.right = '-55px'; iframeC.style.width = (window.innerWidth + 55) + 'px'; } window.onresize = reportWindowSize; function injectBaiduScript() { // return new Promise((resolve, reject) => { // if (!BAIDU_INJECTED) { // if (BAIDU_API_KEY === "") { // let canvas = document.getElementById("player"); // myLog("No Baidu Key") // } // else { myLog("Iframe") const iframe = document.createElement('iframe'); // iframe.src = "https://map.baidu.com/" iframe.frameBorder = 0; iframe.style.position = "absolute"; iframe.id = "PanoramaMap"; if (!isFirefox) { iframe.style.top = '-60px'; iframe.style.height = (window.innerHeight + 200) + 'px'; } else { iframe.style.top = '-60px'; iframe.style.height = (window.innerHeight + 219) + 'px'; } if (!isFirefox) { iframe.style.right = '-55px'; iframe.style.width = (window.innerWidth + 55) + 'px'; } else { iframe.style.right = '-15px'; iframe.style.width = (window.innerWidth + 15) + 'px'; } if (isBattleRoyale) { if (isDuel) { iframe.className = "inactive"; } else { iframe.className = "br-game-layout__panorama"; } } else { iframe.className = "game-layout__panorama"; } var div = document.getElementById("player"); div.style.overflow = "hidden"; div.appendChild(iframe); }