AppleGuessr

Adds Apple Look Around to GeoGuessr

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         AppleGuessr
// @namespace    https://greasyfork.org/en/users/946023-mistystar
// @version      2.1
// @description  Adds Apple Look Around to GeoGuessr
// @author       Mistystar (Mistystar#2205, https://github.com/kittenz) & stocc (stocc#2919, https://github.com/stocc)
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @grant        none
// @license      MIT
// @run-at       document-start
// @require 	 https://cdn.jsdelivr.net/npm/[email protected]/dist/protobuf.js
// @require		 https://cdn.jsdelivr.net/npm/long@5/umd/index.js
// @require 	 https://cdn.jsdelivr.net/gh/chebum/heic2any@2c517409ac73e86e92560312b58fcfd565ad7393/dist/heic2any.min.js

// ==/UserScript==



/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ({

/***/ 297:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


// Blatantly stolen from https://github.com/sk-zk/lookaround-map/blob/main/static/auth.js
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Authenticator_instances, _Authenticator_generateSessionId, _Authenticator_generateTokenP3;
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.Authenticator = void 0;
const options_1 = __webpack_require__(944);
const proto_1 = __webpack_require__(224);
const TOKEN_P1 = "4cjLaD4jGRwlQ9U";
const MANIFEST_URL = "https://gspe35-ssl.ls.apple.com/geo_manifest/dynamic/config?application=geod" +
    "&application_version=1&country_code=US&hardware=MacBookPro11,2&os=osx" +
    "&os_build=20B29&os_version=11.0.1";
var GLOBAL_TOKENP2 = undefined;
class Authenticator {
    constructor() {
        _Authenticator_instances.add(this);
        this.sessionId = null;
    }
    init() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.refreshCredentials();
        });
    }
    refreshCredentials() {
        return __awaiter(this, void 0, void 0, function* () {
            this.sessionId = __classPrivateFieldGet(this, _Authenticator_instances, "m", _Authenticator_generateSessionId).call(this);
        });
    }
    hasSession() {
        return this.sessionId != null;
    }
    getTokenP2() {
        return __awaiter(this, void 0, void 0, function* () {
            if (GLOBAL_TOKENP2 == undefined) {
                GLOBAL_TOKENP2 = (yield this.getResourceManifest()).tokenP2;
            }
            return GLOBAL_TOKENP2;
        });
    }
    authenticateUrl(url) {
        return __awaiter(this, void 0, void 0, function* () {
            const urlObj = new URL(url);
            let p2 = yield this.getTokenP2();
            const tokenP3 = __classPrivateFieldGet(this, _Authenticator_instances, "m", _Authenticator_generateTokenP3).call(this);
            const token = TOKEN_P1 + p2 + tokenP3;
            const timestamp = Math.floor(Date.now() / 1000) + 4200;
            const separator = urlObj.search ? "&" : "?";
            let urlPath = urlObj.pathname;
            if (urlObj.search) {
                urlPath += urlObj.search;
            }
            const plaintext = `${urlPath}${separator}sid=${this.sessionId}${timestamp}${tokenP3}`;
            const plaintextBytes = new TextEncoder().encode(plaintext);
            const key = yield sha256(token);
            const ciphertext = yield aes(key, plaintextBytes);
            const ciphertextB64 = btoa(String.fromCharCode(...new Uint8Array(ciphertext)));
            const ciphertextUrl = encodeURIComponent(ciphertextB64);
            const accessKey = `${timestamp}_${tokenP3}_${ciphertextUrl}`;
            const final = `${url}${separator}sid=${this.sessionId}&accessKey=${accessKey}`;
            return final;
        });
    }
    getResourceManifest() {
        return __awaiter(this, void 0, void 0, function* () {
            const response = yield fetch(options_1.CORS_PROXY + MANIFEST_URL);
            let pb = yield response.arrayBuffer();
            return yield proto_1.default.parseResourceManifest(pb);
        });
    }
}
exports.Authenticator = Authenticator;
_Authenticator_instances = new WeakSet(), _Authenticator_generateSessionId = function _Authenticator_generateSessionId() {
    let id = "";
    for (let i = 0; i < 40; i++) {
        const digit = (Math.random() * 10) | 0;
        id += digit.toString();
    }
    return id;
}, _Authenticator_generateTokenP3 = function _Authenticator_generateTokenP3() {
    const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
    let token = "";
    for (let i = 0; i < 16; i++) {
        const idx = (Math.random() * chars.length) | 0;
        token += chars[idx];
    }
    return token;
};
function sha256(message) {
    return __awaiter(this, void 0, void 0, function* () {
        const msgBuffer = new TextEncoder().encode(message);
        const hashBuffer = yield window.crypto.subtle.digest("SHA-256", msgBuffer);
        return hashBuffer;
    });
}
function aes(key, encodedMessage) {
    return __awaiter(this, void 0, void 0, function* () {
        const iv = new Uint8Array(16); // 16 zeroes
        const cryptoKey = yield window.crypto.subtle.importKey("raw", key, { name: "AES-CBC" }, true, ["encrypt"]);
        return yield window.crypto.subtle.encrypt({
            name: "AES-CBC",
            iv,
        }, cryptoKey, encodedMessage);
    });
}
//# sourceMappingURL=auth.js.map

