From c1559340c7f2728e7321d8ca3dc45ef23c5a19dc Mon Sep 17 00:00:00 2001 From: SwingTheVine Date: Sun, 27 Jul 2025 02:37:25 -0400 Subject: [PATCH] Finished refactoring the overlay to be Object Oriented Also fixed bug with font not loading --- dist/BlueMarble.user.css | 2 +- dist/BlueMarble.user.js | 7 +- docs/README.md | 2 +- package-lock.json | 4 +- package.json | 2 +- src/BlueMarble.meta.js | 2 +- src/apiHandler.js | 9 + src/main.js | 56 +++- src/overlay.css | 4 +- src/overlay.js | 628 ++++++++++++++++++++++----------------- 10 files changed, 431 insertions(+), 285 deletions(-) diff --git a/dist/BlueMarble.user.css b/dist/BlueMarble.user.css index 6813d3f..18d08ed 100644 --- a/dist/BlueMarble.user.css +++ b/dist/BlueMarble.user.css @@ -1 +1 @@ -#bm-overlay{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000}div#bm-overlay{font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-bar-drag{margin-bottom:.5em;background:url('data:image/svg+xml;utf8,') repeat;cursor:grab;width:100%;height:1em}#bm-bar-drag.dragging{cursor:grabbing}#bm-contain-header{margin-bottom:.5em}#bm-overlay img{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}#bm-overlay h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}#bm-contain-automation input[type=checkbox]{vertical-align:middle;margin-right:.5ch}#bm-contain-automation label{margin-right:.5ch}.bm-help{border:white 1px solid;height:1.25em;width:1.25em;margin-top:2px;text-align:center;line-height:1.25em;padding:0!important}#bm-button-coords{vertical-align:middle}#bm-button-coords svg{width:50%;margin:0 auto;fill:#111}#bm-contain-coords input[type=number]{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}#bm-contain-coords input[type=number]::-webkit-outer-spin-button,#bm-contain-coords input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}#bm-contain-buttons{display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;gap:1ch}div:has(>#bm-input-file)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#bm-output-status{font-size:small;background-color:#0003;padding:0 .5ch;height:3.75em;width:100%}#bm-contain-userinfo,#bm-contain-automation,#bm-contain-coords,#bm-contain-buttons,div:has(>#bm-input-file),#bm-output-status{margin-top:.5em}#bm-overlay button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}#bm-overlay button:hover,#bm-overlay button:focus-visible{background-color:#1061e5}#bm-overlay button:active,#bm-overlay button:disabled{background-color:#2e97ff}#bm-overlay button:disabled{text-decoration:line-through} +#bm-overlay{position:fixed;background-color:#153063e6;color:#fff;padding:10px;border-radius:8px;z-index:9000}div#bm-overlay{font-family:Roboto Mono,Courier New,Monaco,DejaVu Sans Mono,monospace,Arial;letter-spacing:.05em}#bm-bar-drag{margin-bottom:.5em;background:url('data:image/svg+xml;utf8,') repeat;cursor:grab;width:100%;height:1em}#bm-bar-drag.dragging{cursor:grabbing}#bm-contain-header{margin-bottom:.5em}#bm-overlay img{display:inline-block;height:2.5em;margin-right:1ch;vertical-align:middle}#bm-overlay h1{display:inline-block;font-size:x-large;font-weight:700;vertical-align:middle}#bm-contain-automation input[type=checkbox]{vertical-align:middle;margin-right:.5ch}#bm-contain-automation label{margin-right:.5ch}.bm-help{border:white 1px solid;height:1.25em;width:1.25em;margin-top:2px;text-align:center;line-height:1.25em;padding:0!important}#bm-button-coords{vertical-align:middle}#bm-button-coords svg{width:50%;margin:0 auto;fill:#111}#bm-contain-coords input[type=number]{appearance:auto;-moz-appearance:textfield;width:5.5ch;margin-left:1ch;background-color:#0003;padding:0 .5ch;font-size:small}#bm-contain-coords input[type=number]::-webkit-outer-spin-button,#bm-contain-coords input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}#bm-contain-buttons{display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;gap:1ch}div:has(>#bm-input-file-template)>button{width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}#bm-output-status{font-size:small;background-color:#0003;padding:0 .5ch;height:3.75em;width:100%}#bm-contain-userinfo,#bm-contain-automation,#bm-contain-coords,#bm-contain-buttons,div:has(>#bm-input-file-template),#bm-output-status{margin-top:.5em}#bm-overlay button{background-color:#144eb9;border-radius:1em;padding:0 .75ch}#bm-overlay button:hover,#bm-overlay button:focus-visible{background-color:#1061e5}#bm-overlay button:active,#bm-overlay button:disabled{background-color:#2e97ff}#bm-overlay button:disabled{text-decoration:line-through} diff --git a/dist/BlueMarble.user.js b/dist/BlueMarble.user.js index 1a6675e..528cbef 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.43.6 +// @version 0.43.40 // @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,5 +20,6 @@ // Wplace --> https://wplace.live // License --> https://www.mozilla.org/en-US/MPL/2.0/ -(()=>{var g=class{constructor(e,s){this.name=e,this.version=s,this.apiHandler=null,this.outputStatusId="bm-output-status",this.overlay=null,this.currentParent=null,this.parentStack=[]}setApiHandler(e){this.apiHandler=e}createElement(e,s={},n={}){let t=document.createElement(e);this.overlay?(this.currentParent.appendChild(t),this.parentStack.push(this.currentParent),this.currentParent=t):(this.overlay=t,this.currentParent=t);for(let[i,o]of Object.entries(s))t[i]=o;for(let[i,o]of Object.entries(n))t[i]=o;return t}buildElement(){return this.parentStack.length>0&&(this.currentParent=this.parentStack.pop()),this}buildOverlay(e){e.appendChild(this.overlay)}addDiv(e={},s=()=>{}){let n={},t=this.createElement("div",n,e);return s(t),this}addImg(e={},s=()=>{}){let n={},t=this.createElement("img",n,e);return s(t),this}create(){let e=document.createElement("div");e.id="bm-overlay",e.style.top="10px",e.style.right="75px";let s=document.createElement("div");s.id="bm-contain-header";let n=document.createElement("div");n.id="bm-bar-drag",s.appendChild(n);let t=document.createElement("img");t.src="https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/src/assets/Favicon.png",t.alt="Blue Marble Icon",s.appendChild(t);let i=document.createElement("h1");i.textContent=this.name,s.appendChild(i);let o=document.createElement("div");o.id="bm-contain-userinfo";let r=document.createElement("p");r.id="bm-user-name",r.textContent="Username:",o.appendChild(r);let a=document.createElement("p");a.id="bm-user-droplets",a.textContent="Droplets:",o.appendChild(a);let m=document.createElement("p");m.id="bm-user-nextlevel",m.textContent="Next level in...",o.appendChild(m);let l=document.createElement("div");l.id="bm-contain-automation",l.appendChild(this.createInputCheckbox("Stealth","bm-input-stealth",!0)),l.appendChild(this.createButtonQuestion("bm-help-stealth","Help: Waits for the website to make requests, instead of sending requests.",this.outputStatusId)),l.appendChild(document.createElement("br")),l.appendChild(this.createInputCheckbox("Possessed","bm-input-possessed",!0)),l.appendChild(this.createButtonQuestion("bm-help-possessed","Help: Controls the website as if it were possessed.",this.outputStatusId)),l.appendChild(document.createElement("br"));let d=document.createElement("div");d.id="bm-contain-coords";let c=document.createElement("button");c.id="bm-button-coords",c.className="bm-help",c.style="margin-top: 0",c.innerHTML='',c.onclick=()=>{let h=this.apiHandler?.coordsTilePixel;if(!h?.[0]){this.handleDisplayError("Coordinates are malformed! Did you try clicking on the canvas first?");return}this.updateInnerHTML("bm-input-tx",h?.[0]||"0000"),this.updateInnerHTML("bm-input-ty",h?.[1]||"0000"),this.updateInnerHTML("bm-input-px",h?.[2]||"000"),this.updateInnerHTML("bm-input-py",h?.[3]||"000")},d.appendChild(c),d.appendChild(this.createInputNumber("bm-input-tx","Tl X","","0","2047","1")),d.appendChild(this.createInputNumber("bm-input-ty","Tl Y","","0","2047","1")),d.appendChild(this.createInputNumber("bm-input-px","Px X","","0","999","1")),d.appendChild(this.createInputNumber("bm-input-py","Px Y","","0","999","1")),l.appendChild(d),l.appendChild(this.createInputFile("bm-input-file"));let u=document.createElement("div");u.id="bm-contain-buttons",u.appendChild(this.createButton("bm-button-enable","Enable")),u.appendChild(this.createButton("bm-button-disable","Disable")),l.appendChild(u);let f=document.createElement("textarea");f.id=this.outputStatusId,f.readOnly=!0,f.placeholder=`Status: Sleeping... -Version: ${this.version}`,l.appendChild(f),e.appendChild(s),e.appendChild(document.createElement("hr")),e.appendChild(o),e.appendChild(document.createElement("hr")),e.appendChild(l),document.body.appendChild(e),this.handleDrag(e,n)}updateInnerHTML(e,s,n=!1){let t=document.getElementById(e);if(t){if(t instanceof HTMLInputElement){t.value=s;return}n?t.textContent=s:t.innerHTML=s}}createButtonQuestion(e,s,n){let t=this.createButton(e,"?");return t.className="bm-help",t.title=s,t.onclick=()=>{this.updateInnerHTML(n,s)},t}createButton(e,s,n=!0){let t=document.createElement("button");return t.id=e,t.textContent=s,t.disabled=!n,t}createInputText(e,s="",n="",t="",i=!1){let o=document.createElement("input");return o.id=e,o.type="text",o.placeholder=s,o.value=n,o.readOnly=i,o.maxLength=t,o}createInputNumber(e,s="",n="",t="",i="",o="",r=!1){let a=this.createInputText(e,s,n,"",r);return a.type="number",a.min=t,a.max=i,a.step=o,a}createInputCheckbox(e,s,n=!1){let t=document.createElement("label");t.textContent=e;let i=document.createElement("input");return i.type="checkbox",i.id=s,i.checked=n,t.prepend(i),t}createInputFile(e){let s=document.createElement("div"),n=document.createElement("input");n.id=e,n.type="file",n.style="display: none";let t=document.createElement("button");return t.textContent="Upload File",t.addEventListener("click",()=>{n.click()}),n.addEventListener("change",()=>{t.style.maxWidth=`${t.offsetWidth}px`,n.files.length>0?t.textContent=n.files[0].name:t.textContent="Upload File"}),s.appendChild(n),s.appendChild(t),s}handleDrag(e,s){let n=!1,t,i=0;s.addEventListener("mousedown",function(o){n=!0,t=o.clientX-e.getBoundingClientRect().left,i=o.clientY-e.getBoundingClientRect().top,document.body.style.userSelect="none",s.classList.add("dragging")}),s.addEventListener("touchstart",function(o){n=!0;let r=o?.touches?.[0];r&&(t=r.clientX-e.getBoundingClientRect().left,i=r.clientY-e.getBoundingClientRect().top,document.body.style.userSelect="none",s.classList.add("dragging"))},{passive:!1}),document.addEventListener("mousemove",function(o){n&&(e.style.left=o.clientX-t+"px",e.style.top=o.clientY-i+"px",e.style.right="")}),document.addEventListener("touchmove",function(o){if(n){let r=o?.touches?.[0];if(!r)return;e.style.left=r.clientX-t+"px",e.style.top=r.clientY-i+"px",o.preventDefault()}},{passive:!1}),document.addEventListener("mouseup",function(){n=!1,document.body.style.userSelect="",s.classList.remove("dragging")}),document.addEventListener("touchend",function(){n=!1,document.body.style.userSelect="",s.classList.remove("dragging")}),document.addEventListener("touchcancel",function(){n=!1,document.body.style.userSelect="",s.classList.remove("dragging")})}handleDisplayError(e){console.error(`${this.name}: ${e}`),this.updateInnerHTML(this.outputStatusId,"Error: "+e,!0)}};var y=class{constructor(){this.observerBody=null,this.observerBodyTarget=null,this.targetDisplayCoords="#bm-display-coords"}createObserverBody(e){return this.observerBodyTarget=e,this.observerBody=new MutationObserver(s=>{for(let n of s)for(let t of n.addedNodes)t instanceof HTMLElement&&t.matches?.(this.targetDisplayCoords)}),this}getObserverBody(){return this.observerBody}observe(e,s=!1,n=!1){e.observe(this.observerBodyTarget,{childList:s,subtree:n})}};var v=class{serverTPtoDisplayTP(e,s){return[parseInt(e[0])%4*1e3+parseInt(s[0]),parseInt(e[1])%4*1e3+parseInt(s[1])]}};var x=class{constructor(e){this.coordsHandler=e,this.disableAll=!1,this.coordsTilePixel=[]}spontaneousResponseListener(e){window.addEventListener("message",s=>{let n=s.data;if(!(n&&n.source==="blue-marble"))return;let t=n.endpoint.split("?")[0].split("/").filter(i=>i&&isNaN(Number(i))).pop();switch(console.log(`Recieved message about "${t}"`),t){case"me":let i=Math.ceil(Math.pow(Math.floor(n.jsonData?.level)*Math.pow(30,.65),1.5384615384615383)-n.jsonData?.pixelsPainted);e.updateInnerHTML("bm-user-name",`Username: ${n.jsonData?.name}`),e.updateInnerHTML("bm-user-droplets",`Droplets: ${new Intl.NumberFormat().format(n.jsonData?.droplets)}`),e.updateInnerHTML("bm-user-nextlevel",`Next level in ${new Intl.NumberFormat().format(i)} pixel${i==1?"":"s"}`);break;case"pixel":let o=n.endpoint.split("?")[0].split("/").filter(d=>d&&!isNaN(Number(d))),r=new URLSearchParams(n.endpoint.split("?")[1]),a=[r.get("x"),r.get("y")];this.coordsTilePixel=[...o,...a];let m=this.coordsHandler.serverTPtoDisplayTP(o,a),l=document.querySelectorAll("span");for(let d of l)if(d.textContent.trim().includes(`${m[0]}, ${m[1]}`)){let c=document.querySelector("#bm-display-coords"),u=`(Tl X: ${o[0]}, Tl Y: ${o[1]}, Px X: ${a[0]}, Px Y: ${a[1]})`;c?c.textContent=u:(c=document.createElement("span"),c.id="bm-display-coords",c.textContent=u,c.style="margin-left: calc(var(--spacing)*3); font-size: small;",d.parentNode.parentNode.parentNode.insertAdjacentElement("afterend",c))}break;case"robots":this.disableAll=n.jsonData?.userscript?.toString().toLowerCase()=="false";break}})}};var E=GM_info.script.name.toString(),I=GM_info.script.version.toString();function T(p){let e=document.createElement("script");e.textContent=`(${p})();`,document.documentElement.appendChild(e),e.remove()}T(()=>{let p=window.fetch;window.fetch=async function(...e){let s=await p.apply(this,e),n=s.clone();if((n.headers.get("content-type")||"").includes("application/json")){let i=(e[0]instanceof Request?e[0]?.url:e[0])||"ignore";console.log(`Sending JSON message about endpoint "${i}"`),n.json().then(o=>{window.postMessage({source:"blue-marble",endpoint:i,jsonData:o},"*")}).catch(o=>{console.error("BM - Failed to parse JSON:",o)})}return s}});var w=GM_getResourceText("CSS-BM-File");GM_addStyle(w);var b=document.createElement("link");b.href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap";b.rel="preload";b.as="style";b.onload="this.onload=null;this.rel='stylesheet'";document.head.appendChild(b);var O=new y,C=new g(E,I),M=new v,L=new x(M);C.setApiHandler(L);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().buildOverlay(document.body);L.spontaneousResponseListener(C);console.log(`${E} (${I}) userscript has loaded!`);})(); +(()=>{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!`);})(); diff --git a/docs/README.md b/docs/README.md index e935894..764280b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,7 +35,7 @@ Software License: MPL-2.0 Contact Me WakaTime -Total Patches +Total Patches Total Lines of Code Total Comments Build diff --git a/package-lock.json b/package-lock.json index b0d6b1c..c218048 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "devDependencies": { "esbuild": "^0.25.0" }, - "version": "0.43.6" + "version": "0.43.40" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.8", @@ -467,5 +467,5 @@ } } }, - "version": "0.43.6" + "version": "0.43.40" } diff --git a/package.json b/package.json index a04acc4..7edf40c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wplace-bluemarble", - "version": "0.43.6", + "version": "0.43.40", "type": "module", "scripts": { "build": "node build/build.js", diff --git a/src/BlueMarble.meta.js b/src/BlueMarble.meta.js index c3b326d..aeff79b 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.43.6 +// @version 0.43.40 // @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 d1797f5..bf91af7 100644 --- a/src/apiHandler.js +++ b/src/apiHandler.js @@ -40,6 +40,15 @@ export class ApiHandler { switch (endpointText) { case 'me': // Request to retrieve user data + + // If the game can not retrieve the userdata... + if (data.jsonData?.status && data.jsonData?.status?.toString()[0] != '2') { + // The server is probably down (NOT a 2xx status) + + overlay.handleDisplayError(`The game is down!\nCould not fetch userdata.`); + return; // Kills itself before attempting to display null userdata + } + 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 diff --git a/src/main.js b/src/main.js index 48a024a..93e45fd 100644 --- a/src/main.js +++ b/src/main.js @@ -70,7 +70,10 @@ var 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 = "this.onload=null;this.rel='stylesheet'"; +stylesheetLink.onload = function () { + this.onload = null; + this.rel = 'stylesheet'; +}; document.head.appendChild(stylesheetLink); const observers = new Observers(); // Constructs a new Observers object @@ -81,12 +84,63 @@ const apiHandler = new ApiHandler(coordsHandler); // Constructs a new ApiHandler overlay.setApiHandler(apiHandler); // Sets the API handler // Deploys the overlay to the page +// Parent/child relationships in the DOM structure below are indicated by indentation overlay.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': name}).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': true}).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': true}).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': ''}, + (instance, button) => { + button.onclick = () => { + const coords = instance.apiHandler?.coordsTilePixel; // Retrieves the coords from the API handler + if (!coords?.[0]) { + instance.handleDisplayError('Coordinates are malformed! Did you try clicking on the canvas first?'); + return; + } + instance.updateInnerHTML('bm-input-tx', coords?.[0] || ''); + instance.updateInnerHTML('bm-input-ty', coords?.[1] || ''); + instance.updateInnerHTML('bm-input-px', coords?.[2] || ''); + instance.updateInnerHTML('bm-input-py', coords?.[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': overlay.outputStatusId, 'placeholder': `Status: Sleeping...\nVersion: ${version}`, 'readOnly': true}).buildElement() + .buildElement() .buildOverlay(document.body); +overlay.handleDrag('#bm-overlay', '#bm-bar-drag'); // Creates dragging capability on the drag bar for dragging the overlay + apiHandler.spontaneousResponseListener(overlay); // Reads spontaneous fetch responces console.log(`${name} (${version}) userscript has loaded!`); \ No newline at end of file diff --git a/src/overlay.css b/src/overlay.css index 7348a21..8ebedf4 100644 --- a/src/overlay.css +++ b/src/overlay.css @@ -123,7 +123,7 @@ div#bm-overlay { } /* The template file upload button */ -div:has(> #bm-input-file) > button { +div:has(> #bm-input-file-template) > button { width: 100%; white-space: nowrap; overflow: hidden; @@ -144,7 +144,7 @@ div:has(> #bm-input-file) > button { #bm-contain-automation, #bm-contain-coords, #bm-contain-buttons, -div:has(> #bm-input-file), +div:has(> #bm-input-file-template), #bm-output-status { margin-top: 0.5em; } diff --git a/src/overlay.js b/src/overlay.js index 9eb0f81..ce78e11 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -1,6 +1,21 @@ -/** The overlay for the Blue Marble script. +/** The overlay builder for the Blue Marble script. * @description This class handles the overlay UI for the Blue Marble script. * @since 0.0.2 + * @example + * const overlay = new Overlay(); + * overlay.addDiv('overlay') + * .addHeader(1, {'textContent': 'Your Overlay'}).buildElement() + * .addP({'textContent': 'This is your overlay. It is versatile.'}).buildElement() + * .addHr().buildElement() + * .buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + *
+ *

