// ==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
// ==/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);
}