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