From 6218c3c95bf1c2244d26c1ea66b131974f26d802 Mon Sep 17 00:00:00 2001 From: SwingTheVine Date: Sat, 14 Feb 2026 02:04:03 -0500 Subject: [PATCH] Added GreasyFork version --- .github/workflows/build.yml | 6 +- build/build.js | 37 +- build/update-version.js | 2 +- dist/BlueMarble-For-GreasyFork.user.js | 1853 +++++++++++++++++ ....user.js => BlueMarble-Standalone.user.js} | 41 +- dist/BlueMarble.user.js | 43 +- docs/README.md | 2 +- package-lock.json | 4 +- package.json | 2 +- src/BlueMarble.meta.js | 43 +- 10 files changed, 1959 insertions(+), 74 deletions(-) create mode 100644 dist/BlueMarble-For-GreasyFork.user.js rename dist/{BlueMarbleStandalone.user.js => BlueMarble-Standalone.user.js} (89%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 86d6449..57211e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -100,7 +100,11 @@ jobs: - name: Update compression badge run: | - dist_size=$(find dist -type f -exec cat {} + | wc -c) + dist_size=$(find dist -type f \ + ! -name "BlueMarble-Standalone.user.js" \ + ! -name "BlueMarble-For-GreasyFork.user.js" \ + -exec cat {} + | wc -c) + src_size=$(find src dist/assets -type f -exec cat {} + | wc -c) percentage=$(awk "BEGIN {printf \"%.2f\", 100 - ($dist_size * 100) / $src_size}") echo "Compression: $percentage" diff --git a/build/build.js b/build/build.js index 7be02a7..6b3fea1 100644 --- a/build/build.js +++ b/build/build.js @@ -36,7 +36,7 @@ console.log(`${consoleStyle.BLUE}Starting build...${consoleStyle.RESET}`); // } // } -console.log(`${consoleStyle.BLUE}Building 1 of 2...${consoleStyle.RESET}`); +console.log(`${consoleStyle.BLUE}Building 1 of 3...${consoleStyle.RESET}`); // Tries to bump the version try { @@ -141,7 +141,10 @@ fs.writeFileSync( 'utf8' ); -console.log(`${consoleStyle.BLUE}Building 2 of 2...${consoleStyle.RESET}`); +console.log(`${consoleStyle.BLUE}Building 2 of 3...${consoleStyle.RESET}`); + +const standaloneName = 'BlueMarble-Standalone.user.js'; // Standalone flavor name of flie +const standaloneBMUpdateURL = `https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/${standaloneName}`; // Fetches the completed, main Blue Marble userscript files const mainBMjs = fs.readFileSync('dist/BlueMarble.user.js', 'utf8'); @@ -151,10 +154,10 @@ let mainBMcss = fs.readFileSync('dist/BlueMarble.user.css', 'utf8'); mainBMcss = mainBMcss.replace(/\r?\n/g, '').trim(); // Injects the CSS into the Blue Marble JavaScript -let compactBMjs = mainBMjs.replace('GM_getResourceText("CSS-BM-File")', `\`${mainBMcss}\``); +let standaloneBMjs = mainBMjs.replace('GM_getResourceText("CSS-BM-File")', `\`${mainBMcss}\``); // Removes the metadata in the header that points to the old CSS location -compactBMjs = compactBMjs.replace(/\/\/\s+\@resource\s+CSS-BM-File.*\r?\n?/g, ''); +standaloneBMjs = standaloneBMjs.replace(/\/\/\s+\@resource\s+CSS-BM-File.*\r?\n?/g, ''); // Obtains the Roboto Mono font to inject const robotoMonoLatin = fs.readFileSync('build/assets/RobotoMonoLatin.woff2'); @@ -162,18 +165,34 @@ const robotoMonoLatinBase64 = robotoMonoLatin.toString('base64'); const fontfaces = `@font-face{font-family:'Roboto Mono';font-style:normal;font-weight:400;src:url(data:font/woff2;base64,${robotoMonoLatinBase64})format('woff2');}`; // Injects Roboto Mono into the JavaScript file -compactBMjs = compactBMjs.replace(/robotoMonoInjectionPoint[^'"]*/g, fontfaces); -// compactBMjs = compactBMjs.replace(/https:\/\/fonts.googleapis.com\/[^'"]*/g, fontfaces); +standaloneBMjs = standaloneBMjs.replace(/robotoMonoInjectionPoint[^'"]*/g, fontfaces); // Obtains the Favicon to inject const favicon = fs.readFileSync('dist/assets/Favicon.png'); const faviconBase64DataURI = `data:image/png;base64,${favicon.toString('base64')}`; // Replaces the 2 different types of icon requests with base64 -compactBMjs = compactBMjs.replace(/\/\/\s+\@icon\s+https.*\r?\n?/g, `// @icon64 ${faviconBase64DataURI}\n`); -compactBMjs = compactBMjs.replace(/https[^'"]+dist\/assets\/Favicon\.png[^'"]*/gi, faviconBase64DataURI); +standaloneBMjs = standaloneBMjs.replace(/\/\/\s+\@icon\s+https.*\r?\n?/g, `// @icon64 ${faviconBase64DataURI}\n`); +standaloneBMjs = standaloneBMjs.replace(/https[^'"]+dist\/assets\/Favicon\.png[^'"]*/gi, faviconBase64DataURI); + +// Updates the update/download URLs +standaloneBMjs = standaloneBMjs.replace(/\/\/\s+\@updateURL\s+https.*\r?\n?/g, `// @updateURL ${standaloneBMUpdateURL}\n`); +standaloneBMjs = standaloneBMjs.replace(/\/\/\s+\@downloadURL\s+https.*\r?\n?/g, `// @downloadURL ${standaloneBMUpdateURL}\n`); // Generates the Blue Marble JS file that contains all external resources -fs.writeFileSync('dist/BlueMarbleStandalone.user.js', compactBMjs, 'utf-8'); +fs.writeFileSync(`dist/${standaloneName}`, standaloneBMjs, 'utf-8'); + +console.log(`${consoleStyle.BLUE}Building 3 of 3...${consoleStyle.RESET}`); + +const greasyForkName = 'BlueMarble-For-GreasyFork.user.js'; // GreasyFork flavor name of file +const greasyForkUpdateURL = `https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/${greasyForkName}`; + +let greasyForkBMjs = metaContent + resultEsbuildJS.text; // Gets the unobfuscated code and adds the metadata banner + +// Updates the update/download URLs +greasyForkBMjs = greasyForkBMjs.replace(/\/\/\s+\@updateURL\s+https.*\r?\n?/g, `// @updateURL ${greasyForkUpdateURL}\n`); +greasyForkBMjs = greasyForkBMjs.replace(/\/\/\s+\@downloadURL\s+https.*\r?\n?/g, `// @downloadURL ${greasyForkUpdateURL}\n`); + +fs.writeFileSync(`dist/${greasyForkName}`, greasyForkBMjs, 'utf-8'); console.log(`${consoleStyle.GREEN + consoleStyle.BOLD + consoleStyle.UNDERLINE}Building complete!${consoleStyle.RESET}`); diff --git a/build/update-version.js b/build/update-version.js index 7531e2d..9ba8272 100644 --- a/build/update-version.js +++ b/build/update-version.js @@ -12,7 +12,7 @@ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8')); const version = pkg.version; let meta = fs.readFileSync('src/BlueMarble.meta.js', 'utf-8'); -meta = meta.replace(/@version\s+[\d.]+/, `@version ${version}`); +meta = meta.replace(/@version\s+[\d.]+/, `@version ${version}`); fs.writeFileSync('src/BlueMarble.meta.js', meta); console.log(`${consoleStyle.GREEN}Updated${consoleStyle.RESET} userscript version to ${consoleStyle.MAGENTA}${version}${consoleStyle.RESET}`); \ No newline at end of file diff --git a/dist/BlueMarble-For-GreasyFork.user.js b/dist/BlueMarble-For-GreasyFork.user.js new file mode 100644 index 0000000..3c7d567 --- /dev/null +++ b/dist/BlueMarble-For-GreasyFork.user.js @@ -0,0 +1,1853 @@ +// ==UserScript== +// @name Blue Marble +// @name:en Blue Marble +// @namespace https://github.com/SwingTheVine/ +// @version 0.88.77 +// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. +// @description:en A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. +// @author SwingTheVine +// @license MPL-2.0 +// @donate https://ko-fi.com/swingthevine +// @supportURL https://discord.gg/tpeBPy46hf +// @homepageURL https://bluemarble.lol/ +// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/0c760b903739e6214f7b8990ffc4089a93e73bd2/dist/assets/Favicon.png +// @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-For-GreasyFork.user.js +// @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-For-GreasyFork.user.js +// @match https://wplace.live/* +// @grant GM_getResourceText +// @grant GM_addStyle +// @grant GM.setValue +// @grant GM_getValue +// @grant GM_xmlhttpRequest +// @connect telemetry.thebluecorner.net +// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/0c760b903739e6214f7b8990ffc4089a93e73bd2/dist/BlueMarble.user.css +// @antifeature tracking Anonymous opt-in telemetry data +// @noframes +// ==/UserScript== + +// Wplace --> https://wplace.live +// License --> https://www.mozilla.org/en-US/MPL/2.0/ + +(() => { + var __typeError = (msg) => { + throw TypeError(msg); + }; + var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); + var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); + var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); + + // src/Overlay.js + var _Overlay_instances, createElement_fn; + var Overlay = class { + /** Constructor for the Overlay class. + * @param {string} name - The name of the userscript + * @param {string} version - The version of the userscript + * @since 0.0.2 + * @see {@link Overlay} + */ + constructor(name2, version2) { + __privateAdd(this, _Overlay_instances); + this.name = name2; + this.version = version2; + this.apiManager = null; + this.outputStatusId = "bm-output-status"; + this.overlay = null; + this.currentParent = null; + this.parentStack = []; + } + /** Populates the apiManager variable with the apiManager class. + * @param {apiManager} apiManager - The apiManager class instance + * @since 0.41.4 + */ + setApiManager(apiManager2) { + this.apiManager = apiManager2; + } + /** Finishes building an element. + * Call this after you are finished adding children. + * If the element will have no children, call it anyways. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.2 + * @example + * overlay + * .addDiv() + * .addHeader(1).buildElement() // Breaks out of the

+ * .addP().buildElement() // Breaks out of the

+ * .buildElement() // Breaks out of the

+ * .addHr() // Since there are no more elements, calling buildElement() is optional + * .buildOverlay(document.body); + */ + buildElement() { + if (this.parentStack.length > 0) { + this.currentParent = this.parentStack.pop(); + } + return this; + } + /** Finishes building the overlay and displays it. + * Call this when you are done chaining methods. + * @param {HTMLElement} parent - The parent HTMLElement this overlay should be appended to as a child. + * @since 0.43.2 + * @example + * overlay + * .addDiv() + * .addP().buildElement() + * .buildElement() + * .buildOverlay(document.body); // Adds DOM structure to document body + * //

+ */ + buildOverlay(parent) { + parent?.appendChild(this.overlay); + this.overlay = null; + this.currentParent = null; + this.parentStack = []; + } + /** Adds a `div` to the overlay. + * This `div` element will have properties shared between all `div` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `div` that are NOT shared between all overlay `div` elements. These should be camelCase. + * @param {function(Overlay, HTMLDivElement):void} [callback=()=>{}] - Additional JS modification to the `div`. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.2 + * @example + * // Assume all
elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addDiv({'id': 'foo'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + *
+ * + */ + addDiv(additionalProperties = {}, callback = () => { + }) { + const properties = {}; + const div = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "div", properties, additionalProperties); + callback(this, div); + return this; + } + /** Adds a `p` to the overlay. + * This `p` element will have properties shared between all `p` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `p` that are NOT shared between all overlay `p` elements. These should be camelCase. + * @param {function(Overlay, HTMLParagraphElement):void} [callback=()=>{}] - Additional JS modification to the `p`. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.2 + * @example + * // Assume all

elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addP({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + *

Foobar.

+ * + */ + addP(additionalProperties = {}, callback = () => { + }) { + const properties = {}; + const p = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "p", properties, additionalProperties); + callback(this, p); + return this; + } + /** Adds a `small` to the overlay. + * This `small` element will have properties shared between all `small` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `small` that are NOT shared between all overlay `small` elements. These should be camelCase. + * @param {function(Overlay, HTMLParagraphElement):void} [callback=()=>{}] - Additional JS modification to the `small`. + * @returns {Overlay} Overlay class instance (this) + * @since 0.55.8 + * @example + * // Assume all elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addSmall({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + * Foobar. + * + */ + addSmall(additionalProperties = {}, callback = () => { + }) { + const properties = {}; + const small = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "small", properties, additionalProperties); + callback(this, small); + return this; + } + /** Adds a `img` to the overlay. + * This `img` element will have properties shared between all `img` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `img` that are NOT shared between all overlay `img` elements. These should be camelCase. + * @param {function(Overlay, HTMLImageElement):void} [callback=()=>{}] - Additional JS modification to the `img`. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.2 + * @example + * // Assume all elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addimg({'id': 'foo', 'src': './img.png'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + * + * + */ + addImg(additionalProperties = {}, callback = () => { + }) { + const properties = {}; + const img = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "img", properties, additionalProperties); + callback(this, img); + return this; + } + /** Adds a header to the overlay. + * This header element will have properties shared between all header elements in the overlay. + * You can override the shared properties by using a callback. + * @param {number} level - The header level. Must be between 1 and 6 (inclusive) + * @param {Object.} [additionalProperties={}] - The DOM properties of the header that are NOT shared between all overlay header elements. These should be camelCase. + * @param {function(Overlay, HTMLHeadingElement):void} [callback=()=>{}] - Additional JS modification to the header. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.7 + * @example + * // Assume all header elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addHeader(6, {'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + *
Foobar.
+ * + */ + addHeader(level, additionalProperties = {}, callback = () => { + }) { + const properties = {}; + const header = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "h" + level, properties, additionalProperties); + callback(this, header); + return this; + } + /** Adds a `hr` to the overlay. + * This `hr` element will have properties shared between all `hr` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `hr` that are NOT shared between all overlay `hr` elements. These should be camelCase. + * @param {function(Overlay, HTMLHRElement):void} [callback=()=>{}] - Additional JS modification to the `hr`. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.7 + * @example + * // Assume all
elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addhr({'id': 'foo'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + *
+ * + */ + addHr(additionalProperties = {}, callback = () => { + }) { + const properties = {}; + const hr = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "hr", properties, additionalProperties); + callback(this, hr); + return this; + } + /** Adds a `br` to the overlay. + * This `br` element will have properties shared between all `br` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `br` that are NOT shared between all overlay `br` elements. These should be camelCase. + * @param {function(Overlay, HTMLBRElement):void} [callback=()=>{}] - Additional JS modification to the `br`. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.11 + * @example + * // Assume all
elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addbr({'id': 'foo'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + *
+ * + */ + addBr(additionalProperties = {}, callback = () => { + }) { + const properties = {}; + const br = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "br", properties, additionalProperties); + callback(this, br); + return this; + } + /** Adds a checkbox to the overlay. + * This checkbox element will have properties shared between all checkbox elements in the overlay. + * You can override the shared properties by using a callback. Note: the checkbox element is inside a label element. + * @param {Object.} [additionalProperties={}] - The DOM properties of the checkbox that are NOT shared between all overlay checkbox elements. These should be camelCase. + * @param {function(Overlay, HTMLLabelElement, HTMLInputElement):void} [callback=()=>{}] - Additional JS modification to the checkbox. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.10 + * @example + * // Assume all checkbox elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addCheckbox({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + * + * + */ + addCheckbox(additionalProperties = {}, callback = () => { + }) { + const properties = { "type": "checkbox" }; + const label = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "label", { "textContent": additionalProperties["textContent"] ?? "" }); + delete additionalProperties["textContent"]; + const checkbox = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "input", properties, additionalProperties); + label.insertBefore(checkbox, label.firstChild); + this.buildElement(); + callback(this, label, checkbox); + return this; + } + /** Adds a `button` to the overlay. + * This `button` element will have properties shared between all `button` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `button` that are NOT shared between all overlay `button` elements. These should be camelCase. + * @param {function(Overlay, HTMLButtonElement):void} [callback=()=>{}] - Additional JS modification to the `button`. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.12 + * @example + * // Assume all + * + */ + addButton(additionalProperties = {}, callback = () => { + }) { + const properties = {}; + const button = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "button", properties, additionalProperties); + callback(this, button); + return this; + } + /** Adds a help button to the overlay. It will have a "?" icon unless overridden in callback. + * On click, the button will attempt to output the title to the output element (ID defined in Overlay constructor). + * This `button` element will have properties shared between all `button` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `button` that are NOT shared between all overlay `button` elements. These should be camelCase. + * @param {function(Overlay, HTMLButtonElement):void} [callback=()=>{}] - Additional JS modification to the `button`. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.12 + * @example + * // Assume all help button elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addButtonHelp({'id': 'foo', 'title': 'Foobar.'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + * + * + * @example + * // Assume all help button elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addButtonHelp({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + * + * + */ + addButtonHelp(additionalProperties = {}, callback = () => { + }) { + const tooltip = additionalProperties["title"] ?? additionalProperties["textContent"] ?? "Help: No info"; + delete additionalProperties["textContent"]; + additionalProperties["title"] = `Help: ${tooltip}`; + const properties = { + "textContent": "?", + "className": "bm-help", + "onclick": () => { + this.updateInnerHTML(this.outputStatusId, tooltip); + } + }; + const help = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "button", properties, additionalProperties); + callback(this, help); + return this; + } + /** Adds a `input` to the overlay. + * This `input` element will have properties shared between all `input` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `input` that are NOT shared between all overlay `input` elements. These should be camelCase. + * @param {function(Overlay, HTMLInputElement):void} [callback=()=>{}] - Additional JS modification to the `input`. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.13 + * @example + * // Assume all elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addInput({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + * Foobar. + * + */ + addInput(additionalProperties = {}, callback = () => { + }) { + const properties = {}; + const input = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "input", properties, additionalProperties); + callback(this, input); + return this; + } + /** Adds a file input to the overlay with enhanced visibility controls. + * This input element will have properties shared between all file input elements in the overlay. + * Uses multiple hiding methods to prevent browser native text from appearing during minimize/maximize. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the file input that are NOT shared between all overlay file input elements. These should be camelCase. + * @param {function(Overlay, HTMLDivElement, HTMLInputElement, HTMLButtonElement):void} [callback=()=>{}] - Additional JS modification to the file input. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.17 + * @example + * // Assume all file input elements have a shared class (e.g. {'className': 'bar'}) + * overlay.addInputFile({'id': 'foo', 'textContent': 'Foobar.'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + *
+ * + * + *
+ * + */ + addInputFile(additionalProperties = {}, callback = () => { + }) { + const properties = { + "type": "file", + "style": "display: none !important; visibility: hidden !important; position: absolute !important; left: -9999px !important; width: 0 !important; height: 0 !important; opacity: 0 !important;" + }; + const text = additionalProperties["textContent"] ?? ""; + delete additionalProperties["textContent"]; + const container = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "div"); + const input = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "input", properties, additionalProperties); + this.buildElement(); + const button = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "button", { "textContent": text }); + this.buildElement(); + this.buildElement(); + input.setAttribute("tabindex", "-1"); + input.setAttribute("aria-hidden", "true"); + button.addEventListener("click", () => { + input.click(); + }); + input.addEventListener("change", () => { + button.style.maxWidth = `${button.offsetWidth}px`; + if (input.files.length > 0) { + button.textContent = input.files[0].name; + } else { + button.textContent = text; + } + }); + callback(this, container, input, button); + return this; + } + /** Adds a `textarea` to the overlay. + * This `textarea` element will have properties shared between all `textarea` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `textarea` that are NOT shared between all overlay `textarea` elements. These should be camelCase. + * @param {function(Overlay, HTMLTextAreaElement):void} [callback=()=>{}] - Additional JS modification to the `textarea`. + * @returns {Overlay} Overlay class instance (this) + * @since 0.43.13 + * @example + * // Assume all + * + */ + addTextarea(additionalProperties = {}, callback = () => { + }) { + const properties = {}; + const textarea = __privateMethod(this, _Overlay_instances, createElement_fn).call(this, "textarea", properties, additionalProperties); + callback(this, textarea); + return this; + } + /** Updates the inner HTML of the element. + * The element is discovered by it's id. + * If the element is an `input`, it will modify the value attribute instead. + * @param {string} id - The ID of the element to change + * @param {string} html - The HTML/text to update with + * @param {boolean} [doSafe] - (Optional) Should `textContent` be used instead of `innerHTML` to avoid XSS? False by default + * @since 0.24.2 + */ + updateInnerHTML(id, html, doSafe = false) { + const element = document.getElementById(id.replace(/^#/, "")); + if (!element) { + return; + } + if (element instanceof HTMLInputElement) { + element.value = html; + return; + } + if (doSafe) { + element.textContent = html; + } else { + element.innerHTML = html; + } + } + /** Handles dragging of the overlay. + * Uses requestAnimationFrame for smooth animations and GPU-accelerated transforms. + * @param {string} moveMe - The ID of the element to be moved + * @param {string} iMoveThings - The ID of the drag handle element + * @since 0.8.2 + */ + handleDrag(moveMe, iMoveThings) { + let isDragging = false; + let offsetX, offsetY = 0; + let animationFrame = null; + let currentX = 0; + let currentY = 0; + let targetX = 0; + let targetY = 0; + moveMe = document.querySelector(moveMe?.[0] == "#" ? moveMe : "#" + moveMe); + iMoveThings = document.querySelector(iMoveThings?.[0] == "#" ? iMoveThings : "#" + iMoveThings); + if (!moveMe || !iMoveThings) { + this.handleDisplayError(`Can not drag! ${!moveMe ? "moveMe" : ""} ${!moveMe && !iMoveThings ? "and " : ""}${!iMoveThings ? "iMoveThings " : ""}was not found!`); + return; + } + const updatePosition = () => { + if (isDragging) { + const deltaX = Math.abs(currentX - targetX); + const deltaY = Math.abs(currentY - targetY); + if (deltaX > 0.5 || deltaY > 0.5) { + currentX = targetX; + currentY = targetY; + moveMe.style.transform = `translate(${currentX}px, ${currentY}px)`; + moveMe.style.left = "0px"; + moveMe.style.top = "0px"; + moveMe.style.right = ""; + } + animationFrame = requestAnimationFrame(updatePosition); + } + }; + let initialRect = null; + const startDrag = (clientX, clientY) => { + isDragging = true; + initialRect = moveMe.getBoundingClientRect(); + offsetX = clientX - initialRect.left; + offsetY = clientY - initialRect.top; + const computedStyle = window.getComputedStyle(moveMe); + const transform = computedStyle.transform; + if (transform && transform !== "none") { + const matrix = new DOMMatrix(transform); + currentX = matrix.m41; + currentY = matrix.m42; + } else { + currentX = initialRect.left; + currentY = initialRect.top; + } + targetX = currentX; + targetY = currentY; + document.body.style.userSelect = "none"; + iMoveThings.classList.add("dragging"); + if (animationFrame) { + cancelAnimationFrame(animationFrame); + } + updatePosition(); + }; + const endDrag = () => { + isDragging = false; + if (animationFrame) { + cancelAnimationFrame(animationFrame); + animationFrame = null; + } + document.body.style.userSelect = ""; + iMoveThings.classList.remove("dragging"); + }; + iMoveThings.addEventListener("mousedown", function(event) { + event.preventDefault(); + startDrag(event.clientX, event.clientY); + }); + iMoveThings.addEventListener("touchstart", function(event) { + const touch = event?.touches?.[0]; + if (!touch) { + return; + } + startDrag(touch.clientX, touch.clientY); + event.preventDefault(); + }, { passive: false }); + document.addEventListener("mousemove", function(event) { + if (isDragging && initialRect) { + targetX = event.clientX - offsetX; + targetY = event.clientY - offsetY; + } + }, { passive: true }); + document.addEventListener("touchmove", function(event) { + if (isDragging && initialRect) { + const touch = event?.touches?.[0]; + if (!touch) { + return; + } + targetX = touch.clientX - offsetX; + targetY = touch.clientY - offsetY; + event.preventDefault(); + } + }, { passive: false }); + document.addEventListener("mouseup", endDrag); + document.addEventListener("touchend", endDrag); + document.addEventListener("touchcancel", endDrag); + } + /** Handles status display. + * This will output plain text into the output Status box. + * Additionally, this will output an info message to the console. + * @param {string} text - The status text to display. + * @since 0.58.4 + */ + handleDisplayStatus(text) { + const consoleInfo = console.info; + consoleInfo(`${this.name}: ${text}`); + this.updateInnerHTML(this.outputStatusId, "Status: " + text, true); + } + /** Handles error display. + * This will output plain text into the output Status box. + * Additionally, this will output an error to the console. + * @param {string} text - The error text to display. + * @since 0.41.6 + */ + handleDisplayError(text) { + const consoleError2 = console.error; + consoleError2(`${this.name}: ${text}`); + this.updateInnerHTML(this.outputStatusId, "Error: " + text, true); + } + }; + _Overlay_instances = new WeakSet(); + /** Creates an element. + * For **internal use** of the {@link Overlay} class. + * @param {string} tag - The tag name as a string. + * @param {Object.} [properties={}] - The DOM properties of the element. + * @returns {HTMLElement} HTML Element + * @since 0.43.2 + */ + createElement_fn = function(tag, properties = {}, additionalProperties = {}) { + const element = document.createElement(tag); + if (!this.overlay) { + this.overlay = element; + this.currentParent = element; + } else { + this.currentParent?.appendChild(element); + this.parentStack.push(this.currentParent); + this.currentParent = element; + } + for (const [property, value] of Object.entries(properties)) { + element[property] = value; + } + for (const [property, value] of Object.entries(additionalProperties)) { + element[property] = value; + } + return element; + }; + + // src/observers.js + var Observers = class { + /** The constructor for the observer class + * @since 0.43.2 + */ + constructor() { + this.observerBody = null; + this.observerBodyTarget = null; + this.targetDisplayCoords = "#bm-display-coords"; + } + /** Creates the MutationObserver for document.body + * @param {HTMLElement} target - Targeted element to watch + * @returns {Observers} this (Observers class) + * @since 0.43.2 + */ + createObserverBody(target) { + this.observerBodyTarget = target; + this.observerBody = new MutationObserver((mutations) => { + for (const mutation of mutations) { + for (const node of mutation.addedNodes) { + if (!(node instanceof HTMLElement)) { + continue; + } + if (node.matches?.(this.targetDisplayCoords)) { + } + } + } + }); + return this; + } + /** Retrieves the MutationObserver that watches document.body + * @returns {MutationObserver} + * @since 0.43.2 + */ + getObserverBody() { + return this.observerBody; + } + /** Observe a MutationObserver + * @param {MutationObserver} observer - The MutationObserver + * @param {boolean} watchChildList - (Optional) Should childList be watched? False by default + * @param {boolean} watchSubtree - (Optional) Should childList be watched? False by default + * @since 0.43.2 + */ + observe(observer, watchChildList = false, watchSubtree = false) { + observer.observe(this.observerBodyTarget, { + childList: watchChildList, + subtree: watchSubtree + }); + } + }; + + // src/utils.js + function serverTPtoDisplayTP(tile, pixel) { + return [parseInt(tile[0]) % 4 * 1e3 + parseInt(pixel[0]), parseInt(tile[1]) % 4 * 1e3 + parseInt(pixel[1])]; + } + function consoleLog(...args) { + ((consoleLog2) => consoleLog2(...args))(console.log); + } + function consoleError(...args) { + ((consoleError2) => consoleError2(...args))(console.error); + } + function consoleWarn(...args) { + ((consoleWarn2) => consoleWarn2(...args))(console.warn); + } + function numberToEncoded(number, encoding) { + if (number === 0) return encoding[0]; + let result = ""; + const base = encoding.length; + while (number > 0) { + result = encoding[number % base] + result; + number = Math.floor(number / base); + } + return result; + } + function uint8ToBase64(uint8) { + let binary = ""; + for (let i = 0; i < uint8.length; i++) { + binary += String.fromCharCode(uint8[i]); + } + return btoa(binary); + } + function base64ToUint8(base64) { + const binary = atob(base64); + const array = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + array[i] = binary.charCodeAt(i); + } + return array; + } + function colorpaletteForBlueMarble(tolerance) { + const colorpaletteBM = colorpalette; + colorpaletteBM.unshift({ "id": -1, "premium": false, "name": "Erased", "rgb": [222, 250, 206] }); + colorpaletteBM.unshift({ "id": -2, "premium": false, "name": "Other", "rgb": [0, 0, 0] }); + const lookupTable = /* @__PURE__ */ new Map(); + for (const color of colorpaletteBM) { + if (color.id == 0 || color.id == -2) continue; + const targetRed = color.rgb[0]; + const targetGreen = color.rgb[1]; + const targetBlue = color.rgb[2]; + for (let deltaRedRange = -tolerance; deltaRedRange <= tolerance; deltaRedRange++) { + for (let deltaGreenRange = -tolerance; deltaGreenRange <= tolerance; deltaGreenRange++) { + for (let deltaBlueRange = -tolerance; deltaBlueRange <= tolerance; deltaBlueRange++) { + const derivativeRed = targetRed + deltaRedRange; + const derivativeGreen = targetGreen + deltaGreenRange; + const derivativeBlue = targetBlue + deltaBlueRange; + if (derivativeRed < 0 || derivativeRed > 255 || derivativeGreen < 0 || derivativeGreen > 255 || derivativeBlue < 0 || derivativeBlue > 255) continue; + const derivativeColor32 = (255 << 24 | derivativeBlue << 16 | derivativeGreen << 8 | derivativeRed) >>> 0; + if (!lookupTable.has(derivativeColor32)) { + lookupTable.set(derivativeColor32, color.id); + } + } + } + } + } + return { palette: colorpaletteBM, LUT: lookupTable }; + } + var colorpalette = [ + { "id": 0, "premium": false, "name": "Transparent", "rgb": [0, 0, 0] }, + { "id": 1, "premium": false, "name": "Black", "rgb": [0, 0, 0] }, + { "id": 2, "premium": false, "name": "Dark Gray", "rgb": [60, 60, 60] }, + { "id": 3, "premium": false, "name": "Gray", "rgb": [120, 120, 120] }, + { "id": 4, "premium": false, "name": "Light Gray", "rgb": [210, 210, 210] }, + { "id": 5, "premium": false, "name": "White", "rgb": [255, 255, 255] }, + { "id": 6, "premium": false, "name": "Deep Red", "rgb": [96, 0, 24] }, + { "id": 7, "premium": false, "name": "Red", "rgb": [237, 28, 36] }, + { "id": 8, "premium": false, "name": "Orange", "rgb": [255, 127, 39] }, + { "id": 9, "premium": false, "name": "Gold", "rgb": [246, 170, 9] }, + { "id": 10, "premium": false, "name": "Yellow", "rgb": [249, 221, 59] }, + { "id": 11, "premium": false, "name": "Light Yellow", "rgb": [255, 250, 188] }, + { "id": 12, "premium": false, "name": "Dark Green", "rgb": [14, 185, 104] }, + { "id": 13, "premium": false, "name": "Green", "rgb": [19, 230, 123] }, + { "id": 14, "premium": false, "name": "Light Green", "rgb": [135, 255, 94] }, + { "id": 15, "premium": false, "name": "Dark Teal", "rgb": [12, 129, 110] }, + { "id": 16, "premium": false, "name": "Teal", "rgb": [16, 174, 166] }, + { "id": 17, "premium": false, "name": "Light Teal", "rgb": [19, 225, 190] }, + { "id": 18, "premium": false, "name": "Dark Blue", "rgb": [40, 80, 158] }, + { "id": 19, "premium": false, "name": "Blue", "rgb": [64, 147, 228] }, + { "id": 20, "premium": false, "name": "Cyan", "rgb": [96, 247, 242] }, + { "id": 21, "premium": false, "name": "Indigo", "rgb": [107, 80, 246] }, + { "id": 22, "premium": false, "name": "Light Indigo", "rgb": [153, 177, 251] }, + { "id": 23, "premium": false, "name": "Dark Purple", "rgb": [120, 12, 153] }, + { "id": 24, "premium": false, "name": "Purple", "rgb": [170, 56, 185] }, + { "id": 25, "premium": false, "name": "Light Purple", "rgb": [224, 159, 249] }, + { "id": 26, "premium": false, "name": "Dark Pink", "rgb": [203, 0, 122] }, + { "id": 27, "premium": false, "name": "Pink", "rgb": [236, 31, 128] }, + { "id": 28, "premium": false, "name": "Light Pink", "rgb": [243, 141, 169] }, + { "id": 29, "premium": false, "name": "Dark Brown", "rgb": [104, 70, 52] }, + { "id": 30, "premium": false, "name": "Brown", "rgb": [149, 104, 42] }, + { "id": 31, "premium": false, "name": "Beige", "rgb": [248, 178, 119] }, + { "id": 32, "premium": true, "name": "Medium Gray", "rgb": [170, 170, 170] }, + { "id": 33, "premium": true, "name": "Dark Red", "rgb": [165, 14, 30] }, + { "id": 34, "premium": true, "name": "Light Red", "rgb": [250, 128, 114] }, + { "id": 35, "premium": true, "name": "Dark Orange", "rgb": [228, 92, 26] }, + { "id": 36, "premium": true, "name": "Light Tan", "rgb": [214, 181, 148] }, + { "id": 37, "premium": true, "name": "Dark Goldenrod", "rgb": [156, 132, 49] }, + { "id": 38, "premium": true, "name": "Goldenrod", "rgb": [197, 173, 49] }, + { "id": 39, "premium": true, "name": "Light Goldenrod", "rgb": [232, 212, 95] }, + { "id": 40, "premium": true, "name": "Dark Olive", "rgb": [74, 107, 58] }, + { "id": 41, "premium": true, "name": "Olive", "rgb": [90, 148, 74] }, + { "id": 42, "premium": true, "name": "Light Olive", "rgb": [132, 197, 115] }, + { "id": 43, "premium": true, "name": "Dark Cyan", "rgb": [15, 121, 159] }, + { "id": 44, "premium": true, "name": "Light Cyan", "rgb": [187, 250, 242] }, + { "id": 45, "premium": true, "name": "Light Blue", "rgb": [125, 199, 255] }, + { "id": 46, "premium": true, "name": "Dark Indigo", "rgb": [77, 49, 184] }, + { "id": 47, "premium": true, "name": "Dark Slate Blue", "rgb": [74, 66, 132] }, + { "id": 48, "premium": true, "name": "Slate Blue", "rgb": [122, 113, 196] }, + { "id": 49, "premium": true, "name": "Light Slate Blue", "rgb": [181, 174, 241] }, + { "id": 50, "premium": true, "name": "Light Brown", "rgb": [219, 164, 99] }, + { "id": 51, "premium": true, "name": "Dark Beige", "rgb": [209, 128, 81] }, + { "id": 52, "premium": true, "name": "Light Beige", "rgb": [255, 197, 165] }, + { "id": 53, "premium": true, "name": "Dark Peach", "rgb": [155, 82, 73] }, + { "id": 54, "premium": true, "name": "Peach", "rgb": [209, 128, 120] }, + { "id": 55, "premium": true, "name": "Light Peach", "rgb": [250, 182, 164] }, + { "id": 56, "premium": true, "name": "Dark Tan", "rgb": [123, 99, 82] }, + { "id": 57, "premium": true, "name": "Tan", "rgb": [156, 132, 107] }, + { "id": 58, "premium": true, "name": "Dark Slate", "rgb": [51, 57, 65] }, + { "id": 59, "premium": true, "name": "Slate", "rgb": [109, 117, 141] }, + { "id": 60, "premium": true, "name": "Light Slate", "rgb": [179, 185, 209] }, + { "id": 61, "premium": true, "name": "Dark Stone", "rgb": [109, 100, 63] }, + { "id": 62, "premium": true, "name": "Stone", "rgb": [148, 140, 107] }, + { "id": 63, "premium": true, "name": "Light Stone", "rgb": [205, 197, 158] } + ]; + + // src/Template.js + var _Template_instances, calculateTotalPixelsFromTemplateData_fn; + var Template = class { + /** The constructor for the {@link Template} class with enhanced pixel tracking. + * @param {Object} [params={}] - Object containing all optional parameters + * @param {string} [params.displayName='My template'] - The display name of the template + * @param {number} [params.sortID=0] - The sort number of the template for rendering priority + * @param {string} [params.authorID=''] - The user ID of the person who exported the template (prevents sort ID collisions) + * @param {string} [params.url=''] - The URL to the source image + * @param {File} [params.file=null] - The template file (pre-processed File or processed bitmap) + * @param {Array} [params.coords=null] - The coordinates of the top left corner as (tileX, tileY, pixelX, pixelY) + * @param {Object} [params.chunked=null] - The affected chunks of the template, and their template for each chunk + * @param {number} [params.tileSize=1000] - The size of a tile in pixels (assumes square tiles) + * @param {Object} [params.pixelCount={total:0, colors:Map}] - Total number of pixels in the template (calculated automatically during processing) + * @since 0.65.2 + */ + constructor({ + displayName = "My template", + sortID = 0, + authorID = "", + url = "", + file = null, + coords: coords2 = null, + chunked = null, + tileSize = 1e3 + } = {}) { + __privateAdd(this, _Template_instances); + this.displayName = displayName; + this.sortID = sortID; + this.authorID = authorID; + this.url = url; + this.file = file; + this.coords = coords2; + this.chunked = chunked; + this.tileSize = tileSize; + this.pixelCount = { total: 0, colors: /* @__PURE__ */ new Map() }; + } + /** Creates chunks of the template for each tile. + * @param {Number} tileSize - Size of the tile as determined by templateManager + * @param {Object} paletteBM - An collection of Uint32Arrays containing the palette BM uses + * @returns {Object} Collection of template bitmaps & buffers organized by tile coordinates + * @since 0.65.4 + */ + async createTemplateTiles(tileSize, paletteBM) { + console.log("Template coordinates:", this.coords); + const shreadSize = 3; + const bitmap = await createImageBitmap(this.file); + const imageWidth = bitmap.width; + const imageHeight = bitmap.height; + this.tileSize = tileSize; + const templateTiles = {}; + const templateTilesBuffers = {}; + const canvas = new OffscreenCanvas(this.tileSize, this.tileSize); + const context = canvas.getContext("2d", { willReadFrequently: true }); + canvas.width = imageWidth; + canvas.height = imageHeight; + context.imageSmoothingEnabled = false; + context.drawImage(bitmap, 0, 0); + let timer = Date.now(); + const totalPixelMap = __privateMethod(this, _Template_instances, calculateTotalPixelsFromTemplateData_fn).call(this, context.getImageData(0, 0, imageWidth, imageHeight), paletteBM); + console.log(`Calculating total pixels took ${(Date.now() - timer) / 1e3} seconds`); + let totalPixels = 0; + const transparentColorID = 0; + for (const [color, total] of totalPixelMap) { + if (color == transparentColorID) { + continue; + } + totalPixels += total; + } + this.pixelCount = { total: totalPixels, colors: totalPixelMap }; + timer = Date.now(); + const canvasMask = new OffscreenCanvas(3, 3); + const contextMask = canvasMask.getContext("2d"); + contextMask.clearRect(0, 0, 3, 3); + contextMask.fillStyle = "white"; + contextMask.fillRect(1, 1, 1, 1); + for (let pixelY = this.coords[3]; pixelY < imageHeight + this.coords[3]; ) { + const drawSizeY = Math.min(this.tileSize - pixelY % this.tileSize, imageHeight - (pixelY - this.coords[3])); + console.log(`Math.min(${this.tileSize} - (${pixelY} % ${this.tileSize}), ${imageHeight} - (${pixelY - this.coords[3]}))`); + for (let pixelX = this.coords[2]; pixelX < imageWidth + this.coords[2]; ) { + console.log(`Pixel X: ${pixelX} +Pixel Y: ${pixelY}`); + const drawSizeX = Math.min(this.tileSize - pixelX % this.tileSize, imageWidth - (pixelX - this.coords[2])); + console.log(`Math.min(${this.tileSize} - (${pixelX} % ${this.tileSize}), ${imageWidth} - (${pixelX - this.coords[2]}))`); + console.log(`Draw Size X: ${drawSizeX} +Draw Size Y: ${drawSizeY}`); + const canvasWidth = drawSizeX * shreadSize; + const canvasHeight = drawSizeY * shreadSize; + canvas.width = canvasWidth; + canvas.height = canvasHeight; + console.log(`Draw X: ${drawSizeX} +Draw Y: ${drawSizeY} +Canvas Width: ${canvasWidth} +Canvas Height: ${canvasHeight}`); + context.imageSmoothingEnabled = false; + console.log(`Getting X ${pixelX}-${pixelX + drawSizeX} +Getting Y ${pixelY}-${pixelY + drawSizeY}`); + context.clearRect(0, 0, canvasWidth, canvasHeight); + context.drawImage( + bitmap, + // Bitmap image to draw + pixelX - this.coords[2], + // Coordinate X to draw from + pixelY - this.coords[3], + // Coordinate Y to draw from + drawSizeX, + // X width to draw from + drawSizeY, + // Y height to draw from + 0, + // Coordinate X to draw at + 0, + // Coordinate Y to draw at + drawSizeX * shreadSize, + // X width to draw at + drawSizeY * shreadSize + // Y height to draw at + ); + context.save(); + context.globalCompositeOperation = "destination-in"; + context.fillStyle = context.createPattern(canvasMask, "repeat"); + context.fillRect(0, 0, canvasWidth, canvasHeight); + context.restore(); + const imageData = context.getImageData(0, 0, canvasWidth, canvasHeight); + console.log(`Shreaded pixels for ${pixelX}, ${pixelY}`, imageData); + context.putImageData(imageData, 0, 0); + const templateTileName = `${(this.coords[0] + Math.floor(pixelX / 1e3)).toString().padStart(4, "0")},${(this.coords[1] + Math.floor(pixelY / 1e3)).toString().padStart(4, "0")},${(pixelX % 1e3).toString().padStart(3, "0")},${(pixelY % 1e3).toString().padStart(3, "0")}`; + templateTiles[templateTileName] = await createImageBitmap(canvas); + const canvasBlob = await canvas.convertToBlob(); + const canvasBuffer = await canvasBlob.arrayBuffer(); + const canvasBufferBytes = Array.from(new Uint8Array(canvasBuffer)); + templateTilesBuffers[templateTileName] = uint8ToBase64(canvasBufferBytes); + console.log(templateTiles); + pixelX += drawSizeX; + } + pixelY += drawSizeY; + } + console.log(`Parsing template took ${(Date.now() - timer) / 1e3} seconds`); + console.log("Template Tiles: ", templateTiles); + console.log("Template Tiles Buffers: ", templateTilesBuffers); + return { templateTiles, templateTilesBuffers }; + } + }; + _Template_instances = new WeakSet(); + /** Calculates the total pixels for each color for the template. + * + * @param {ImageData} imageData - The pre-shreaded template "casted" onto a canvas + * @param {Object} paletteBM - The palette Blue Marble uses for colors + * @param {Number} paletteTolerance - How close an RGB color has to be in order to be considered a palette color. A tolerance of "3" means the sum of the RGB can be up to 3 away from the actual value. + * @returns {Map} A map where the key is the color ID, and the value is the total pixels for that color ID + * @since 0.88.6 + */ + calculateTotalPixelsFromTemplateData_fn = function(imageData, paletteBM) { + const buffer32Arr = new Uint32Array(imageData.data.buffer); + const { palette, LUT: lookupTable } = paletteBM; + const _colorpalette = /* @__PURE__ */ new Map(); + for (let pixelIndex = 0; pixelIndex < buffer32Arr.length; pixelIndex++) { + const pixel = buffer32Arr[pixelIndex]; + let bestColorID = -2; + if (pixel >>> 24 == 0) { + bestColorID = 0; + } else { + bestColorID = lookupTable.get(pixel) ?? -2; + } + if (_colorpalette.get(bestColorID) == null) { + _colorpalette.set(bestColorID, 1); + } else { + _colorpalette.set(bestColorID, _colorpalette.get(bestColorID) + 1); + } + } + console.log(_colorpalette); + return _colorpalette; + }; + + // src/templateManager.js + var _TemplateManager_instances, loadTemplate_fn, storeTemplates_fn, parseBlueMarble_fn, parseOSU_fn; + var TemplateManager = class { + /** The constructor for the {@link TemplateManager} class. + * @since 0.55.8 + */ + constructor(name2, version2, overlay) { + __privateAdd(this, _TemplateManager_instances); + this.name = name2; + this.version = version2; + this.overlay = overlay; + this.templatesVersion = "1.0.0"; + this.userID = null; + this.encodingBase = "!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~"; + this.tileSize = 1e3; + this.drawMult = 3; + this.paletteTolerance = 3; + this.paletteBM = colorpaletteForBlueMarble(this.paletteTolerance); + this.canvasTemplate = null; + this.canvasTemplateZoomed = null; + this.canvasTemplateID = "bm-canvas"; + this.canvasMainID = "div#map canvas.maplibregl-canvas"; + this.template = null; + this.templateState = ""; + this.templatesArray = []; + this.templatesJSON = null; + this.templatesShouldBeDrawn = true; + } + /** Retrieves the pixel art canvas. + * If the canvas has been updated/replaced, it retrieves the new one. + * @param {string} selector - The CSS selector to use to find the canvas. + * @returns {HTMLCanvasElement|null} The canvas as an HTML Canvas Element, or null if the canvas does not exist + * @since 0.58.3 + * @deprecated Not in use since 0.63.25 + */ + getCanvas() { + if (document.body.contains(this.canvasTemplate)) { + return this.canvasTemplate; + } + document.getElementById(this.canvasTemplateID)?.remove(); + const canvasMain = document.querySelector(this.canvasMainID); + const canvasTemplateNew = document.createElement("canvas"); + canvasTemplateNew.id = this.canvasTemplateID; + canvasTemplateNew.className = "maplibregl-canvas"; + canvasTemplateNew.style.position = "absolute"; + canvasTemplateNew.style.top = "0"; + canvasTemplateNew.style.left = "0"; + canvasTemplateNew.style.height = `${canvasMain?.clientHeight * (window.devicePixelRatio || 1)}px`; + canvasTemplateNew.style.width = `${canvasMain?.clientWidth * (window.devicePixelRatio || 1)}px`; + canvasTemplateNew.height = canvasMain?.clientHeight * (window.devicePixelRatio || 1); + canvasTemplateNew.width = canvasMain?.clientWidth * (window.devicePixelRatio || 1); + canvasTemplateNew.style.zIndex = "8999"; + canvasTemplateNew.style.pointerEvents = "none"; + canvasMain?.parentElement?.appendChild(canvasTemplateNew); + this.canvasTemplate = canvasTemplateNew; + window.addEventListener("move", this.onMove); + window.addEventListener("zoom", this.onZoom); + window.addEventListener("resize", this.onResize); + return this.canvasTemplate; + } + /** Creates the JSON object to store templates in + * @returns {{ whoami: string, scriptVersion: string, schemaVersion: string, templates: Object }} The JSON object + * @since 0.65.4 + */ + async createJSON() { + return { + "whoami": this.name.replace(" ", ""), + // Name of userscript without spaces + "scriptVersion": this.version, + // Version of userscript + "schemaVersion": this.templatesVersion, + // Version of JSON schema + "templates": {} + // The templates + }; + } + /** Creates the template from the inputed file blob + * @param {File} blob - The file blob to create a template from + * @param {string} name - The display name of the template + * @param {Array} coords - The coordinates of the top left corner of the template + * @since 0.65.77 + */ + async createTemplate(blob, name2, coords2) { + if (!this.templatesJSON) { + this.templatesJSON = await this.createJSON(); + console.log(`Creating JSON...`); + } + this.overlay.handleDisplayStatus(`Creating template at ${coords2.join(", ")}...`); + const template = new Template({ + displayName: name2, + sortID: 0, + // Object.keys(this.templatesJSON.templates).length || 0, // Uncomment this to enable multiple templates (1/2) + authorID: numberToEncoded(this.userID || 0, this.encodingBase), + file: blob, + coords: coords2 + }); + const { templateTiles, templateTilesBuffers } = await template.createTemplateTiles(this.tileSize, this.paletteBM); + template.chunked = templateTiles; + const _pixels = { "total": template.pixelCount.total, "colors": Object.fromEntries(template.pixelCount.colors) }; + this.templatesJSON.templates[`${template.sortID} ${template.authorID}`] = { + "name": template.displayName, + // Display name of template + "coords": coords2.join(", "), + // The coords of the template + "enabled": true, + "pixels": _pixels, + // The total pixels in the template + "tiles": templateTilesBuffers + // Stores the chunked tile buffers + }; + this.templatesArray = []; + this.templatesArray.push(template); + this.overlay.handleDisplayStatus(`Template created at ${coords2.join(", ")}!`); + console.log(Object.keys(this.templatesJSON.templates).length); + console.log(this.templatesJSON); + console.log(this.templatesArray); + console.log(JSON.stringify(this.templatesJSON)); + await __privateMethod(this, _TemplateManager_instances, storeTemplates_fn).call(this); + } + /** Deletes a template from the JSON object. + * Also delete's the corrosponding {@link Template} class instance + */ + deleteTemplate() { + } + /** Disables the template from view + */ + async disableTemplate() { + if (!this.templatesJSON) { + this.templatesJSON = await this.createJSON(); + console.log(`Creating JSON...`); + } + } + /** Draws all templates on the specified tile. + * This method handles the rendering of template overlays on individual tiles. + * @param {File} tileBlob - The pixels that are placed on a tile + * @param {Array} tileCoords - The tile coordinates [x, y] + * @since 0.65.77 + */ + async drawTemplateOnTile(tileBlob, tileCoords) { + if (!this.templatesShouldBeDrawn) { + return tileBlob; + } + const drawSize = this.tileSize * this.drawMult; + tileCoords = tileCoords[0].toString().padStart(4, "0") + "," + tileCoords[1].toString().padStart(4, "0"); + console.log(`Searching for templates in tile: "${tileCoords}"`); + const templateArray = this.templatesArray; + console.log(templateArray); + templateArray.sort((a, b) => { + return a.sortID - b.sortID; + }); + console.log(templateArray); + const templatesToDraw = templateArray.map((template) => { + const matchingTiles = Object.keys(template.chunked).filter( + (tile) => tile.startsWith(tileCoords) + ); + if (matchingTiles.length === 0) { + return null; + } + const matchingTileBlobs = matchingTiles.map((tile) => { + const coords2 = tile.split(","); + return { + bitmap: template.chunked[tile], + tileCoords: [coords2[0], coords2[1]], + pixelCoords: [coords2[2], coords2[3]] + }; + }); + return matchingTileBlobs?.[0]; + }).filter(Boolean); + console.log(templatesToDraw); + const templateCount = templatesToDraw?.length || 0; + console.log(`templateCount = ${templateCount}`); + if (templateCount > 0) { + const totalPixels = templateArray.filter((template) => { + const matchingTiles = Object.keys(template.chunked).filter( + (tile) => tile.startsWith(tileCoords) + ); + return matchingTiles.length > 0; + }).reduce((sum, template) => sum + (template.pixelCount.total || 0), 0); + const pixelCountFormatted = new Intl.NumberFormat().format(totalPixels); + this.overlay.handleDisplayStatus( + `Displaying ${templateCount} template${templateCount == 1 ? "" : "s"}. +Total pixels: ${pixelCountFormatted}` + ); + } else { + this.overlay.handleDisplayStatus(`Sleeping +Version: ${this.version}`); + return tileBlob; + } + const tileBitmap = await createImageBitmap(tileBlob); + const canvas = new OffscreenCanvas(drawSize, drawSize); + const context = canvas.getContext("2d"); + context.imageSmoothingEnabled = false; + context.beginPath(); + context.rect(0, 0, drawSize, drawSize); + context.clip(); + context.clearRect(0, 0, drawSize, drawSize); + context.drawImage(tileBitmap, 0, 0, drawSize, drawSize); + for (const template of templatesToDraw) { + console.log(`Template:`); + console.log(template); + context.drawImage(template.bitmap, Number(template.pixelCoords[0]) * this.drawMult, Number(template.pixelCoords[1]) * this.drawMult); + } + return await canvas.convertToBlob({ type: "image/png" }); + } + /** Imports the JSON object, and appends it to any JSON object already loaded + * @param {string} json - The JSON string to parse + */ + importJSON(json) { + console.log(`Importing JSON...`); + console.log(json); + if (json?.whoami == "BlueMarble") { + __privateMethod(this, _TemplateManager_instances, parseBlueMarble_fn).call(this, json); + } + } + /** Sets the `templatesShouldBeDrawn` boolean to a value. + * @param {boolean} value - The value to set the boolean to + * @since 0.73.7 + */ + setTemplatesShouldBeDrawn(value) { + this.templatesShouldBeDrawn = value; + } + }; + _TemplateManager_instances = new WeakSet(); + /** Generates a {@link Template} class instance from the JSON object template + */ + loadTemplate_fn = function() { + }; + storeTemplates_fn = async function() { + GM.setValue("bmTemplates", JSON.stringify(this.templatesJSON)); + }; + parseBlueMarble_fn = async function(json) { + console.log(`Parsing BlueMarble...`); + const templates = json.templates; + console.log(`BlueMarble length: ${Object.keys(templates).length}`); + if (Object.keys(templates).length > 0) { + for (const template in templates) { + const templateKey = template; + const templateValue = templates[template]; + console.log(`Template Key: ${templateKey}`); + if (templates.hasOwnProperty(template)) { + const templateKeyArray = templateKey.split(" "); + const sortID = Number(templateKeyArray?.[0]); + const authorID = templateKeyArray?.[1] || "0"; + const displayName = templateValue.name || `Template ${sortID || ""}`; + const pixelCount = { + total: templateValue.pixels.total, + colors: new Map(Object.entries(templateValue.pixels.colors).map(([key, value]) => [Number(key), value])) + }; + console.log(pixelCount); + const tilesbase64 = templateValue.tiles; + const templateTiles = {}; + for (const tile in tilesbase64) { + console.log(tile); + if (tilesbase64.hasOwnProperty(tile)) { + const encodedTemplateBase64 = tilesbase64[tile]; + const templateUint8Array = base64ToUint8(encodedTemplateBase64); + const templateBlob = new Blob([templateUint8Array], { type: "image/png" }); + const templateBitmap = await createImageBitmap(templateBlob); + templateTiles[tile] = templateBitmap; + } + } + const template2 = new Template({ + displayName, + sortID: sortID || this.templatesArray?.length || 0, + authorID: authorID || "" + //coords: coords, + }); + template2.pixelCount = pixelCount; + template2.chunked = templateTiles; + this.templatesArray.push(template2); + console.log(this.templatesArray); + console.log(`^^^ This ^^^`); + } + } + } + }; + /** Parses the OSU! Place JSON object + */ + parseOSU_fn = function() { + }; + + // src/apiManager.js + var _ApiManager_instances, getBrowserFromUA_fn, getOS_fn; + var ApiManager = class { + /** Constructor for ApiManager class + * @param {TemplateManager} templateManager + * @since 0.11.34 + */ + constructor(templateManager2) { + __privateAdd(this, _ApiManager_instances); + this.templateManager = templateManager2; + this.disableAll = false; + this.coordsTilePixel = []; + this.templateCoordsTilePixel = []; + } + /** Determines if the spontaneously received response is something we want. + * Otherwise, we can ignore it. + * Note: Due to aggressive compression, make your calls like `data['jsonData']['name']` instead of `data.jsonData.name` + * + * @param {Overlay} overlay - The Overlay class instance + * @since 0.11.1 + */ + spontaneousResponseListener(overlay) { + window.addEventListener("message", async (event) => { + const data = event.data; + const dataJSON = data["jsonData"]; + if (!(data && data["source"] === "blue-marble")) { + return; + } + if (!data["endpoint"]) { + return; + } + const endpointText = data["endpoint"]?.split("?")[0].split("/").filter((s) => s && isNaN(Number(s))).filter((s) => s && !s.includes(".")).pop(); + console.log(`%cBlue Marble%c: Recieved message about "%s"`, "color: cornflowerblue;", "", endpointText); + switch (endpointText) { + case "me": + if (dataJSON["status"] && dataJSON["status"]?.toString()[0] != "2") { + overlay.handleDisplayError(`You are not logged in! +Could not fetch userdata.`); + return; + } + const nextLevelPixels = Math.ceil(Math.pow(Math.floor(dataJSON["level"]) * Math.pow(30, 0.65), 1 / 0.65) - dataJSON["pixelsPainted"]); + console.log(dataJSON["id"]); + if (!!dataJSON["id"] || dataJSON["id"] === 0) { + console.log(numberToEncoded( + dataJSON["id"], + "!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~" + )); + } + this.templateManager.userID = dataJSON["id"]; + overlay.updateInnerHTML("bm-user-droplets", `Droplets: ${new Intl.NumberFormat().format(dataJSON["droplets"])}`); + overlay.updateInnerHTML("bm-user-nextlevel", `Next level in ${new Intl.NumberFormat().format(nextLevelPixels)} pixel${nextLevelPixels == 1 ? "" : "s"}`); + break; + case "pixel": + const coordsTile = data["endpoint"].split("?")[0].split("/").filter((s) => s && !isNaN(Number(s))); + const payloadExtractor = new URLSearchParams(data["endpoint"].split("?")[1]); + const coordsPixel = [payloadExtractor.get("x"), payloadExtractor.get("y")]; + if (this.coordsTilePixel.length && (!coordsTile.length || !coordsPixel.length)) { + overlay.handleDisplayError(`Coordinates are malformed! +Did you try clicking the canvas first?`); + return; + } + this.coordsTilePixel = [...coordsTile, ...coordsPixel]; + const displayTP = serverTPtoDisplayTP(coordsTile, coordsPixel); + const spanElements = document.querySelectorAll("span"); + for (const element of spanElements) { + if (element.textContent.trim().includes(`${displayTP[0]}, ${displayTP[1]}`)) { + let displayCoords = document.querySelector("#bm-display-coords"); + const text = `(Tl X: ${coordsTile[0]}, Tl Y: ${coordsTile[1]}, Px X: ${coordsPixel[0]}, Px Y: ${coordsPixel[1]})`; + if (!displayCoords) { + displayCoords = document.createElement("span"); + displayCoords.id = "bm-display-coords"; + displayCoords.textContent = text; + displayCoords.style = "margin-left: calc(var(--spacing)*3); font-size: small;"; + element.parentNode.parentNode.insertAdjacentElement("afterend", displayCoords); + } else { + displayCoords.textContent = text; + } + } + } + break; + case "tiles": + let tileCoordsTile = data["endpoint"].split("/"); + tileCoordsTile = [parseInt(tileCoordsTile[tileCoordsTile.length - 2]), parseInt(tileCoordsTile[tileCoordsTile.length - 1].replace(".png", ""))]; + const blobUUID = data["blobID"]; + const blobData = data["blobData"]; + const templateBlob = await this.templateManager.drawTemplateOnTile(blobData, tileCoordsTile); + window.postMessage({ + source: "blue-marble", + blobID: blobUUID, + blobData: templateBlob, + blink: data["blink"] + }); + break; + case "robots": + this.disableAll = dataJSON["userscript"]?.toString().toLowerCase() == "false"; + break; + } + }); + } + // Sends a heartbeat to the telemetry server + async sendHeartbeat(version2) { + console.log("Sending heartbeat to telemetry server..."); + let userSettings2 = GM_getValue("bmUserSettings", "{}"); + userSettings2 = JSON.parse(userSettings2); + if (!userSettings2 || !userSettings2.telemetry || !userSettings2.uuid) { + console.log("Telemetry is disabled, not sending heartbeat."); + return; + } + const ua = navigator.userAgent; + let browser = await __privateMethod(this, _ApiManager_instances, getBrowserFromUA_fn).call(this, ua); + let os = __privateMethod(this, _ApiManager_instances, getOS_fn).call(this, ua); + GM_xmlhttpRequest({ + method: "POST", + url: "https://telemetry.thebluecorner.net/heartbeat", + headers: { + "Content-Type": "application/json" + }, + data: JSON.stringify({ + uuid: userSettings2.uuid, + version: version2, + browser, + os + }), + onload: (response) => { + if (response.status !== 200) { + consoleError("Failed to send heartbeat:", response.statusText); + } + }, + onerror: (error) => { + consoleError("Error sending heartbeat:", error); + } + }); + } + }; + _ApiManager_instances = new WeakSet(); + getBrowserFromUA_fn = async function(ua = navigator.userAgent) { + ua = ua || ""; + if (ua.includes("OPR/") || ua.includes("Opera")) return "Opera"; + if (ua.includes("Edg/")) return "Edge"; + if (ua.includes("Vivaldi")) return "Vivaldi"; + if (ua.includes("YaBrowser")) return "Yandex"; + if (ua.includes("Kiwi")) return "Kiwi"; + if (ua.includes("Brave")) return "Brave"; + if (ua.includes("Firefox/")) return "Firefox"; + if (ua.includes("Chrome/")) return "Chrome"; + if (ua.includes("Safari/")) return "Safari"; + if (navigator.brave && typeof navigator.brave.isBrave === "function") { + if (await navigator.brave.isBrave()) return "Brave"; + } + return "Unknown"; + }; + getOS_fn = function(ua = navigator.userAgent) { + ua = ua || ""; + if (/Windows NT 11/i.test(ua)) return "Windows 11"; + if (/Windows NT 10/i.test(ua)) return "Windows 10"; + if (/Windows NT 6\.3/i.test(ua)) return "Windows 8.1"; + if (/Windows NT 6\.2/i.test(ua)) return "Windows 8"; + if (/Windows NT 6\.1/i.test(ua)) return "Windows 7"; + if (/Windows NT 6\.0/i.test(ua)) return "Windows Vista"; + if (/Windows NT 5\.1|Windows XP/i.test(ua)) return "Windows XP"; + if (/Mac OS X 10[_\.]15/i.test(ua)) return "macOS Catalina"; + if (/Mac OS X 10[_\.]14/i.test(ua)) return "macOS Mojave"; + if (/Mac OS X 10[_\.]13/i.test(ua)) return "macOS High Sierra"; + if (/Mac OS X 10[_\.]12/i.test(ua)) return "macOS Sierra"; + if (/Mac OS X 10[_\.]11/i.test(ua)) return "OS X El Capitan"; + if (/Mac OS X 10[_\.]10/i.test(ua)) return "OS X Yosemite"; + if (/Mac OS X 10[_\.]/i.test(ua)) return "macOS"; + if (/Android/i.test(ua)) return "Android"; + if (/iPhone|iPad|iPod/i.test(ua)) return "iOS"; + if (/Linux/i.test(ua)) return "Linux"; + return "Unknown"; + }; + + // src/main.js + var name = GM_info.script.name.toString(); + var version = GM_info.script.version.toString(); + var consoleStyle = "color: cornflowerblue;"; + function inject(callback) { + const script = document.createElement("script"); + script.setAttribute("bm-name", name); + script.setAttribute("bm-cStyle", consoleStyle); + script.textContent = `(${callback})();`; + document.documentElement?.appendChild(script); + script.remove(); + } + inject(() => { + const script = document.currentScript; + const name2 = script?.getAttribute("bm-name") || "Blue Marble"; + const consoleStyle2 = script?.getAttribute("bm-cStyle") || ""; + const fetchedBlobQueue = /* @__PURE__ */ new Map(); + window.addEventListener("message", (event) => { + const { source, endpoint, blobID, blobData, blink } = event.data; + const elapsed = Date.now() - blink; + console.groupCollapsed(`%c${name2}%c: ${fetchedBlobQueue.size} Recieved IMAGE message about blob "${blobID}"`, consoleStyle2, ""); + console.log(`Blob fetch took %c${String(Math.floor(elapsed / 6e4)).padStart(2, "0")}:${String(Math.floor(elapsed / 1e3) % 60).padStart(2, "0")}.${String(elapsed % 1e3).padStart(3, "0")}%c MM:SS.mmm`, consoleStyle2, ""); + console.log(fetchedBlobQueue); + console.groupEnd(); + if (source == "blue-marble" && !!blobID && !!blobData && !endpoint) { + const callback = fetchedBlobQueue.get(blobID); + if (typeof callback === "function") { + callback(blobData); + } else { + consoleWarn(`%c${name2}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`, consoleStyle2, "", blobID); + } + fetchedBlobQueue.delete(blobID); + } + }); + const originalFetch = window.fetch; + window.fetch = async function(...args) { + const response = await originalFetch.apply(this, args); + const cloned = response.clone(); + const endpointName = (args[0] instanceof Request ? args[0]?.url : args[0]) || "ignore"; + const contentType = cloned.headers.get("content-type") || ""; + if (contentType.includes("application/json")) { + console.log(`%c${name2}%c: Sending JSON message about endpoint "${endpointName}"`, consoleStyle2, ""); + cloned.json().then((jsonData) => { + window.postMessage({ + source: "blue-marble", + endpoint: endpointName, + jsonData + }, "*"); + }).catch((err) => { + console.error(`%c${name2}%c: Failed to parse JSON: `, consoleStyle2, "", err); + }); + } else if (contentType.includes("image/") && (!endpointName.includes("openfreemap") && !endpointName.includes("maps"))) { + const blink = Date.now(); + const blob = await cloned.blob(); + console.log(`%c${name2}%c: ${fetchedBlobQueue.size} Sending IMAGE message about endpoint "${endpointName}"`, consoleStyle2, ""); + return new Promise((resolve) => { + const blobUUID = crypto.randomUUID(); + fetchedBlobQueue.set(blobUUID, (blobProcessed) => { + resolve(new Response(blobProcessed, { + headers: cloned.headers, + status: cloned.status, + statusText: cloned.statusText + })); + console.log(`%c${name2}%c: ${fetchedBlobQueue.size} Processed blob "${blobUUID}"`, consoleStyle2, ""); + }); + window.postMessage({ + source: "blue-marble", + endpoint: endpointName, + blobID: blobUUID, + blobData: blob, + blink + }); + }).catch((exception) => { + const elapsed = Date.now(); + console.error(`%c${name2}%c: Failed to Promise blob!`, consoleStyle2, ""); + console.groupCollapsed(`%c${name2}%c: Details of failed blob Promise:`, consoleStyle2, ""); + console.log(`Endpoint: ${endpointName} +There are ${fetchedBlobQueue.size} blobs processing... +Blink: ${blink.toLocaleString()} +Time Since Blink: ${String(Math.floor(elapsed / 6e4)).padStart(2, "0")}:${String(Math.floor(elapsed / 1e3) % 60).padStart(2, "0")}.${String(elapsed % 1e3).padStart(3, "0")} MM:SS.mmm`); + console.error(`Exception stack:`, exception); + console.groupEnd(); + }); + } + return response; + }; + }); + var cssOverlay = GM_getResourceText("CSS-BM-File"); + GM_addStyle(cssOverlay); + var robotoMonoInjectionPoint = "robotoMonoInjectionPoint"; + if (!!(robotoMonoInjectionPoint.indexOf("@font-face") + 1)) { + console.log(`Loading Roboto Mono as a file...`); + GM_addStyle(robotoMonoInjectionPoint); + } else { + stylesheetLink = document.createElement("link"); + stylesheetLink.href = "https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap"; + stylesheetLink.rel = "preload"; + stylesheetLink.as = "style"; + stylesheetLink.onload = function() { + this.onload = null; + this.rel = "stylesheet"; + }; + document.head?.appendChild(stylesheetLink); + } + var stylesheetLink; + var observers = new Observers(); + var overlayMain = new Overlay(name, version); + var overlayTabTemplate = new Overlay(name, version); + var templateManager = new TemplateManager(name, version, overlayMain); + var apiManager = new ApiManager(templateManager); + overlayMain.setApiManager(apiManager); + var storageTemplates = JSON.parse(GM_getValue("bmTemplates", "{}")); + console.log(storageTemplates); + templateManager.importJSON(storageTemplates); + var userSettings = JSON.parse(GM_getValue("bmUserSettings", "{}")); + console.log(userSettings); + console.log(Object.keys(userSettings).length); + if (Object.keys(userSettings).length == 0) { + const uuid = crypto.randomUUID(); + console.log(uuid); + GM.setValue("bmUserSettings", JSON.stringify({ + "uuid": uuid + })); + } + setInterval(() => apiManager.sendHeartbeat(version), 1e3 * 60 * 30); + console.log(`Telemetry is ${!(userSettings?.telemetry == void 0)}`); + if (userSettings?.telemetry == void 0 || userSettings?.telemetry > 1) { + const telemetryOverlay = new Overlay(name, version); + telemetryOverlay.setApiManager(apiManager); + buildTelemetryOverlay(telemetryOverlay); + } + buildOverlayMain(); + overlayMain.handleDrag("#bm-overlay", "#bm-bar-drag"); + apiManager.spontaneousResponseListener(overlayMain); + observeBlack(); + consoleLog(`%c${name}%c (${version}) userscript has loaded!`, "color: cornflowerblue;", ""); + function observeBlack() { + const observer = new MutationObserver((mutations, observer2) => { + const black = document.querySelector("#color-1"); + if (!black) { + return; + } + let move = document.querySelector("#bm-button-move"); + if (!move) { + move = document.createElement("button"); + move.id = "bm-button-move"; + move.textContent = "Move \u2191"; + move.className = "btn btn-soft"; + move.onclick = function() { + const roundedBox = this.parentNode.parentNode.parentNode.parentNode; + const shouldMoveUp = this.textContent == "Move \u2191"; + roundedBox.parentNode.className = roundedBox.parentNode.className.replace(shouldMoveUp ? "bottom" : "top", shouldMoveUp ? "top" : "bottom"); + roundedBox.style.borderTopLeftRadius = shouldMoveUp ? "0px" : "var(--radius-box)"; + roundedBox.style.borderTopRightRadius = shouldMoveUp ? "0px" : "var(--radius-box)"; + roundedBox.style.borderBottomLeftRadius = shouldMoveUp ? "var(--radius-box)" : "0px"; + roundedBox.style.borderBottomRightRadius = shouldMoveUp ? "var(--radius-box)" : "0px"; + this.textContent = shouldMoveUp ? "Move \u2193" : "Move \u2191"; + }; + const paintPixel = black.parentNode.parentNode.parentNode.parentNode.querySelector("h2"); + paintPixel.parentNode?.appendChild(move); + } + }); + observer.observe(document.body, { childList: true, subtree: true }); + } + function buildOverlayMain() { + let isMinimized = false; + overlayMain.addDiv({ "id": "bm-overlay", "style": "top: 10px; right: 75px;" }).addDiv({ "id": "bm-contain-header" }).addDiv({ "id": "bm-bar-drag" }).buildElement().addImg( + { "alt": "Blue Marble Icon - Click to minimize/maximize", "src": "https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png", "style": "cursor: pointer;" }, + (instance, img) => { + img.addEventListener("click", () => { + isMinimized = !isMinimized; + const overlay = document.querySelector("#bm-overlay"); + const header = document.querySelector("#bm-contain-header"); + const dragBar = document.querySelector("#bm-bar-drag"); + const coordsContainer = document.querySelector("#bm-contain-coords"); + const coordsButton = document.querySelector("#bm-button-coords"); + const createButton = document.querySelector("#bm-button-create"); + const enableButton = document.querySelector("#bm-button-enable"); + const disableButton = document.querySelector("#bm-button-disable"); + const coordInputs = document.querySelectorAll("#bm-contain-coords input"); + if (!isMinimized) { + overlay.style.width = "auto"; + overlay.style.maxWidth = "300px"; + overlay.style.minWidth = "200px"; + overlay.style.padding = "10px"; + } + const elementsToToggle = [ + "#bm-overlay h1", + // Main title "Blue Marble" + "#bm-contain-userinfo", + // User information section (username, droplets, level) + "#bm-overlay hr", + // Visual separator lines + "#bm-contain-automation > *:not(#bm-contain-coords)", + // Automation section excluding coordinates + "#bm-input-file-template", + // Template file upload interface + "#bm-contain-buttons-action", + // Action buttons container + `#${instance.outputStatusId}` + // Status log textarea for user feedback + ]; + elementsToToggle.forEach((selector) => { + const elements = document.querySelectorAll(selector); + elements.forEach((element) => { + element.style.display = isMinimized ? "none" : ""; + }); + }); + if (isMinimized) { + if (coordsContainer) { + coordsContainer.style.display = "none"; + } + if (coordsButton) { + coordsButton.style.display = "none"; + } + if (createButton) { + createButton.style.display = "none"; + } + if (enableButton) { + enableButton.style.display = "none"; + } + if (disableButton) { + disableButton.style.display = "none"; + } + coordInputs.forEach((input) => { + input.style.display = "none"; + }); + overlay.style.width = "60px"; + overlay.style.height = "76px"; + overlay.style.maxWidth = "60px"; + overlay.style.minWidth = "60px"; + overlay.style.padding = "8px"; + img.style.marginLeft = "3px"; + header.style.textAlign = "center"; + header.style.margin = "0"; + header.style.marginBottom = "0"; + if (dragBar) { + dragBar.style.display = ""; + dragBar.style.marginBottom = "0.25em"; + } + } else { + if (coordsContainer) { + coordsContainer.style.display = ""; + coordsContainer.style.flexDirection = ""; + coordsContainer.style.justifyContent = ""; + coordsContainer.style.alignItems = ""; + coordsContainer.style.gap = ""; + coordsContainer.style.textAlign = ""; + coordsContainer.style.margin = ""; + } + if (coordsButton) { + coordsButton.style.display = ""; + } + if (createButton) { + createButton.style.display = ""; + createButton.style.marginTop = ""; + } + if (enableButton) { + enableButton.style.display = ""; + enableButton.style.marginTop = ""; + } + if (disableButton) { + disableButton.style.display = ""; + disableButton.style.marginTop = ""; + } + coordInputs.forEach((input) => { + input.style.display = ""; + }); + img.style.marginLeft = ""; + overlay.style.padding = "10px"; + header.style.textAlign = ""; + header.style.margin = ""; + header.style.marginBottom = ""; + if (dragBar) { + dragBar.style.marginBottom = "0.5em"; + } + overlay.style.width = ""; + overlay.style.height = ""; + } + img.alt = isMinimized ? "Blue Marble Icon - Minimized (Click to maximize)" : "Blue Marble Icon - Maximized (Click to minimize)"; + }); + } + ).buildElement().addHeader(1, { "textContent": name }).buildElement().buildElement().addHr().buildElement().addDiv({ "id": "bm-contain-userinfo" }).addP({ "id": "bm-user-droplets", "textContent": "Droplets:" }).buildElement().addP({ "id": "bm-user-nextlevel", "textContent": "Next level in..." }).buildElement().buildElement().addHr().buildElement().addDiv({ "id": "bm-contain-automation" }).addDiv({ "id": "bm-contain-coords" }).addButton( + { "id": "bm-button-coords", "className": "bm-help", "style": "margin-top: 0;", "innerHTML": '' }, + (instance, button) => { + button.onclick = () => { + const coords2 = instance.apiManager?.coordsTilePixel; + if (!coords2?.[0]) { + instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); + return; + } + instance.updateInnerHTML("bm-input-tx", coords2?.[0] || ""); + instance.updateInnerHTML("bm-input-ty", coords2?.[1] || ""); + instance.updateInnerHTML("bm-input-px", coords2?.[2] || ""); + instance.updateInnerHTML("bm-input-py", coords2?.[3] || ""); + }; + } + ).buildElement().addInput({ "type": "number", "id": "bm-input-tx", "placeholder": "Tl X", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { + input.addEventListener("paste", (event) => { + let splitText = (event.clipboardData || window.clipboardData).getData("text").split(" ").filter((n) => n).map(Number).filter((n) => !isNaN(n)); + if (splitText.length !== 4) { + return; + } + let coords2 = selectAllCoordinateInputs(document); + for (let i = 0; i < coords2.length; i++) { + coords2[i].value = splitText[i]; + } + event.preventDefault(); + }); + const handler = () => persistCoords(); + input.addEventListener("input", handler); + input.addEventListener("change", handler); + }).buildElement().addInput({ "type": "number", "id": "bm-input-ty", "placeholder": "Tl Y", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { + const handler = () => persistCoords(); + input.addEventListener("input", handler); + input.addEventListener("change", handler); + }).buildElement().addInput({ "type": "number", "id": "bm-input-px", "placeholder": "Px X", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { + const handler = () => persistCoords(); + input.addEventListener("input", handler); + input.addEventListener("change", handler); + }).buildElement().addInput({ "type": "number", "id": "bm-input-py", "placeholder": "Px Y", "min": 0, "max": 2047, "step": 1, "required": true }, (instance, input) => { + const handler = () => persistCoords(); + input.addEventListener("input", handler); + input.addEventListener("change", handler); + }).buildElement().buildElement().addInputFile({ "id": "bm-input-file-template", "textContent": "Upload Template", "accept": "image/png, image/jpeg, image/webp, image/bmp, image/gif" }).buildElement().addDiv({ "id": "bm-contain-buttons-template" }).addButton({ "id": "bm-button-enable", "textContent": "Enable" }, (instance, button) => { + button.onclick = () => { + instance.apiManager?.templateManager?.setTemplatesShouldBeDrawn(true); + instance.handleDisplayStatus(`Enabled templates!`); + }; + }).buildElement().addButton({ "id": "bm-button-create", "textContent": "Create" }, (instance, button) => { + button.onclick = () => { + const input = document.querySelector("#bm-input-file-template"); + const coordTlX = document.querySelector("#bm-input-tx"); + if (!coordTlX.checkValidity()) { + coordTlX.reportValidity(); + instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); + return; + } + const coordTlY = document.querySelector("#bm-input-ty"); + if (!coordTlY.checkValidity()) { + coordTlY.reportValidity(); + instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); + return; + } + const coordPxX = document.querySelector("#bm-input-px"); + if (!coordPxX.checkValidity()) { + coordPxX.reportValidity(); + instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); + return; + } + const coordPxY = document.querySelector("#bm-input-py"); + if (!coordPxY.checkValidity()) { + coordPxY.reportValidity(); + instance.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?"); + return; + } + if (!input?.files[0]) { + instance.handleDisplayError(`No file selected!`); + return; + } + templateManager.createTemplate(input.files[0], input.files[0]?.name.replace(/\.[^/.]+$/, ""), [Number(coordTlX.value), Number(coordTlY.value), Number(coordPxX.value), Number(coordPxY.value)]); + instance.handleDisplayStatus(`Drew to canvas!`); + }; + }).buildElement().addButton({ "id": "bm-button-disable", "textContent": "Disable" }, (instance, button) => { + button.onclick = () => { + instance.apiManager?.templateManager?.setTemplatesShouldBeDrawn(false); + instance.handleDisplayStatus(`Disabled templates!`); + }; + }).buildElement().buildElement().addTextarea({ "id": overlayMain.outputStatusId, "placeholder": `Status: Sleeping... +Version: ${version}`, "readOnly": true }).buildElement().addDiv({ "id": "bm-contain-buttons-action" }).addDiv().addButton( + { "id": "bm-button-convert", "className": "bm-help", "innerHTML": "\u{1F3A8}", "title": "Template Color Converter" }, + (instance, button) => { + button.addEventListener("click", () => { + window.open("https://pepoafonso.github.io/color_converter_wplace/", "_blank", "noopener noreferrer"); + }); + } + ).buildElement().addButton( + { "id": "bm-button-website", "className": "bm-help", "innerHTML": "\u{1F310}", "title": "Official Blue Marble Website" }, + (instance, button) => { + button.addEventListener("click", () => { + window.open("https://bluemarble.lol/", "_blank", "noopener noreferrer"); + }); + } + ).buildElement().buildElement().addSmall({ "textContent": "Made by SwingTheVine", "style": "margin-top: auto;" }).buildElement().buildElement().buildElement().buildOverlay(document.body); + } + function buildTelemetryOverlay(overlay) { + overlay.addDiv({ "id": "bm-overlay-telemetry", style: "top: 0px; left: 0px; width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; z-index: 9999;" }).addDiv({ "id": "bm-contain-all-telemetry", style: "display: flex; flex-direction: column; align-items: center;" }).addDiv({ "id": "bm-contain-header-telemetry", style: "margin-top: 10%;" }).addHeader(1, { "textContent": `${name} Telemetry` }).buildElement().buildElement().addDiv({ "id": "bm-contain-telemetry", style: "max-width: 50%; overflow-y: auto; max-height: 80vh;" }).addHr().buildElement().addBr().buildElement().addDiv({ "style": "width: fit-content; margin: auto; text-align: center;" }).addButton({ "id": "bm-button-telemetry-more", "textContent": "More Information" }, (instance, button) => { + button.onclick = () => { + window.open("https://github.com/SwingTheVine/Wplace-TelemetryServer#telemetry-data", "_blank", "noopener noreferrer"); + }; + }).buildElement().buildElement().addBr().buildElement().addDiv({ style: "width: fit-content; margin: auto; text-align: center;" }).addButton({ "id": "bm-button-telemetry-enable", "textContent": "Enable Telemetry", "style": "margin-right: 2ch;" }, (instance, button) => { + button.onclick = () => { + const userSettings2 = JSON.parse(GM_getValue("bmUserSettings", "{}")); + userSettings2.telemetry = 1; + GM.setValue("bmUserSettings", JSON.stringify(userSettings2)); + const element = document.getElementById("bm-overlay-telemetry"); + if (element) { + element.style.display = "none"; + } + }; + }).buildElement().addButton({ "id": "bm-button-telemetry-disable", "textContent": "Disable Telemetry" }, (instance, button) => { + button.onclick = () => { + const userSettings2 = JSON.parse(GM_getValue("bmUserSettings", "{}")); + userSettings2.telemetry = 0; + GM.setValue("bmUserSettings", JSON.stringify(userSettings2)); + const element = document.getElementById("bm-overlay-telemetry"); + if (element) { + element.style.display = "none"; + } + }; + }).buildElement().buildElement().addBr().buildElement().addP({ "textContent": "We collect anonymous telemetry data such as your browser, OS, and script version to make the experience better for everyone. The data is never shared personally. The data is never sold. You can turn this off by pressing the 'Disable' button, but keeping it on helps us improve features and reliability faster. Thank you for supporting the Blue Marble!" }).buildElement().addP({ "textContent": 'You can disable telemetry by pressing the "Disable" button below.' }).buildElement().buildElement().buildElement().buildOverlay(document.body); + } +})(); diff --git a/dist/BlueMarbleStandalone.user.js b/dist/BlueMarble-Standalone.user.js similarity index 89% rename from dist/BlueMarbleStandalone.user.js rename to dist/BlueMarble-Standalone.user.js index e53eaea..d5d7d87 100644 --- a/dist/BlueMarbleStandalone.user.js +++ b/dist/BlueMarble-Standalone.user.js @@ -1,27 +1,30 @@ // ==UserScript== -// @name Blue Marble -// @namespace https://github.com/SwingTheVine/ -// @version 0.88.71 -// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. -// @author SwingTheVine -// @license MPL-2.0 -// @supportURL https://discord.gg/tpeBPy46hf -// @homepageURL https://bluemarble.lol/ -// @icon64 data:image/png;base64,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 -// @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js -// @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js -// @match https://wplace.live/* -// @grant GM_getResourceText -// @grant GM_addStyle -// @grant GM.setValue -// @grant GM_getValue -// @grant GM_xmlhttpRequest -// @connect telemetry.thebluecorner.net +// @name Blue Marble +// @name:en Blue Marble +// @namespace https://github.com/SwingTheVine/ +// @version 0.88.77 +// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. +// @description:en A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. +// @author SwingTheVine +// @license MPL-2.0 +// @donate https://ko-fi.com/swingthevine +// @supportURL https://discord.gg/tpeBPy46hf +// @homepageURL https://bluemarble.lol/ +// @icon64 data:image/png;base64,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 +// @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-Standalone.user.js +// @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble-Standalone.user.js +// @match https://wplace.live/* +// @grant GM_getResourceText +// @grant GM_addStyle +// @grant GM.setValue +// @grant GM_getValue +// @grant GM_xmlhttpRequest +// @connect telemetry.thebluecorner.net +// @antifeature tracking Anonymous opt-in telemetry data // @noframes // ==/UserScript== // Wplace --> https://wplace.live // License --> https://www.mozilla.org/en-US/MPL/2.0/ -// Donate --> https://ko-fi.com/swingthevine (()=>{var e,t,n=e=>{throw TypeError(e)},i=(e,t,i)=>t.has(e)?n("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),o=(e,t,i)=>(((e,t)=>{t.has(e)||n("Cannot access private method")})(e,t),i),s=class{constructor(t,n){i(this,e),this.name=t,this.version=n,this.t=null,this.i="bm-o",this.o=null,this.l=null,this.m=[]}u(e){this.t=e}h(){return this.m.length>0&&(this.l=this.m.pop()),this}p(e){e?.appendChild(this.o),this.o=null,this.l=null,this.m=[]}v(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"div",{},n)),this}S(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"p",{},n)),this}$(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"small",{},n)),this}M(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"img",{},n)),this}T(n,i={},s=()=>{}){return s(this,o(this,e,t).call(this,"h"+n,{},i)),this}k(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"hr",{},n)),this}D(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"br",{},n)),this}O(n={},i=()=>{}){const s=o(this,e,t).call(this,"label",{textContent:n.textContent??""});delete n.textContent;const a=o(this,e,t).call(this,"input",{type:"checkbox"},n);return s.insertBefore(a,s.firstChild),this.h(),i(this,s,a),this}C(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"button",{},n)),this}N(n={},i=()=>{}){const s=n.title??n.textContent??"Help: No info";delete n.textContent,n.title=`Help: ${s}`;const a={textContent:"?",className:"bm-D",onclick:()=>{this.B(this.i,s)}};return i(this,o(this,e,t).call(this,"button",a,n)),this}I(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"input",{},n)),this}L(n={},i=()=>{}){const s=n.textContent??"";delete n.textContent;const a=o(this,e,t).call(this,"div"),r=o(this,e,t).call(this,"input",{type:"file",style:"display: none !important; visibility: hidden !important; position: absolute !important; left: -9999px !important; width: 0 !important; height: 0 !important; opacity: 0 !important;"},n);this.h();const l=o(this,e,t).call(this,"button",{textContent:s});return this.h(),this.h(),r.setAttribute("tabindex","-1"),r.setAttribute("aria-hidden","true"),l.addEventListener("click",()=>{r.click()}),r.addEventListener("change",()=>{l.style.maxWidth=`${l.offsetWidth}px`,r.files.length>0?l.textContent=r.files[0].name:l.textContent=s}),i(this,a,r,l),this}P(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"textarea",{},n)),this}B(e,t,n=!1){const i=document.getElementById(e.replace(/^#/,""));i&&(i instanceof HTMLInputElement?i.value=t:n?i.textContent=t:i.innerHTML=t)}G(e,t){let n,i=!1,o=0,s=null,a=0,r=0,l=0,m=0;if(e=document.querySelector("#"==e?.[0]?e:"#"+e),t=document.querySelector("#"==t?.[0]?t:"#"+t),!e||!t)return void this.W(`Can not drag! ${e?"":"moveMe"} ${e||t?"":"and "}${t?"":"iMoveThings "}was not found!`);const c=()=>{if(i){const t=Math.abs(a-l),n=Math.abs(r-m);(t>.5||n>.5)&&(a=l,r=m,e.style.transform=`translate(${a}px, ${r}px)`,e.style.left="0px",e.style.top="0px",e.style.right=""),s=requestAnimationFrame(c)}};let u=null;const d=(d,h)=>{i=!0,u=e.getBoundingClientRect(),n=d-u.left,o=h-u.top;const b=window.getComputedStyle(e).transform;if(b&&"none"!==b){const e=new DOMMatrix(b);a=e.m41,r=e.m42}else a=u.left,r=u.top;l=a,m=r,document.body.style.userSelect="none",t.classList.add("dragging"),s&&cancelAnimationFrame(s),c()},h=()=>{i=!1,s&&(cancelAnimationFrame(s),s=null),document.body.style.userSelect="",t.classList.remove("dragging")};t.addEventListener("mousedown",function(e){e.preventDefault(),d(e.clientX,e.clientY)}),t.addEventListener("touchstart",function(e){const t=e?.touches?.[0];t&&(d(t.clientX,t.clientY),e.preventDefault())},{passive:!1}),document.addEventListener("mousemove",function(e){i&&u&&(l=e.clientX-n,m=e.clientY-o)},{passive:!0}),document.addEventListener("touchmove",function(e){if(i&&u){const t=e?.touches?.[0];if(!t)return;l=t.clientX-n,m=t.clientY-o,e.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",h),document.addEventListener("touchend",h),document.addEventListener("touchcancel",h)}_(e){(0,console.info)(`${this.name}: ${e}`),this.B(this.i,"Status: "+e,!0)}W(e){(0,console.error)(`${this.name}: ${e}`),this.B(this.i,"Error: "+e,!0)}};function a(...e){(0,console.error)(...e)}function r(e,t){if(0===e)return t[0];let n="";const i=t.length;for(;e>0;)n=t[e%i]+n,e=Math.floor(e/i);return n}function l(e){let t="";for(let n=0;n>>24==0?0:o.get(t)??-2,null==s.get(i)?s.set(i,1):s.set(i,s.get(i)+1)}return console.log(s),s},d=new WeakSet,h=async function(){GM.setValue("bmTemplates",JSON.stringify(this.q))},b=async function(e){console.log("Parsing BlueMarble...");const t=e.templates;if(console.log(`BlueMarble length: ${Object.keys(t).length}`),Object.keys(t).length>0)for(const e in t){const n=e,i=t[e];if(console.log(`Template Key: ${n}`),t.hasOwnProperty(e)){const e=n.split(" "),t=Number(e?.[0]),o=e?.[1]||"0",s=i.name||`Template ${t||""}`,a={total:i.pixels.total,colors:new Map(Object.entries(i.pixels.colors).map(([e,t])=>[Number(e),t]))};console.log(a);const r=i.tiles,l={};for(const e in r)if(console.log(e),r.hasOwnProperty(e)){const t=m(r[e]),n=new Blob([t],{type:"image/png"}),i=await createImageBitmap(n);l[e]=i}const c=new y({displayName:s,X:t||this.K?.length||0,J:o||""});c.U=a,c.j=l,this.K.push(c),console.log(this.K),console.log("^^^ This ^^^")}}},p=new WeakSet,g=async function(e=navigator.userAgent){return(e=e||"").includes("OPR/")||e.includes("Opera")?"Opera":e.includes("Edg/")?"Edge":e.includes("Vivaldi")?"Vivaldi":e.includes("YaBrowser")?"Yandex":e.includes("Kiwi")?"Kiwi":e.includes("Brave")?"Brave":e.includes("Firefox/")?"Firefox":e.includes("Chrome/")?"Chrome":e.includes("Safari/")?"Safari":navigator.brave&&"function"==typeof navigator.brave.isBrave&&await navigator.brave.isBrave()?"Brave":"Unknown"},f=function(e=navigator.userAgent){return/Windows NT 11/i.test(e=e||"")?"Windows 11":/Windows NT 10/i.test(e)?"Windows 10":/Windows NT 6\.3/i.test(e)?"Windows 8.1":/Windows NT 6\.2/i.test(e)?"Windows 8":/Windows NT 6\.1/i.test(e)?"Windows 7":/Windows NT 6\.0/i.test(e)?"Windows Vista":/Windows NT 5\.1|Windows XP/i.test(e)?"Windows XP":/Mac OS X 10[_\.]15/i.test(e)?"macOS Catalina":/Mac OS X 10[_\.]14/i.test(e)?"macOS Mojave":/Mac OS X 10[_\.]13/i.test(e)?"macOS High Sierra":/Mac OS X 10[_\.]12/i.test(e)?"macOS Sierra":/Mac OS X 10[_\.]11/i.test(e)?"OS X El Capitan":/Mac OS X 10[_\.]10/i.test(e)?"OS X Yosemite":/Mac OS X 10[_\.]/i.test(e)?"macOS":/Android/i.test(e)?"Android":/iPhone|iPad|iPod/i.test(e)?"iOS":/Linux/i.test(e)?"Linux":"Unknown"};var v=GM_info.script.name.toString(),x=GM_info.script.version.toString();!function(e){const t=document.createElement("script");t.setAttribute("bm-E",v),t.setAttribute("bm-B","color: cornflowerblue;"),t.textContent=`(${e})();`,document.documentElement?.appendChild(t),t.remove()}(()=>{const e=document.currentScript,t=e?.getAttribute("bm-E")||"Blue Marble",n=e?.getAttribute("bm-B")||"",i=new Map;window.addEventListener("message",e=>{const{source:o,endpoint:s,blobID:a,blobData:r,blink:l}=e.data,m=Date.now()-l;if(console.groupCollapsed(`%c${t}%c: ${i.size} Recieved IMAGE message about blob "${a}"`,n,""),console.log(`Blob fetch took %c${String(Math.floor(m/6e4)).padStart(2,"0")}:${String(Math.floor(m/1e3)%60).padStart(2,"0")}.${String(m%1e3).padStart(3,"0")}%c MM:SS.mmm`,n,""),console.log(i),console.groupEnd(),"blue-marble"==o&&a&&r&&!s){const e=i.get(a);"function"==typeof e?e(r):function(...e){(0,console.warn)(...e)}(`%c${t}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,n,"",a),i.delete(a)}});const o=window.fetch;window.fetch=async function(...e){const s=await o.apply(this,e),a=s.clone(),r=(e[0]instanceof Request?e[0]?.url:e[0])||"ignore",l=a.headers.get("content-type")||"";if(l.includes("application/json"))console.log(`%c${t}%c: Sending JSON message about endpoint "${r}"`,n,""),a.json().then(e=>{window.postMessage({source:"blue-marble",endpoint:r,jsonData:e},"*")}).catch(e=>{console.error(`%c${t}%c: Failed to parse JSON: `,n,"",e)});else if(l.includes("image/")&&!r.includes("openfreemap")&&!r.includes("maps")){const e=Date.now(),o=await a.blob();return console.log(`%c${t}%c: ${i.size} Sending IMAGE message about endpoint "${r}"`,n,""),new Promise(s=>{const l=crypto.randomUUID();i.set(l,e=>{s(new Response(e,{headers:a.headers,status:a.status,statusText:a.statusText})),console.log(`%c${t}%c: ${i.size} Processed blob "${l}"`,n,"")}),window.postMessage({source:"blue-marble",endpoint:r,blobID:l,blobData:o,blink:e})}).catch(o=>{const s=Date.now();console.error(`%c${t}%c: Failed to Promise blob!`,n,""),console.groupCollapsed(`%c${t}%c: Details of failed blob Promise:`,n,""),console.log(`Endpoint: ${r}\nThere are ${i.size} blobs processing...\nBlink: ${e.toLocaleString()}\nTime Since Blink: ${String(Math.floor(s/6e4)).padStart(2,"0")}:${String(Math.floor(s/1e3)%60).padStart(2,"0")}.${String(s%1e3).padStart(3,"0")} MM:SS.mmm`),console.error("Exception stack:",o),console.groupEnd()})}return s}});var S=`#bm-A,#bm-d{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000;transition:all .3s ease,transform 0s;max-width:300px;width:auto;will-change:transform;backface-visibility:hidden;-webkit-backface-visibility:hidden;transform-style:preserve-3d;-webkit-transform-style:preserve-3d}#bm-f,#bm-A hr,#bm-d hr,#bm-c,#bm-6{transition:opacity .2s ease,height .2s ease}div#bm-A,div#bm-d{font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-z,#bm-z-telemetry{margin-bottom:.5em;background:url('data:image/svg+xml;utf8,') repeat;cursor:grab;width:100%;height:1em}#bm-z.dragging,#bm-z-telemetry.dragging{cursor:grabbing}#bm-A:has(#bm-z.dragging),#bm-d:has(#bm-z-telemetry.dragging){pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}#bm-z.dragging,#bm-z-telemetry.dragging{pointer-events:auto}#bm-j,#bm-1{margin-bottom:.5em}#bm-j[style*="text-align: center"],#bm-1[style*="text-align: center"]{display:flex;flex-direction:column;align-items:center;justify-content:center}#bm-A[style*="padding: 5px"],#bm-d[style*="padding: 5px"]{width:auto!important;max-width:300px;min-width:200px}#bm-A img{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle;transition:opacity .2s ease}#bm-j[style*="text-align: center"] img{display:block;margin:0 auto}#bm-z,#bm-z-telemetry{transition:margin-bottom .2s ease}#bm-A h1,#bm-d h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}#bm-c input[type=checkbox]{vertical-align:middle;margin-right:.5ch}#bm-c label{margin-right:.5ch}.bm-D{border:white 1px solid;height:1.5em;width:1.5em;margin-top:2px;text-align:center;line-height:1em;padding:0!important}#bm-q{vertical-align:middle}#bm-q svg{width:50%;margin:0 auto;fill:#111}#bm-f{margin-bottom:.5em}div:has(>#bm-button-teleport){display:flex;gap:.5ch}#bm-button-favorite svg,#bm-button-template svg{height:1em;margin:2px auto 0;text-align:center;line-height:1em;vertical-align:bottom}#bm-k input[type=number]{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}#bm-k input[type=number]::-webkit-outer-spin-button,#bm-k input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}#bm-4{display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;gap:1ch}div:has(>#bm-a)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#bm-a,input[type=file][id*=template]{display:none!important;visibility:hidden!important;position:absolute!important;left:-9999px!important;top:-9999px!important;width:0!important;height:0!important;opacity:0!important;z-index:-9999!important;pointer-events:none!important}#bm-o{font-size:small;background-color:#0003;padding:0 .5ch;height:3.75em;width:100%}#bm-6{display:flex;justify-content:space-between}div#bm-6 button#bm-D{margin-right:2px}#bm-A small{font-size:x-small;color:#d3d3d3}#bm-f,#bm-c,#bm-k,#bm-4,div:has(>#bm-a),#bm-o{margin-top:.5em}#bm-A button,#bm-d button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}#bm-A button:hover,#bm-A button:focus-visible,#bm-d button:hover,#bm-d button:focus-visible{background-color:#1061e5}#bm-A button:active,#bm-d button:active #bm-A button:disabled,#bm-d button:disabled{background-color:#2e97ff}#bm-A button:disabled,#bm-d button:disabled{text-decoration:line-through}`;GM_addStyle(S);var $,M="@font-face{font-family:'Roboto Mono';font-style:normal;font-weight:400;src:url(data:font/woff2;base64,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)format('woff2');}";M.indexOf("@font-face")+1?(console.log("Loading Roboto Mono as a file..."),GM_addStyle(M)):(($=document.createElement("link")).href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",$.rel="preload",$.as="style",$.onload=function(){this.onload=null,this.rel="stylesheet"},document.head?.appendChild($)),new class{constructor(){this.Z=null,this.ee=null,this.te="#bm-h"}ne(e){return this.ee=e,this.Z=new MutationObserver(e=>{for(const t of e)for(const e of t.addedNodes)e instanceof HTMLElement&&e.matches?.(this.te)}),this}ie(){return this.Z}observe(e,t=!1,n=!1){e.observe(this.ee,{childList:t,subtree:n})}};var T=new s(v,x),k=(new s(v,x),new class{constructor(e,t,n){i(this,d),this.name=e,this.version=t,this.o=n,this.oe="1.0.0",this.se=null,this.ae="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.R=1e3,this.re=3,this.le=3,this.me=function(e){const t=w;t.unshift({id:-1,premium:!1,name:"Erased",rgb:[222,250,206]}),t.unshift({id:-2,premium:!1,name:"Other",rgb:[0,0,0]});const n=new Map;for(const i of t){if(0==i.id||-2==i.id)continue;const t=i.rgb[0],o=i.rgb[1],s=i.rgb[2];for(let a=-e;a<=e;a++)for(let r=-e;r<=e;r++)for(let l=-e;l<=e;l++){const e=t+a,m=o+r,c=s+l;if(e<0||e>255||m<0||m>255||c<0||c>255)continue;const u=(255<<24|c<<16|m<<8|e)>>>0;n.has(u)||n.set(u,i.id)}}return{palette:t,F:n}}(this.le),this.ce=null,this.ue=null,this.de="bm-C",this.he="div#map canvas.maplibregl-canvas",this.be=null,this.pe="",this.K=[],this.q=null,this.ge=!0}fe(){if(document.body.contains(this.ce))return this.ce;document.getElementById(this.de)?.remove();const e=document.querySelector(this.he),t=document.createElement("canvas");return t.id=this.de,t.className="maplibregl-canvas",t.style.position="absolute",t.style.top="0",t.style.left="0",t.style.height=e?.clientHeight*(window.devicePixelRatio||1)+"px",t.style.width=e?.clientWidth*(window.devicePixelRatio||1)+"px",t.height=e?.clientHeight*(window.devicePixelRatio||1),t.width=e?.clientWidth*(window.devicePixelRatio||1),t.style.zIndex="8999",t.style.pointerEvents="none",e?.parentElement?.appendChild(t),this.ce=t,window.addEventListener("move",this.we),window.addEventListener("zoom",this.ye),window.addEventListener("resize",this.ve),this.ce}async xe(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.oe,templates:{}}}async Se(e,t,n){this.q||(this.q=await this.xe(),console.log("Creating JSON...")),this.o._(`Creating template at ${n.join(", ")}...`);const i=new y({displayName:t,X:0,J:r(this.se||0,this.ae),file:e,coords:n}),{A:s,H:a}=await i.Y(this.R,this.me);i.j=s;const l={total:i.U.total,colors:Object.fromEntries(i.U.colors)};this.q.templates[`${i.X} ${i.J}`]={name:i.displayName,coords:n.join(", "),enabled:!0,pixels:l,tiles:a},this.K=[],this.K.push(i),this.o._(`Template created at ${n.join(", ")}!`),console.log(Object.keys(this.q.templates).length),console.log(this.q),console.log(this.K),console.log(JSON.stringify(this.q)),await o(this,d,h).call(this)}$e(){}async Me(){this.q||(this.q=await this.xe(),console.log("Creating JSON..."))}async Te(e,t){if(!this.ge)return e;const n=this.R*this.re;t=t[0].toString().padStart(4,"0")+","+t[1].toString().padStart(4,"0"),console.log(`Searching for templates in tile: "${t}"`);const i=this.K;console.log(i),i.sort((e,t)=>e.X-t.X),console.log(i);const o=i.map(e=>{const n=Object.keys(e.j).filter(e=>e.startsWith(t));if(0===n.length)return null;const i=n.map(t=>{const n=t.split(",");return{ke:e.j[t],De:[n[0],n[1]],Oe:[n[2],n[3]]}});return i?.[0]}).filter(Boolean);console.log(o);const s=o?.length||0;if(console.log(`templateCount = ${s}`),!(s>0))return this.o._(`Sleeping\nVersion: ${this.version}`),e;{const e=i.filter(e=>Object.keys(e.j).filter(e=>e.startsWith(t)).length>0).reduce((e,t)=>e+(t.U.total||0),0),n=(new Intl.NumberFormat).format(e);this.o._(`Displaying ${s} template${1==s?"":"s"}.\nTotal pixels: ${n}`)}const a=await createImageBitmap(e),r=new OffscreenCanvas(n,n),l=r.getContext("2d");l.imageSmoothingEnabled=!1,l.beginPath(),l.rect(0,0,n,n),l.clip(),l.clearRect(0,0,n,n),l.drawImage(a,0,0,n,n);for(const e of o)console.log("Template:"),console.log(e),l.drawImage(e.ke,Number(e.Oe[0])*this.re,Number(e.Oe[1])*this.re);return await r.convertToBlob({type:"image/png"})}Ce(e){console.log("Importing JSON..."),console.log(e),"BlueMarble"==e?.whoami&&o(this,d,b).call(this,e)}Ne(e){this.ge=e}}(v,x,T)),D=new class{constructor(e){i(this,p),this.Be=e,this.Ie=!1,this.Le=[],this.Pe=[]}Ge(e){window.addEventListener("message",async t=>{const n=t.data,i=n.jsonData;if(!n||"blue-marble"!==n.source)return;if(!n.endpoint)return;const o=n.endpoint?.split("?")[0].split("/").filter(e=>e&&isNaN(Number(e))).filter(e=>e&&!e.includes(".")).pop();switch(console.log('%cBlue Marble%c: Recieved message about "%s"',"color: cornflowerblue;","",o),o){case"me":if(i.status&&"2"!=i.status?.toString()[0])return void e.W("You are not logged in!\nCould not fetch userdata.");const t=Math.ceil(Math.pow(Math.floor(i.level)*Math.pow(30,.65),1/.65)-i.pixelsPainted);console.log(i.id),(i.id||0===i.id)&&console.log(r(i.id,"!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~")),this.Be.se=i.id,e.B("bm-p",`Droplets: ${(new Intl.NumberFormat).format(i.droplets)}`),e.B("bm-i",`Next level in ${(new Intl.NumberFormat).format(t)} pixel${1==t?"":"s"}`);break;case"pixel":const o=n.endpoint.split("?")[0].split("/").filter(e=>e&&!isNaN(Number(e))),l=new URLSearchParams(n.endpoint.split("?")[1]),m=[l.get("x"),l.get("y")];if(this.Le.length&&(!o.length||!m.length))return void e.W("Coordinates are malformed!\nDid you try clicking the canvas first?");this.Le=[...o,...m];const c=(s=o,a=m,[parseInt(s[0])%4*1e3+parseInt(a[0]),parseInt(s[1])%4*1e3+parseInt(a[1])]),u=document.querySelectorAll("span");for(const e of u)if(e.textContent.trim().includes(`${c[0]}, ${c[1]}`)){let t=document.querySelector("#bm-h");const n=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${m[0]}, Px Y: ${m[1]})`;t?t.textContent=n:(t=document.createElement("span"),t.id="bm-h",t.textContent=n,t.style="margin-left: calc(var(--spacing)*3); font-size: small;",e.parentNode.parentNode.insertAdjacentElement("afterend",t))}break;case"tiles":let d=n.endpoint.split("/");d=[parseInt(d[d.length-2]),parseInt(d[d.length-1].replace(".png",""))];const h=n.blobID,b=n.blobData,p=await this.Be.Te(b,d);window.postMessage({source:"blue-marble",blobID:h,blobData:p,blink:n.blink});break;case"robots":this.Ie="false"==i.userscript?.toString().toLowerCase();break}var s,a})}async We(e){console.log("Sending heartbeat to telemetry server...");let t=GM_getValue("bmUserSettings","{}");if(t=JSON.parse(t),!t||!t.telemetry||!t.uuid)return void console.log("Telemetry is disabled, not sending heartbeat.");const n=navigator.userAgent;let i=await o(this,p,g).call(this,n),s=o(this,p,f).call(this,n);GM_xmlhttpRequest({method:"POST",url:"https://telemetry.thebluecorner.net/heartbeat",headers:{"Content-Type":"application/json"},data:JSON.stringify({uuid:t.uuid,version:e,browser:i,os:s}),onload:e=>{200!==e.status&&a("Failed to send heartbeat:",e.statusText)},onerror:e=>{a("Error sending heartbeat:",e)}})}}(k);T.u(D);var O=JSON.parse(GM_getValue("bmTemplates","{}"));console.log(O),k.Ce(O);var C=JSON.parse(GM_getValue("bmUserSettings","{}"));if(console.log(C),console.log(Object.keys(C).length),0==Object.keys(C).length){const e=crypto.randomUUID();console.log(e),GM.setValue("bmUserSettings",JSON.stringify({uuid:e}))}if(setInterval(()=>D.We(x),18e5),console.log(`Telemetry is ${!(null==C?.telemetry)}`),null==C?.telemetry||C?.telemetry>1){const e=new s(v,x);e.u(D),e.v({id:"bm-d",style:"top: 0px; left: 0px; width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; z-index: 9999;"}).v({id:"bm-7",style:"display: flex; flex-direction: column; align-items: center;"}).v({id:"bm-1",style:"margin-top: 10%;"}).T(1,{textContent:`${v} Telemetry`}).h().h().v({id:"bm-e",style:"max-width: 50%; overflow-y: auto; max-height: 80vh;"}).k().h().D().h().v({style:"width: fit-content; margin: auto; text-align: center;"}).C({id:"bm-8",textContent:"More Information"},(e,t)=>{t.onclick=()=>{window.open("https://github.com/SwingTheVine/Wplace-TelemetryServer#telemetry-data","_blank","noopener noreferrer")}}).h().h().D().h().v({style:"width: fit-content; margin: auto; text-align: center;"}).C({id:"bm-5",textContent:"Enable Telemetry",style:"margin-right: 2ch;"},(e,t)=>{t.onclick=()=>{const e=JSON.parse(GM_getValue("bmUserSettings","{}"));e.telemetry=1,GM.setValue("bmUserSettings",JSON.stringify(e));const t=document.getElementById("bm-d");t&&(t.style.display="none")}}).h().C({id:"bm-2",textContent:"Disable Telemetry"},(e,t)=>{t.onclick=()=>{const e=JSON.parse(GM_getValue("bmUserSettings","{}"));e.telemetry=0,GM.setValue("bmUserSettings",JSON.stringify(e));const t=document.getElementById("bm-d");t&&(t.style.display="none")}}).h().h().D().h().S({textContent:"We collect anonymous telemetry data such as your browser, OS, and script version to make the experience better for everyone. The data is never shared personally. The data is never sold. You can turn this off by pressing the 'Disable' button, but keeping it on helps us improve features and reliability faster. Thank you for supporting the Blue Marble!"}).h().S({textContent:'You can disable telemetry by pressing the "Disable" button below.'}).h().h().h().p(document.body)}!function(){let e=!1;T.v({id:"bm-A",style:"top: 10px; right: 75px;"}).v({id:"bm-j"}).v({id:"bm-z"}).h().M({alt:"Blue Marble Icon - Click to minimize/maximize",src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALEQa0zv0AAAACBjSFJNAACHDwAAjA8AAP1SAACBQAAAfXkAAOmLAAA85QAAGcxzPIV3AAABF2lDQ1BJQ0MgUHJvZmlsZQAAKM9jYGDiyUnOLWYSYGDIzSspCnJ3UoiIjFJgv8PAyCDJwMygyWCZmFxc4BgQ4MOAE3y7BlQNBJd1QWYxkAa4UlKLk4H0HyCOSy4oKmFgYIwBsrnLSwpA7AwgWyQpG8yuAbGLgA4EsieA2OkQ9hKwGgh7B1hNSJAzkH0GyHZIR2InIbGh9oIAc7IRA9VBSWpFCYh2c2JgAIUpelghxJjFgNgYGBdLEGL5ixgYLL4CxScgxJJmMjBsb2VgkLiFEFNZwMDA38LAsO18cmlRGdRqKSA+zXiSOZl1Ekc29zcBe9FAaRPFj5oTjCSsJ7mxBpbHvs0uqGLt3DirZk3m/trLh18a/P8PAN5BU32YWvgkAAAACXBIWXMAAA7BAAAOwQG4kWvtAAAAGHRFWHRTb2Z0d2FyZQBQYWludC5ORVQgNS4xLjgbaeqoAAAAjGVYSWZJSSoACAAAAAUAGgEFAAEAAABKAAAAGwEFAAEAAABSAAAAKAEDAAEAAAACAAAAMQECABAAAABaAAAAaYcEAAEAAABqAAAAAAAAANl2AQDoAwAA2XYBAOgDAABQYWludC5ORVQgNS4xLjgAAgAAkAcABAAAADAyMzABoAMAAQAAAP//AAAAAAAAubU+IZJzuMAAAAtoSURBVFhHlZZ3fJSFGce/NzKOhITL4kJCEgmJ7D2UXQKJghVBFEWkLC3ioNWigFrhg9ZRKBZUWigtcTBEQUEgBDAESEJCQvYk+7LnZV4u6+2TV8unfqRqnz9yd2/unvF7fs/veTT8HxaXVKBk52QSNGQSN65dxeThTktbG0tWPkhWtpmq8ho65fOTT87+xX5/9ouRV9MV38BRlKZl4qLvwdJQi03RU9fSQmuFGX9fD3q7e+g3ZAS2tibq65rxDwzEXFjKmjVhP+tf+/3rHS0lvlSZP3YUyWdP4NxazgCNFZMzuGg7aKsq5mjERSy2LmbOmQ3VhXSaywjw82XPnn0cPXmSLa8fUN58M1z53t0d7Y4Z5uQ3KSXJmQTfZaIgJ4Wapka8DY70dzLQqXSRVVROVXMHGYUFpOcUsPG3q4lPSaOsrJ4unQMjh48iJSUFo9GIp7s7OvTs/2jTHWP96OGXX11XTHZ2dHVY8PPxwCLBK2obyMnJU/9fUlsDenvqmhrwMQ1i+tQptJcV0m61EpVXh2mQD7m3CnBwcECvs0ej9PLrB+8nIz2Xd7av+1E83fevqqXlNSiDNBpKSouwaXuJS8wk6VYhCZlZFNfVUCdBrN0KS5c9Rn/n/thLIuYSM55+AQSPu4dDn30uXnoZM3o0nbYOdFo7enp6ce3fDw/3Abh4jtiWkRy1/bto39kPMsq4VqzEpSeTlZUjUHfTam3hMQlWUFBAUZEZN0836i11LJwbQoetmcK8QhRF4S8f7cPbN0AQ88PT012SMxJ9JZqgoEAyMrLQ2Ot4as1akm9m0iRcOvLP3bfj3n5z8JNYpaailAaBeOKUiXR1W7GTVujt9Nw1xBtHgxMbX3yJ3yx/mAN/O0j05dM8/8ImTpw4xdMbt2Ls78zhw4fZsWMb02eMRiee1659lt9v3EhMQgaJiamUmSvwMg3k2KG3bsdVp+C5V95QstLTKSuvZOS48Zw5F0FRcZEgkcF9908TOLsxDnBBo+1h+vQpvL97F4Iss341l4eWLMXoaiA27hLOBmdC50xjzpwFzJy1iH4GN4YODWbnzr8wYcI4nJ0N0iGFdc+8dHsy1Ex2vHdGeXbDAjZufBeFDuqqyqWy/bS22jh+/Dg6nZan1i3n8pU4cjLycHJ25IknliF0IXTBozg7uXDg4G7GCg+CgkZQU9uIl5cXDZZmdQpqa2v59uJZPg4/Rn6h8MvazqEDO9TY6h+LVVGOH7tO5IVvWPrIElKSEklMiWX08BEMDQpg1LBRZGalS/JdpKcms/ihJSTciMfPP5AVKxbx1ekobt7M5datXCqqanBzd1PbFxQUJChmERERQei8+fK7B2U0Pbh+I4709BjOnTym0Z77Nls5+ukVNDobK1YuIV4cOzo50c/BhbgbN5kbMotBvp6kZ6TRabWpaAQFD2HavbPw8vFl3VObpV3VvP76ejy9XAWVTkkmkaTEJKKiolQCL168hJDQEM6cPStC5UeJoGDo59pXO7qpUx/Ydv3GdemNla9PfUNzk4WczEz8/Qbx4d93kxYfS8yVqxgMBiZPGsvqVU9icHTgwoXLglIWZnM5MVejVIjvHjmJkSNHkF9QwiOPLCM//5aqBzU1VcTExuLlaWLixAm0t7ZT1dhMdmrcds3W7V8oVZX5rF2zgkmTfZg3r6+3dvRzdsFqbeXhkAk0WazMDw0lPimewMAgTkecE/oqtHTYizo6SzU6snIShTP2ooZlzJ41i9TUVFz6D6BFdoajqKhOWhIWFkbUpUt0dnaxfsPTrFz6K422trpJoPPlhqBwITKVltZuCdiGz6BB7N3zIff/ehUd1nKK067grBdxirtIeVmukKdbHHVSJgsp8lwkPgN9qayoAEVLQnwigUOCmCIq2dLaohZTVVktvNDjIOM8NPhuIs9HMWHKDEXrNdBNCCFEuXCBzVvfYOaM8fQ9W7XyNzg66Aj/4hCeHgMJmxtKjyCydetrEribygbZgMKV1rZWnFwHkJVbhr2d7AvRA6ObkUQh8uiRQQwWngyQZ31INArsM+6dJm0zy0grOLu5oXn+pXDFIrru4aEVFt+iuqqeKqlk6LBArB1WQcLEc+tX4+/iyF/ff5dRkyeg2PfnVESs6qSns1cds+amViGyXtrQiru7USXY3j27cXfzFOleKpA/x/79/xAS6wgICKC+sY6Y859qNO/uvqTEJ8TgN9gLk7cn0d9eJOy+aQTeNVKd488OfyIE1GNtrOT8ha9F+8vY9/dwFj68nEOHPiYvL4+OjnaZdy8KCovp7OrkgYUP0GipJ1aIFx4ezvhx/gwJnin74LvEFi1aRHV1BQf3/lGj9ZIFH7bgXh5dtoSvvzqlVmPQG2XU7qLcXMILzz9PY50FvZMrkZfjaLPCn/70KtPH+7Hzzd8Rff4zHIUbztKOzq4ORowIFkLexEP2Bppetm9/g7lhK1i8YBZdXV00NzfLdOTLDulRk9Gk5jQqJtMA0lIzOLA/nIeXPsTE8aNlzC7hIFBfkypy5Azz9R2EU39H2ltauW9hGKsef4C2duGBQSuvLdg5uvDOe/tISLyJr7TN5O2Ng07h48Of8/QzzxEaMofHV6xh2oyZ+PkO5tq1y0Sc2K/Rjh1m1OzaeZDBgz1EFXQsk37Nnj2bM2dOsPyJecTFR+Ef4MumV15k7/tvU1FWypYtWzh46DhOMn595mhwIepyNsNEA4xurjRbmnCVdd3c1q0iM3XyFHJyC7hnyiRir8XIiCarwft+q3qYH7Z6W8jscYg+sHBhqDDZDUtjIyufWCwtaSYlNY2YmGvs2f2hZH6WTb/bwJixI9GKi6y8IiGji6qW2bK8CkX5Nr/ysozgZJrFh5u7C9djbzJ82HAcHA2YBpqorq0kNyNBvQvUBJ7d+MY2g5OGiZPGcPTIFwTfPUyqX46vj1HQmMrMmfNEUm/x1o63ZKcnS2UdREcn8cG+f1EnQbJzs4RYi2WSPKmoKGdY8HAyMzKZLaNrsbQKqUPlONGLgjoK+xuorDRLAolqAioMffbt1QJloMlFUtKTmpRFQX42pSWlBAf58uhjK9TxKikuxUNmvEUSOHLkKM7CiQ0bNpCansGpM6eIjDjLp58cFq14leXLH+fk16fp7e1l7pwQBvsHSBuyqSg3c+Rfu27HvX0VN7Y2U1ZSK4pVw+dHD7N+/Vr0ej0h8+/H0bFPYisYM2Y8v//DJpxEft95b5squ2ZzKTnZ6WTKUbrl5dfYvHmzzPsBQegqrq5Gurt71ZugsaGR+IREiSRj9F92O5M+O3e1SPngrx/xzLpVjBoeQHFpGa/+8TXOfvM5WmnWgX3HGDduNK5GV+GJK0Z3J24mpaki5D14IPvlUhou7auqrcNPtp4ok/r5auw1GurrcHPz4u1tT/0g5g8+9FlWoaLs/eDPFOYX4T/YW86uFzj55XF1scyfN0NgN1ApatnT06PefFpZzyXFZpKS06VlZZSUFLN69TpsNhtNMrI1VdWUV9RIZ7Ukxl8k8uxnP51An52KKFCKi7JEt++RSvUoIihGo5sEKMfHx0fOMRGUFgtNcrL3wdvb262qodXWwwDhyIVz0XJHDqGisk5dyc1CvLyCdL48/NGP4t0xgT775ny6YudgwMEOhgT4S0VWGTeNVKLB3l4r46PBJrIrkiYHrCLP7KmptshysoladtHR3ibPHLgUFS0ciSbi9LE7xvqfCfzHXt56QBk/ZYK0w0cNanDSy8WrCLm61Tb09blVBKTvBLN1dFHbYJHv6UTrq8gTBd2968WfjPGzCfzHPj6aoJi8B1JfU42Laz/Z8U4qIl0dNiFhG1qZ84aGeiFbm2zTUnbt3PCLfP/iBP7b/nbwjOLq6isEq5XrqQ9+PfWibq9uXf5/+oN/A9GVF7dbp9A3AAAAAElFTkSuQmCC",style:"cursor: pointer;"},(t,n)=>{n.addEventListener("click",()=>{e=!e;const i=document.querySelector("#bm-A"),o=document.querySelector("#bm-j"),s=document.querySelector("#bm-z"),a=document.querySelector("#bm-k"),r=document.querySelector("#bm-q"),l=document.querySelector("#bm-r"),m=document.querySelector("#bm-s"),c=document.querySelector("#bm-l"),u=document.querySelectorAll("#bm-k input");e||(i.style.width="auto",i.style.maxWidth="300px",i.style.minWidth="200px",i.style.padding="10px"),["#bm-A h1","#bm-f","#bm-A hr","#bm-c > *:not(#bm-k)","#bm-a","#bm-6",`#${t.i}`].forEach(t=>{document.querySelectorAll(t).forEach(t=>{t.style.display=e?"none":""})}),e?(a&&(a.style.display="none"),r&&(r.style.display="none"),l&&(l.style.display="none"),m&&(m.style.display="none"),c&&(c.style.display="none"),u.forEach(e=>{e.style.display="none"}),i.style.width="60px",i.style.height="76px",i.style.maxWidth="60px",i.style.minWidth="60px",i.style.padding="8px",n.style.marginLeft="3px",o.style.textAlign="center",o.style.margin="0",o.style.marginBottom="0",s&&(s.style.display="",s.style.marginBottom="0.25em")):(a&&(a.style.display="",a.style.flexDirection="",a.style.justifyContent="",a.style.alignItems="",a.style.gap="",a.style.textAlign="",a.style.margin=""),r&&(r.style.display=""),l&&(l.style.display="",l.style.marginTop=""),m&&(m.style.display="",m.style.marginTop=""),c&&(c.style.display="",c.style.marginTop=""),u.forEach(e=>{e.style.display=""}),n.style.marginLeft="",i.style.padding="10px",o.style.textAlign="",o.style.margin="",o.style.marginBottom="",s&&(s.style.marginBottom="0.5em"),i.style.width="",i.style.height=""),n.alt=e?"Blue Marble Icon - Minimized (Click to maximize)":"Blue Marble Icon - Maximized (Click to minimize)"})}).h().T(1,{textContent:v}).h().h().k().h().v({id:"bm-f"}).S({id:"bm-p",textContent:"Droplets:"}).h().S({id:"bm-i",textContent:"Next level in..."}).h().h().k().h().v({id:"bm-c"}).v({id:"bm-k"}).C({id:"bm-q",className:"bm-D",style:"margin-top: 0;",innerHTML:''},(e,t)=>{t.onclick=()=>{const t=e.t?.Le;t?.[0]?(e.B("bm-v",t?.[0]||""),e.B("bm-w",t?.[1]||""),e.B("bm-x",t?.[2]||""),e.B("bm-y",t?.[3]||"")):e.W("Coordinates are malformed! Did you try clicking on the canvas first?")}}).h().I({type:"number",id:"bm-v",placeholder:"Tl X",min:0,max:2047,step:1,required:!0},(e,t)=>{t.addEventListener("paste",e=>{let t=(e.clipboardData||window.clipboardData).getData("text").split(" ").filter(e=>e).map(Number).filter(e=>!isNaN(e));if(4!==t.length)return;let n=selectAllCoordinateInputs(document);for(let e=0;epersistCoords();t.addEventListener("input",n),t.addEventListener("change",n)}).h().I({type:"number",id:"bm-w",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0},(e,t)=>{const n=()=>persistCoords();t.addEventListener("input",n),t.addEventListener("change",n)}).h().I({type:"number",id:"bm-x",placeholder:"Px X",min:0,max:2047,step:1,required:!0},(e,t)=>{const n=()=>persistCoords();t.addEventListener("input",n),t.addEventListener("change",n)}).h().I({type:"number",id:"bm-y",placeholder:"Px Y",min:0,max:2047,step:1,required:!0},(e,t)=>{const n=()=>persistCoords();t.addEventListener("input",n),t.addEventListener("change",n)}).h().h().L({id:"bm-a",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).h().v({id:"bm-4"}).C({id:"bm-s",textContent:"Enable"},(e,t)=>{t.onclick=()=>{e.t?.Be?.Ne(!0),e._("Enabled templates!")}}).h().C({id:"bm-r",textContent:"Create"},(e,t)=>{t.onclick=()=>{const t=document.querySelector("#bm-a"),n=document.querySelector("#bm-v");if(!n.checkValidity())return n.reportValidity(),void e.W("Coordinates are malformed! Did you try clicking on the canvas first?");const i=document.querySelector("#bm-w");if(!i.checkValidity())return i.reportValidity(),void e.W("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-x");if(!o.checkValidity())return o.reportValidity(),void e.W("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-y");if(!s.checkValidity())return s.reportValidity(),void e.W("Coordinates are malformed! Did you try clicking on the canvas first?");t?.files[0]?(k.Se(t.files[0],t.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(n.value),Number(i.value),Number(o.value),Number(s.value)]),e._("Drew to canvas!")):e.W("No file selected!")}}).h().C({id:"bm-l",textContent:"Disable"},(e,t)=>{t.onclick=()=>{e.t?.Be?.Ne(!1),e._("Disabled templates!")}}).h().h().P({id:T.i,placeholder:`Status: Sleeping...\nVersion: ${x}`,readOnly:!0}).h().v({id:"bm-6"}).v().C({id:"bm-m",className:"bm-D",innerHTML:"🎨",title:"Template Color Converter"},(e,t)=>{t.addEventListener("click",()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")})}).h().C({id:"bm-n",className:"bm-D",innerHTML:"🌐",title:"Official Blue Marble Website"},(e,t)=>{t.addEventListener("click",()=>{window.open("https://bluemarble.lol/","_blank","noopener noreferrer")})}).h().h().$({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).h().h().h().p(document.body)}(),T.G("#bm-A","#bm-z"),D.Ge(T),new MutationObserver((e,t)=>{const n=document.querySelector("#color-1");if(!n)return;let i=document.querySelector("#bm-t");if(!i){i=document.createElement("button"),i.id="bm-t",i.textContent="Move ↑",i.className="btn btn-soft",i.onclick=function(){const e=this.parentNode.parentNode.parentNode.parentNode,t="Move ↑"==this.textContent;e.parentNode.className=e.parentNode.className.replace(t?"bottom":"top",t?"top":"bottom"),e.style.borderTopLeftRadius=t?"0px":"var(--radius-box)",e.style.borderTopRightRadius=t?"0px":"var(--radius-box)",e.style.borderBottomLeftRadius=t?"var(--radius-box)":"0px",e.style.borderBottomRightRadius=t?"var(--radius-box)":"0px",this.textContent=t?"Move ↓":"Move ↑"};const e=n.parentNode.parentNode.parentNode.parentNode.querySelector("h2");e.parentNode?.appendChild(i)}}).observe(document.body,{childList:!0,subtree:!0}),function(...e){(0,console.log)(...e)}(`%c${v}%c (${x}) userscript has loaded!`,"color: cornflowerblue;","")})(); \ No newline at end of file diff --git a/dist/BlueMarble.user.js b/dist/BlueMarble.user.js index aa7479d..0241c48 100644 --- a/dist/BlueMarble.user.js +++ b/dist/BlueMarble.user.js @@ -1,28 +1,31 @@ // ==UserScript== -// @name Blue Marble -// @namespace https://github.com/SwingTheVine/ -// @version 0.88.71 -// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. -// @author SwingTheVine -// @license MPL-2.0 -// @supportURL https://discord.gg/tpeBPy46hf -// @homepageURL https://bluemarble.lol/ -// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/0c760b903739e6214f7b8990ffc4089a93e73bd2/dist/assets/Favicon.png -// @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js -// @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js -// @match https://wplace.live/* -// @grant GM_getResourceText -// @grant GM_addStyle -// @grant GM.setValue -// @grant GM_getValue -// @grant GM_xmlhttpRequest -// @connect telemetry.thebluecorner.net -// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/0c760b903739e6214f7b8990ffc4089a93e73bd2/dist/BlueMarble.user.css +// @name Blue Marble +// @name:en Blue Marble +// @namespace https://github.com/SwingTheVine/ +// @version 0.88.77 +// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. +// @description:en A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. +// @author SwingTheVine +// @license MPL-2.0 +// @donate https://ko-fi.com/swingthevine +// @supportURL https://discord.gg/tpeBPy46hf +// @homepageURL https://bluemarble.lol/ +// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/0c760b903739e6214f7b8990ffc4089a93e73bd2/dist/assets/Favicon.png +// @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js +// @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js +// @match https://wplace.live/* +// @grant GM_getResourceText +// @grant GM_addStyle +// @grant GM.setValue +// @grant GM_getValue +// @grant GM_xmlhttpRequest +// @connect telemetry.thebluecorner.net +// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/0c760b903739e6214f7b8990ffc4089a93e73bd2/dist/BlueMarble.user.css +// @antifeature tracking Anonymous opt-in telemetry data // @noframes // ==/UserScript== // Wplace --> https://wplace.live // License --> https://www.mozilla.org/en-US/MPL/2.0/ -// Donate --> https://ko-fi.com/swingthevine (()=>{var e,t,n=e=>{throw TypeError(e)},i=(e,t,i)=>t.has(e)?n("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),o=(e,t,i)=>(((e,t)=>{t.has(e)||n("Cannot access private method")})(e,t),i),s=class{constructor(t,n){i(this,e),this.name=t,this.version=n,this.t=null,this.i="bm-o",this.o=null,this.l=null,this.m=[]}u(e){this.t=e}h(){return this.m.length>0&&(this.l=this.m.pop()),this}p(e){e?.appendChild(this.o),this.o=null,this.l=null,this.m=[]}v(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"div",{},n)),this}S(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"p",{},n)),this}$(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"small",{},n)),this}M(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"img",{},n)),this}T(n,i={},s=()=>{}){return s(this,o(this,e,t).call(this,"h"+n,{},i)),this}k(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"hr",{},n)),this}D(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"br",{},n)),this}O(n={},i=()=>{}){const s=o(this,e,t).call(this,"label",{textContent:n.textContent??""});delete n.textContent;const a=o(this,e,t).call(this,"input",{type:"checkbox"},n);return s.insertBefore(a,s.firstChild),this.h(),i(this,s,a),this}C(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"button",{},n)),this}N(n={},i=()=>{}){const s=n.title??n.textContent??"Help: No info";delete n.textContent,n.title=`Help: ${s}`;const a={textContent:"?",className:"bm-D",onclick:()=>{this.B(this.i,s)}};return i(this,o(this,e,t).call(this,"button",a,n)),this}I(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"input",{},n)),this}L(n={},i=()=>{}){const s=n.textContent??"";delete n.textContent;const a=o(this,e,t).call(this,"div"),r=o(this,e,t).call(this,"input",{type:"file",style:"display: none !important; visibility: hidden !important; position: absolute !important; left: -9999px !important; width: 0 !important; height: 0 !important; opacity: 0 !important;"},n);this.h();const l=o(this,e,t).call(this,"button",{textContent:s});return this.h(),this.h(),r.setAttribute("tabindex","-1"),r.setAttribute("aria-hidden","true"),l.addEventListener("click",()=>{r.click()}),r.addEventListener("change",()=>{l.style.maxWidth=`${l.offsetWidth}px`,r.files.length>0?l.textContent=r.files[0].name:l.textContent=s}),i(this,a,r,l),this}P(n={},i=()=>{}){return i(this,o(this,e,t).call(this,"textarea",{},n)),this}B(e,t,n=!1){const i=document.getElementById(e.replace(/^#/,""));i&&(i instanceof HTMLInputElement?i.value=t:n?i.textContent=t:i.innerHTML=t)}G(e,t){let n,i=!1,o=0,s=null,a=0,r=0,l=0,m=0;if(e=document.querySelector("#"==e?.[0]?e:"#"+e),t=document.querySelector("#"==t?.[0]?t:"#"+t),!e||!t)return void this.W(`Can not drag! ${e?"":"moveMe"} ${e||t?"":"and "}${t?"":"iMoveThings "}was not found!`);const c=()=>{if(i){const t=Math.abs(a-l),n=Math.abs(r-m);(t>.5||n>.5)&&(a=l,r=m,e.style.transform=`translate(${a}px, ${r}px)`,e.style.left="0px",e.style.top="0px",e.style.right=""),s=requestAnimationFrame(c)}};let u=null;const d=(d,h)=>{i=!0,u=e.getBoundingClientRect(),n=d-u.left,o=h-u.top;const b=window.getComputedStyle(e).transform;if(b&&"none"!==b){const e=new DOMMatrix(b);a=e.m41,r=e.m42}else a=u.left,r=u.top;l=a,m=r,document.body.style.userSelect="none",t.classList.add("dragging"),s&&cancelAnimationFrame(s),c()},h=()=>{i=!1,s&&(cancelAnimationFrame(s),s=null),document.body.style.userSelect="",t.classList.remove("dragging")};t.addEventListener("mousedown",function(e){e.preventDefault(),d(e.clientX,e.clientY)}),t.addEventListener("touchstart",function(e){const t=e?.touches?.[0];t&&(d(t.clientX,t.clientY),e.preventDefault())},{passive:!1}),document.addEventListener("mousemove",function(e){i&&u&&(l=e.clientX-n,m=e.clientY-o)},{passive:!0}),document.addEventListener("touchmove",function(e){if(i&&u){const t=e?.touches?.[0];if(!t)return;l=t.clientX-n,m=t.clientY-o,e.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",h),document.addEventListener("touchend",h),document.addEventListener("touchcancel",h)}_(e){(0,console.info)(`${this.name}: ${e}`),this.B(this.i,"Status: "+e,!0)}W(e){(0,console.error)(`${this.name}: ${e}`),this.B(this.i,"Error: "+e,!0)}};function a(...e){(0,console.error)(...e)}function r(e,t){if(0===e)return t[0];let n="";const i=t.length;for(;e>0;)n=t[e%i]+n,e=Math.floor(e/i);return n}function l(e){let t="";for(let n=0;n>>24==0?0:o.get(t)??-2,null==s.get(i)?s.set(i,1):s.set(i,s.get(i)+1)}return console.log(s),s},d=new WeakSet,h=async function(){GM.setValue("bmTemplates",JSON.stringify(this.q))},b=async function(e){console.log("Parsing BlueMarble...");const t=e.templates;if(console.log(`BlueMarble length: ${Object.keys(t).length}`),Object.keys(t).length>0)for(const e in t){const n=e,i=t[e];if(console.log(`Template Key: ${n}`),t.hasOwnProperty(e)){const e=n.split(" "),t=Number(e?.[0]),o=e?.[1]||"0",s=i.name||`Template ${t||""}`,a={total:i.pixels.total,colors:new Map(Object.entries(i.pixels.colors).map(([e,t])=>[Number(e),t]))};console.log(a);const r=i.tiles,l={};for(const e in r)if(console.log(e),r.hasOwnProperty(e)){const t=m(r[e]),n=new Blob([t],{type:"image/png"}),i=await createImageBitmap(n);l[e]=i}const c=new y({displayName:s,X:t||this.K?.length||0,J:o||""});c.U=a,c.j=l,this.K.push(c),console.log(this.K),console.log("^^^ This ^^^")}}},p=new WeakSet,g=async function(e=navigator.userAgent){return(e=e||"").includes("OPR/")||e.includes("Opera")?"Opera":e.includes("Edg/")?"Edge":e.includes("Vivaldi")?"Vivaldi":e.includes("YaBrowser")?"Yandex":e.includes("Kiwi")?"Kiwi":e.includes("Brave")?"Brave":e.includes("Firefox/")?"Firefox":e.includes("Chrome/")?"Chrome":e.includes("Safari/")?"Safari":navigator.brave&&"function"==typeof navigator.brave.isBrave&&await navigator.brave.isBrave()?"Brave":"Unknown"},f=function(e=navigator.userAgent){return/Windows NT 11/i.test(e=e||"")?"Windows 11":/Windows NT 10/i.test(e)?"Windows 10":/Windows NT 6\.3/i.test(e)?"Windows 8.1":/Windows NT 6\.2/i.test(e)?"Windows 8":/Windows NT 6\.1/i.test(e)?"Windows 7":/Windows NT 6\.0/i.test(e)?"Windows Vista":/Windows NT 5\.1|Windows XP/i.test(e)?"Windows XP":/Mac OS X 10[_\.]15/i.test(e)?"macOS Catalina":/Mac OS X 10[_\.]14/i.test(e)?"macOS Mojave":/Mac OS X 10[_\.]13/i.test(e)?"macOS High Sierra":/Mac OS X 10[_\.]12/i.test(e)?"macOS Sierra":/Mac OS X 10[_\.]11/i.test(e)?"OS X El Capitan":/Mac OS X 10[_\.]10/i.test(e)?"OS X Yosemite":/Mac OS X 10[_\.]/i.test(e)?"macOS":/Android/i.test(e)?"Android":/iPhone|iPad|iPod/i.test(e)?"iOS":/Linux/i.test(e)?"Linux":"Unknown"};var v=GM_info.script.name.toString(),x=GM_info.script.version.toString();!function(e){const t=document.createElement("script");t.setAttribute("bm-E",v),t.setAttribute("bm-B","color: cornflowerblue;"),t.textContent=`(${e})();`,document.documentElement?.appendChild(t),t.remove()}(()=>{const e=document.currentScript,t=e?.getAttribute("bm-E")||"Blue Marble",n=e?.getAttribute("bm-B")||"",i=new Map;window.addEventListener("message",e=>{const{source:o,endpoint:s,blobID:a,blobData:r,blink:l}=e.data,m=Date.now()-l;if(console.groupCollapsed(`%c${t}%c: ${i.size} Recieved IMAGE message about blob "${a}"`,n,""),console.log(`Blob fetch took %c${String(Math.floor(m/6e4)).padStart(2,"0")}:${String(Math.floor(m/1e3)%60).padStart(2,"0")}.${String(m%1e3).padStart(3,"0")}%c MM:SS.mmm`,n,""),console.log(i),console.groupEnd(),"blue-marble"==o&&a&&r&&!s){const e=i.get(a);"function"==typeof e?e(r):function(...e){(0,console.warn)(...e)}(`%c${t}%c: Attempted to retrieve a blob (%s) from queue, but the blobID was not a function! Skipping...`,n,"",a),i.delete(a)}});const o=window.fetch;window.fetch=async function(...e){const s=await o.apply(this,e),a=s.clone(),r=(e[0]instanceof Request?e[0]?.url:e[0])||"ignore",l=a.headers.get("content-type")||"";if(l.includes("application/json"))console.log(`%c${t}%c: Sending JSON message about endpoint "${r}"`,n,""),a.json().then(e=>{window.postMessage({source:"blue-marble",endpoint:r,jsonData:e},"*")}).catch(e=>{console.error(`%c${t}%c: Failed to parse JSON: `,n,"",e)});else if(l.includes("image/")&&!r.includes("openfreemap")&&!r.includes("maps")){const e=Date.now(),o=await a.blob();return console.log(`%c${t}%c: ${i.size} Sending IMAGE message about endpoint "${r}"`,n,""),new Promise(s=>{const l=crypto.randomUUID();i.set(l,e=>{s(new Response(e,{headers:a.headers,status:a.status,statusText:a.statusText})),console.log(`%c${t}%c: ${i.size} Processed blob "${l}"`,n,"")}),window.postMessage({source:"blue-marble",endpoint:r,blobID:l,blobData:o,blink:e})}).catch(o=>{const s=Date.now();console.error(`%c${t}%c: Failed to Promise blob!`,n,""),console.groupCollapsed(`%c${t}%c: Details of failed blob Promise:`,n,""),console.log(`Endpoint: ${r}\nThere are ${i.size} blobs processing...\nBlink: ${e.toLocaleString()}\nTime Since Blink: ${String(Math.floor(s/6e4)).padStart(2,"0")}:${String(Math.floor(s/1e3)%60).padStart(2,"0")}.${String(s%1e3).padStart(3,"0")} MM:SS.mmm`),console.error("Exception stack:",o),console.groupEnd()})}return s}});var S=GM_getResourceText("CSS-BM-File");GM_addStyle(S);var $,M="robotoMonoInjectionPoint";M.indexOf("@font-face")+1?(console.log("Loading Roboto Mono as a file..."),GM_addStyle(M)):(($=document.createElement("link")).href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap",$.rel="preload",$.as="style",$.onload=function(){this.onload=null,this.rel="stylesheet"},document.head?.appendChild($)),new class{constructor(){this.Z=null,this.ee=null,this.te="#bm-h"}ne(e){return this.ee=e,this.Z=new MutationObserver(e=>{for(const t of e)for(const e of t.addedNodes)e instanceof HTMLElement&&e.matches?.(this.te)}),this}ie(){return this.Z}observe(e,t=!1,n=!1){e.observe(this.ee,{childList:t,subtree:n})}};var T=new s(v,x),k=(new s(v,x),new class{constructor(e,t,n){i(this,d),this.name=e,this.version=t,this.o=n,this.oe="1.0.0",this.se=null,this.ae="!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",this.R=1e3,this.re=3,this.le=3,this.me=function(e){const t=w;t.unshift({id:-1,premium:!1,name:"Erased",rgb:[222,250,206]}),t.unshift({id:-2,premium:!1,name:"Other",rgb:[0,0,0]});const n=new Map;for(const i of t){if(0==i.id||-2==i.id)continue;const t=i.rgb[0],o=i.rgb[1],s=i.rgb[2];for(let a=-e;a<=e;a++)for(let r=-e;r<=e;r++)for(let l=-e;l<=e;l++){const e=t+a,m=o+r,c=s+l;if(e<0||e>255||m<0||m>255||c<0||c>255)continue;const u=(255<<24|c<<16|m<<8|e)>>>0;n.has(u)||n.set(u,i.id)}}return{palette:t,F:n}}(this.le),this.ce=null,this.ue=null,this.de="bm-C",this.he="div#map canvas.maplibregl-canvas",this.be=null,this.pe="",this.K=[],this.q=null,this.ge=!0}fe(){if(document.body.contains(this.ce))return this.ce;document.getElementById(this.de)?.remove();const e=document.querySelector(this.he),t=document.createElement("canvas");return t.id=this.de,t.className="maplibregl-canvas",t.style.position="absolute",t.style.top="0",t.style.left="0",t.style.height=e?.clientHeight*(window.devicePixelRatio||1)+"px",t.style.width=e?.clientWidth*(window.devicePixelRatio||1)+"px",t.height=e?.clientHeight*(window.devicePixelRatio||1),t.width=e?.clientWidth*(window.devicePixelRatio||1),t.style.zIndex="8999",t.style.pointerEvents="none",e?.parentElement?.appendChild(t),this.ce=t,window.addEventListener("move",this.we),window.addEventListener("zoom",this.ye),window.addEventListener("resize",this.ve),this.ce}async xe(){return{whoami:this.name.replace(" ",""),scriptVersion:this.version,schemaVersion:this.oe,templates:{}}}async Se(e,t,n){this.q||(this.q=await this.xe(),console.log("Creating JSON...")),this.o._(`Creating template at ${n.join(", ")}...`);const i=new y({displayName:t,X:0,J:r(this.se||0,this.ae),file:e,coords:n}),{A:s,H:a}=await i.Y(this.R,this.me);i.j=s;const l={total:i.U.total,colors:Object.fromEntries(i.U.colors)};this.q.templates[`${i.X} ${i.J}`]={name:i.displayName,coords:n.join(", "),enabled:!0,pixels:l,tiles:a},this.K=[],this.K.push(i),this.o._(`Template created at ${n.join(", ")}!`),console.log(Object.keys(this.q.templates).length),console.log(this.q),console.log(this.K),console.log(JSON.stringify(this.q)),await o(this,d,h).call(this)}$e(){}async Me(){this.q||(this.q=await this.xe(),console.log("Creating JSON..."))}async Te(e,t){if(!this.ge)return e;const n=this.R*this.re;t=t[0].toString().padStart(4,"0")+","+t[1].toString().padStart(4,"0"),console.log(`Searching for templates in tile: "${t}"`);const i=this.K;console.log(i),i.sort((e,t)=>e.X-t.X),console.log(i);const o=i.map(e=>{const n=Object.keys(e.j).filter(e=>e.startsWith(t));if(0===n.length)return null;const i=n.map(t=>{const n=t.split(",");return{ke:e.j[t],De:[n[0],n[1]],Oe:[n[2],n[3]]}});return i?.[0]}).filter(Boolean);console.log(o);const s=o?.length||0;if(console.log(`templateCount = ${s}`),!(s>0))return this.o._(`Sleeping\nVersion: ${this.version}`),e;{const e=i.filter(e=>Object.keys(e.j).filter(e=>e.startsWith(t)).length>0).reduce((e,t)=>e+(t.U.total||0),0),n=(new Intl.NumberFormat).format(e);this.o._(`Displaying ${s} template${1==s?"":"s"}.\nTotal pixels: ${n}`)}const a=await createImageBitmap(e),r=new OffscreenCanvas(n,n),l=r.getContext("2d");l.imageSmoothingEnabled=!1,l.beginPath(),l.rect(0,0,n,n),l.clip(),l.clearRect(0,0,n,n),l.drawImage(a,0,0,n,n);for(const e of o)console.log("Template:"),console.log(e),l.drawImage(e.ke,Number(e.Oe[0])*this.re,Number(e.Oe[1])*this.re);return await r.convertToBlob({type:"image/png"})}Ce(e){console.log("Importing JSON..."),console.log(e),"BlueMarble"==e?.whoami&&o(this,d,b).call(this,e)}Ne(e){this.ge=e}}(v,x,T)),D=new class{constructor(e){i(this,p),this.Be=e,this.Ie=!1,this.Le=[],this.Pe=[]}Ge(e){window.addEventListener("message",async t=>{const n=t.data,i=n.jsonData;if(!n||"blue-marble"!==n.source)return;if(!n.endpoint)return;const o=n.endpoint?.split("?")[0].split("/").filter(e=>e&&isNaN(Number(e))).filter(e=>e&&!e.includes(".")).pop();switch(console.log('%cBlue Marble%c: Recieved message about "%s"',"color: cornflowerblue;","",o),o){case"me":if(i.status&&"2"!=i.status?.toString()[0])return void e.W("You are not logged in!\nCould not fetch userdata.");const t=Math.ceil(Math.pow(Math.floor(i.level)*Math.pow(30,.65),1/.65)-i.pixelsPainted);console.log(i.id),(i.id||0===i.id)&&console.log(r(i.id,"!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~")),this.Be.se=i.id,e.B("bm-p",`Droplets: ${(new Intl.NumberFormat).format(i.droplets)}`),e.B("bm-i",`Next level in ${(new Intl.NumberFormat).format(t)} pixel${1==t?"":"s"}`);break;case"pixel":const o=n.endpoint.split("?")[0].split("/").filter(e=>e&&!isNaN(Number(e))),l=new URLSearchParams(n.endpoint.split("?")[1]),m=[l.get("x"),l.get("y")];if(this.Le.length&&(!o.length||!m.length))return void e.W("Coordinates are malformed!\nDid you try clicking the canvas first?");this.Le=[...o,...m];const c=(s=o,a=m,[parseInt(s[0])%4*1e3+parseInt(a[0]),parseInt(s[1])%4*1e3+parseInt(a[1])]),u=document.querySelectorAll("span");for(const e of u)if(e.textContent.trim().includes(`${c[0]}, ${c[1]}`)){let t=document.querySelector("#bm-h");const n=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${m[0]}, Px Y: ${m[1]})`;t?t.textContent=n:(t=document.createElement("span"),t.id="bm-h",t.textContent=n,t.style="margin-left: calc(var(--spacing)*3); font-size: small;",e.parentNode.parentNode.insertAdjacentElement("afterend",t))}break;case"tiles":let d=n.endpoint.split("/");d=[parseInt(d[d.length-2]),parseInt(d[d.length-1].replace(".png",""))];const h=n.blobID,b=n.blobData,p=await this.Be.Te(b,d);window.postMessage({source:"blue-marble",blobID:h,blobData:p,blink:n.blink});break;case"robots":this.Ie="false"==i.userscript?.toString().toLowerCase();break}var s,a})}async We(e){console.log("Sending heartbeat to telemetry server...");let t=GM_getValue("bmUserSettings","{}");if(t=JSON.parse(t),!t||!t.telemetry||!t.uuid)return void console.log("Telemetry is disabled, not sending heartbeat.");const n=navigator.userAgent;let i=await o(this,p,g).call(this,n),s=o(this,p,f).call(this,n);GM_xmlhttpRequest({method:"POST",url:"https://telemetry.thebluecorner.net/heartbeat",headers:{"Content-Type":"application/json"},data:JSON.stringify({uuid:t.uuid,version:e,browser:i,os:s}),onload:e=>{200!==e.status&&a("Failed to send heartbeat:",e.statusText)},onerror:e=>{a("Error sending heartbeat:",e)}})}}(k);T.u(D);var O=JSON.parse(GM_getValue("bmTemplates","{}"));console.log(O),k.Ce(O);var C=JSON.parse(GM_getValue("bmUserSettings","{}"));if(console.log(C),console.log(Object.keys(C).length),0==Object.keys(C).length){const e=crypto.randomUUID();console.log(e),GM.setValue("bmUserSettings",JSON.stringify({uuid:e}))}if(setInterval(()=>D.We(x),18e5),console.log(`Telemetry is ${!(null==C?.telemetry)}`),null==C?.telemetry||C?.telemetry>1){const e=new s(v,x);e.u(D),e.v({id:"bm-d",style:"top: 0px; left: 0px; width: 100vw; max-width: 100vw; height: 100vh; max-height: 100vh; z-index: 9999;"}).v({id:"bm-7",style:"display: flex; flex-direction: column; align-items: center;"}).v({id:"bm-1",style:"margin-top: 10%;"}).T(1,{textContent:`${v} Telemetry`}).h().h().v({id:"bm-e",style:"max-width: 50%; overflow-y: auto; max-height: 80vh;"}).k().h().D().h().v({style:"width: fit-content; margin: auto; text-align: center;"}).C({id:"bm-8",textContent:"More Information"},(e,t)=>{t.onclick=()=>{window.open("https://github.com/SwingTheVine/Wplace-TelemetryServer#telemetry-data","_blank","noopener noreferrer")}}).h().h().D().h().v({style:"width: fit-content; margin: auto; text-align: center;"}).C({id:"bm-5",textContent:"Enable Telemetry",style:"margin-right: 2ch;"},(e,t)=>{t.onclick=()=>{const e=JSON.parse(GM_getValue("bmUserSettings","{}"));e.telemetry=1,GM.setValue("bmUserSettings",JSON.stringify(e));const t=document.getElementById("bm-d");t&&(t.style.display="none")}}).h().C({id:"bm-2",textContent:"Disable Telemetry"},(e,t)=>{t.onclick=()=>{const e=JSON.parse(GM_getValue("bmUserSettings","{}"));e.telemetry=0,GM.setValue("bmUserSettings",JSON.stringify(e));const t=document.getElementById("bm-d");t&&(t.style.display="none")}}).h().h().D().h().S({textContent:"We collect anonymous telemetry data such as your browser, OS, and script version to make the experience better for everyone. The data is never shared personally. The data is never sold. You can turn this off by pressing the 'Disable' button, but keeping it on helps us improve features and reliability faster. Thank you for supporting the Blue Marble!"}).h().S({textContent:'You can disable telemetry by pressing the "Disable" button below.'}).h().h().h().p(document.body)}!function(){let e=!1;T.v({id:"bm-A",style:"top: 10px; right: 75px;"}).v({id:"bm-j"}).v({id:"bm-z"}).h().M({alt:"Blue Marble Icon - Click to minimize/maximize",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png",style:"cursor: pointer;"},(t,n)=>{n.addEventListener("click",()=>{e=!e;const i=document.querySelector("#bm-A"),o=document.querySelector("#bm-j"),s=document.querySelector("#bm-z"),a=document.querySelector("#bm-k"),r=document.querySelector("#bm-q"),l=document.querySelector("#bm-r"),m=document.querySelector("#bm-s"),c=document.querySelector("#bm-l"),u=document.querySelectorAll("#bm-k input");e||(i.style.width="auto",i.style.maxWidth="300px",i.style.minWidth="200px",i.style.padding="10px"),["#bm-A h1","#bm-f","#bm-A hr","#bm-c > *:not(#bm-k)","#bm-a","#bm-6",`#${t.i}`].forEach(t=>{document.querySelectorAll(t).forEach(t=>{t.style.display=e?"none":""})}),e?(a&&(a.style.display="none"),r&&(r.style.display="none"),l&&(l.style.display="none"),m&&(m.style.display="none"),c&&(c.style.display="none"),u.forEach(e=>{e.style.display="none"}),i.style.width="60px",i.style.height="76px",i.style.maxWidth="60px",i.style.minWidth="60px",i.style.padding="8px",n.style.marginLeft="3px",o.style.textAlign="center",o.style.margin="0",o.style.marginBottom="0",s&&(s.style.display="",s.style.marginBottom="0.25em")):(a&&(a.style.display="",a.style.flexDirection="",a.style.justifyContent="",a.style.alignItems="",a.style.gap="",a.style.textAlign="",a.style.margin=""),r&&(r.style.display=""),l&&(l.style.display="",l.style.marginTop=""),m&&(m.style.display="",m.style.marginTop=""),c&&(c.style.display="",c.style.marginTop=""),u.forEach(e=>{e.style.display=""}),n.style.marginLeft="",i.style.padding="10px",o.style.textAlign="",o.style.margin="",o.style.marginBottom="",s&&(s.style.marginBottom="0.5em"),i.style.width="",i.style.height=""),n.alt=e?"Blue Marble Icon - Minimized (Click to maximize)":"Blue Marble Icon - Maximized (Click to minimize)"})}).h().T(1,{textContent:v}).h().h().k().h().v({id:"bm-f"}).S({id:"bm-p",textContent:"Droplets:"}).h().S({id:"bm-i",textContent:"Next level in..."}).h().h().k().h().v({id:"bm-c"}).v({id:"bm-k"}).C({id:"bm-q",className:"bm-D",style:"margin-top: 0;",innerHTML:''},(e,t)=>{t.onclick=()=>{const t=e.t?.Le;t?.[0]?(e.B("bm-v",t?.[0]||""),e.B("bm-w",t?.[1]||""),e.B("bm-x",t?.[2]||""),e.B("bm-y",t?.[3]||"")):e.W("Coordinates are malformed! Did you try clicking on the canvas first?")}}).h().I({type:"number",id:"bm-v",placeholder:"Tl X",min:0,max:2047,step:1,required:!0},(e,t)=>{t.addEventListener("paste",e=>{let t=(e.clipboardData||window.clipboardData).getData("text").split(" ").filter(e=>e).map(Number).filter(e=>!isNaN(e));if(4!==t.length)return;let n=selectAllCoordinateInputs(document);for(let e=0;epersistCoords();t.addEventListener("input",n),t.addEventListener("change",n)}).h().I({type:"number",id:"bm-w",placeholder:"Tl Y",min:0,max:2047,step:1,required:!0},(e,t)=>{const n=()=>persistCoords();t.addEventListener("input",n),t.addEventListener("change",n)}).h().I({type:"number",id:"bm-x",placeholder:"Px X",min:0,max:2047,step:1,required:!0},(e,t)=>{const n=()=>persistCoords();t.addEventListener("input",n),t.addEventListener("change",n)}).h().I({type:"number",id:"bm-y",placeholder:"Px Y",min:0,max:2047,step:1,required:!0},(e,t)=>{const n=()=>persistCoords();t.addEventListener("input",n),t.addEventListener("change",n)}).h().h().L({id:"bm-a",textContent:"Upload Template",accept:"image/png, image/jpeg, image/webp, image/bmp, image/gif"}).h().v({id:"bm-4"}).C({id:"bm-s",textContent:"Enable"},(e,t)=>{t.onclick=()=>{e.t?.Be?.Ne(!0),e._("Enabled templates!")}}).h().C({id:"bm-r",textContent:"Create"},(e,t)=>{t.onclick=()=>{const t=document.querySelector("#bm-a"),n=document.querySelector("#bm-v");if(!n.checkValidity())return n.reportValidity(),void e.W("Coordinates are malformed! Did you try clicking on the canvas first?");const i=document.querySelector("#bm-w");if(!i.checkValidity())return i.reportValidity(),void e.W("Coordinates are malformed! Did you try clicking on the canvas first?");const o=document.querySelector("#bm-x");if(!o.checkValidity())return o.reportValidity(),void e.W("Coordinates are malformed! Did you try clicking on the canvas first?");const s=document.querySelector("#bm-y");if(!s.checkValidity())return s.reportValidity(),void e.W("Coordinates are malformed! Did you try clicking on the canvas first?");t?.files[0]?(k.Se(t.files[0],t.files[0]?.name.replace(/\.[^/.]+$/,""),[Number(n.value),Number(i.value),Number(o.value),Number(s.value)]),e._("Drew to canvas!")):e.W("No file selected!")}}).h().C({id:"bm-l",textContent:"Disable"},(e,t)=>{t.onclick=()=>{e.t?.Be?.Ne(!1),e._("Disabled templates!")}}).h().h().P({id:T.i,placeholder:`Status: Sleeping...\nVersion: ${x}`,readOnly:!0}).h().v({id:"bm-6"}).v().C({id:"bm-m",className:"bm-D",innerHTML:"🎨",title:"Template Color Converter"},(e,t)=>{t.addEventListener("click",()=>{window.open("https://pepoafonso.github.io/color_converter_wplace/","_blank","noopener noreferrer")})}).h().C({id:"bm-n",className:"bm-D",innerHTML:"🌐",title:"Official Blue Marble Website"},(e,t)=>{t.addEventListener("click",()=>{window.open("https://bluemarble.lol/","_blank","noopener noreferrer")})}).h().h().$({textContent:"Made by SwingTheVine",style:"margin-top: auto;"}).h().h().h().p(document.body)}(),T.G("#bm-A","#bm-z"),D.Ge(T),new MutationObserver((e,t)=>{const n=document.querySelector("#color-1");if(!n)return;let i=document.querySelector("#bm-t");if(!i){i=document.createElement("button"),i.id="bm-t",i.textContent="Move ↑",i.className="btn btn-soft",i.onclick=function(){const e=this.parentNode.parentNode.parentNode.parentNode,t="Move ↑"==this.textContent;e.parentNode.className=e.parentNode.className.replace(t?"bottom":"top",t?"top":"bottom"),e.style.borderTopLeftRadius=t?"0px":"var(--radius-box)",e.style.borderTopRightRadius=t?"0px":"var(--radius-box)",e.style.borderBottomLeftRadius=t?"var(--radius-box)":"0px",e.style.borderBottomRightRadius=t?"var(--radius-box)":"0px",this.textContent=t?"Move ↓":"Move ↑"};const e=n.parentNode.parentNode.parentNode.parentNode.querySelector("h2");e.parentNode?.appendChild(i)}}).observe(document.body,{childList:!0,subtree:!0}),function(...e){(0,console.log)(...e)}(`%c${v}%c (${x}) userscript has loaded!`,"color: cornflowerblue;","")})(); \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 1b473f6..f33973e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,7 +51,7 @@ Contact Me Blue Marble Website WakaTime -Total Patches +Total Patches Total Lines of Code Total Comments Compression diff --git a/package-lock.json b/package-lock.json index ea6b912..af2cb23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wplace-bluemarble", - "version": "0.88.71", + "version": "0.88.77", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wplace-bluemarble", - "version": "0.88.71", + "version": "0.88.77", "devDependencies": { "esbuild": "^0.25.0", "jsdoc": "^4.0.5", diff --git a/package.json b/package.json index 707aa02..a7fc591 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wplace-bluemarble", - "version": "0.88.71", + "version": "0.88.77", "type": "module", "homepage": "https://bluemarble.lol/", "repository": { diff --git a/src/BlueMarble.meta.js b/src/BlueMarble.meta.js index 6b3c828..2c3ed07 100644 --- a/src/BlueMarble.meta.js +++ b/src/BlueMarble.meta.js @@ -1,27 +1,30 @@ // ==UserScript== -// @name Blue Marble -// @namespace https://github.com/SwingTheVine/ -// @version 0.88.71 -// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. -// @author SwingTheVine -// @license MPL-2.0 -// @supportURL https://discord.gg/tpeBPy46hf -// @homepageURL https://bluemarble.lol/ -// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/0c760b903739e6214f7b8990ffc4089a93e73bd2/dist/assets/Favicon.png -// @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js -// @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js -// @match https://wplace.live/* -// @grant GM_getResourceText -// @grant GM_addStyle -// @grant GM.setValue -// @grant GM_getValue -// @grant GM_xmlhttpRequest -// @connect telemetry.thebluecorner.net -// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/0c760b903739e6214f7b8990ffc4089a93e73bd2/dist/BlueMarble.user.css +// @name Blue Marble +// @name:en Blue Marble +// @namespace https://github.com/SwingTheVine/ +// @version 0.88.77 +// @description A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. +// @description:en A userscript to automate and/or enhance the user experience on Wplace.live. Make sure to comply with the site's Terms of Service, and rules! This script is not affiliated with Wplace.live in any way, use at your own risk. This script is not affiliated with TamperMonkey. The author of this userscript is not responsible for any damages, issues, loss of data, or punishment that may occur as a result of using this script. This script is provided "as is" under the MPL-2.0 license. The "Blue Marble" icon is licensed under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication. The image is owned by NASA. +// @author SwingTheVine +// @license MPL-2.0 +// @donate https://ko-fi.com/swingthevine +// @supportURL https://discord.gg/tpeBPy46hf +// @homepageURL https://bluemarble.lol/ +// @icon https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/0c760b903739e6214f7b8990ffc4089a93e73bd2/dist/assets/Favicon.png +// @updateURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js +// @downloadURL https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/BlueMarble.user.js +// @match https://wplace.live/* +// @grant GM_getResourceText +// @grant GM_addStyle +// @grant GM.setValue +// @grant GM_getValue +// @grant GM_xmlhttpRequest +// @connect telemetry.thebluecorner.net +// @resource CSS-BM-File https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/0c760b903739e6214f7b8990ffc4089a93e73bd2/dist/BlueMarble.user.css +// @antifeature tracking Anonymous opt-in telemetry data // @noframes // ==/UserScript== // Wplace --> https://wplace.live // License --> https://www.mozilla.org/en-US/MPL/2.0/ -// Donate --> https://ko-fi.com/swingthevine