/***/ }),

/***/ 97:
/***/ ((__unused_webpack_module, exports) => {


Object.defineProperty(exports, "__esModule", ({ value: true }));
const TILE_SIZE = 256;
class GeoUtils {
    static haversineDistance(coords1, coords2) {
        function toRad(x) {
            return x * Math.PI / 180;
        }
        var lon1 = coords1[0];
        var lat1 = coords1[1];
        var lon2 = coords2[0];
        var lat2 = coords2[1];
        var R = 6371; // km
        var x1 = lat2 - lat1;
        var dLat = toRad(x1);
        var x2 = lon2 - lon1;
        var dLon = toRad(x2);
        var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
            Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
                Math.sin(dLon / 2) * Math.sin(dLon / 2);
        var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        var d = R * c;
        return d;
    }
    static radians_to_degrees(radians) {
        var pi = Math.PI;
        return radians * (180 / pi);
    }
    static headingFromUnknowns(unknown10, unknown11) {
        let westmin = 1;
        let westmax = 2159;
        let eastmin = 16383; // looking (north/south) and very slightly east
        let eastmax = 14318; // looking slightly (north/south) directly east
        let northmin = 8204; // this is likely lower
        let northmax = 6054;
        let southmin = 8204; // this is likely lower
        let southmax = 10173;
        var ew = 0;
        if (unknown10 < westmax) {
            ew = -((unknown10 - westmin) / (westmax - westmin));
        }
        else if (unknown10 > eastmax) {
            ew = ((unknown10 - eastmin) / (eastmax - eastmin));
        }
        var ns = 0;
        if (unknown11 <= northmin) {
            ns = ((unknown11 - northmin) / (northmax - northmin));
        }
        else {
            ns = -((unknown11 - southmin) / (southmax - southmin));
        }
        var r = GeoUtils.radians_to_degrees(Math.atan2(ew, ns));
        if (r < 0) {
            r += 360;
        }
        return r;
    }
    static mercator_to_wgs84(x, y) {
        let lat = (2 * Math.atan(Math.exp((y - 128) / -(256 / (2 * Math.PI)))) - Math.PI / 2) / (Math.PI / 180);
        let lon = (x - 128) / (256 / 360);
        return [lat, lon];
    }
    static tile_coord_to_wgs84(x, y, z) {
        let scale = 1 << z;
        let pixel_coord = [x * TILE_SIZE, y * TILE_SIZE];
        let world_coord = [pixel_coord[0] / scale, pixel_coord[1] / scale];
        let lat_lon = GeoUtils.mercator_to_wgs84(world_coord[0], world_coord[1]);
        return [lat_lon[0], lat_lon[1]];
    }
    static protobuf_tile_offset_to_wsg84(x_offset, y_offset, tile_x, tile_y) {
        let pano_x = tile_x + (x_offset / 64.0) / (TILE_SIZE - 1);
        let pano_y = tile_y + (255 - (y_offset / 64.0)) / (TILE_SIZE - 1);
        let coords = GeoUtils.tile_coord_to_wgs84(pano_x, pano_y, 17);
        return coords;
    }
    static wgs84_to_mercator(lat, lon) {
        var siny = Math.sin(lat * Math.PI / 180);
        siny = Math.min(Math.max(siny, -0.9999), 0.9999);
        return [
            TILE_SIZE * (0.5 + lon / 360),
            TILE_SIZE * (0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI))
        ];
    }
    static wgs84_to_tile_coord(lat, lon, zoom) {
        var scale = 1 << zoom;
        var world_coord = this.wgs84_to_mercator(lat, lon);
        var tile_coord = [
            Math.floor((world_coord[0] * scale) / TILE_SIZE),
            Math.floor((world_coord[1] * scale) / TILE_SIZE)
        ];
        return tile_coord;
    }
    static heading(coords1, coords2) {
        try {
            let c1 = new google.maps.LatLng(coords1[0], coords1[1]);
            let c2 = new google.maps.LatLng(coords2[0], coords2[1]);
            let result = google.maps.geometry.spherical.computeHeading(c1, c2);
            if (result < 0) {
                result += 360;
            }
            return result;
        }
        catch (e) {
            console.log(e);
        }
    }
}
exports["default"] = GeoUtils;
//# sourceMappingURL=geoutils.js.map

/***/ }),

