AppleGuessr

Adds Apple Look Around to GeoGuessr

当前为 2022-08-19 提交的版本,查看 最新版本

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

QingJ © 2025

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