Your Overlay

+ *

This is your overlay. It is versatile.

+ *
+ * */ export class Overlay { @@ -30,13 +45,13 @@ export class Overlay { setApiHandler(apiHandler) {this.apiHandler = apiHandler;} /** Creates an element. - * For internal use of the {@link Overlay} class + * 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(tag, properties = {}, additionalProperties={}) { + #createElement(tag, properties = {}, additionalProperties={}) { const element = document.createElement(tag); // Creates the element @@ -71,11 +86,11 @@ export class Overlay { * @example * overlay * .addDiv() - * .addHeader1().buildElement() // Breaks out of the

- * .addParagraph().buildElement() // Breaks out of the

+ * .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(); + * .buildOverlay(document.body); */ buildElement() { if (this.parentStack.length > 0) { @@ -91,33 +106,67 @@ export class Overlay { * @example * overlay * .addDiv() - * .addParagraph().buildElement() + * .addP().buildElement() * .buildElement() * .buildOverlay(document.body); // Adds DOM structure to document body * //

*/ buildOverlay(parent) { parent.appendChild(this.overlay); + + // Resets the class-bound variables of this class instance back to default so overlay can be build again later + this.overlay = null; + this.currentParent = null; + this.parentStack = []; } /** Adds a `
` to the overlay. * This `
` element will have properties shared between all `
` elements in the overlay. * You can override the shared properties by using a callback. * @param {Object.} [additionalProperties={}] - The DOM properties of the `
` that are NOT shared between all overlay `
` elements. These should be camelCase. - * @param {function(HTMLElement):void} [callback=()=>{}] - Additional modification to the div. + * @param {function(Overlay, HTMLDivElement):void} [callback=()=>{}] - Additional JS modification to the `
`. * @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(); - * //
+ * overlay.addDiv({'id': 'foo'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + *
+ * */ addDiv(additionalProperties = {}, callback = () => {}) { const properties = {}; // Shared
DOM properties - const div = this.createElement('div', properties, additionalProperties); // Creates the div element - callback(div); // Runs (optionally) passed in script + const div = this.#createElement('div', properties, additionalProperties); // Creates the
element + callback(this, div); // Runs any script passed in through the callback + return this; + } + + /** Adds a `

` to the overlay. + * This `

` element will have properties shared between all `

` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `

` that are NOT shared between all overlay `

` elements. These should be camelCase. + * @param {function(Overlay, HTMLParagraphElement):void} [callback=()=>{}] - Additional JS modification to the `

`. + * @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 = {}; // Shared

DOM properties + + const p = this.#createElement('p', properties, additionalProperties); // Creates the

element + callback(this, p); // Runs any script passed in through the callback return this; } @@ -125,157 +174,306 @@ export class Overlay { * This `` element will have properties shared between all `` elements in the overlay. * You can override the shared properties by using a callback. * @param {Object.} [additionalProperties={}] - The DOM properties of the `` that are NOT shared between all overlay `` elements. These should be camelCase. - * @param {function(HTMLElement):void} [callback=()=>{}] - Additional modification to the img. + * @param {function(Overlay, HTMLImageElement):void} [callback=()=>{}] - Additional JS modification to the ``. * @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'}).buildOverlay(); - * // + * overlay.addimg({'id': 'foo', 'src': './img.png'}).buildOverlay(document.body); + * // Output: + * // (Assume already exists in the webpage) + * + * + * */ addImg(additionalProperties = {}, callback = () => {}) { - const properties = {}; // Shared

DOM properties + const properties = {}; // Shared DOM properties - const img = this.createElement('img', properties, additionalProperties); // Creates the div element - callback(img); // Runs (optionally) passed in script + const img = this.#createElement('img', properties, additionalProperties); // Creates the element + callback(this, img); // Runs any script passed in through the callback return this; } - /** Creates and deploys the overlay element - * @since 0.0.2 + /** 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.
+ * */ - create() { + addHeader(level, additionalProperties = {}, callback = () => {}) { - const overlay = document.createElement('div'); // Creates a new
element for the overlay - overlay.id = 'bm-overlay'; - overlay.style.top = '10px'; // Position from top of viewport - overlay.style.right = '75px'; // Position from right of viewport + const properties = {}; // Shared header DOM properties - const containerOverlayHeader = document.createElement('div'); - containerOverlayHeader.id = 'bm-contain-header'; + const header = this.#createElement('h' + level, properties, additionalProperties); // Creates the header element + callback(this, header); // Runs any script passed in through the callback + return this; + } - const barDrag = document.createElement('div'); // Drag bar for the overlay - barDrag.id = 'bm-bar-drag'; - containerOverlayHeader.appendChild(barDrag); // Adds the drag bar to the overlay header container + /** Adds a `
` to the overlay. + * This `
` element will have properties shared between all `
` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `
` that are NOT shared between all overlay `
` elements. These should be camelCase. + * @param {function(Overlay, HTMLHRElement):void} [callback=()=>{}] - Additional JS modification to the `
`. + * @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 barHeaderImage = document.createElement('img'); // Image in header - barHeaderImage.src = 'https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/src/assets/Favicon.png'; - barHeaderImage.alt = 'Blue Marble Icon'; - containerOverlayHeader.appendChild(barHeaderImage); // Adds the header image to the overlay header container + const properties = {}; // Shared
DOM properties - const barHeader = document.createElement('h1'); // Header bar for the overlay - barHeader.textContent = this.name; - containerOverlayHeader.appendChild(barHeader); // Adds the header to the overlay header container + const hr = this.#createElement('hr', properties, additionalProperties); // Creates the
element + callback(this, hr); // Runs any script passed in through the callback + return this; + } - const containerUserInfo = document.createElement('div'); // User info container - containerUserInfo.id = 'bm-contain-userinfo'; + /** Adds a `
` to the overlay. + * This `
` element will have properties shared between all `
` elements in the overlay. + * You can override the shared properties by using a callback. + * @param {Object.} [additionalProperties={}] - The DOM properties of the `
` that are NOT shared between all overlay `
` elements. These should be camelCase. + * @param {function(Overlay, HTMLBRElement):void} [callback=()=>{}] - Additional JS modification to the `
`. + * @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 userName = document.createElement('p'); // User name field - userName.id = 'bm-user-name'; - userName.textContent = 'Username:'; - containerUserInfo.appendChild(userName); // Adds the username field to the user info container + const properties = {}; // Shared
DOM properties - const userDroplets = document.createElement('p'); // User droplet field - userDroplets.id = 'bm-user-droplets'; - userDroplets.textContent = 'Droplets:'; - containerUserInfo.appendChild(userDroplets); // Adds the droplet field to the user info container + const br = this.#createElement('br', properties, additionalProperties); // Creates the
element + callback(this, br); // Runs any script passed in through the callback + return this; + } - const userNextLevel = document.createElement('p'); // Amount to next level - userNextLevel.id = 'bm-user-nextlevel'; - userNextLevel.textContent = 'Next level in...'; - containerUserInfo.appendChild(userNextLevel); // Adds the "amount to next level" field to the user info container + /** 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 containerAutomation = document.createElement('div'); // Automated stuff container - containerAutomation.id = 'bm-contain-automation'; + const properties = {'type': 'checkbox'}; // Shared checkbox DOM properties - // Stealth Mode checkbox - containerAutomation.appendChild(this.createInputCheckbox( - 'Stealth', - 'bm-input-stealth', - true - )); - - // Adds the help icon for stealth mode - containerAutomation.appendChild(this.createButtonQuestion( - 'bm-help-stealth', - 'Help: Waits for the website to make requests, instead of sending requests.', - this.outputStatusId - )); + const label = this.#createElement('label', {'textContent': additionalProperties['textContent'] ?? ''}); // Creates the label element + delete additionalProperties['textContent']; // Deletes 'textContent' DOM property before adding the properties to the checkbox + const checkbox = this.#createElement('input', properties, additionalProperties); // Creates the checkbox element + label.insertBefore(checkbox, label.firstChild); // Makes the checkbox the first child of the label (before the text content) + this.buildElement(); // Signifies that we are done adding children to the checkbox + callback(this, label, checkbox); // Runs any script passed in through the callback + return this; + } + + /** Adds a ` + * + */ + addButton(additionalProperties = {}, callback = () => {}) { - containerAutomation.appendChild(document.createElement('br')); // Line break + const properties = {}; // Shared + * + * @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 = () => {}) { - containerAutomation.appendChild(document.createElement('br')); // Line break + const tooltip = additionalProperties['title'] ?? additionalProperties['textContent'] ?? 'Help: No info'; // Retrieves the tooltip - const containerAutomationCoords = document.createElement('div'); // Coords container - containerAutomationCoords.id = 'bm-contain-coords'; + // Makes sure the tooltip is stored in the title property + delete additionalProperties['textContent']; + additionalProperties['title'] = `Help: ${tooltip}`; - const buttonCoords = document.createElement('button'); - buttonCoords.id = 'bm-button-coords'; - buttonCoords.className = 'bm-help'; - buttonCoords.style = 'margin-top: 0'; - buttonCoords.innerHTML = ''; - buttonCoords.onclick = () => { - const coords = this.apiHandler?.coordsTilePixel; // Retrieves the coords from the API handler - if (!coords?.[0]) { - this.handleDisplayError('Coordinates are malformed! Did you try clicking on the canvas first?'); - return; + // Shared help button DOM properties + const properties = { + 'textContent': '?', + 'className': 'bm-help', + 'onclick': () => { + this.updateInnerHTML(this.outputStatusId, tooltip); } - this.updateInnerHTML('bm-input-tx', coords?.[0] || '0000'); - this.updateInnerHTML('bm-input-ty', coords?.[1] || '0000'); - this.updateInnerHTML('bm-input-px', coords?.[2] || '000'); - this.updateInnerHTML('bm-input-py', coords?.[3] || '000'); - } - containerAutomationCoords.appendChild(buttonCoords); // Adds the coordinate button to the automation container + }; - // Tile (x,y) and Pixel (x,y) input fields - containerAutomationCoords.appendChild(this.createInputNumber('bm-input-tx', 'Tl X', '', '0', '2047', '1')); - containerAutomationCoords.appendChild(this.createInputNumber('bm-input-ty', 'Tl Y', '', '0', '2047', '1')); - containerAutomationCoords.appendChild(this.createInputNumber('bm-input-px', 'Px X', '', '0', '999', '1')); - containerAutomationCoords.appendChild(this.createInputNumber('bm-input-py', 'Px Y', '', '0', '999', '1')); + const help = this.#createElement('button', properties, additionalProperties); // Creates the + *
+ * + */ + addInputFile(additionalProperties = {}, callback = () => {}) { + + const properties = {'type': 'file', 'style': 'display: none;'}; // Shared file input DOM properties + const text = additionalProperties['textContent'] ?? ''; // Retrieves the text content - containerAutomation.appendChild(containerAutomationButtons); // Adds button container to automation container + delete additionalProperties['textContent']; // Deletes the text content before applying the additional properties to the file input - const outputStatus = document.createElement('textarea'); // Outputs script status - outputStatus.id = this.outputStatusId; - outputStatus.readOnly = true; // Read-only input field - outputStatus.placeholder = `Status: Sleeping...\nVersion: ${this.version}`; // Default text value - containerAutomation.appendChild(outputStatus); + const container = this.#createElement('div'); // Container for file input + const input = this.#createElement('input', properties, additionalProperties); // Creates the file input + this.buildElement(); // Signifies that we are done adding children to the file input + const button = this.#createElement('button', {'textContent': text}); + this.buildElement(); // Signifies that we are done adding children to the button + this.buildElement(); // Signifies that we are done adding children to the container - // Construction of the overlay element - overlay.appendChild(containerOverlayHeader); // Adds the overlay header container to the overlay - overlay.appendChild(document.createElement('hr')); // Adds a horizontal line to the overlay - overlay.appendChild(containerUserInfo); // Adds the user info container to the overlay - overlay.appendChild(document.createElement('hr')); // Adds a horizontal line to the overlay - overlay.appendChild(containerAutomation); // Adds the automation stuff container to the overlay - document.body.appendChild(overlay); // Adds the overlay to the body of the webpage + button.addEventListener('click', () => { + input.click(); // Clicks the file input + }); - this.handleDrag(overlay, barDrag); // Starts handling the drag functionality + // Changes the button text content (and trims the file name) + 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); // Runs any script passed in through the callback + return this; + } + + /** Adds a ` + * + */ + addTextarea(additionalProperties = {}, callback = () => {}) { + + const properties = {}; // Shared