/***/ 590:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


// ==UserScript==
// @name         AppleGuessr
// @namespace    https://greasyfork.org/en/users/946023-mistystar
// @version      2.1
// @description  Adds Apple Look Around to GeoGuessr
// @author       Mistystar (Mistystar#2205, https://github.com/kittenz) & stocc (stocc#2919, https://github.com/stocc)
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @grant        none
// @license      MIT
// @run-at       document-start
// @require 	 https://cdn.jsdelivr.net/gh/chebum/heic2any@master/dist/heic2any.min.js
// @require 	 https://cdn.jsdelivr.net/npm/[email protected]/dist/protobuf.js
// @require		 https://cdn.jsdelivr.net/npm/long@5/umd/index.js
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
// ==/UserScript==
/*
CREDITS

Massive thank you to the following people:
    - skzk#8049 - Without https://github.com/sk-zk/lookaround-map this script would not have been possible to make
    - Jupaoqq#7742 - I learned a lot from looking at Unity Script's source code
    - mattisinthesky#1294 or kowalski - For hosting the lookaround-map in Heroku and helping with issues
    - efefury#0519 and Apfeloxid#1368 - For making the Take A Look Around Germany map
*/
// BEGIN CODE SECTION
const Options = __webpack_require__(944);
const Lookaround = __webpack_require__(215);
const lookaround_1 = __webpack_require__(215);
const geoutils_1 = __webpack_require__(97);
protobuf.util.Long = Long;
protobuf.configure();
const MENU_HTML = (/* unused pure expression or super */ null && (`
<div class="start-standard-game_settings__x94PU">
	<div class="game-settings_default__DIBgs">
		<div class="game-settings_toggleLabel__nipwm">
			<div class="label_sizeXSmall__mFnrR">Apple Look Around</div>
			<span></span>
		</div>
		<div>
			<input type="checkbox" class="apple-look-around-toggle" checked>
		</div>
	</div>
</div>
`));
const isGamePage = () => location.pathname.startsWith("/challenge/") || location.pathname.startsWith("/results/") ||
    location.pathname.startsWith("/game/") || location.pathname.startsWith("/battle-royale/") ||
    location.pathname.startsWith("/duels/") || location.pathname.startsWith("/team-duels/") ||
    location.pathname.startsWith("/bullseye/") ||
    location.pathname.startsWith("/live-challenge/");
