From f672534dc754b81a312ff0191f4289cdce640a17 Mon Sep 17 00:00:00 2001 From: SwingTheVine Date: Sun, 27 Jul 2025 03:29:29 -0400 Subject: [PATCH] Added Utils class and removed XSS issue --- .github/workflows/build.yml | 3 +++ dist/BlueMarble.user.js | 8 ++++---- docs/README.md | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- src/BlueMarble.meta.js | 2 +- src/apiHandler.js | 7 +++++-- src/coordsHandler.js | 2 +- src/main.js | 8 ++++---- src/observers.js | 4 ++-- src/overlay.js | 2 +- src/utils.js | 21 +++++++++++++++++++++ 12 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 src/utils.js diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5b62cc2..704d684 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,9 @@ on: jobs: build: + permissions: + contents: write + runs-on: ubuntu-latest steps: diff --git a/dist/BlueMarble.user.js b/dist/BlueMarble.user.js index bb53101..f227406 100644 --- a/dist/BlueMarble.user.js +++ b/dist/BlueMarble.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.44.0 +// @version 0.44.4 // @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 @@ -20,6 +20,6 @@ // Wplace --> https://wplace.live // License --> https://www.mozilla.org/en-US/MPL/2.0/ -(()=>{var m=class{constructor(t,e){this.name=t,this.version=e,this.apiHandler=null,this.outputStatusId="bm-output-status",this.overlay=null,this.currentParent=null,this.parentStack=[]}setApiHandler(t){this.apiHandler=t}#t(t,e={},n={}){let s=document.createElement(t);this.overlay?(this.currentParent.appendChild(s),this.parentStack.push(this.currentParent),this.currentParent=s):(this.overlay=s,this.currentParent=s);for(let[r,o]of Object.entries(e))s[r]=o;for(let[r,o]of Object.entries(n))s[r]=o;return s}buildElement(){return this.parentStack.length>0&&(this.currentParent=this.parentStack.pop()),this}buildOverlay(t){t.appendChild(this.overlay),this.overlay=null,this.currentParent=null,this.parentStack=[]}addDiv(t={},e=()=>{}){let n={},s=this.#t("div",n,t);return e(this,s),this}addP(t={},e=()=>{}){let n={},s=this.#t("p",n,t);return e(this,s),this}addImg(t={},e=()=>{}){let n={},s=this.#t("img",n,t);return e(this,s),this}addHeader(t,e={},n=()=>{}){let s={},r=this.#t("h"+t,s,e);return n(this,r),this}addHr(t={},e=()=>{}){let n={},s=this.#t("hr",n,t);return e(this,s),this}addBr(t={},e=()=>{}){let n={},s=this.#t("br",n,t);return e(this,s),this}addCheckbox(t={},e=()=>{}){let n={type:"checkbox"},s=this.#t("label",{textContent:t.textContent??""});delete t.textContent;let r=this.#t("input",n,t);return s.insertBefore(r,s.firstChild),this.buildElement(),e(this,s,r),this}addButton(t={},e=()=>{}){let n={},s=this.#t("button",n,t);return e(this,s),this}addButtonHelp(t={},e=()=>{}){let n=t.title??t.textContent??"Help: No info";delete t.textContent,t.title=`Help: ${n}`;let s={textContent:"?",className:"bm-help",onclick:()=>{this.updateInnerHTML(this.outputStatusId,n)}},r=this.#t("button",s,t);return e(this,r),this}addInput(t={},e=()=>{}){let n={},s=this.#t("input",n,t);return e(this,s),this}addInputFile(t={},e=()=>{}){let n={type:"file",style:"display: none;"},s=t.textContent??"";delete t.textContent;let r=this.#t("div"),o=this.#t("input",n,t);this.buildElement();let i=this.#t("button",{textContent:s});return this.buildElement(),this.buildElement(),i.addEventListener("click",()=>{o.click()}),o.addEventListener("change",()=>{i.style.maxWidth=`${i.offsetWidth}px`,o.files.length>0?i.textContent=o.files[0].name:i.textContent=s}),e(this,r,o,i),this}addTextarea(t={},e=()=>{}){let n={},s=this.#t("textarea",n,t);return e(this,s),this}updateInnerHTML(t,e,n=!1){let s=document.getElementById(t.replace(/^#/,""));if(s){if(s instanceof HTMLInputElement){s.value=e;return}n?s.textContent=e:s.innerHTML=e}}handleDrag(t,e){let n=!1,s,r=0;if(t=document.querySelector(t?.[0]=="#"?t:"#"+t),e=document.querySelector(e?.[0]=="#"?e:"#"+e),!t||!e){this.handleDisplayError(`Can not drag! ${t?"":"moveMe"} ${!t&&!e?"and ":""}${e?"":"iMoveThings "}was not found!`);return}e.addEventListener("mousedown",function(o){n=!0,s=o.clientX-t.getBoundingClientRect().left,r=o.clientY-t.getBoundingClientRect().top,document.body.style.userSelect="none",e.classList.add("dragging")}),e.addEventListener("touchstart",function(o){n=!0;let i=o?.touches?.[0];i&&(s=i.clientX-t.getBoundingClientRect().left,r=i.clientY-t.getBoundingClientRect().top,document.body.style.userSelect="none",e.classList.add("dragging"))},{passive:!1}),document.addEventListener("mousemove",function(o){n&&(t.style.left=o.clientX-s+"px",t.style.top=o.clientY-r+"px",t.style.right="")}),document.addEventListener("touchmove",function(o){if(n){let i=o?.touches?.[0];if(!i)return;t.style.left=i.clientX-s+"px",t.style.top=i.clientY-r+"px",o.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",function(){n=!1,document.body.style.userSelect="",e.classList.remove("dragging")}),document.addEventListener("touchend",function(){n=!1,document.body.style.userSelect="",e.classList.remove("dragging")}),document.addEventListener("touchcancel",function(){n=!1,document.body.style.userSelect="",e.classList.remove("dragging")})}handleDisplayError(t){console.error(`${this.name}: ${t}`),this.updateInnerHTML(this.outputStatusId,"Error: "+t,!0)}};var h=class{constructor(){this.observerBody=null,this.observerBodyTarget=null,this.targetDisplayCoords="#bm-display-coords"}createObserverBody(t){return this.observerBodyTarget=t,this.observerBody=new MutationObserver(e=>{for(let n of e)for(let s of n.addedNodes)s instanceof HTMLElement&&s.matches?.(this.targetDisplayCoords)}),this}getObserverBody(){return this.observerBody}observe(t,e=!1,n=!1){t.observe(this.observerBodyTarget,{childList:e,subtree:n})}};var b=class{serverTPtoDisplayTP(t,e){return[parseInt(t[0])%4*1e3+parseInt(e[0]),parseInt(t[1])%4*1e3+parseInt(e[1])]}};var f=class{constructor(t){this.coordsHandler=t,this.disableAll=!1,this.coordsTilePixel=[]}spontaneousResponseListener(t){window.addEventListener("message",e=>{let n=e.data;if(!(n&&n.source==="blue-marble"))return;let s=n.endpoint.split("?")[0].split("/").filter(r=>r&&isNaN(Number(r))).pop();switch(console.log(`Recieved message about "${s}"`),s){case"me":if(n.jsonData?.status&&n.jsonData?.status?.toString()[0]!="2"){t.handleDisplayError(`The game is down! -Could not fetch userdata.`);return}let r=Math.ceil(Math.pow(Math.floor(n.jsonData?.level)*Math.pow(30,.65),1/.65)-n.jsonData?.pixelsPainted);t.updateInnerHTML("bm-user-name",`Username: ${n.jsonData?.name}`),t.updateInnerHTML("bm-user-droplets",`Droplets: ${new Intl.NumberFormat().format(n.jsonData?.droplets)}`),t.updateInnerHTML("bm-user-nextlevel",`Next level in ${new Intl.NumberFormat().format(r)} pixel${r==1?"":"s"}`);break;case"pixel":let o=n.endpoint.split("?")[0].split("/").filter(d=>d&&!isNaN(Number(d))),i=new URLSearchParams(n.endpoint.split("?")[1]),p=[i.get("x"),i.get("y")];this.coordsTilePixel=[...o,...p];let g=this.coordsHandler.serverTPtoDisplayTP(o,p),v=document.querySelectorAll("span");for(let d of v)if(d.textContent.trim().includes(`${g[0]}, ${g[1]}`)){let a=document.querySelector("#bm-display-coords"),E=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${p[0]}, Px Y: ${p[1]})`;a?a.textContent=E:(a=document.createElement("span"),a.id="bm-display-coords",a.textContent=E,a.style="margin-left: calc(var(--spacing)*3); font-size: small;",d.parentNode.parentNode.parentNode.insertAdjacentElement("afterend",a))}break;case"robots":this.disableAll=n.jsonData?.userscript?.toString().toLowerCase()=="false";break}})}};var y=GM_info.script.name.toString(),x=GM_info.script.version.toString();function L(l){let t=document.createElement("script");t.textContent=`(${l})();`,document.documentElement.appendChild(t),t.remove()}L(()=>{let l=window.fetch;window.fetch=async function(...t){let e=await l.apply(this,t),n=e.clone();if((n.headers.get("content-type")||"").includes("application/json")){let r=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore";console.log(`Sending JSON message about endpoint "${r}"`),n.json().then(o=>{window.postMessage({source:"blue-marble",endpoint:r,jsonData:o},"*")}).catch(o=>{console.error("BM - Failed to parse JSON:",o)})}return e}});var w=GM_getResourceText("CSS-BM-File");GM_addStyle(w);var u=document.createElement("link");u.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap";u.rel="preload";u.as="style";u.onload=function(){this.onload=null,this.rel="stylesheet"};document.head.appendChild(u);var P=new h,c=new m(y,x),H=new b,C=new f(H);c.setApiHandler(C);c.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",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/src/assets/Favicon.png"}).buildElement().addHeader(1,{textContent:y}).buildElement().buildElement().addHr().buildElement().addDiv({id:"bm-contain-userinfo"}).addP({id:"bm-user-name",textContent:"Username:"}).buildElement().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"}).addCheckbox({id:"bm-input-stealth",textContent:"Stealth",checked:!0}).buildElement().addButtonHelp({title:"Waits for the website to make requests, instead of sending requests."}).buildElement().addBr().buildElement().addCheckbox({id:"bm-input-possessed",textContent:"Possessed",checked:!0}).buildElement().addButtonHelp({title:"Controls the website as if it were possessed."}).buildElement().addBr().buildElement().addDiv({id:"bm-contain-coords"}).addButton({id:"bm-button-coords",className:"bm-help",style:"margin-top: 0;",innerHTML:''},(l,t)=>{t.onclick=()=>{let e=l.apiHandler?.coordsTilePixel;if(!e?.[0]){l.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?");return}l.updateInnerHTML("bm-input-tx",e?.[0]||""),l.updateInnerHTML("bm-input-ty",e?.[1]||""),l.updateInnerHTML("bm-input-px",e?.[2]||""),l.updateInnerHTML("bm-input-py",e?.[3]||"")}}).buildElement().addInput({type:"number",id:"bm-input-tx",placeholder:"Tl X",min:0,max:2047,step:1}).buildElement().addInput({type:"number",id:"bm-input-ty",placeholder:"Tl Y",min:0,max:2047,step:1}).buildElement().addInput({type:"number",id:"bm-input-px",placeholder:"Px X",min:0,max:2047,step:1}).buildElement().addInput({type:"number",id:"bm-input-py",placeholder:"Px Y",min:0,max:2047,step:1}).buildElement().buildElement().addInputFile({id:"bm-input-file-template",textContent:"Upload Template"}).buildElement().addDiv({id:"bm-contain-buttons"}).addButton({id:"bm-button-enable",textContent:"Enable"}).buildElement().addButton({id:"bm-button-disable",textContent:"Disable"}).buildElement().buildElement().addTextarea({id:c.outputStatusId,placeholder:`Status: Sleeping... -Version: ${x}`,readOnly:!0}).buildElement().buildElement().buildOverlay(document.body);c.handleDrag("#bm-overlay","#bm-bar-drag");C.spontaneousResponseListener(c);console.log(`${y} (${x}) userscript has loaded!`);})(); +(()=>{var c=class{constructor(t,e){this.name=t,this.version=e,this.apiHandler=null,this.outputStatusId="bm-output-status",this.overlay=null,this.currentParent=null,this.parentStack=[]}setApiHandler(t){this.apiHandler=t}#t(t,e={},n={}){let s=document.createElement(t);this.overlay?(this.currentParent.appendChild(s),this.parentStack.push(this.currentParent),this.currentParent=s):(this.overlay=s,this.currentParent=s);for(let[r,o]of Object.entries(e))s[r]=o;for(let[r,o]of Object.entries(n))s[r]=o;return s}buildElement(){return this.parentStack.length>0&&(this.currentParent=this.parentStack.pop()),this}buildOverlay(t){t.appendChild(this.overlay),this.overlay=null,this.currentParent=null,this.parentStack=[]}addDiv(t={},e=()=>{}){let n={},s=this.#t("div",n,t);return e(this,s),this}addP(t={},e=()=>{}){let n={},s=this.#t("p",n,t);return e(this,s),this}addImg(t={},e=()=>{}){let n={},s=this.#t("img",n,t);return e(this,s),this}addHeader(t,e={},n=()=>{}){let s={},r=this.#t("h"+t,s,e);return n(this,r),this}addHr(t={},e=()=>{}){let n={},s=this.#t("hr",n,t);return e(this,s),this}addBr(t={},e=()=>{}){let n={},s=this.#t("br",n,t);return e(this,s),this}addCheckbox(t={},e=()=>{}){let n={type:"checkbox"},s=this.#t("label",{textContent:t.textContent??""});delete t.textContent;let r=this.#t("input",n,t);return s.insertBefore(r,s.firstChild),this.buildElement(),e(this,s,r),this}addButton(t={},e=()=>{}){let n={},s=this.#t("button",n,t);return e(this,s),this}addButtonHelp(t={},e=()=>{}){let n=t.title??t.textContent??"Help: No info";delete t.textContent,t.title=`Help: ${n}`;let s={textContent:"?",className:"bm-help",onclick:()=>{this.updateInnerHTML(this.outputStatusId,n)}},r=this.#t("button",s,t);return e(this,r),this}addInput(t={},e=()=>{}){let n={},s=this.#t("input",n,t);return e(this,s),this}addInputFile(t={},e=()=>{}){let n={type:"file",style:"display: none;"},s=t.textContent??"";delete t.textContent;let r=this.#t("div"),o=this.#t("input",n,t);this.buildElement();let i=this.#t("button",{textContent:s});return this.buildElement(),this.buildElement(),i.addEventListener("click",()=>{o.click()}),o.addEventListener("change",()=>{i.style.maxWidth=`${i.offsetWidth}px`,o.files.length>0?i.textContent=o.files[0].name:i.textContent=s}),e(this,r,o,i),this}addTextarea(t={},e=()=>{}){let n={},s=this.#t("textarea",n,t);return e(this,s),this}updateInnerHTML(t,e,n=!1){let s=document.getElementById(t.replace(/^#/,""));if(s){if(s instanceof HTMLInputElement){s.value=e;return}n?s.textContent=e:s.innerHTML=e}}handleDrag(t,e){let n=!1,s,r=0;if(t=document.querySelector(t?.[0]=="#"?t:"#"+t),e=document.querySelector(e?.[0]=="#"?e:"#"+e),!t||!e){this.handleDisplayError(`Can not drag! ${t?"":"moveMe"} ${!t&&!e?"and ":""}${e?"":"iMoveThings "}was not found!`);return}e.addEventListener("mousedown",function(o){n=!0,s=o.clientX-t.getBoundingClientRect().left,r=o.clientY-t.getBoundingClientRect().top,document.body.style.userSelect="none",e.classList.add("dragging")}),e.addEventListener("touchstart",function(o){n=!0;let i=o?.touches?.[0];i&&(s=i.clientX-t.getBoundingClientRect().left,r=i.clientY-t.getBoundingClientRect().top,document.body.style.userSelect="none",e.classList.add("dragging"))},{passive:!1}),document.addEventListener("mousemove",function(o){n&&(t.style.left=o.clientX-s+"px",t.style.top=o.clientY-r+"px",t.style.right="")}),document.addEventListener("touchmove",function(o){if(n){let i=o?.touches?.[0];if(!i)return;t.style.left=i.clientX-s+"px",t.style.top=i.clientY-r+"px",o.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",function(){n=!1,document.body.style.userSelect="",e.classList.remove("dragging")}),document.addEventListener("touchend",function(){n=!1,document.body.style.userSelect="",e.classList.remove("dragging")}),document.addEventListener("touchcancel",function(){n=!1,document.body.style.userSelect="",e.classList.remove("dragging")})}handleDisplayError(t){console.error(`${this.name}: ${t}`),this.updateInnerHTML(this.outputStatusId,"Error: "+t,!0)}};var u=class{constructor(){this.observerBody=null,this.observerBodyTarget=null,this.targetDisplayCoords="#bm-display-coords"}createObserverBody(t){return this.observerBodyTarget=t,this.observerBody=new MutationObserver(e=>{for(let n of e)for(let s of n.addedNodes)s instanceof HTMLElement&&s.matches?.(this.targetDisplayCoords)}),this}getObserverBody(){return this.observerBody}observe(t,e=!1,n=!1){t.observe(this.observerBodyTarget,{childList:e,subtree:n})}};var p=class{serverTPtoDisplayTP(t,e){return[parseInt(t[0])%4*1e3+parseInt(e[0]),parseInt(t[1])%4*1e3+parseInt(e[1])]}};var m=class{static escapeHTML(t){let e=document.createElement("div");return e.textContent=t,e.innerHTML}};var h=class{constructor(t){this.coordsHandler=t,this.disableAll=!1,this.coordsTilePixel=[]}spontaneousResponseListener(t){window.addEventListener("message",e=>{let n=e.data;if(!(n&&n.source==="blue-marble"))return;let s=n.endpoint.split("?")[0].split("/").filter(r=>r&&isNaN(Number(r))).pop();switch(console.log(`Recieved message about "${s}"`),s){case"me":if(n.jsonData?.status&&n.jsonData?.status?.toString()[0]!="2"){t.handleDisplayError(`The game is down! +Could not fetch userdata.`);return}let r=Math.ceil(Math.pow(Math.floor(n.jsonData?.level)*Math.pow(30,.65),1/.65)-n.jsonData?.pixelsPainted);t.updateInnerHTML("bm-user-name",`Username: ${m.escapeHTML(n.jsonData?.name)}`),t.updateInnerHTML("bm-user-droplets",`Droplets: ${new Intl.NumberFormat().format(n.jsonData?.droplets)}`),t.updateInnerHTML("bm-user-nextlevel",`Next level in ${new Intl.NumberFormat().format(r)} pixel${r==1?"":"s"}`);break;case"pixel":let o=n.endpoint.split("?")[0].split("/").filter(d=>d&&!isNaN(Number(d))),i=new URLSearchParams(n.endpoint.split("?")[1]),y=[i.get("x"),i.get("y")];this.coordsTilePixel=[...o,...y];let E=this.coordsHandler.serverTPtoDisplayTP(o,y),L=document.querySelectorAll("span");for(let d of L)if(d.textContent.trim().includes(`${E[0]}, ${E[1]}`)){let a=document.querySelector("#bm-display-coords"),v=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${y[0]}, Px Y: ${y[1]})`;a?a.textContent=v:(a=document.createElement("span"),a.id="bm-display-coords",a.textContent=v,a.style="margin-left: calc(var(--spacing)*3); font-size: small;",d.parentNode.parentNode.parentNode.insertAdjacentElement("afterend",a))}break;case"robots":this.disableAll=n.jsonData?.userscript?.toString().toLowerCase()=="false";break}})}};var x=GM_info.script.name.toString(),g=GM_info.script.version.toString();function w(l){let t=document.createElement("script");t.textContent=`(${l})();`,document.documentElement.appendChild(t),t.remove()}w(()=>{let l=window.fetch;window.fetch=async function(...t){let e=await l.apply(this,t),n=e.clone();if((n.headers.get("content-type")||"").includes("application/json")){let r=(t[0]instanceof Request?t[0]?.url:t[0])||"ignore";console.log(`Sending JSON message about endpoint "${r}"`),n.json().then(o=>{window.postMessage({source:"blue-marble",endpoint:r,jsonData:o},"*")}).catch(o=>{console.error("BM - Failed to parse JSON:",o)})}return e}});var H=GM_getResourceText("CSS-BM-File");GM_addStyle(H);var f=document.createElement("link");f.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap";f.rel="preload";f.as="style";f.onload=function(){this.onload=null,this.rel="stylesheet"};document.head.appendChild(f);var R=new u,b=new c(x,g),S=new p,C=new h(S);b.setApiHandler(C);b.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",src:"https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/src/assets/Favicon.png"}).buildElement().addHeader(1,{textContent:x}).buildElement().buildElement().addHr().buildElement().addDiv({id:"bm-contain-userinfo"}).addP({id:"bm-user-name",textContent:"Username:"}).buildElement().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"}).addCheckbox({id:"bm-input-stealth",textContent:"Stealth",checked:!0}).buildElement().addButtonHelp({title:"Waits for the website to make requests, instead of sending requests."}).buildElement().addBr().buildElement().addCheckbox({id:"bm-input-possessed",textContent:"Possessed",checked:!0}).buildElement().addButtonHelp({title:"Controls the website as if it were possessed."}).buildElement().addBr().buildElement().addDiv({id:"bm-contain-coords"}).addButton({id:"bm-button-coords",className:"bm-help",style:"margin-top: 0;",innerHTML:''},(l,t)=>{t.onclick=()=>{let e=l.apiHandler?.coordsTilePixel;if(!e?.[0]){l.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?");return}l.updateInnerHTML("bm-input-tx",e?.[0]||""),l.updateInnerHTML("bm-input-ty",e?.[1]||""),l.updateInnerHTML("bm-input-px",e?.[2]||""),l.updateInnerHTML("bm-input-py",e?.[3]||"")}}).buildElement().addInput({type:"number",id:"bm-input-tx",placeholder:"Tl X",min:0,max:2047,step:1}).buildElement().addInput({type:"number",id:"bm-input-ty",placeholder:"Tl Y",min:0,max:2047,step:1}).buildElement().addInput({type:"number",id:"bm-input-px",placeholder:"Px X",min:0,max:2047,step:1}).buildElement().addInput({type:"number",id:"bm-input-py",placeholder:"Px Y",min:0,max:2047,step:1}).buildElement().buildElement().addInputFile({id:"bm-input-file-template",textContent:"Upload Template"}).buildElement().addDiv({id:"bm-contain-buttons"}).addButton({id:"bm-button-enable",textContent:"Enable"}).buildElement().addButton({id:"bm-button-disable",textContent:"Disable"}).buildElement().buildElement().addTextarea({id:b.outputStatusId,placeholder:`Status: Sleeping... +Version: ${g}`,readOnly:!0}).buildElement().buildElement().buildOverlay(document.body);b.handleDrag("#bm-overlay","#bm-bar-drag");C.spontaneousResponseListener(b);console.log(`${x} (${g}) userscript has loaded!`);})(); diff --git a/docs/README.md b/docs/README.md index 780b49b..29c452c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,8 +34,8 @@ Latest Release Software License: MPL-2.0 Contact Me -WakaTime -Total Patches +WakaTime +Total Patches Total Lines of Code Total Comments Build diff --git a/package-lock.json b/package-lock.json index c218048..04689ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "devDependencies": { "esbuild": "^0.25.0" }, - "version": "0.43.40" + "version": "0.44.4" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.8", @@ -467,5 +467,5 @@ } } }, - "version": "0.43.40" + "version": "0.44.4" } diff --git a/package.json b/package.json index 912929d..6e747f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wplace-bluemarble", - "version": "0.44.0", + "version": "0.44.4", "type": "module", "scripts": { "build": "node build/build.js", diff --git a/src/BlueMarble.meta.js b/src/BlueMarble.meta.js index f1187d7..55e485f 100644 --- a/src/BlueMarble.meta.js +++ b/src/BlueMarble.meta.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Blue Marble // @namespace https://github.com/SwingTheVine/ -// @version 0.44.0 +// @version 0.44.4 // @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 diff --git a/src/apiHandler.js b/src/apiHandler.js index bf91af7..ad6e8c1 100644 --- a/src/apiHandler.js +++ b/src/apiHandler.js @@ -2,7 +2,10 @@ * Note: Fetch spying is done in main.js, not here. * @since 0.11.1 */ -export class ApiHandler { + +import Utils from "./utils"; + +export default class ApiHandler { /** Constructor for ApiHandler class * @param {CoordsHandler} coordsHandler - The CoordsHandler instance @@ -51,7 +54,7 @@ export class ApiHandler { const nextLevelPixels = Math.ceil(Math.pow(Math.floor(data.jsonData?.level) * Math.pow(30, 0.65), (1/0.65)) - data.jsonData?.pixelsPainted); // Calculates pixels to the next level - overlay.updateInnerHTML('bm-user-name', `Username: ${data.jsonData?.name}`); // Updates the text content of the username field + overlay.updateInnerHTML('bm-user-name', `Username: ${Utils.escapeHTML(data.jsonData?.name)}`); // Updates the text content of the username field overlay.updateInnerHTML('bm-user-droplets', `Droplets: ${new Intl.NumberFormat().format(data.jsonData?.droplets)}`); // Updates the text content of the droplets field overlay.updateInnerHTML('bm-user-nextlevel', `Next level in ${new Intl.NumberFormat().format(nextLevelPixels)} pixel${nextLevelPixels == 1 ? '' : 's'}`); // Updates the text content of the next level field break; diff --git a/src/coordsHandler.js b/src/coordsHandler.js index 48d3ae8..9e34258 100644 --- a/src/coordsHandler.js +++ b/src/coordsHandler.js @@ -1,7 +1,7 @@ /** Handles translation of coordinate systems. * @since 0.42.4 */ -export class CoordsHandler { +export default class CoordsHandler { /** Converts the server tile-pixel coordinate system to the displayed tile-pixel coordinate system. * @param {string[]} tile - The tile to convert (as an array like ["12", "124"]) diff --git a/src/main.js b/src/main.js index 93e45fd..13341a3 100644 --- a/src/main.js +++ b/src/main.js @@ -1,7 +1,7 @@ -import { Overlay } from './overlay.js'; -import { Observers } from './observers.js'; -import { CoordsHandler } from './coordsHandler.js'; -import { ApiHandler } from './apiHandler.js'; +import Overlay from './overlay.js'; +import Observers from './observers.js'; +import CoordsHandler from './coordsHandler.js'; +import ApiHandler from './apiHandler.js'; const name = GM_info.script.name.toString(); const version = GM_info.script.version.toString(); diff --git a/src/observers.js b/src/observers.js index 00f78e6..b64febd 100644 --- a/src/observers.js +++ b/src/observers.js @@ -2,7 +2,7 @@ * This is not an object, but rather a "collection" of functions (in a class). * @since 0.43.2 */ -export class Observers { +export default class Observers { /** The constructor for the observer class * @since 0.43.2 @@ -29,7 +29,7 @@ export class Observers { if (!(node instanceof HTMLElement)) {continue;} // Does not track non-HTMLElements if (node.matches?.(this.targetDisplayCoords)) { - + } } } diff --git a/src/overlay.js b/src/overlay.js index ce78e11..659eabb 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -17,7 +17,7 @@ * * */ -export class Overlay { +export default class Overlay { /** Constructor for the Overlay class. * @param {string} name - The name of the userscript diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..5f9ce2f --- /dev/null +++ b/src/utils.js @@ -0,0 +1,21 @@ +/** Contains miscellaneous helper functions. + * @since 0.44.2 + */ +export default class Utils { + + /** Sanitizes HTML to display as plain-text. + * This prevents some Cross Site Scripting (XSS). + * This is handy when you are displaying user-made data, and you *must* use innerHTML. + * @param {string} text - The text to sanitize + * @returns {string} HTML escaped string + * @since 0.44.2 + * @example + * const paragraph = document.createElement('p'); + * paragraph.innerHTML = escapeHTML('Foobar.'); + */ + static escapeHTML(text) { + const div = document.createElement('div'); // Creates a div + div.textContent = text; // Puts the text in a PLAIN-TEXT property + return div.innerHTML; // Returns the HTML property of the div + } +} \ No newline at end of file