// ==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/protobufjs@7.0.0/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 // @downloadURL https://update.greasyfork.icu/scripts/449488/AppleGuessr.user.js // @updateURL https://update.greasyfork.icu/scripts/449488/AppleGuessr.meta.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/protobufjs@7.0.0/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 && (`
Apple Look Around
`)); 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/", 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); /******/ /******/ })() ;