function overrideOnLoad(googleScript, observer, overrider) {
    const oldOnload = googleScript.onload;
    googleScript.onload = (event) => {
        const google = window.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 /* Please shut up, it works in JS so it must work here as well */) {
            const asScript = newNode;
            if (asScript && asScript.src && asScript.src.startsWith("https://maps.googleapis.com/")) {
                return asScript;
            }
        }
    }
    return null;
}
function injecter(overrider) {
    if (document.documentElement) {
        injecterCallback(overrider);
    }
    else {
        alert("Script didn't load, refresh to try loading the script");
    }
}
function injecterCallback(overrider) {
    new MutationObserver((mutations, observer) => {
        const googleScript = grabGoogleScript(mutations);
        if (googleScript) {
            overrideOnLoad(googleScript, observer, overrider);
        }
    }).observe(document.documentElement, { childList: true, subtree: true });
}
// End Script injection --------------------------------------------------------------s
function injectMenu() {
    const inject = () => {
        if (document.querySelector(".apple-look-around-toggle") !== null)
            return;
        const settingsSection = document.querySelector('.section_sectionMedium__yXgE6');
        if (settingsSection === null)
            return;
        settingsSection.insertAdjacentHTML("beforeend", MENU_HTML);
        const checkbox = document.querySelector(".apple-look-around-toggle");
        if (checkbox) {
            let isChecked = localStorage.getItem("applelookaroundchecked");
            if (isChecked === null) {
                checkbox.checked = false;
                localStorage.setItem("applelookaroundchecked", "false");
            }
            else if (isChecked === "true") {
                checkbox.checked = true;
            }
            else {
                checkbox.checked = false;
            }
            checkbox.addEventListener("change", (event) => {
                if (event.currentTarget === null)
                    return;
                if (event.currentTarget.checked) {
                    localStorage.setItem("applelookaroundchecked", "true");
                }
                else {
                    localStorage.setItem("applelookaroundchecked", "false");
                }
            });
        }
    };
    // We want the page to be loaded before trying to inject anything
    let documentLoadedInterval = setInterval(function () {
        if (document.readyState === "complete") {
            clearInterval(documentLoadedInterval);
            inject();
        }
    }, 100);
}
// ----------------------------------------------------------------------------
// Sate vars
// TODO: Is there a better way to do this?
var loadingInProgress = false;
var currentPano = new lookaround_1.PanoInfo("", "", "", 0, 0, 0);
var currentlyLoadedPanoTiles = [];
var curNeighbors = [];
// When moving, this is used to keep the current viewport while loading the next pano
var oldHeading = 0;
// ----------------------------------------------------------------------------
// Google Maps API callbacks
// Return a pano image given the panoID.
const getCustomPanoramaTileUrl = (pano, zoom, tileX, tileY) => {
    // Currently loading first image in a round, return a blank image
    //if (pano.startsWith("r")){
    if (currentlyLoadedPanoTiles.length === 0) {
        return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==";
    }
    return currentlyLoadedPanoTiles[tileX];
};
const getPano = (pano) => {
    let rp = Options.RESOLUTION_PROFILES[Options.RESOLUTION_SETTING];
    let fullWidth = 2 * rp.big.width + 2 * rp.small.width - 4 * rp.overlap;
    return {
        location: {
            pano: pano,
            description: "Apple Look Around",
            latLng: new google.maps.LatLng(currentPano.lat, currentPano.lon),
        },
        links: [],
        // The text for the copyright control.
        copyright: "(C) Apple",
        // The definition of the tiles for this panorama.
        tiles: {
            tileSize: new google.maps.Size(Math.round(fullWidth / 4), Math.round(Options.EXTENSION_FACTOR * rp.big.height)),
            worldSize: new google.maps.Size(fullWidth, Math.round(rp.big.height * Options.EXTENSION_FACTOR)),
            // The heading in degrees at the origin of the panorama
            // tile set.
            centerHeading: function () {
                // While loading: use the old heading so that when moving, you keep the same viewport while loading the next pano
                if (loadingInProgress) {
                    return oldHeading;
                }
                else {
                    var newHeading = (currentPano.heading + Options.HEADING_CALIBRATION) % 360;
                    oldHeading = newHeading;
                    return newHeading;
                }
            }(),
            getTileUrl: getCustomPanoramaTileUrl,
        },
    };
};
// ----------------------------------------------------------------------------
// Init
function initLookAround() {
    google.maps.StreetViewPanorama = class extends google.maps.StreetViewPanorama {
        constructor(...args) {
            super(...args);
            let isChecked = localStorage.getItem("applelookaroundchecked");
            if (isChecked === "true") {
                this.registerPanoProvider(getPano);
                // Position is being changed by GeoGuessr at the beginning of each round. this.getPosition() contains lat/lng of round.
                this.addListener("position_changed", () => {
                    console.log("Position changed " + this.getPosition());
                    try {
                        // Detect if this is a new round. Normally, currentPano is already updated if this is a move in the same round.
                        if ((this.getPosition().lat() === currentPano.lat && this.getPosition().lng() === currentPano.lon)) {
                            console.log("Position is currentPano => same round");
                            return;
                        }
                        console.warn("Position actually changed => new round; full reload");
                        currentlyLoadedPanoTiles = []; // Causes black screen again
                        this.getFirstPanoId();
                    }
                    catch (e) {
                        console.error(e);
                    }
                });
                // Called after setPano(). If the pano is "r<panoId>/<regioId>", then we load the tiles for that pano.
                // If it doesn't start with "r", then loading is done.
                this.addListener("pano_changed", () => {
                    console.log("Pano changed " + this.getPano());
                    if (this.getPano() != null && this.getPano() != currentPano.panoFullId() && this.getPano() != "" && this.getPano().startsWith("r")) {
                        console.log("New pano requested " + this.getPano());
                        try {
                            this.beginLoadingPanos(this, this.getPano().replace("r", ""));
                        }
                        catch (_a) { }
                    }
                });
                this.addListener("links_changed", () => {
                    console.log("Links changed " + this.getLinks());
                    if (!this.getPano().startsWith("r") && curNeighbors != null) {
                        //this.getLinks().push(curNeighbors[0])
                        let neighborLinks = curNeighbors.map(neighbor => {
                            return {
                                "descripton": "",
                                "pano": "r" + neighbor.panoFullId(),
                                "heading": Math.round(geoutils_1.default.heading([neighbor.lat, neighbor.lon], [currentPano.lat, currentPano.lon]) + 180) % 360,
                            };
                        });
                        console.log("Pushing Links " + neighborLinks.length);
                        for (const neighbor of neighborLinks) {
                            if (neighbor.pano != "") {
                                this.getLinks().push(neighbor);
                            }
                        }
                    }
                });
            }
        }
        getFirstPanoId() {
            return __awaiter(this, void 0, void 0, function* () {
                let isChecked = localStorage.getItem("applelookaroundchecked");
                if (isChecked !== "true")
                    return;
                try {
                    let lat = this.position.lat();
                    let lon = this.position.lng();
                    let lookAroundPanoId, regionId;
                    let closestObject = yield Lookaround.getClosestPanoAtCoords(lat, lon);
                    lookAroundPanoId = closestObject.panoId;
                    regionId = closestObject.regionId;
                    // Request pano to load
                    currentPano = closestObject;
                    this.setPano("r" + lookAroundPanoId + "/" + regionId);
                }
                catch (_a) { }
            });
        }
        // param panoFullId is "panoId/regionId"
        beginLoadingPanos(_t, panoFullId) {
            return __awaiter(this, void 0, void 0, function* () {
                if (loadingInProgress)
                    return;
                //console.warn("http://localhost:5000/#c=17/"+currentPano.lat+"/"+currentPano.lon+"&p="+currentPano.lat+"/"+currentPano.lon);
                // Moved. Find the selected neigbor from ID.
                if (curNeighbors.length > 0) {
                    let selectedNeighbor = curNeighbors.filter(n => n.panoFullId() == panoFullId)[0];
                    if (selectedNeighbor != null) {
                        currentPano = selectedNeighbor;
                    }
                }
                console.log("Start loading Panos");
                loadingInProgress = true;
                let pano0 = Lookaround.loadTileForPano(panoFullId, 0);
                let pano1 = Lookaround.loadTileForPano(panoFullId, 1);
                let pano2 = Lookaround.loadTileForPano(panoFullId, 2);
                let pano3 = Lookaround.loadTileForPano(panoFullId, 3);
                curNeighbors = yield (yield Lookaround.getNeighbors(currentPano));
                loadingInProgress = false;
                currentlyLoadedPanoTiles = [yield pano0, yield pano1, yield pano2, yield pano3];
                // Set another panoId to refresh the view
                this.setPano(panoFullId);
            });
        }
    };
}
function launchObserver() {
    initLookAround();
    //let observer3 = new MutationObserver((mutations) => {
    //	const PATH_NAME = window.location.pathname;
    //	if (PATH_NAME.startsWith("/maps/") && PATH_NAME.endsWith("/play")) { // Inject the options menu if the path name is /maps/XXXXXXX/play
    //		//injectMenu();
    //	}
    //});
    //observer3.observe(document.body, {childList: true, subtree: true, attributes: false, characterData: false});
}
function onLoad() {
    let isChecked = localStorage.getItem("applelookaroundchecked");
    if (isChecked === null) {
        localStorage.setItem("applelookaroundchecked", "true");
    }
    //const PATH_NAME = window.location.pathname;
    //if (PATH_NAME.startsWith("/maps/") && PATH_NAME.endsWith("/play")) { // Inject the options menu if the path name is /maps/XXXXXXX/play
    //	//injectMenu();
    //}
    injecter(() => {
        launchObserver();
    });
}
(function () {
    onLoad();
})();
window.onload = onLoad;
//# sourceMappingURL=index.js.map

