From 1bad323c36b7d53ac6ccee299c7e2c0420fdc1a3 Mon Sep 17 00:00:00 2001 From: Roni Nevalainen Date: Tue, 16 Aug 2022 16:10:32 +0300 Subject: [PATCH] feat: migrate to TS --- .DS_Store | Bin 0 -> 6148 bytes userscript/.gitignore | 130 +++++++++++ userscript/package-lock.json | 37 +++ userscript/package.json | 22 ++ userscript/src/index.ts | 427 ++++++++++++++++++++++++++++++++++ userscript/src/options.ts | 26 +++ userscript/tsconfig.json | 15 ++ userscript/types/globals.d.ts | 10 + 8 files changed, 667 insertions(+) create mode 100644 .DS_Store create mode 100644 userscript/.gitignore create mode 100644 userscript/package-lock.json create mode 100644 userscript/package.json create mode 100644 userscript/src/index.ts create mode 100644 userscript/src/options.ts create mode 100644 userscript/tsconfig.json create mode 100644 userscript/types/globals.d.ts diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..95202526efb40d4e51f071a999b91538427587a7 GIT binary patch literal 6148 zcmeHK%}yIJ7#!0mB#S6H_EKq|AQEq|KypBW3N;Uql4T2lg$>CfmD}F?4t<3_A16BB zXREB8a7C!~7unQP%UVEYB`IhwhaZuxYF8Y`-6{6OuGPGlZLao4to>#%*U47+f>RtO0w5}k>Z@kW>^1x z@XVIz?O2R?bRaqq9r){j><=lEVCJx~s9Og!y#f$Btkz&z{w#ZftWdLEV70X P{|KlJF^dkosRMriR#4-C literal 0 HcmV?d00001 diff --git a/userscript/.gitignore b/userscript/.gitignore new file mode 100644 index 0000000..6a7d6d8 --- /dev/null +++ b/userscript/.gitignore @@ -0,0 +1,130 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* \ No newline at end of file diff --git a/userscript/package-lock.json b/userscript/package-lock.json new file mode 100644 index 0000000..c7441a4 --- /dev/null +++ b/userscript/package-lock.json @@ -0,0 +1,37 @@ +{ + "name": "userscript", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "userscript", + "version": "1.0.0", + "license": "UNLICENSED", + "devDependencies": { + "typescript": "^4.7.4" + } + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true + } + } +} diff --git a/userscript/package.json b/userscript/package.json new file mode 100644 index 0000000..e79b56a --- /dev/null +++ b/userscript/package.json @@ -0,0 +1,22 @@ +{ + "name": "userscript", + "version": "1.0.0", + "description": "A Tampermonkey script that adds Apple Look Around to GeoGuessr", + "main": "dist/script.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/stocc/apple-lookaround-heroku.git" + }, + "author": "Mistystar, stocc, Kowalski", + "license": "UNLICENSED", + "bugs": { + "url": "https://github.com/stocc/apple-lookaround-heroku/issues" + }, + "homepage": "https://github.com/stocc/apple-lookaround-heroku#readme", + "devDependencies": { + "typescript": "^4.7.4" + } +} diff --git a/userscript/src/index.ts b/userscript/src/index.ts new file mode 100644 index 0000000..92e57ab --- /dev/null +++ b/userscript/src/index.ts @@ -0,0 +1,427 @@ +/* +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 + +import * as Options from "./options"; + +async function blobToBase64(blob: Blob): Promise { + return new Promise((resolve, _) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result); + reader.readAsDataURL(blob); + }); +} + +const MENU_HTML = ` +
+
+
+
Apple Look Around
+ +
+
+ +
+
+
+`; + + +let resoultionProfiles = { + 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, + } + } + +} + + + +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/"); + +// ---------------------------------------------------------------------------- +// Script injection, extracted from extenssr: +// https://gitlab.com/nonreviad/extenssr/-/blob/main/src/injected_scripts/maps_api_injecter.ts +export type GoogleOverrider = (g: typeof window.google) => void; + +function overrideOnLoad(googleScript: HTMLScriptElement, observer: MutationObserver, overrider: GoogleOverrider): void { + 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: MutationRecord[]): HTMLScriptElement | null { + for (const mutation of mutations) { + for (const newNode of (mutation.addedNodes as any /* 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: GoogleOverrider) { + if (document.documentElement) { + injecterCallback(overrider); + } else { + alert("Script didn't load, refresh to try loading the script"); + } +} + + +function injecterCallback(overrider: GoogleOverrider) { + 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") as HTMLInputElement; + 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 as any).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); +} + +// Return a pano image given the panoID. +const getCustomPanoramaTileUrl = (pano, zoom, tileX, tileY) => { + return currentPanos[tileX]; + +}; + +const getPano = (pano) => { + let rp = resoultionProfiles[Options.RESOLUTION_SETTING]; + let fullWidth = 2 * rp.big.width + 2 * rp.small.width - 4 * rp.overlap; + return { + location: { + pano: pano, + description: "Apple Look Around", + }, + 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: 180, + getTileUrl: getCustomPanoramaTileUrl, + }, + }; +}; + +var currentPanos = []; +async function loadPanoTest(lookAroundPanoId, regionId, x) { + try { + + // New endpoint /panourl in the python server returns just the Apple URL for the pano + var testURL = Options.BASE_URL + "panourl/" + lookAroundPanoId.toString() + "/" + regionId.toString() + "/"+x.toString()+"/" + Options.RESOLUTION_SETTING.toString() + "/"; + + + + var thing = await fetch(testURL); + var parsed = await thing.json(); + + + //var panoURL = "https://cors-anywhere.herokuapp.com/"+parsed.url; + + // CORS Proxy running locally + // docker run --publish 8080:8080 testcab/cors-anywhere + var panoURL = "http://localhost:8080/"+parsed.url; + //var panoURL = parsed.url; + + console.log("Requesting tile " + [x, parsed.url, panoURL]) + + var blobres = await fetch(panoURL); + var blob = await blobres.blob(); + + console.log("Fetched tile, converting " + [x, parsed.url, panoURL]) + // Convert blob to jpeg using heic2any + var jpegblob = heic2any({"blob": blob, "type": "image/jpeg"}); + //var b64 = await blobToBase64(jpegblob); + + // Tiles 0 and 2 are 3072 x 4352 + // Tiles 1 and 3 are 5632 x 4352 + + console.log("Converted tile, resizing " + [x, parsed.url, panoURL]) + let rp = resoultionProfiles[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(await jpegblob); + + // Wait for context to finish loading + console.log("Scaling " + [x, parsed.url, panoURL]) + + const delay = ms => new Promise(res => setTimeout(res, ms)); + + await delay(100); + + + return result; + + } catch (error) { + console.log(error); + } + + // .then(response => response.json()).then(data => { + // var canvas = document.createElement('canvas'); + // canvas.width = 500; + // canvas.height = 400; + + // // Get the drawing context + // var ctx = canvas.getContext('2d'); + // var img = new Image().src = data.url; + + // const delay = ms => new Promise(res => setTimeout(res, ms)); + // img.onload = () => { + // ctx.drawImage(img, 0, 0, 500, 400); + // } + // delay(1000).then(() => { + // console.log("Image loaded") + // blob = canvas.toBlob(function(blob) { + + // }); + // }); + + // .then(response => response.blob()).then(data => { + // blobToBase64(blob).then((base64)=>{ + // return base64; + // }); + // }); + + // // fetch("https://cors-anywhere.herokuapp.com/"+data.url) + // // .then(function (x) { + // // return x.blob(); + // // }) + // // .then(function (x) { + // // console.log("working on", x); + // // return heic2any({ blob: x, toType: "image/jpeg" }); + // // }) + // // .then(function (blob) { + // // blobToBase64(blob).then(function (base64) { + // // return base64; + // // }); + // // }) + // // .catch(function (e) { + // // console.log(e); + // // }); + + + // // const delay = ms => new Promise(res => setTimeout(res, ms)); + + // // delay(5000).then(() => { + // // return canvas.toDataURL("image/jpeg"); + // // }); + // }).catch(error => console.log(error)); +} + +function initLookAround() { + google.maps.StreetViewPanorama = class extends google.maps.StreetViewPanorama { + constructor(...args: any[]) { + super(...args); + + let isChecked = localStorage.getItem("applelookaroundchecked"); + if (isChecked === "true") { + this.registerPanoProvider(getPano); + this.addListener("position_changed", () => { + try { + this.appleLookAround(); + } catch {} + }); + } + } + + async appleLookAround() { + let isChecked = localStorage.getItem("applelookaroundchecked"); + if (isChecked !== "true") return; + + try { + //currentPanos = [loading,loading,loading,loading]; + //this.setPano("loading"); + let lat = this.position.lat(); + let lon = this.position.lng(); + let lookAroundPanoId, regionId; + + let response = await fetch(Options.BASE_URL + "closest/" + lat + "/" + lon + "/"); + let data = await response.text(); + let closestObject = JSON.parse(data); + + lookAroundPanoId = closestObject.panoid; + regionId = closestObject.region_id; + + // TODO This can probably parallelized + console.log("Start loading Panos"); + let pano0 = loadPanoTest(lookAroundPanoId, regionId,0); + let pano1 = loadPanoTest(lookAroundPanoId, regionId,1); + let pano2 = loadPanoTest(lookAroundPanoId, regionId,2); + let pano3 = loadPanoTest(lookAroundPanoId, regionId,3); + currentPanos = [await pano0, await pano1, await pano2, await pano3]; + // TODO Run async and wait until all panos have loaded + this.setPano(lookAroundPanoId); + } catch {} + } + } +} + +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(); +})(); diff --git a/userscript/src/options.ts b/userscript/src/options.ts new file mode 100644 index 0000000..b47641f --- /dev/null +++ b/userscript/src/options.ts @@ -0,0 +1,26 @@ +// ==UserScript== +// @name AppleGuessr +// @namespace https://greasyfork.org/en/users/946023-mistystar +// @version 1.0 +// @description Adds Apple Look Around to GeoGuessr +// @author Mistystar (Mistystar#2205 on Discord, https://www.youtube.com/channel/UC4IHxYw9Aoz8cf9BIdHKd3A on YT) +// @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://cdnjs.cloudflare.com/ajax/libs/heic2any/0.0.3/heic2any.min.js + +// ==/UserScript== + +const RESOLUTION_SETTING = 2; // 0 best, 4 worst + +const EXTENSION_FACTOR = 2; // TODO Play around with this value for best results with image stretching + +const BASE_URL = "http://127.0.0.1:5001/"; + +export { + RESOLUTION_SETTING, + EXTENSION_FACTOR, + BASE_URL, +}; diff --git a/userscript/tsconfig.json b/userscript/tsconfig.json new file mode 100644 index 0000000..f6250c9 --- /dev/null +++ b/userscript/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "System", + "lib": ["DOM", "ES2022"], + "target": "ES6", + "strict": false, + "noImplicitAny": false, + "removeComments": false, + "preserveConstEnums": true, + "outFile": "./dist/script.js", + "sourceMap": true + }, + "include": ["src/**/*", "types/globals.d.ts"], + "exclude": ["node_modules", "**/*.spec.ts"] +} \ No newline at end of file diff --git a/userscript/types/globals.d.ts b/userscript/types/globals.d.ts new file mode 100644 index 0000000..01f7fcb --- /dev/null +++ b/userscript/types/globals.d.ts @@ -0,0 +1,10 @@ +declare global { + interface Window { + google: any; + } + + let google: any; + let heic2any: any; +} + +export {};