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 Battle Royale
// @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;
// }