/***/ }),

/***/ 215:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {


var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getNeighbors = exports.getClosestPanoAtCoords = exports.loadTileForPano = exports.PanoInfo = void 0;
const Options = __webpack_require__(944);
const auth_1 = __webpack_require__(297);
const geoutils_1 = __webpack_require__(97);
const proto_1 = __webpack_require__(224);
const auth = new auth_1.Authenticator();
var tileCache = {};
class PanoInfo {
    constructor(date, panoId, regionId, heading, lat, lon) {
        this.date = date;
        this.panoId = panoId;
        this.regionId = regionId;
        this.heading = heading;
        this.lat = lat;
        this.lon = lon;
    }
    panoFullId() {
        return this.panoId + "/" + this.regionId;
    }
}
exports.PanoInfo = PanoInfo;
function getCoverageTileRaw(tile_x, tile_y) {
    return __awaiter(this, void 0, void 0, function* () {
        let headers = new Headers({
            "maps-tile-style": "style=57&size=2&scale=0&v=0&preflight=2",
            "maps-tile-x": tile_x.toString(),
            "maps-tile-y": tile_y.toString(),
            "maps-tile-z": "17",
            "maps-auth-token": "w31CPGRO/n7BsFPh8X7kZnFG0LDj9pAuR8nTtH3xhH8=",
        });
        let response = yield (yield fetch(Options.CORS_PROXY + "https://gspe76-ssl.ls.apple.com/api/tile?", { headers: headers })).arrayBuffer();
        let tile = yield proto_1.default.parseMapTile(response);
        return tile;
    });
}
function getCoverageInMapTile(x, y) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            if (tileCache["" + x + "/" + y]) {
                return tileCache[x + "/" + y];
            }
            let response = yield getCoverageTileRaw(x, y);
            var coverage = [];
            for (let pano of response.pano) {
                let coords = geoutils_1.default.protobuf_tile_offset_to_wsg84(pano.unknown4.longitudeOffset, pano.unknown4.latitudeOffset, x, y);
                let p = new PanoInfo(pano.timestamp.toString(), pano.panoid.toString(), response.unknown13[pano.regionIdIdx].regionId.toString(), geoutils_1.default.headingFromUnknowns(pano.unknown4.unknown10, pano.unknown4.unknown11), coords[0], coords[1]);
                coverage.push(p);
            }
            tileCache["" + x + "/" + y] = coverage;
            return coverage;
        }
        catch (error) {
            console.log(error);
        }
    });
}
function getClosestPanoAtCoords(lat, lon) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            let tile = geoutils_1.default.wgs84_to_tile_coord(lat, lon, 17);
            let coverage = yield getCoverageInMapTile(tile[0], tile[1]);
            if (coverage.length == 0) {
                return null;
            }
            let smallestDistance = 9999999;
            let closest = null;
            for (let pano of coverage) {
                let distance = geoutils_1.default.haversineDistance([lat, lon], [pano.lat, pano.lon]);
                if (distance < smallestDistance) {
                    smallestDistance = distance;
                    closest = pano;
                }
            }
            return closest;
        }
        catch (error) {
            console.log(error);
            return null;
        }
    });
}
exports.getClosestPanoAtCoords = getClosestPanoAtCoords;
function getNeighbors(panoInfo) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            let tile = geoutils_1.default.wgs84_to_tile_coord(panoInfo.lat, panoInfo.lon, 17);
            var coverage = yield getCoverageInMapTile(tile[0], tile[1]);
            // TODO Only extend when needed (we're close to the edge of the tile)
            coverage = coverage.concat(yield getCoverageInMapTile(tile[0] + 1, tile[1]));
            coverage = coverage.concat(yield getCoverageInMapTile(tile[0] - 1, tile[1]));
            coverage = coverage.concat(yield getCoverageInMapTile(tile[0], tile[1] + 1));
            coverage = coverage.concat(yield getCoverageInMapTile(tile[0], tile[1] - 1));
            coverage = coverage.concat(yield getCoverageInMapTile(tile[0] - 1, tile[1] - 1));
            coverage = coverage.concat(yield getCoverageInMapTile(tile[0] + 1, tile[1] - 1));
            coverage = coverage.concat(yield getCoverageInMapTile(tile[0] - 1, tile[1] + 1));
            coverage = coverage.concat(yield getCoverageInMapTile(tile[0] + 1, tile[1] + 1));
            coverage = coverage.sort((a, b) => Math.abs(geoutils_1.default.haversineDistance([panoInfo.lat, panoInfo.lon], [a.lat, a.lon])) - Math.abs(geoutils_1.default.haversineDistance([panoInfo.lat, panoInfo.lon], [b.lat, b.lon])));
            coverage = coverage.filter(pano => pano.panoFullId() != panoInfo.panoFullId());
            let minDist = 0.030; // 30 meters
            let maxDist = 0.300; // 300 meters
            coverage = coverage.filter(n => (minDist < Math.abs(geoutils_1.default.haversineDistance([panoInfo.lat, panoInfo.lon], [n.lat, n.lon])) &&
                Math.abs(geoutils_1.default.haversineDistance([panoInfo.lat, panoInfo.lon], [n.lat, n.lon])) < maxDist));
            return coverage.slice(0, 8);
        }
        catch (error) {
            console.log(error);
        }
    });
}
exports.getNeighbors = getNeighbors;
function getUrlForTile(panoFullId, x, resolution) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            //if (!auth.hasSession()) {
            yield auth.init();
            //}
            let segments = panoFullId.split("/");
            let panoId = segments[0];
            let regionId = segments[1];
            let panoid_padded = panoId.padStart(20, "0");
            let region_id_padded = regionId.padStart(10, "0");
            let panoid_split = panoid_padded.slice(0, 4) + "/" + panoid_padded.slice(4, 8) + "/" + panoid_padded.slice(8, 12) + "/" + panoid_padded.slice(12, 16) + "/" + panoid_padded.slice(16, 20);
            return auth.authenticateUrl(Options.APPLE_MAPS_TILE_ENDPOINT + panoid_split + "/" + region_id_padded + "/t/" + x + "/" + resolution);
        }
        catch (error) {
            console.log(error);
        }
    });
}
// param panoFullId is "panoId/regionId"
function loadTileForPano(panoFullId, x) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            var jpegblob;
            if (Options.CONVERT_LOCALLY) {
                // Step 1: Get the URL of the tile to load
                // New endpoint /panourl in the python server returns just the Apple URL for the pano
                var appleMapsPanoURL = yield getUrlForTile(panoFullId, x, Options.RESOLUTION_SETTING);
                appleMapsPanoURL = Options.CORS_PROXY + appleMapsPanoURL;
                // Step 2: Load the tile
                //console.log("Requesting tile " + [appleMapsPanoURL])
                var blobres = yield fetch(appleMapsPanoURL);
                var blob = yield blobres.blob();
                // Step 3: Convert from HEIC to JPEG with heic2any
                //console.log("Fetched tile, converting and resizing... " + [appleMapsPanoURL])
                //let startTime = Math.floor(Date.now() / 1000);
                jpegblob = heic2any({ "blob": blob, "type": "image/jpeg" });
            }
            else {
                jpegblob = yield (yield fetch(Options.BASE_URL + "pano/" + panoFullId + "/" + Options.RESOLUTION_SETTING + "/" + x + "/")).blob();
            }
            // Step 4: Process image
            // Cut off the overlap from the right of the tile using canvas
            // and add black bars on top and bottom because we don't have sky/ground tiles
            let rp = Options.RESOLUTION_PROFILES[Options.RESOLUTION_SETTING];
            // Putting the jpeg blob into a canvas to remove 256 px from the right (removes overlap)
            var w = rp.big.width;
            if (x == 1 || x == 3) {
                w = rp.small.width;
            }
            w = w - rp.overlap;
            var canvas = document.createElement('canvas');
            canvas.height = Math.round(Options.EXTENSION_FACTOR * rp.big.height);
            canvas.width = w;
            var ctx = canvas.getContext('2d');
            var img = new Image();
            var result = "";
            img.onload = function () {
                ctx.drawImage(img, 0, (canvas.height - rp.big.height) / 2);
                // This is a big data:image/jpeg;base64, URL
                result = canvas.toDataURL("image/jpeg");
            };
            img.src = URL.createObjectURL(yield jpegblob);
            //let endTime = Math.floor(Date.now() / 1000);
            //console.log("Time to convert: " + (endTime - startTime) + " seconds");
            // Wait for context to finish loading
            // TODO: Is there a better way?
            const delay = ms => new Promise(res => setTimeout(res, ms));
            yield delay(100);
            //let endTime2 = Math.floor(Date.now() / 1000);
            //console.log("Full time: " + (endTime - startTime) + " seconds");
            URL.revokeObjectURL(img.src);
            canvas.remove();
            img.remove();
            return result;
        }
        catch (error) {
            console.log(error);
        }
    });
}
exports.loadTileForPano = loadTileForPano;
//# sourceMappingURL=lookaround.js.map

