AppleGuessr

Adds Apple Look Around to GeoGuessr

  1. // ==UserScript==
  2. // @name AppleGuessr
  3. // @namespace https://gf.qytechs.cn/en/users/946023-mistystar
  4. // @version 2.1
  5. // @description Adds Apple Look Around to GeoGuessr
  6. // @author Mistystar (Mistystar#2205, https://github.com/kittenz) & stocc (stocc#2919, https://github.com/stocc)
  7. // @match https://www.geoguessr.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
  9. // @grant none
  10. // @license MIT
  11. // @run-at document-start
  12. // @require https://cdn.jsdelivr.net/npm/protobufjs@7.0.0/dist/protobuf.js
  13. // @require https://cdn.jsdelivr.net/npm/long@5/umd/index.js
  14. // @require https://cdn.jsdelivr.net/gh/chebum/heic2any@2c517409ac73e86e92560312b58fcfd565ad7393/dist/heic2any.min.js
  15.  
  16. // ==/UserScript==
  17.  
  18.  
  19.  
  20. /******/ (() => { // webpackBootstrap
  21. /******/ "use strict";
  22. /******/ var __webpack_modules__ = ({
  23.  
  24. /***/ 297:
  25. /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
  26.  
  27.  
  28. // Blatantly stolen from https://github.com/sk-zk/lookaround-map/blob/main/static/auth.js
  29. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  30. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  31. return new (P || (P = Promise))(function (resolve, reject) {
  32. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  33. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  34. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  35. step((generator = generator.apply(thisArg, _arguments || [])).next());
  36. });
  37. };
  38. var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
  39. if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
  40. 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");
  41. return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
  42. };
  43. var _Authenticator_instances, _Authenticator_generateSessionId, _Authenticator_generateTokenP3;
  44. Object.defineProperty(exports, "__esModule", ({ value: true }));
  45. exports.Authenticator = void 0;
  46. const options_1 = __webpack_require__(944);
  47. const proto_1 = __webpack_require__(224);
  48. const TOKEN_P1 = "4cjLaD4jGRwlQ9U";
  49. const MANIFEST_URL = "https://gspe35-ssl.ls.apple.com/geo_manifest/dynamic/config?application=geod" +
  50. "&application_version=1&country_code=US&hardware=MacBookPro11,2&os=osx" +
  51. "&os_build=20B29&os_version=11.0.1";
  52. var GLOBAL_TOKENP2 = undefined;
  53. class Authenticator {
  54. constructor() {
  55. _Authenticator_instances.add(this);
  56. this.sessionId = null;
  57. }
  58. init() {
  59. return __awaiter(this, void 0, void 0, function* () {
  60. yield this.refreshCredentials();
  61. });
  62. }
  63. refreshCredentials() {
  64. return __awaiter(this, void 0, void 0, function* () {
  65. this.sessionId = __classPrivateFieldGet(this, _Authenticator_instances, "m", _Authenticator_generateSessionId).call(this);
  66. });
  67. }
  68. hasSession() {
  69. return this.sessionId != null;
  70. }
  71. getTokenP2() {
  72. return __awaiter(this, void 0, void 0, function* () {
  73. if (GLOBAL_TOKENP2 == undefined) {
  74. GLOBAL_TOKENP2 = (yield this.getResourceManifest()).tokenP2;
  75. }
  76. return GLOBAL_TOKENP2;
  77. });
  78. }
  79. authenticateUrl(url) {
  80. return __awaiter(this, void 0, void 0, function* () {
  81. const urlObj = new URL(url);
  82. let p2 = yield this.getTokenP2();
  83. const tokenP3 = __classPrivateFieldGet(this, _Authenticator_instances, "m", _Authenticator_generateTokenP3).call(this);
  84. const token = TOKEN_P1 + p2 + tokenP3;
  85. const timestamp = Math.floor(Date.now() / 1000) + 4200;
  86. const separator = urlObj.search ? "&" : "?";
  87. let urlPath = urlObj.pathname;
  88. if (urlObj.search) {
  89. urlPath += urlObj.search;
  90. }
  91. const plaintext = `${urlPath}${separator}sid=${this.sessionId}${timestamp}${tokenP3}`;
  92. const plaintextBytes = new TextEncoder().encode(plaintext);
  93. const key = yield sha256(token);
  94. const ciphertext = yield aes(key, plaintextBytes);
  95. const ciphertextB64 = btoa(String.fromCharCode(...new Uint8Array(ciphertext)));
  96. const ciphertextUrl = encodeURIComponent(ciphertextB64);
  97. const accessKey = `${timestamp}_${tokenP3}_${ciphertextUrl}`;
  98. const final = `${url}${separator}sid=${this.sessionId}&accessKey=${accessKey}`;
  99. return final;
  100. });
  101. }
  102. getResourceManifest() {
  103. return __awaiter(this, void 0, void 0, function* () {
  104. const response = yield fetch(options_1.CORS_PROXY + MANIFEST_URL);
  105. let pb = yield response.arrayBuffer();
  106. return yield proto_1.default.parseResourceManifest(pb);
  107. });
  108. }
  109. }
  110. exports.Authenticator = Authenticator;
  111. _Authenticator_instances = new WeakSet(), _Authenticator_generateSessionId = function _Authenticator_generateSessionId() {
  112. let id = "";
  113. for (let i = 0; i < 40; i++) {
  114. const digit = (Math.random() * 10) | 0;
  115. id += digit.toString();
  116. }
  117. return id;
  118. }, _Authenticator_generateTokenP3 = function _Authenticator_generateTokenP3() {
  119. const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
  120. let token = "";
  121. for (let i = 0; i < 16; i++) {
  122. const idx = (Math.random() * chars.length) | 0;
  123. token += chars[idx];
  124. }
  125. return token;
  126. };
  127. function sha256(message) {
  128. return __awaiter(this, void 0, void 0, function* () {
  129. const msgBuffer = new TextEncoder().encode(message);
  130. const hashBuffer = yield window.crypto.subtle.digest("SHA-256", msgBuffer);
  131. return hashBuffer;
  132. });
  133. }
  134. function aes(key, encodedMessage) {
  135. return __awaiter(this, void 0, void 0, function* () {
  136. const iv = new Uint8Array(16); // 16 zeroes
  137. const cryptoKey = yield window.crypto.subtle.importKey("raw", key, { name: "AES-CBC" }, true, ["encrypt"]);
  138. return yield window.crypto.subtle.encrypt({
  139. name: "AES-CBC",
  140. iv,
  141. }, cryptoKey, encodedMessage);
  142. });
  143. }
  144. //# sourceMappingURL=auth.js.map
  145.  
  146. /***/ }),
  147.  
  148. /***/ 97:
  149. /***/ ((__unused_webpack_module, exports) => {
  150.  
  151.  
  152. Object.defineProperty(exports, "__esModule", ({ value: true }));
  153. const TILE_SIZE = 256;
  154. class GeoUtils {
  155. static haversineDistance(coords1, coords2) {
  156. function toRad(x) {
  157. return x * Math.PI / 180;
  158. }
  159. var lon1 = coords1[0];
  160. var lat1 = coords1[1];
  161. var lon2 = coords2[0];
  162. var lat2 = coords2[1];
  163. var R = 6371; // km
  164. var x1 = lat2 - lat1;
  165. var dLat = toRad(x1);
  166. var x2 = lon2 - lon1;
  167. var dLon = toRad(x2);
  168. var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
  169. Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
  170. Math.sin(dLon / 2) * Math.sin(dLon / 2);
  171. var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  172. var d = R * c;
  173. return d;
  174. }
  175. static radians_to_degrees(radians) {
  176. var pi = Math.PI;
  177. return radians * (180 / pi);
  178. }
  179. static headingFromUnknowns(unknown10, unknown11) {
  180. let westmin = 1;
  181. let westmax = 2159;
  182. let eastmin = 16383; // looking (north/south) and very slightly east
  183. let eastmax = 14318; // looking slightly (north/south) directly east
  184. let northmin = 8204; // this is likely lower
  185. let northmax = 6054;
  186. let southmin = 8204; // this is likely lower
  187. let southmax = 10173;
  188. var ew = 0;
  189. if (unknown10 < westmax) {
  190. ew = -((unknown10 - westmin) / (westmax - westmin));
  191. }
  192. else if (unknown10 > eastmax) {
  193. ew = ((unknown10 - eastmin) / (eastmax - eastmin));
  194. }
  195. var ns = 0;
  196. if (unknown11 <= northmin) {
  197. ns = ((unknown11 - northmin) / (northmax - northmin));
  198. }
  199. else {
  200. ns = -((unknown11 - southmin) / (southmax - southmin));
  201. }
  202. var r = GeoUtils.radians_to_degrees(Math.atan2(ew, ns));
  203. if (r < 0) {
  204. r += 360;
  205. }
  206. return r;
  207. }
  208. static mercator_to_wgs84(x, y) {
  209. let lat = (2 * Math.atan(Math.exp((y - 128) / -(256 / (2 * Math.PI)))) - Math.PI / 2) / (Math.PI / 180);
  210. let lon = (x - 128) / (256 / 360);
  211. return [lat, lon];
  212. }
  213. static tile_coord_to_wgs84(x, y, z) {
  214. let scale = 1 << z;
  215. let pixel_coord = [x * TILE_SIZE, y * TILE_SIZE];
  216. let world_coord = [pixel_coord[0] / scale, pixel_coord[1] / scale];
  217. let lat_lon = GeoUtils.mercator_to_wgs84(world_coord[0], world_coord[1]);
  218. return [lat_lon[0], lat_lon[1]];
  219. }
  220. static protobuf_tile_offset_to_wsg84(x_offset, y_offset, tile_x, tile_y) {
  221. let pano_x = tile_x + (x_offset / 64.0) / (TILE_SIZE - 1);
  222. let pano_y = tile_y + (255 - (y_offset / 64.0)) / (TILE_SIZE - 1);
  223. let coords = GeoUtils.tile_coord_to_wgs84(pano_x, pano_y, 17);
  224. return coords;
  225. }
  226. static wgs84_to_mercator(lat, lon) {
  227. var siny = Math.sin(lat * Math.PI / 180);
  228. siny = Math.min(Math.max(siny, -0.9999), 0.9999);
  229. return [
  230. TILE_SIZE * (0.5 + lon / 360),
  231. TILE_SIZE * (0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI))
  232. ];
  233. }
  234. static wgs84_to_tile_coord(lat, lon, zoom) {
  235. var scale = 1 << zoom;
  236. var world_coord = this.wgs84_to_mercator(lat, lon);
  237. var tile_coord = [
  238. Math.floor((world_coord[0] * scale) / TILE_SIZE),
  239. Math.floor((world_coord[1] * scale) / TILE_SIZE)
  240. ];
  241. return tile_coord;
  242. }
  243. static heading(coords1, coords2) {
  244. try {
  245. let c1 = new google.maps.LatLng(coords1[0], coords1[1]);
  246. let c2 = new google.maps.LatLng(coords2[0], coords2[1]);
  247. let result = google.maps.geometry.spherical.computeHeading(c1, c2);
  248. if (result < 0) {
  249. result += 360;
  250. }
  251. return result;
  252. }
  253. catch (e) {
  254. console.log(e);
  255. }
  256. }
  257. }
  258. exports["default"] = GeoUtils;
  259. //# sourceMappingURL=geoutils.js.map
  260.  
  261. /***/ }),
  262.  
  263. /***/ 590:
  264. /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
  265.  
  266.  
  267. // ==UserScript==
  268. // @name AppleGuessr
  269. // @namespace https://gf.qytechs.cn/en/users/946023-mistystar
  270. // @version 2.1
  271. // @description Adds Apple Look Around to GeoGuessr
  272. // @author Mistystar (Mistystar#2205, https://github.com/kittenz) & stocc (stocc#2919, https://github.com/stocc)
  273. // @match https://www.geoguessr.com/*
  274. // @icon https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
  275. // @grant none
  276. // @license MIT
  277. // @run-at document-start
  278. // @require https://cdn.jsdelivr.net/gh/chebum/heic2any@master/dist/heic2any.min.js
  279. // @require https://cdn.jsdelivr.net/npm/protobufjs@7.0.0/dist/protobuf.js
  280. // @require https://cdn.jsdelivr.net/npm/long@5/umd/index.js
  281. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  282. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  283. return new (P || (P = Promise))(function (resolve, reject) {
  284. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  285. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  286. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  287. step((generator = generator.apply(thisArg, _arguments || [])).next());
  288. });
  289. };
  290. Object.defineProperty(exports, "__esModule", ({ value: true }));
  291. // ==/UserScript==
  292. /*
  293. CREDITS
  294.  
  295. Massive thank you to the following people:
  296. - skzk#8049 - Without https://github.com/sk-zk/lookaround-map this script would not have been possible to make
  297. - Jupaoqq#7742 - I learned a lot from looking at Unity Script's source code
  298. - mattisinthesky#1294 or kowalski - For hosting the lookaround-map in Heroku and helping with issues
  299. - efefury#0519 and Apfeloxid#1368 - For making the Take A Look Around Germany map
  300. */
  301. // BEGIN CODE SECTION
  302. const Options = __webpack_require__(944);
  303. const Lookaround = __webpack_require__(215);
  304. const lookaround_1 = __webpack_require__(215);
  305. const geoutils_1 = __webpack_require__(97);
  306. protobuf.util.Long = Long;
  307. protobuf.configure();
  308. const MENU_HTML = (/* unused pure expression or super */ null && (`
  309. <div class="start-standard-game_settings__x94PU">
  310. <div class="game-settings_default__DIBgs">
  311. <div class="game-settings_toggleLabel__nipwm">
  312. <div class="label_sizeXSmall__mFnrR">Apple Look Around</div>
  313. <span></span>
  314. </div>
  315. <div>
  316. <input type="checkbox" class="apple-look-around-toggle" checked>
  317. </div>
  318. </div>
  319. </div>
  320. `));
  321. const isGamePage = () => location.pathname.startsWith("/challenge/") || location.pathname.startsWith("/results/") ||
  322. location.pathname.startsWith("/game/") || location.pathname.startsWith("/battle-royale/") ||
  323. location.pathname.startsWith("/duels/") || location.pathname.startsWith("/team-duels/") ||
  324. location.pathname.startsWith("/bullseye/") ||
  325. location.pathname.startsWith("/live-challenge/");
  326. function overrideOnLoad(googleScript, observer, overrider) {
  327. const oldOnload = googleScript.onload;
  328. googleScript.onload = (event) => {
  329. const google = window.google;
  330. if (google) {
  331. observer.disconnect();
  332. overrider(google);
  333. }
  334. if (oldOnload) {
  335. oldOnload.call(googleScript, event);
  336. }
  337. };
  338. }
  339. function grabGoogleScript(mutations) {
  340. for (const mutation of mutations) {
  341. for (const newNode of mutation.addedNodes /* Please shut up, it works in JS so it must work here as well */) {
  342. const asScript = newNode;
  343. if (asScript && asScript.src && asScript.src.startsWith("https://maps.googleapis.com/")) {
  344. return asScript;
  345. }
  346. }
  347. }
  348. return null;
  349. }
  350. function injecter(overrider) {
  351. if (document.documentElement) {
  352. injecterCallback(overrider);
  353. }
  354. else {
  355. alert("Script didn't load, refresh to try loading the script");
  356. }
  357. }
  358. function injecterCallback(overrider) {
  359. new MutationObserver((mutations, observer) => {
  360. const googleScript = grabGoogleScript(mutations);
  361. if (googleScript) {
  362. overrideOnLoad(googleScript, observer, overrider);
  363. }
  364. }).observe(document.documentElement, { childList: true, subtree: true });
  365. }
  366. // End Script injection --------------------------------------------------------------s
  367. function injectMenu() {
  368. const inject = () => {
  369. if (document.querySelector(".apple-look-around-toggle") !== null)
  370. return;
  371. const settingsSection = document.querySelector('.section_sectionMedium__yXgE6');
  372. if (settingsSection === null)
  373. return;
  374. settingsSection.insertAdjacentHTML("beforeend", MENU_HTML);
  375. const checkbox = document.querySelector(".apple-look-around-toggle");
  376. if (checkbox) {
  377. let isChecked = localStorage.getItem("applelookaroundchecked");
  378. if (isChecked === null) {
  379. checkbox.checked = false;
  380. localStorage.setItem("applelookaroundchecked", "false");
  381. }
  382. else if (isChecked === "true") {
  383. checkbox.checked = true;
  384. }
  385. else {
  386. checkbox.checked = false;
  387. }
  388. checkbox.addEventListener("change", (event) => {
  389. if (event.currentTarget === null)
  390. return;
  391. if (event.currentTarget.checked) {
  392. localStorage.setItem("applelookaroundchecked", "true");
  393. }
  394. else {
  395. localStorage.setItem("applelookaroundchecked", "false");
  396. }
  397. });
  398. }
  399. };
  400. // We want the page to be loaded before trying to inject anything
  401. let documentLoadedInterval = setInterval(function () {
  402. if (document.readyState === "complete") {
  403. clearInterval(documentLoadedInterval);
  404. inject();
  405. }
  406. }, 100);
  407. }
  408. // ----------------------------------------------------------------------------
  409. // Sate vars
  410. // TODO: Is there a better way to do this?
  411. var loadingInProgress = false;
  412. var currentPano = new lookaround_1.PanoInfo("", "", "", 0, 0, 0);
  413. var currentlyLoadedPanoTiles = [];
  414. var curNeighbors = [];
  415. // When moving, this is used to keep the current viewport while loading the next pano
  416. var oldHeading = 0;
  417. // ----------------------------------------------------------------------------
  418. // Google Maps API callbacks
  419. // Return a pano image given the panoID.
  420. const getCustomPanoramaTileUrl = (pano, zoom, tileX, tileY) => {
  421. // Currently loading first image in a round, return a blank image
  422. //if (pano.startsWith("r")){
  423. if (currentlyLoadedPanoTiles.length === 0) {
  424. return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==";
  425. }
  426. return currentlyLoadedPanoTiles[tileX];
  427. };
  428. const getPano = (pano) => {
  429. let rp = Options.RESOLUTION_PROFILES[Options.RESOLUTION_SETTING];
  430. let fullWidth = 2 * rp.big.width + 2 * rp.small.width - 4 * rp.overlap;
  431. return {
  432. location: {
  433. pano: pano,
  434. description: "Apple Look Around",
  435. latLng: new google.maps.LatLng(currentPano.lat, currentPano.lon),
  436. },
  437. links: [],
  438. // The text for the copyright control.
  439. copyright: "(C) Apple",
  440. // The definition of the tiles for this panorama.
  441. tiles: {
  442. tileSize: new google.maps.Size(Math.round(fullWidth / 4), Math.round(Options.EXTENSION_FACTOR * rp.big.height)),
  443. worldSize: new google.maps.Size(fullWidth, Math.round(rp.big.height * Options.EXTENSION_FACTOR)),
  444. // The heading in degrees at the origin of the panorama
  445. // tile set.
  446. centerHeading: function () {
  447. // While loading: use the old heading so that when moving, you keep the same viewport while loading the next pano
  448. if (loadingInProgress) {
  449. return oldHeading;
  450. }
  451. else {
  452. var newHeading = (currentPano.heading + Options.HEADING_CALIBRATION) % 360;
  453. oldHeading = newHeading;
  454. return newHeading;
  455. }
  456. }(),
  457. getTileUrl: getCustomPanoramaTileUrl,
  458. },
  459. };
  460. };
  461. // ----------------------------------------------------------------------------
  462. // Init
  463. function initLookAround() {
  464. google.maps.StreetViewPanorama = class extends google.maps.StreetViewPanorama {
  465. constructor(...args) {
  466. super(...args);
  467. let isChecked = localStorage.getItem("applelookaroundchecked");
  468. if (isChecked === "true") {
  469. this.registerPanoProvider(getPano);
  470. // Position is being changed by GeoGuessr at the beginning of each round. this.getPosition() contains lat/lng of round.
  471. this.addListener("position_changed", () => {
  472. console.log("Position changed " + this.getPosition());
  473. try {
  474. // Detect if this is a new round. Normally, currentPano is already updated if this is a move in the same round.
  475. if ((this.getPosition().lat() === currentPano.lat && this.getPosition().lng() === currentPano.lon)) {
  476. console.log("Position is currentPano => same round");
  477. return;
  478. }
  479. console.warn("Position actually changed => new round; full reload");
  480. currentlyLoadedPanoTiles = []; // Causes black screen again
  481. this.getFirstPanoId();
  482. }
  483. catch (e) {
  484. console.error(e);
  485. }
  486. });
  487. // Called after setPano(). If the pano is "r<panoId>/<regioId>", then we load the tiles for that pano.
  488. // If it doesn't start with "r", then loading is done.
  489. this.addListener("pano_changed", () => {
  490. console.log("Pano changed " + this.getPano());
  491. if (this.getPano() != null && this.getPano() != currentPano.panoFullId() && this.getPano() != "" && this.getPano().startsWith("r")) {
  492. console.log("New pano requested " + this.getPano());
  493. try {
  494. this.beginLoadingPanos(this, this.getPano().replace("r", ""));
  495. }
  496. catch (_a) { }
  497. }
  498. });
  499. this.addListener("links_changed", () => {
  500. console.log("Links changed " + this.getLinks());
  501. if (!this.getPano().startsWith("r") && curNeighbors != null) {
  502. //this.getLinks().push(curNeighbors[0])
  503. let neighborLinks = curNeighbors.map(neighbor => {
  504. return {
  505. "descripton": "",
  506. "pano": "r" + neighbor.panoFullId(),
  507. "heading": Math.round(geoutils_1.default.heading([neighbor.lat, neighbor.lon], [currentPano.lat, currentPano.lon]) + 180) % 360,
  508. };
  509. });
  510. console.log("Pushing Links " + neighborLinks.length);
  511. for (const neighbor of neighborLinks) {
  512. if (neighbor.pano != "") {
  513. this.getLinks().push(neighbor);
  514. }
  515. }
  516. }
  517. });
  518. }
  519. }
  520. getFirstPanoId() {
  521. return __awaiter(this, void 0, void 0, function* () {
  522. let isChecked = localStorage.getItem("applelookaroundchecked");
  523. if (isChecked !== "true")
  524. return;
  525. try {
  526. let lat = this.position.lat();
  527. let lon = this.position.lng();
  528. let lookAroundPanoId, regionId;
  529. let closestObject = yield Lookaround.getClosestPanoAtCoords(lat, lon);
  530. lookAroundPanoId = closestObject.panoId;
  531. regionId = closestObject.regionId;
  532. // Request pano to load
  533. currentPano = closestObject;
  534. this.setPano("r" + lookAroundPanoId + "/" + regionId);
  535. }
  536. catch (_a) { }
  537. });
  538. }
  539. // param panoFullId is "panoId/regionId"
  540. beginLoadingPanos(_t, panoFullId) {
  541. return __awaiter(this, void 0, void 0, function* () {
  542. if (loadingInProgress)
  543. return;
  544. //console.warn("http://localhost:5000/#c=17/"+currentPano.lat+"/"+currentPano.lon+"&p="+currentPano.lat+"/"+currentPano.lon);
  545. // Moved. Find the selected neigbor from ID.
  546. if (curNeighbors.length > 0) {
  547. let selectedNeighbor = curNeighbors.filter(n => n.panoFullId() == panoFullId)[0];
  548. if (selectedNeighbor != null) {
  549. currentPano = selectedNeighbor;
  550. }
  551. }
  552. console.log("Start loading Panos");
  553. loadingInProgress = true;
  554. let pano0 = Lookaround.loadTileForPano(panoFullId, 0);
  555. let pano1 = Lookaround.loadTileForPano(panoFullId, 1);
  556. let pano2 = Lookaround.loadTileForPano(panoFullId, 2);
  557. let pano3 = Lookaround.loadTileForPano(panoFullId, 3);
  558. curNeighbors = yield (yield Lookaround.getNeighbors(currentPano));
  559. loadingInProgress = false;
  560. currentlyLoadedPanoTiles = [yield pano0, yield pano1, yield pano2, yield pano3];
  561. // Set another panoId to refresh the view
  562. this.setPano(panoFullId);
  563. });
  564. }
  565. };
  566. }
  567. function launchObserver() {
  568. initLookAround();
  569. //let observer3 = new MutationObserver((mutations) => {
  570. // const PATH_NAME = window.location.pathname;
  571. // if (PATH_NAME.startsWith("/maps/") && PATH_NAME.endsWith("/play")) { // Inject the options menu if the path name is /maps/XXXXXXX/play
  572. // //injectMenu();
  573. // }
  574. //});
  575. //observer3.observe(document.body, {childList: true, subtree: true, attributes: false, characterData: false});
  576. }
  577. function onLoad() {
  578. let isChecked = localStorage.getItem("applelookaroundchecked");
  579. if (isChecked === null) {
  580. localStorage.setItem("applelookaroundchecked", "true");
  581. }
  582. //const PATH_NAME = window.location.pathname;
  583. //if (PATH_NAME.startsWith("/maps/") && PATH_NAME.endsWith("/play")) { // Inject the options menu if the path name is /maps/XXXXXXX/play
  584. // //injectMenu();
  585. //}
  586. injecter(() => {
  587. launchObserver();
  588. });
  589. }
  590. (function () {
  591. onLoad();
  592. })();
  593. window.onload = onLoad;
  594. //# sourceMappingURL=index.js.map
  595.  
  596. /***/ }),
  597.  
  598. /***/ 215:
  599. /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
  600.  
  601.  
  602. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  603. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  604. return new (P || (P = Promise))(function (resolve, reject) {
  605. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  606. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  607. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  608. step((generator = generator.apply(thisArg, _arguments || [])).next());
  609. });
  610. };
  611. Object.defineProperty(exports, "__esModule", ({ value: true }));
  612. exports.getNeighbors = exports.getClosestPanoAtCoords = exports.loadTileForPano = exports.PanoInfo = void 0;
  613. const Options = __webpack_require__(944);
  614. const auth_1 = __webpack_require__(297);
  615. const geoutils_1 = __webpack_require__(97);
  616. const proto_1 = __webpack_require__(224);
  617. const auth = new auth_1.Authenticator();
  618. var tileCache = {};
  619. class PanoInfo {
  620. constructor(date, panoId, regionId, heading, lat, lon) {
  621. this.date = date;
  622. this.panoId = panoId;
  623. this.regionId = regionId;
  624. this.heading = heading;
  625. this.lat = lat;
  626. this.lon = lon;
  627. }
  628. panoFullId() {
  629. return this.panoId + "/" + this.regionId;
  630. }
  631. }
  632. exports.PanoInfo = PanoInfo;
  633. function getCoverageTileRaw(tile_x, tile_y) {
  634. return __awaiter(this, void 0, void 0, function* () {
  635. let headers = new Headers({
  636. "maps-tile-style": "style=57&size=2&scale=0&v=0&preflight=2",
  637. "maps-tile-x": tile_x.toString(),
  638. "maps-tile-y": tile_y.toString(),
  639. "maps-tile-z": "17",
  640. "maps-auth-token": "w31CPGRO/n7BsFPh8X7kZnFG0LDj9pAuR8nTtH3xhH8=",
  641. });
  642. let response = yield (yield fetch(Options.CORS_PROXY + "https://gspe76-ssl.ls.apple.com/api/tile?", { headers: headers })).arrayBuffer();
  643. let tile = yield proto_1.default.parseMapTile(response);
  644. return tile;
  645. });
  646. }
  647. function getCoverageInMapTile(x, y) {
  648. return __awaiter(this, void 0, void 0, function* () {
  649. try {
  650. if (tileCache["" + x + "/" + y]) {
  651. return tileCache[x + "/" + y];
  652. }
  653. let response = yield getCoverageTileRaw(x, y);
  654. var coverage = [];
  655. for (let pano of response.pano) {
  656. let coords = geoutils_1.default.protobuf_tile_offset_to_wsg84(pano.unknown4.longitudeOffset, pano.unknown4.latitudeOffset, x, y);
  657. 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]);
  658. coverage.push(p);
  659. }
  660. tileCache["" + x + "/" + y] = coverage;
  661. return coverage;
  662. }
  663. catch (error) {
  664. console.log(error);
  665. }
  666. });
  667. }
  668. function getClosestPanoAtCoords(lat, lon) {
  669. return __awaiter(this, void 0, void 0, function* () {
  670. try {
  671. let tile = geoutils_1.default.wgs84_to_tile_coord(lat, lon, 17);
  672. let coverage = yield getCoverageInMapTile(tile[0], tile[1]);
  673. if (coverage.length == 0) {
  674. return null;
  675. }
  676. let smallestDistance = 9999999;
  677. let closest = null;
  678. for (let pano of coverage) {
  679. let distance = geoutils_1.default.haversineDistance([lat, lon], [pano.lat, pano.lon]);
  680. if (distance < smallestDistance) {
  681. smallestDistance = distance;
  682. closest = pano;
  683. }
  684. }
  685. return closest;
  686. }
  687. catch (error) {
  688. console.log(error);
  689. return null;
  690. }
  691. });
  692. }
  693. exports.getClosestPanoAtCoords = getClosestPanoAtCoords;
  694. function getNeighbors(panoInfo) {
  695. return __awaiter(this, void 0, void 0, function* () {
  696. try {
  697. let tile = geoutils_1.default.wgs84_to_tile_coord(panoInfo.lat, panoInfo.lon, 17);
  698. var coverage = yield getCoverageInMapTile(tile[0], tile[1]);
  699. // TODO Only extend when needed (we're close to the edge of the tile)
  700. coverage = coverage.concat(yield getCoverageInMapTile(tile[0] + 1, tile[1]));
  701. coverage = coverage.concat(yield getCoverageInMapTile(tile[0] - 1, tile[1]));
  702. coverage = coverage.concat(yield getCoverageInMapTile(tile[0], tile[1] + 1));
  703. coverage = coverage.concat(yield getCoverageInMapTile(tile[0], tile[1] - 1));
  704. coverage = coverage.concat(yield getCoverageInMapTile(tile[0] - 1, tile[1] - 1));
  705. coverage = coverage.concat(yield getCoverageInMapTile(tile[0] + 1, tile[1] - 1));
  706. coverage = coverage.concat(yield getCoverageInMapTile(tile[0] - 1, tile[1] + 1));
  707. coverage = coverage.concat(yield getCoverageInMapTile(tile[0] + 1, tile[1] + 1));
  708. 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])));
  709. coverage = coverage.filter(pano => pano.panoFullId() != panoInfo.panoFullId());
  710. let minDist = 0.030; // 30 meters
  711. let maxDist = 0.300; // 300 meters
  712. coverage = coverage.filter(n => (minDist < Math.abs(geoutils_1.default.haversineDistance([panoInfo.lat, panoInfo.lon], [n.lat, n.lon])) &&
  713. Math.abs(geoutils_1.default.haversineDistance([panoInfo.lat, panoInfo.lon], [n.lat, n.lon])) < maxDist));
  714. return coverage.slice(0, 8);
  715. }
  716. catch (error) {
  717. console.log(error);
  718. }
  719. });
  720. }
  721. exports.getNeighbors = getNeighbors;
  722. function getUrlForTile(panoFullId, x, resolution) {
  723. return __awaiter(this, void 0, void 0, function* () {
  724. try {
  725. //if (!auth.hasSession()) {
  726. yield auth.init();
  727. //}
  728. let segments = panoFullId.split("/");
  729. let panoId = segments[0];
  730. let regionId = segments[1];
  731. let panoid_padded = panoId.padStart(20, "0");
  732. let region_id_padded = regionId.padStart(10, "0");
  733. 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);
  734. return auth.authenticateUrl(Options.APPLE_MAPS_TILE_ENDPOINT + panoid_split + "/" + region_id_padded + "/t/" + x + "/" + resolution);
  735. }
  736. catch (error) {
  737. console.log(error);
  738. }
  739. });
  740. }
  741. // param panoFullId is "panoId/regionId"
  742. function loadTileForPano(panoFullId, x) {
  743. return __awaiter(this, void 0, void 0, function* () {
  744. try {
  745. var jpegblob;
  746. if (Options.CONVERT_LOCALLY) {
  747. // Step 1: Get the URL of the tile to load
  748. // New endpoint /panourl in the python server returns just the Apple URL for the pano
  749. var appleMapsPanoURL = yield getUrlForTile(panoFullId, x, Options.RESOLUTION_SETTING);
  750. appleMapsPanoURL = Options.CORS_PROXY + appleMapsPanoURL;
  751. // Step 2: Load the tile
  752. //console.log("Requesting tile " + [appleMapsPanoURL])
  753. var blobres = yield fetch(appleMapsPanoURL);
  754. var blob = yield blobres.blob();
  755. // Step 3: Convert from HEIC to JPEG with heic2any
  756. //console.log("Fetched tile, converting and resizing... " + [appleMapsPanoURL])
  757. //let startTime = Math.floor(Date.now() / 1000);
  758. jpegblob = heic2any({ "blob": blob, "type": "image/jpeg" });
  759. }
  760. else {
  761. jpegblob = yield (yield fetch(Options.BASE_URL + "pano/" + panoFullId + "/" + Options.RESOLUTION_SETTING + "/" + x + "/")).blob();
  762. }
  763. // Step 4: Process image
  764. // Cut off the overlap from the right of the tile using canvas
  765. // and add black bars on top and bottom because we don't have sky/ground tiles
  766. let rp = Options.RESOLUTION_PROFILES[Options.RESOLUTION_SETTING];
  767. // Putting the jpeg blob into a canvas to remove 256 px from the right (removes overlap)
  768. var w = rp.big.width;
  769. if (x == 1 || x == 3) {
  770. w = rp.small.width;
  771. }
  772. w = w - rp.overlap;
  773. var canvas = document.createElement('canvas');
  774. canvas.height = Math.round(Options.EXTENSION_FACTOR * rp.big.height);
  775. canvas.width = w;
  776. var ctx = canvas.getContext('2d');
  777. var img = new Image();
  778. var result = "";
  779. img.onload = function () {
  780. ctx.drawImage(img, 0, (canvas.height - rp.big.height) / 2);
  781. // This is a big data:image/jpeg;base64, URL
  782. result = canvas.toDataURL("image/jpeg");
  783. };
  784. img.src = URL.createObjectURL(yield jpegblob);
  785. //let endTime = Math.floor(Date.now() / 1000);
  786. //console.log("Time to convert: " + (endTime - startTime) + " seconds");
  787. // Wait for context to finish loading
  788. // TODO: Is there a better way?
  789. const delay = ms => new Promise(res => setTimeout(res, ms));
  790. yield delay(100);
  791. //let endTime2 = Math.floor(Date.now() / 1000);
  792. //console.log("Full time: " + (endTime - startTime) + " seconds");
  793. URL.revokeObjectURL(img.src);
  794. canvas.remove();
  795. img.remove();
  796. return result;
  797. }
  798. catch (error) {
  799. console.log(error);
  800. }
  801. });
  802. }
  803. exports.loadTileForPano = loadTileForPano;
  804. //# sourceMappingURL=lookaround.js.map
  805.  
  806. /***/ }),
  807.  
  808. /***/ 944:
  809. /***/ ((__unused_webpack_module, exports) => {
  810.  
  811.  
  812. Object.defineProperty(exports, "__esModule", ({ value: true }));
  813. 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;
  814. // Determines the resolution of images requested from Apple
  815. // Setting a higher resolution will make rounds load WAY slower, until browsers start to support HEIC
  816. // 0 = highest resolution available, 4 = lowest resolution available.
  817. // Default: 2
  818. const RESOLUTION_SETTING = 2;
  819. exports.RESOLUTION_SETTING = RESOLUTION_SETTING;
  820. // Constant value added to calculated heading to calibrate the GeoGuessr compass
  821. const HEADING_CALIBRATION = 45;
  822. exports.HEADING_CALIBRATION = HEADING_CALIBRATION;
  823. const EXTENSION_FACTOR = 2.12; // TODO Play around with this value for best results with image stretching
  824. exports.EXTENSION_FACTOR = EXTENSION_FACTOR;
  825. const BASE_URL = "https://lookaround.stocc.dev/";
  826. exports.BASE_URL = BASE_URL;
  827. const CONVERT_LOCALLY = true;
  828. exports.CONVERT_LOCALLY = CONVERT_LOCALLY;
  829. const CORS_PROXY = "https://nameless-bastion-28139.herokuapp.com/";
  830. exports.CORS_PROXY = CORS_PROXY;
  831. const APPLE_MAPS_TILE_ENDPOINT = "https://gspe72-ssl.ls.apple.com/mnn_us/";
  832. exports.APPLE_MAPS_TILE_ENDPOINT = APPLE_MAPS_TILE_ENDPOINT;
  833. const RESOLUTION_PROFILES = {
  834. 0: {
  835. "overlap": 256,
  836. "big": {
  837. "width": 5632,
  838. "height": 4352,
  839. },
  840. "small": {
  841. "width": 3072,
  842. "height": 4352,
  843. }
  844. },
  845. 1: {
  846. "overlap": 188,
  847. "big": {
  848. "width": 4128,
  849. "height": 3088,
  850. },
  851. "small": {
  852. "width": 2256,
  853. "height": 3088,
  854. },
  855. },
  856. 2: {
  857. "overlap": 100,
  858. "big": {
  859. "width": 2208,
  860. "height": 1648,
  861. },
  862. "small": {
  863. "width": 1200,
  864. "height": 1648,
  865. }
  866. },
  867. 3: {
  868. "overlap": 71,
  869. "big": {
  870. "width": 1568,
  871. "height": 1168,
  872. },
  873. "small": {
  874. "width": 848,
  875. "height": 1168,
  876. }
  877. },
  878. 4: {
  879. "overlap": 50,
  880. "big": {
  881. "width": 1104,
  882. "height": 832,
  883. },
  884. "small": {
  885. "width": 608,
  886. "height": 832,
  887. }
  888. }
  889. };
  890. exports.RESOLUTION_PROFILES = RESOLUTION_PROFILES;
  891. //# sourceMappingURL=options.js.map
  892.  
  893. /***/ }),
  894.  
  895. /***/ 224:
  896. /***/ (function(__unused_webpack_module, exports) {
  897.  
  898.  
  899. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  900. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  901. return new (P || (P = Promise))(function (resolve, reject) {
  902. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  903. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  904. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  905. step((generator = generator.apply(thisArg, _arguments || [])).next());
  906. });
  907. };
  908. Object.defineProperty(exports, "__esModule", ({ value: true }));
  909. let mapTile = `
  910. syntax = "proto3";
  911.  
  912. message MapTile {
  913. repeated Pano pano = 1;
  914. repeated Unknown13 unknown13 = 4;
  915. repeated Unknown22 unknown22 = 5;
  916. TileCoordinate tileCoordinate = 6;
  917.  
  918. message Pano {
  919. uint64 panoid = 1;
  920. int32 unknown1 = 4;
  921. int64 timestamp = 5; // time the pano was taken
  922. int32 region_id_idx = 7;
  923. repeated int32 unknown3 = 9; // goes from 0 to 5. available sizes maybe?
  924. Unknown4 unknown4 = 10;
  925. Unknown5 unknown5 = 12;
  926.  
  927. message Unknown4 {
  928. int32 longitude_offset = 1;
  929. int32 latitude_offset = 2;
  930. int32 unknown8 = 3;
  931. int32 unknown9 = 4;
  932. int32 unknown10 = 5;
  933. int32 unknown11 = 6;
  934. }
  935.  
  936. message Unknown5 {
  937. repeated int32 unknown12 = 1;
  938. }
  939. }
  940.  
  941. message Unknown13 {
  942. int32 unknown14 = 1;
  943. // this is the param that appears in pano URLs after the pano ID.
  944. // no idea what this does exactly.
  945. int32 region_id = 3;
  946. int32 unknown15 = 4;
  947. int32 unknown16 = 5;
  948. int32 unknown17 = 6;
  949. int32 unknown18 = 9;
  950. int32 unknown19 = 10;
  951. int32 unknown20 = 11;
  952. int32 unknown21 = 12;
  953. }
  954.  
  955. message Unknown22 {
  956. int32 unknown23 = 1;
  957. Unknown24 unknown24 = 4;
  958. Unknown25 unknown25 = 5;
  959. int32 unknown26 = 6;
  960.  
  961. message Unknown24 {
  962. int32 unknown27 = 1;
  963. double unknown28 = 2;
  964. double unknown29 = 3;
  965. double unknown30 = 4;
  966. double unknown31 = 5;
  967. double unknown32 = 6;
  968. double unknown33 = 7;
  969. double unknown34 = 8;
  970. double unknown35 = 9;
  971. double unknown36 = 10;
  972. }
  973.  
  974. message Unknown25 {
  975. double unknown37 = 1;
  976. double unknown38 = 2;
  977. double unknown39 = 3;
  978. double unknown40 = 4;
  979. double unknown41 = 5;
  980. double unknown42 = 6;
  981. }
  982. }
  983.  
  984. message TileCoordinate {
  985. int32 x = 1;
  986. int32 y = 2;
  987. int32 z = 3;
  988. }
  989.  
  990. }`;
  991. let resourceManifest = `
  992. syntax = "proto3";
  993.  
  994. message ResourceManifest {
  995. repeated StyleConfig style_config = 2;
  996. string token_p2 = 30;
  997. string cache_base_url = 31;
  998. repeated CacheFile cache_file = 72;
  999. repeated string cache_file_2 = 9;
  1000.  
  1001. message CacheFile {
  1002. string file_name = 2;
  1003. }
  1004.  
  1005. message StyleConfig {
  1006. string url_prefix_1 = 1;
  1007. string url_prefix_2 = 9;
  1008. StyleID style_id = 3;
  1009.  
  1010. enum StyleID {
  1011. _ = 0;
  1012. C3MM_1 = 14;
  1013. C3M = 15;
  1014. DTM_1 = 16;
  1015. DTM_2 = 17;
  1016. C3MM_2 = 52;
  1017. }
  1018. }
  1019. }
  1020. `;
  1021. class Proto {
  1022. static parseResourceManifest(payload) {
  1023. return __awaiter(this, void 0, void 0, function* () {
  1024. const array = new Uint8Array(payload);
  1025. let manifest = protobuf.parse(resourceManifest).root.lookup("ResourceManifest");
  1026. let message = manifest.decode(array);
  1027. return message;
  1028. });
  1029. }
  1030. static parseMapTile(payload) {
  1031. return __awaiter(this, void 0, void 0, function* () {
  1032. const array = new Uint8Array(payload);
  1033. let manifest = protobuf.parse(mapTile).root.lookup("MapTile");
  1034. let message = manifest.decode(array);
  1035. return message;
  1036. });
  1037. }
  1038. }
  1039. exports["default"] = Proto;
  1040. //# sourceMappingURL=proto.js.map
  1041.  
  1042. /***/ })
  1043.  
  1044. /******/ });
  1045. /************************************************************************/
  1046. /******/ // The module cache
  1047. /******/ var __webpack_module_cache__ = {};
  1048. /******/
  1049. /******/ // The require function
  1050. /******/ function __webpack_require__(moduleId) {
  1051. /******/ // Check if module is in cache
  1052. /******/ var cachedModule = __webpack_module_cache__[moduleId];
  1053. /******/ if (cachedModule !== undefined) {
  1054. /******/ return cachedModule.exports;
  1055. /******/ }
  1056. /******/ // Create a new module (and put it into the cache)
  1057. /******/ var module = __webpack_module_cache__[moduleId] = {
  1058. /******/ // no module.id needed
  1059. /******/ // no module.loaded needed
  1060. /******/ exports: {}
  1061. /******/ };
  1062. /******/
  1063. /******/ // Execute the module function
  1064. /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  1065. /******/
  1066. /******/ // Return the exports of the module
  1067. /******/ return module.exports;
  1068. /******/ }
  1069. /******/
  1070. /************************************************************************/
  1071. /******/
  1072. /******/ // startup
  1073. /******/ // Load entry module and return exports
  1074. /******/ // This entry module is referenced by other modules so it can't be inlined
  1075. /******/ var __webpack_exports__ = __webpack_require__(590);
  1076. /******/
  1077. /******/ })()
  1078. ;

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址