NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Geoguessr Yandex + Baidu Plugin with BR // @namespace Jupaoqq // @description WARNING: PLEASE DISABLE THIS SCRIPT FOR NORMAL BR MAPS - Play Geoguessr with Yandex and Baidu Panoramas. A big shout out to kommu, who made the original Yandex script, and @MrAmericanMike who helped kommu coding this script and to @Alok who had the idea and help us to test the script // @version 1.0.1ba // @include https://www.geoguessr.com/* // @run-at document-start // @license MIT // ==/UserScript== const YANDEX_API_KEY = "4ca8ac1d-2ecb-4f1d-9bae-fa012e2136ff"; const BAIDU_API_KEY = "8dQ9hZPGEQnqg9r0O1C8Ate2N6P8Zk92"; let BR = false; function myLog(...args) { console.log(...args); } function myHighlight(...args) { console.log(`%c${[...args]}`, "color: dodgerblue; font-size: 24px;"); } myLog("Geoguessr Yandex Plugin"); const MAP_NAME_INCLUDES = "Yandex"; const BAIDU_MAP_NAME_INCLUDES = "baidu"; const PANORAMA_MAP_NAME_INCLUDES = "Yandex Air Panorama"; const MAPS_API_URL = "https://maps.googleapis.com/maps/api/js?"; let PLAYER = null; let ROUND = 0; let YANDEX_INJECTED = false; let NEW_ROUND_LOADED = false; let COMPASS = null; let PANORAMA_MAP = false; let CURRENT_ROUND_DATA = null; let BAIDU_INJECTED = false; // let COMPASS_HIDDEN = false; /** * Resolves succesfully after detecting that Google Maps API was loaded in a page * * @returns Promise */ function gmp() { return new Promise((resolve, reject) => { let scriptObserver = new MutationObserver((mutations, observer) => { for (let mutation of mutations) { for (let node of mutation.addedNodes) { if (node.tagName === "SCRIPT" && node.src.startsWith(MAPS_API_URL)) { scriptObserver.disconnect(); scriptObserver = undefined; myLog("Detected Google Maps API"); node.onload = () => resolve(); } } } }); let bodyDone = false; let headDone = false; let injectorObserver = new MutationObserver((mutations, observer) => { if (!bodyDone && document.body) { bodyDone = true; myLog("Body Observer Injected"); scriptObserver && scriptObserver.observe(document.body, { childList: true }); } if (!headDone && document.head) { headDone = true; myLog("Head Observer Injected"); scriptObserver && scriptObserver.observe(document.head, { childList: true }); } if (headDone && bodyDone) { myLog("Body and Head Observers Injected"); observer.disconnect(); } }); injectorObserver.observe(document.documentElement, { childList: true, subtree: true }); }); } /** * Once the Google Maps API was loaded we can do more stuff */ gmp().then(() => { launchObserver(); }); /** * 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 }); } /** * Detects if the current page contains /game/ or /challenge/ in it * If it doesn't it sets: * ROUND = 0 * PLAYER = null * COMPASS = null */ function detectGamePage() { const PATHNAME = window.location.pathname; if (PATHNAME.startsWith("/game/") || PATHNAME.startsWith("/challenge/")){ myLog("Game page"); BR = false; checkRound(); } else if (PATHNAME.startsWith("/battle-royale/")){ myLog("BR"); BR = true; checkRound(); } else { myLog("Not a Game page"); ROUND = 0; PLAYER = null; COMPASS = null; BAIDU_INJECTED = false; } } /** * Checks for round changes */ function checkRound() { if (!BR){ let currentRound = getRoundFromPage(); if (ROUND != currentRound) { myHighlight("New round"); ROUND = currentRound; NEW_ROUND_LOADED = true; COMPASS = null; getMapData(); } } else{ myHighlight("New round2"); NEW_ROUND_LOADED = true; COMPASS = null; getMapData(); } } /** * Gets data for the current map and checks if name contains `MAP_NAME_INCLUDES` * If so it continues calling other functions, otherway it does nothing else */ function getMapData() { getSeed().then((data) => { // myHighlight("Seed data"); // myLog(data); if (BR) { if (!CURRENT_ROUND_DATA) { CURRENT_ROUND_DATA = data hideGoogleCanvas(); injectBaiduCanvas(); if (PLAYER) { goToBaiduLocation(data); } else { myLog("Ready to inject baidu player"); injectBaiduPlayer(data); } } if (!(data.currentRoundNumber === CURRENT_ROUND_DATA.currentRoundNumber)) { CURRENT_ROUND_DATA = data hideGoogleCanvas(); injectBaiduCanvas(); if (PLAYER) { goToBaiduLocation(data); } else { myLog("Ready to inject baidu player"); injectBaiduPlayer(data); } } else{ } } else { if (data.mapName.includes(MAP_NAME_INCLUDES)) { PANORAMA_MAP = (data.mapName.includes(PANORAMA_MAP_NAME_INCLUDES)); myHighlight("Yandex Map"); hideGoogleCanvas(); hideDefaultZoomControls(); injectYandexCanvas(); CURRENT_ROUND_DATA = data; if (PLAYER) { goToLocation(data); } else { injectYandexScript().then(() => { myLog("Ready to inject player"); injectYandexPlayer(data); }).catch((error) => { myLog(error); }); } } else if (data.mapName.includes(BAIDU_MAP_NAME_INCLUDES)) { // if (!COMPASS_HIDDEN) // { // hideDefaultCompass(); // } PANORAMA_MAP = (data.mapName.includes(PANORAMA_MAP_NAME_INCLUDES)); myLog(data); hideGoogleCanvas(); hideDefaultZoomControls(); injectBaiduCanvas(); CURRENT_ROUND_DATA = data; if (PLAYER) { myLog("PLAYER"); myLog(PLAYER); goToBaiduLocation(data); } else { myLog("Ready to inject baidu player"); injectBaiduPlayer(data); } } } }).catch((error) => { myLog(error); }); } /** * 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}`; } if (BR) { 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/", ""); } } /** * 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; } } /** * Hides Google Canvas */ function hideGoogleCanvas() { let GOOGLE_MAPS_CANVAS = "" if (!BR) { GOOGLE_MAPS_CANVAS = document.querySelector(".game-layout__panorama-canvas"); } else{ GOOGLE_MAPS_CANVAS = document.querySelector(".br-game-layout__panorama-canvas"); } GOOGLE_MAPS_CANVAS.style.display = "none"; myLog("Google Canvas hidden"); } /** * Hides default zoom controls and Yandex GOTO link */ function hideDefaultZoomControls() { let style = ` .ymaps-2-1-79-panorama-gotoymaps {display: none !important;} .game-layout__controls {bottom: 8rem !important; left: 1rem !important;} .game-layout__controls .styles_controlGroup__2pd1f, .styles_horizontalControls__ewOLi {display: none !important;} `; let style_element = document.createElement("style"); style_element.innerHTML = style; document.body.appendChild(style_element); } /** * Injects Yandex Canvas */ function injectYandexCanvas() { const GAME_CANVAS = document.querySelector(".game-layout__panorama"); GAME_CANVAS.id = "yandex_player"; myLog("Yandex Canvas injected"); } /** * Injects Yandex Script */ function injectYandexScript() { return new Promise((resolve, reject) => { if (!YANDEX_INJECTED) { if (YANDEX_API_KEY === "") { let canvas = document.getElementById("yandex_player"); canvas.innerHTML = ` <div style="text-align: center;"> <h1 style="margin-top: 80px; font-size: 48px;">YOU NEED YANDEX API KEY<h1> <p><a target="_blank" href="https://yandex.com/dev/maps/jsapi/doc/2.1/quick-start/index.html?from=techmapsmain">Get it here</a></p> <br/> <p>After that you need to add that key into this script in</p> <code>const YANDEX_API_KEY = "";</code> </div> `; reject(); } else { const SCRIPT = document.createElement("script"); SCRIPT.type = "text/javascript"; SCRIPT.async = true; SCRIPT.onload = () => { ymaps.ready(() => { YANDEX_INJECTED = true; myHighlight("Yandex API Loaded"); resolve(); }); } SCRIPT.src = `https://api-maps.yandex.ru/2.1/?lang=en_US&apikey=${YANDEX_API_KEY}`; document.body.appendChild(SCRIPT); } } else { resolve(); } }); } /** * Injects Yandex Player and calls handleReturnToStart */ function injectYandexPlayer(data) { const lat = data.rounds[data.round - 1].lat; const lng = data.rounds[data.round - 1].lng; if (PLAYER === null) { let options = { "direction": [0, 16], "span": [10, 67], "controls": ["zoomControl"] }; if (PANORAMA_MAP) { options.layer = 'yandex#airPanorama'; } ymaps.panorama.createPlayer("yandex_player", [lat, lng], options) .done((player) => { PLAYER = player; PLAYER.events.add("directionchange", (e) => { updateCompass(data); }); myHighlight("Player injected"); }); } else { goToLocation(data); } } function injectBaiduCanvas() { let GAME_CANVAS = "" if (!BR) { GAME_CANVAS = document.querySelector(".game-layout__canvas"); } else { GAME_CANVAS = document.querySelector(".br-game-layout__canvas"); } GAME_CANVAS.id = "baidu_player"; myLog("Baidu Canvas injected"); } /** * Injects Baidu */ function injectBaiduScript() { // return new Promise(function(resolve, reject) { // let canvas = document.getElementById("baidu_player"); // canvas.innerHTML = ` // <div id="PanoramaMap"> </div> // `; // var script = document.createElement('script') // script.type = 'text/javascript' // script.src = `https://api.map.baidu.com/api?v=3.0&ak=${BAIDU_API_KEY}&callback=init`; // script.onerror = reject; // document.head.appendChild(script) // myLog("BMap2"); // window.init = () => { // resolve(window.BMap) // } // }) // } return new Promise((resolve, reject) => { if (!BAIDU_INJECTED) { if (BAIDU_API_KEY === "") { let canvas = document.getElementById("baidu_player"); canvas.innerHTML = ` <div style="text-align: center;"> <h1 style="margin-top: 80px; font-size: 48px;">YOU NEED Baidu API KEY<h1> <br/> <p>After that you need to add that key into this script in</p> <code>const YANDEX_API_KEY = "";</code> </div> `; reject(); } else { const SCRIPT = document.createElement("script"); SCRIPT.type = "text/javascript"; SCRIPT.async = true; SCRIPT.src = `https://api.map.baidu.com/api?v=3.0&ak=${BAIDU_API_KEY}&callback=init`; document.body.appendChild(SCRIPT); let canvas = document.createElement("game-layout__canvas_new"); if (BR) { canvas.innerHTML = ` <div id="PanoramaMap" class="br-game-layout__panorama" style="zIndex: 99999,position: "absolute", top: 0, left: 0, width: '100%', height: '100%',"> </div> `; } else{ canvas.innerHTML = ` <div id="PanoramaMap" class="game-layout__panorama" style="zIndex: 99999,position: "absolute", top: 0, left: 0, width: '100%', height: '100%',"> </div> `; } var div = document.getElementById("baidu_player"); div.appendChild(canvas); SCRIPT.addEventListener('load', () => { BAIDU_INJECTED = true; myHighlight("Baidu API Loaded"); // resolve(BMap); setTimeout(function(){resolve(BMap);}.bind(this), 3000); }) } } else { resolve(); } }); } /** * Injects Baidu Player and calls handleReturnToStart */ function injectBaiduPlayer(data) { let lat = 0; let lng = 0; if (BR) { lat = data.rounds[data.currentRoundNumber - 1].lat; lng = data.rounds[data.currentRoundNumber - 1].lng; } else { lat = data.rounds[data.round - 1].lat; lng = data.rounds[data.round - 1].lng; } if (PLAYER === null) { injectBaiduScript().then(BMap => { PLAYER = new BMap.Panorama('PanoramaMap'); PLAYER.setPov({heading: -40, pitch: 6}); PLAYER.setPosition(new BMap.Point(lng, lat)); handleBaiduReturnToStart() }); } else { myLog("No"); goToBaiduLocation(data); } } /** * Goes to location when PLAYER already exists * * @param {*} data */ function goToLocation(data) { myLog("Going to location"); const lat = data.rounds[data.round - 1].lat; const lng = data.rounds[data.round - 1].lng; let options = {}; if (PANORAMA_MAP) { options.layer = 'yandex#airPanorama'; } PLAYER.moveTo([lat, lng], options); PLAYER.setDirection([0, 16]); PLAYER.setSpan([10, 67]); } /** * Updates the compass to match Yandex Panorama facing */ function updateCompass() { if (!COMPASS) { let compass = document.querySelector("img.compass__indicator"); if (compass != null) { COMPASS = compass; let direction = PLAYER.getDirection()[0] * -1; COMPASS.setAttribute("style", `transform: rotate(${direction}deg);`); } handleReturnToStart(); } else { let direction = PLAYER.getDirection()[0] * -1; COMPASS.setAttribute("style", `transform: rotate(${direction}deg);`); } } /** * Injects behavior to go back to start when the return to start flag is clicked * * @param {*} data */ function handleReturnToStart() { let flag = document.querySelector("button[data-qa='return-to-start']"); if (flag != null) { myLog("Return to start attached"); let FLAG = flag; FLAG.addEventListener("click", () => { goToLocation(CURRENT_ROUND_DATA); }); } } /** * Goes to location when PLAYER already exists * * @param {*} data */ function goToBaiduLocation(data) { myLog("Going to location"); let lat = 0; let lng = 0; if (BR) { lat = data.rounds[data.currentRoundNumber - 1].lat; lng = data.rounds[data.currentRoundNumber - 1].lng; } else { lat = data.rounds[data.round - 1].lat; lng = data.rounds[data.round - 1].lng; } PLAYER.setPov({heading: -40, pitch: 6}); PLAYER.setPosition(new BMap.Point(lng, lat)); } function handleBaiduReturnToStart() { let flag = document.querySelector("button[data-qa='return-to-start']"); if (flag != null) { myLog("Return to start attached"); let FLAG = flag; FLAG.addEventListener("click", () => { goToBaiduLocation(CURRENT_ROUND_DATA); }); } } // function hideDefaultCompass() { // var comp = document.querySelector(".compass__indicator"); // comp.style.display = "none"; // var comp2 = document.querySelector(".compass__circle"); // comp2.style.display = "none"; // COMPASS_HIDDEN = true; // }