/***/ }),

/***/ 944:
/***/ ((__unused_webpack_module, exports) => {


Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.CONVERT_LOCALLY = exports.BASE_URL = exports.RESOLUTION_PROFILES = exports.APPLE_MAPS_TILE_ENDPOINT = exports.CORS_PROXY = exports.EXTENSION_FACTOR = exports.HEADING_CALIBRATION = exports.RESOLUTION_SETTING = void 0;
// Determines the resolution of images requested from Apple
// Setting a higher resolution will make rounds load WAY slower, until browsers start to support HEIC
// 0 = highest resolution available, 4 = lowest resolution available.
// Default: 2
const RESOLUTION_SETTING = 2;
exports.RESOLUTION_SETTING = RESOLUTION_SETTING;
// Constant value added to calculated heading to calibrate the GeoGuessr compass
const HEADING_CALIBRATION = 45;
exports.HEADING_CALIBRATION = HEADING_CALIBRATION;
const EXTENSION_FACTOR = 2.12; // TODO Play around with this value for best results with image stretching
exports.EXTENSION_FACTOR = EXTENSION_FACTOR;
const BASE_URL = "https://lookaround.stocc.dev/";
exports.BASE_URL = BASE_URL;
const CONVERT_LOCALLY = true;
exports.CONVERT_LOCALLY = CONVERT_LOCALLY;
const CORS_PROXY = "https://nameless-bastion-28139.herokuapp.com/";
exports.CORS_PROXY = CORS_PROXY;
const APPLE_MAPS_TILE_ENDPOINT = "https://gspe72-ssl.ls.apple.com/mnn_us/";
exports.APPLE_MAPS_TILE_ENDPOINT = APPLE_MAPS_TILE_ENDPOINT;
const RESOLUTION_PROFILES = {
    0: {
        "overlap": 256,
        "big": {
            "width": 5632,
            "height": 4352,
        },
        "small": {
            "width": 3072,
            "height": 4352,
        }
    },
    1: {
        "overlap": 188,
        "big": {
            "width": 4128,
            "height": 3088,
        },
        "small": {
            "width": 2256,
            "height": 3088,
        },
    },
    2: {
        "overlap": 100,
        "big": {
            "width": 2208,
            "height": 1648,
        },
        "small": {
            "width": 1200,
            "height": 1648,
        }
    },
    3: {
        "overlap": 71,
        "big": {
            "width": 1568,
            "height": 1168,
        },
        "small": {
            "width": 848,
            "height": 1168,
        }
    },
    4: {
        "overlap": 50,
        "big": {
            "width": 1104,
            "height": 832,
        },
        "small": {
            "width": 608,
            "height": 832,
        }
    }
};
exports.RESOLUTION_PROFILES = RESOLUTION_PROFILES;
//# sourceMappingURL=options.js.map

/***/ }),

