Geoguessr百度街景脚本

Geoguessr Unity Script的简化版, 国内可直接玩(无需使用VPN服务)

目前为 2022-01-20 提交的版本。查看 最新版本

// ==UserScript==
// @name          Geoguessr百度街景脚本
// @description   Geoguessr Unity Script的简化版, 国内可直接玩(无需使用VPN服务)
// @version       0.1.1
// @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;
let OBSERVER;

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

/**
 * This observer stays alive while the script is running
 */

function launchObserver() {
    myHighlight("Main Observer");
    OBSERVER = new MutationObserver((mutations, observer) => {
        detectGamePage();
    });
    obsCallback();
}

function obsCallback()
{
    if (document.head)
    {
    OBSERVER.observe(document.head, { attributes: true, childList: true, subtree: true });
    }
    else
    {
        setTimeout(obsCallback, 500);
    }
}

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 = "";
            BAIDU_MAPS_CANVAS.style.position = "absolute";
            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() {
    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);
    // myLog(div)
}