/***/ 224:
/***/ (function(__unused_webpack_module, exports) {


var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
let mapTile = `
syntax = "proto3";

message MapTile {
  repeated Pano pano = 1;
  repeated Unknown13 unknown13 = 4;
  repeated Unknown22 unknown22 = 5;
  TileCoordinate tileCoordinate = 6;

  message Pano {
    uint64 panoid = 1;
    int32 unknown1 = 4;
    int64 timestamp = 5; // time the pano was taken
    int32 region_id_idx = 7;
    repeated int32 unknown3 = 9; // goes from 0 to 5. available sizes maybe?
    Unknown4 unknown4 = 10;
    Unknown5 unknown5 = 12;

    message Unknown4 {
      int32 longitude_offset = 1;
      int32 latitude_offset = 2;
      int32 unknown8 = 3;
      int32 unknown9 = 4;
      int32 unknown10 = 5;
      int32 unknown11 = 6;
    }

    message Unknown5 {
      repeated int32 unknown12 = 1;
    }
  }

  message Unknown13 {
    int32 unknown14 = 1;
    // this is the param that appears in pano URLs after the pano ID.
    // no idea what this does exactly.
    int32 region_id = 3;
    int32 unknown15 = 4;
    int32 unknown16 = 5;
    int32 unknown17 = 6;
    int32 unknown18 = 9;
    int32 unknown19 = 10;
    int32 unknown20 = 11;
    int32 unknown21 = 12;
  }

  message Unknown22 {
    int32 unknown23 = 1;
    Unknown24 unknown24 = 4;
    Unknown25 unknown25 = 5;
    int32 unknown26 = 6;

    message Unknown24 {
      int32 unknown27 = 1;
      double unknown28 = 2;
      double unknown29 = 3;
      double unknown30 = 4;
      double unknown31 = 5;
      double unknown32 = 6;
      double unknown33 = 7;
      double unknown34 = 8;
      double unknown35 = 9;
      double unknown36 = 10;
    }

    message Unknown25 {
      double unknown37 = 1;
      double unknown38 = 2;
      double unknown39 = 3;
      double unknown40 = 4;
      double unknown41 = 5;
      double unknown42 = 6;
    }
  }

  message TileCoordinate {
    int32 x = 1;
    int32 y = 2;
    int32 z = 3;
  }

}`;
let resourceManifest = `
syntax = "proto3";

message ResourceManifest {
	repeated StyleConfig style_config = 2;
	string token_p2 = 30;
	string cache_base_url = 31;
	repeated CacheFile cache_file = 72;
	repeated string cache_file_2 = 9;

	message CacheFile {
		string file_name = 2;
	}

	message StyleConfig {
		string url_prefix_1 = 1;
		string url_prefix_2 = 9;
		StyleID style_id = 3;

		enum StyleID {
			_ = 0;
			C3MM_1 = 14;
			C3M = 15;
			DTM_1 = 16;
			DTM_2 = 17;
			C3MM_2 = 52;
		}
	}
}
`;
class Proto {
    static parseResourceManifest(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            const array = new Uint8Array(payload);
            let manifest = protobuf.parse(resourceManifest).root.lookup("ResourceManifest");
            let message = manifest.decode(array);
            return message;
        });
    }
    static parseMapTile(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            const array = new Uint8Array(payload);
            let manifest = protobuf.parse(mapTile).root.lookup("MapTile");
            let message = manifest.decode(array);
            return message;
        });
    }
}
exports["default"] = Proto;
//# sourceMappingURL=proto.js.map

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/************************************************************************/
/******/
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module is referenced by other modules so it can't be inlined
/******/ 	var __webpack_exports__ = __webpack_require__(590);
/******/
/******/ })()
;