From 3af750cd706a01aad1a74b55b2320851a4d6433c Mon Sep 17 00:00:00 2001 From: Sophie Calixto <156002567+datapanthom@users.noreply.github.com> Date: Wed, 13 Aug 2025 08:45:48 -0300 Subject: [PATCH 1/2] feat: 60 pixels at a time --- Auto-Image.js | 879 ++++++++++++++++++++++++++++---------------------- 1 file changed, 491 insertions(+), 388 deletions(-) diff --git a/Auto-Image.js b/Auto-Image.js index b1f2352..5dfe2c9 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -6,15 +6,15 @@ WHITE_THRESHOLD: 250, LOG_INTERVAL: 10, THEME: { - primary: '#000000', - secondary: '#111111', - accent: '#222222', - text: '#ffffff', - highlight: '#775ce3', - success: '#00ff00', - error: '#ff0000', - warning: '#ffaa00' - } + primary: "#000000", + secondary: "#111111", + accent: "#222222", + text: "#ffffff", + highlight: "#775ce3", + success: "#00ff00", + error: "#ff0000", + warning: "#ffaa00", + }, }; // BILINGUAL TEXT STRINGS @@ -33,7 +33,8 @@ loadingImage: "🖼️ Carregando imagem...", imageLoaded: "✅ Imagem carregada com {count} pixels válidos", imageError: "❌ Erro ao carregar imagem", - selectPositionAlert: "Pinte o primeiro pixel na localização onde deseja que a arte comece!", + selectPositionAlert: + "Pinte o primeiro pixel na localização onde deseja que a arte comece!", waitingPosition: "👆 Aguardando você pintar o pixel de referência...", positionSet: "✅ Posição definida com sucesso!", positionTimeout: "❌ Tempo esgotado para selecionar posição", @@ -43,7 +44,8 @@ paintingStopped: "⏹️ Pintura interrompida pelo usuário", paintingComplete: "✅ Pintura concluída! {count} pixels pintados.", paintingError: "❌ Erro durante a pintura", - missingRequirements: "❌ Carregue uma imagem e selecione uma posição primeiro", + missingRequirements: + "❌ Carregue uma imagem e selecione uma posição primeiro", progress: "Progresso", pixels: "Pixels", charges: "Cargas", @@ -52,7 +54,8 @@ waitingInit: "Aguardando inicialização...", resizeSuccess: "✅ Imagem redimensionada para {width}x{height}", paintingPaused: "⏸️ Pintura pausada na posição X: {x}, Y: {y}", - captchaNeeded: "❗ Token CAPTCHA necessário. Pinte um pixel manualmente para continuar." + captchaNeeded: + "❗ Token CAPTCHA necessário. Pinte um pixel manualmente para continuar.", }, en: { title: "WPlace Auto-Image", @@ -68,7 +71,8 @@ loadingImage: "🖼️ Loading image...", imageLoaded: "✅ Image loaded with {count} valid pixels", imageError: "❌ Error loading image", - selectPositionAlert: "Paint the first pixel at the location where you want the art to start!", + selectPositionAlert: + "Paint the first pixel at the location where you want the art to start!", waitingPosition: "👆 Waiting for you to paint the reference pixel...", positionSet: "✅ Position set successfully!", positionTimeout: "❌ Timeout for position selection", @@ -87,8 +91,9 @@ waitingInit: "Waiting for initialization...", resizeSuccess: "✅ Image resized to {width}x{height}", paintingPaused: "⏸️ Painting paused at position X: {x}, Y: {y}", - captchaNeeded: "❗ CAPTCHA token needed. Paint one pixel manually to continue." - } + captchaNeeded: + "❗ CAPTCHA token needed. Paint one pixel manually to continue.", + }, }; // GLOBAL STATE @@ -110,7 +115,7 @@ minimized: false, lastPosition: { x: 0, y: 0 }, estimatedTime: 0, - language: 'en' + language: "en", }; // Global variable to store the captured CAPTCHA token. @@ -120,7 +125,10 @@ const originalFetch = window.fetch; window.fetch = async (url, options) => { // Check if the request is for painting a pixel on wplace. - if (typeof url === 'string' && url.includes('https://backend.wplace.live/s0/pixel/')) { + if ( + typeof url === "string" && + url.includes("https://backend.wplace.live/s0/pixel/") + ) { try { const payload = JSON.parse(options.body); // If the request body contains the 't' field, it's our CAPTCHA token. @@ -129,197 +137,218 @@ // Store the token for our bot to use. capturedCaptchaToken = payload.t; // Notify the user that the token is captured and they can start the bot. - if(document.querySelector('#statusText')?.textContent.includes('CAPTCHA')){ - Utils.showAlert("Token captured successfully! You can start the bot now.", "success"); - updateUI('colorsFound', 'success', { count: state.availableColors.length }); + if ( + document + .querySelector("#statusText") + ?.textContent.includes("CAPTCHA") + ) { + Utils.showAlert( + "Token captured successfully! You can start the bot now.", + "success" + ); + updateUI("colorsFound", "success", { + count: state.availableColors.length, + }); } } - } catch (e) { /* Ignore errors if the request body isn't valid JSON */ } + } catch (e) { + /* Ignore errors if the request body isn't valid JSON */ + } } // Finally, execute the original request, whether we inspected it or not. return originalFetch(url, options); }; - async function detectLanguage() { try { - const response = await fetch('https://ipapi.co/json/'); + const response = await fetch("https://ipapi.co/json/"); const data = await response.json(); - state.language = data.country === 'BR' ? 'pt' : 'en'; + state.language = data.country === "BR" ? "pt" : "en"; } catch { - state.language = 'en'; + state.language = "en"; } } // UTILITY FUNCTIONS const Utils = { - sleep: ms => new Promise(r => setTimeout(r, ms)), - - colorDistance: (a, b) => Math.sqrt( - Math.pow(a[0] - b[0], 2) + - Math.pow(a[1] - b[1], 2) + - Math.pow(a[2] - b[2], 2) - ), - - createImageUploader: () => new Promise(resolve => { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'image/png,image/jpeg'; - input.onchange = () => { - const fr = new FileReader(); - fr.onload = () => resolve(fr.result); - fr.readAsDataURL(input.files[0]); - }; - input.click(); - }), - + sleep: (ms) => new Promise((r) => setTimeout(r, ms)), + + colorDistance: (a, b) => + Math.sqrt( + Math.pow(a[0] - b[0], 2) + + Math.pow(a[1] - b[1], 2) + + Math.pow(a[2] - b[2], 2) + ), + + createImageUploader: () => + new Promise((resolve) => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/png,image/jpeg"; + input.onchange = () => { + const fr = new FileReader(); + fr.onload = () => resolve(fr.result); + fr.readAsDataURL(input.files[0]); + }; + input.click(); + }), + extractAvailableColors: () => { const colorElements = document.querySelectorAll('[id^="color-"]'); return Array.from(colorElements) - .filter(el => !el.querySelector('svg')) - .filter(el => { - const id = parseInt(el.id.replace('color-', '')); + .filter((el) => !el.querySelector("svg")) + .filter((el) => { + const id = parseInt(el.id.replace("color-", "")); return id !== 0 && id !== 5; }) - .map(el => { - const id = parseInt(el.id.replace('color-', '')); + .map((el) => { + const id = parseInt(el.id.replace("color-", "")); const rgbStr = el.style.backgroundColor.match(/\d+/g); const rgb = rgbStr ? rgbStr.map(Number) : [0, 0, 0]; return { id, rgb }; }); }, - - formatTime: ms => { + + formatTime: (ms) => { const seconds = Math.floor((ms / 1000) % 60); const minutes = Math.floor((ms / (1000 * 60)) % 60); const hours = Math.floor((ms / (1000 * 60 * 60)) % 24); const days = Math.floor(ms / (1000 * 60 * 60 * 24)); - - let result = ''; + + let result = ""; if (days > 0) result += `${days}d `; if (hours > 0 || days > 0) result += `${hours}h `; if (minutes > 0 || hours > 0 || days > 0) result += `${minutes}m `; result += `${seconds}s`; - + return result; }, - - showAlert: (message, type = 'info') => { - const alert = document.createElement('div'); - alert.style.position = 'fixed'; - alert.style.top = '20px'; - alert.style.left = '50%'; - alert.style.transform = 'translateX(-50%)'; - alert.style.padding = '15px 20px'; + + showAlert: (message, type = "info") => { + const alert = document.createElement("div"); + alert.style.position = "fixed"; + alert.style.top = "20px"; + alert.style.left = "50%"; + alert.style.transform = "translateX(-50%)"; + alert.style.padding = "15px 20px"; alert.style.background = CONFIG.THEME[type] || CONFIG.THEME.accent; alert.style.color = CONFIG.THEME.text; - alert.style.borderRadius = '5px'; - alert.style.zIndex = '10000'; - alert.style.boxShadow = '0 3px 10px rgba(0,0,0,0.3)'; - alert.style.display = 'flex'; - alert.style.alignItems = 'center'; - alert.style.gap = '10px'; - + alert.style.borderRadius = "5px"; + alert.style.zIndex = "10000"; + alert.style.boxShadow = "0 3px 10px rgba(0,0,0,0.3)"; + alert.style.display = "flex"; + alert.style.alignItems = "center"; + alert.style.gap = "10px"; + const icons = { - error: 'exclamation-circle', - success: 'check-circle', - warning: 'exclamation-triangle', - info: 'info-circle' + error: "exclamation-circle", + success: "check-circle", + warning: "exclamation-triangle", + info: "info-circle", }; - + alert.innerHTML = ` - + ${message} `; - + document.body.appendChild(alert); - + setTimeout(() => { - alert.style.opacity = '0'; - alert.style.transition = 'opacity 0.5s'; + alert.style.opacity = "0"; + alert.style.transition = "opacity 0.5s"; setTimeout(() => alert.remove(), 500); }, 3000); }, - + calculateEstimatedTime: (remainingPixels, currentCharges, cooldown) => { const pixelsPerCharge = currentCharges > 0 ? currentCharges : 0; - const fullCycles = Math.ceil((remainingPixels - pixelsPerCharge) / Math.max(currentCharges, 1)); - return (fullCycles * cooldown) + ((remainingPixels - 1) * 100); + const fullCycles = Math.ceil( + (remainingPixels - pixelsPerCharge) / Math.max(currentCharges, 1) + ); + return fullCycles * cooldown + (remainingPixels - 1) * 100; }, - + isWhitePixel: (r, g, b) => { - return r >= CONFIG.WHITE_THRESHOLD && - g >= CONFIG.WHITE_THRESHOLD && - b >= CONFIG.WHITE_THRESHOLD; + return ( + r >= CONFIG.WHITE_THRESHOLD && + g >= CONFIG.WHITE_THRESHOLD && + b >= CONFIG.WHITE_THRESHOLD + ); }, - + t: (key, params = {}) => { let text = TEXTS[state.language][key] || TEXTS.en[key] || key; for (const [k, v] of Object.entries(params)) { text = text.replace(`{${k}}`, v); } return text; - } + }, }; // WPLACE API SERVICE const WPlaceService = { async paintPixelInRegion(regionX, regionY, pixelX, pixelY, color) { - try { - // Construct the payload including the captured 't' token. - const payload = { - coords: [pixelX, pixelY], - colors: [color], - t: capturedCaptchaToken - }; - const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, { - method: 'POST', - headers: { 'Content-Type': 'text/plain;charset=UTF-8' }, - credentials: 'include', - body: JSON.stringify(payload) - }); + try { + // Construct the payload including the captured 't' token. + const payload = { + coords: [pixelX, pixelY], + colors: [color], + t: capturedCaptchaToken, + }; + const res = await fetch( + `https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, + { + method: "POST", + headers: { "Content-Type": "text/plain;charset=UTF-8" }, + credentials: "include", + body: JSON.stringify(payload), + } + ); - // If we get a 403 Forbidden error, our token is likely expired. - if (res.status === 403) { - console.error("❌ 403 Forbidden. CAPTCHA token might be invalid or expired."); - capturedCaptchaToken = null; // Invalidate our stored token. - return 'token_error'; // Return a special status to stop the bot. - } - - const data = await res.json(); - return data?.painted === 1; - } catch(e) { - console.error("Paint request failed:", e); - return false; + // If we get a 403 Forbidden error, our token is likely expired. + if (res.status === 403) { + console.error( + "❌ 403 Forbidden. CAPTCHA token might be invalid or expired." + ); + capturedCaptchaToken = null; // Invalidate our stored token. + return "token_error"; // Return a special status to stop the bot. } + + const data = await res.json(); + return data?.painted === 1; + } catch (e) { + console.error("Paint request failed:", e); + return false; + } }, - + async getCharges() { try { - const res = await fetch('https://backend.wplace.live/me', { - credentials: 'include' + const res = await fetch("https://backend.wplace.live/me", { + credentials: "include", }); const data = await res.json(); - return { - charges: data.charges?.count || 0, - cooldown: data.charges?.cooldownMs || CONFIG.COOLDOWN_DEFAULT + return { + charges: data.charges?.count || 0, + cooldown: data.charges?.cooldownMs || CONFIG.COOLDOWN_DEFAULT, }; } catch { return { charges: 0, cooldown: CONFIG.COOLDOWN_DEFAULT }; } - } + }, }; class ImageProcessor { constructor(imageSrc) { this.imageSrc = imageSrc; this.img = new Image(); - this.canvas = document.createElement('canvas'); - this.ctx = this.canvas.getContext('2d'); - this.previewCanvas = document.createElement('canvas'); - this.previewCtx = this.previewCanvas.getContext('2d'); + this.canvas = document.createElement("canvas"); + this.ctx = this.canvas.getContext("2d"); + this.previewCanvas = document.createElement("canvas"); + this.previewCtx = this.previewCanvas.getContext("2d"); } - + async load() { return new Promise((resolve, reject) => { this.img.onload = () => { @@ -332,30 +361,31 @@ this.img.src = this.imageSrc; }); } - + getPixelData() { - return this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height).data; + return this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height) + .data; } - + getDimensions() { return { width: this.canvas.width, height: this.canvas.height }; } - + resize(newWidth, newHeight) { - const tempCanvas = document.createElement('canvas'); + const tempCanvas = document.createElement("canvas"); tempCanvas.width = newWidth; tempCanvas.height = newHeight; - const tempCtx = tempCanvas.getContext('2d'); - + const tempCtx = tempCanvas.getContext("2d"); + tempCtx.drawImage(this.img, 0, 0, newWidth, newHeight); - + this.canvas.width = newWidth; this.canvas.height = newHeight; this.ctx.drawImage(tempCanvas, 0, 0); - + return this.getPixelData(); } - + generatePreview(newWidth, newHeight) { this.previewCanvas.width = newWidth; this.previewCanvas.height = newHeight; @@ -366,23 +396,27 @@ } function findClosestColor(rgb, palette) { - return palette.reduce((closest, current) => { - const currentDistance = Utils.colorDistance(rgb, current.rgb); - return currentDistance < closest.distance - ? { color: current, distance: currentDistance } - : closest; - }, { color: palette[0], distance: Utils.colorDistance(rgb, palette[0].rgb) }).color.id; + return palette.reduce( + (closest, current) => { + const currentDistance = Utils.colorDistance(rgb, current.rgb); + return currentDistance < closest.distance + ? { color: current, distance: currentDistance } + : closest; + }, + { color: palette[0], distance: Utils.colorDistance(rgb, palette[0].rgb) } + ).color.id; } async function createUI() { await detectLanguage(); - const fontAwesome = document.createElement('link'); - fontAwesome.rel = 'stylesheet'; - fontAwesome.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css'; + const fontAwesome = document.createElement("link"); + fontAwesome.rel = "stylesheet"; + fontAwesome.href = + "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"; document.head.appendChild(fontAwesome); - const style = document.createElement('style'); + const style = document.createElement("style"); style.textContent = ` @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0.7); } @@ -610,16 +644,18 @@ `; document.head.appendChild(style); - const container = document.createElement('div'); - container.id = 'wplace-image-bot-container'; + const container = document.createElement("div"); + container.id = "wplace-image-bot-container"; container.innerHTML = `
- ${Utils.t('title')} + ${Utils.t("title")}
-
@@ -628,27 +664,27 @@
@@ -659,191 +695,212 @@
-
${Utils.t('initMessage')}
+
${Utils.t( + "initMessage" + )}
- ${Utils.t('waitingInit')} + ${Utils.t("waitingInit")}
`; - - const resizeContainer = document.createElement('div'); - resizeContainer.className = 'resize-container'; + + const resizeContainer = document.createElement("div"); + resizeContainer.className = "resize-container"; resizeContainer.innerHTML = ` -

${Utils.t('resizeImage')}

+

${Utils.t( + "resizeImage" + )}

`; - - const resizeOverlay = document.createElement('div'); - resizeOverlay.className = 'resize-overlay'; - + + const resizeOverlay = document.createElement("div"); + resizeOverlay.className = "resize-overlay"; + document.body.appendChild(container); document.body.appendChild(resizeOverlay); document.body.appendChild(resizeContainer); - - const header = container.querySelector('.wplace-header'); - let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; - + + const header = container.querySelector(".wplace-header"); + let pos1 = 0, + pos2 = 0, + pos3 = 0, + pos4 = 0; + header.onmousedown = dragMouseDown; - + function dragMouseDown(e) { - if (e.target.closest('.wplace-header-btn')) return; - + if (e.target.closest(".wplace-header-btn")) return; + e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } - + function elementDrag(e) { e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; - container.style.top = (container.offsetTop - pos2) + "px"; - container.style.left = (container.offsetLeft - pos1) + "px"; + container.style.top = container.offsetTop - pos2 + "px"; + container.style.left = container.offsetLeft - pos1 + "px"; } - + function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } - - const initBotBtn = container.querySelector('#initBotBtn'); - const uploadBtn = container.querySelector('#uploadBtn'); - const resizeBtn = container.querySelector('#resizeBtn'); - const selectPosBtn = container.querySelector('#selectPosBtn'); - const startBtn = container.querySelector('#startBtn'); - const stopBtn = container.querySelector('#stopBtn'); - const minimizeBtn = container.querySelector('#minimizeBtn'); - const statusText = container.querySelector('#statusText'); - const progressBar = container.querySelector('#progressBar'); - const statsArea = container.querySelector('#statsArea'); - const content = container.querySelector('.wplace-content'); - - const widthSlider = resizeContainer.querySelector('#widthSlider'); - const heightSlider = resizeContainer.querySelector('#heightSlider'); - const widthValue = resizeContainer.querySelector('#widthValue'); - const heightValue = resizeContainer.querySelector('#heightValue'); - const keepAspect = resizeContainer.querySelector('#keepAspect'); - const resizePreview = resizeContainer.querySelector('#resizePreview'); - const confirmResize = resizeContainer.querySelector('#confirmResize'); - const cancelResize = resizeContainer.querySelector('#cancelResize'); - - minimizeBtn.addEventListener('click', () => { + + const initBotBtn = container.querySelector("#initBotBtn"); + const uploadBtn = container.querySelector("#uploadBtn"); + const resizeBtn = container.querySelector("#resizeBtn"); + const selectPosBtn = container.querySelector("#selectPosBtn"); + const startBtn = container.querySelector("#startBtn"); + const stopBtn = container.querySelector("#stopBtn"); + const minimizeBtn = container.querySelector("#minimizeBtn"); + const statusText = container.querySelector("#statusText"); + const progressBar = container.querySelector("#progressBar"); + const statsArea = container.querySelector("#statsArea"); + const content = container.querySelector(".wplace-content"); + + const widthSlider = resizeContainer.querySelector("#widthSlider"); + const heightSlider = resizeContainer.querySelector("#heightSlider"); + const widthValue = resizeContainer.querySelector("#widthValue"); + const heightValue = resizeContainer.querySelector("#heightValue"); + const keepAspect = resizeContainer.querySelector("#keepAspect"); + const resizePreview = resizeContainer.querySelector("#resizePreview"); + const confirmResize = resizeContainer.querySelector("#confirmResize"); + const cancelResize = resizeContainer.querySelector("#cancelResize"); + + minimizeBtn.addEventListener("click", () => { state.minimized = !state.minimized; if (state.minimized) { - container.classList.add('wplace-minimized'); + container.classList.add("wplace-minimized"); minimizeBtn.innerHTML = ''; } else { - container.classList.remove('wplace-minimized'); + container.classList.remove("wplace-minimized"); minimizeBtn.innerHTML = ''; } }); - - window.updateUI = (messageKey, type = 'default', params = {}) => { + + window.updateUI = (messageKey, type = "default", params = {}) => { const message = Utils.t(messageKey, params); statusText.textContent = message; statusText.className = `wplace-status status-${type}`; - statusText.style.animation = 'none'; + statusText.style.animation = "none"; void statusText.offsetWidth; - statusText.style.animation = 'slideIn 0.3s ease-out'; + statusText.style.animation = "slideIn 0.3s ease-out"; }; window.updateStats = async () => { if (!state.colorsChecked || !state.imageLoaded) return; - + const { charges, cooldown } = await WPlaceService.getCharges(); state.currentCharges = Math.floor(charges); state.cooldown = cooldown; - - const progress = state.totalPixels > 0 ? - Math.round((state.paintedPixels / state.totalPixels) * 100) : 0; + + const progress = + state.totalPixels > 0 + ? Math.round((state.paintedPixels / state.totalPixels) * 100) + : 0; const remainingPixels = state.totalPixels - state.paintedPixels; - + state.estimatedTime = Utils.calculateEstimatedTime( - remainingPixels, - state.currentCharges, + remainingPixels, + state.currentCharges, state.cooldown ); - + progressBar.style.width = `${progress}%`; - + statsArea.innerHTML = `
-
${Utils.t('progress')}
+
${Utils.t( + "progress" + )}
${progress}%
-
${Utils.t('pixels')}
+
${Utils.t( + "pixels" + )}
${state.paintedPixels}/${state.totalPixels}
-
${Utils.t('charges')}
+
${Utils.t( + "charges" + )}
${Math.floor(state.currentCharges)}
- ${state.imageLoaded ? ` + ${ + state.imageLoaded + ? `
-
${Utils.t('estimatedTime')}
+
${Utils.t( + "estimatedTime" + )}
${Utils.formatTime(state.estimatedTime)}
- ` : ''} + ` + : "" + } `; }; - + function showResizeDialog(processor) { const { width, height } = processor.getDimensions(); const aspectRatio = width / height; - + widthSlider.value = width; heightSlider.value = height; widthValue.textContent = width; heightValue.textContent = height; resizePreview.src = processor.img.src; - - resizeOverlay.style.display = 'block'; - resizeContainer.style.display = 'block'; - + + resizeOverlay.style.display = "block"; + resizeContainer.style.display = "block"; + const updatePreview = () => { const newWidth = parseInt(widthSlider.value); const newHeight = parseInt(heightSlider.value); - + widthValue.textContent = newWidth; heightValue.textContent = newHeight; - + resizePreview.src = processor.generatePreview(newWidth, newHeight); }; - - widthSlider.addEventListener('input', () => { + + widthSlider.addEventListener("input", () => { if (keepAspect.checked) { const newWidth = parseInt(widthSlider.value); const newHeight = Math.round(newWidth / aspectRatio); @@ -851,8 +908,8 @@ } updatePreview(); }); - - heightSlider.addEventListener('input', () => { + + heightSlider.addEventListener("input", () => { if (keepAspect.checked) { const newHeight = parseInt(heightSlider.value); const newWidth = Math.round(newHeight * aspectRatio); @@ -860,13 +917,13 @@ } updatePreview(); }); - + confirmResize.onclick = () => { const newWidth = parseInt(widthSlider.value); const newHeight = parseInt(heightSlider.value); - + const newPixels = processor.resize(newWidth, newHeight); - + let totalValidPixels = 0; for (let y = 0; y < newHeight; y++) { for (let x = 0; x < newWidth; x++) { @@ -875,71 +932,75 @@ const g = newPixels[idx + 1]; const b = newPixels[idx + 2]; const alpha = newPixels[idx + 3]; - + if (alpha < CONFIG.TRANSPARENCY_THRESHOLD) continue; if (Utils.isWhitePixel(r, g, b)) continue; - + totalValidPixels++; } } - + state.imageData.pixels = newPixels; state.imageData.width = newWidth; state.imageData.height = newHeight; state.imageData.totalPixels = totalValidPixels; state.totalPixels = totalValidPixels; state.paintedPixels = 0; - + updateStats(); - updateUI('resizeSuccess', 'success', { width: newWidth, height: newHeight }); - + updateUI("resizeSuccess", "success", { + width: newWidth, + height: newHeight, + }); + closeResizeDialog(); }; - + cancelResize.onclick = closeResizeDialog; } - + function closeResizeDialog() { - resizeOverlay.style.display = 'none'; - resizeContainer.style.display = 'none'; + resizeOverlay.style.display = "none"; + resizeContainer.style.display = "none"; } - - initBotBtn.addEventListener('click', async () => { + + initBotBtn.addEventListener("click", async () => { try { - updateUI('checkingColors', 'default'); - + updateUI("checkingColors", "default"); + state.availableColors = Utils.extractAvailableColors(); - + if (state.availableColors.length === 0) { - Utils.showAlert(Utils.t('noColorsFound'), 'error'); - updateUI('noColorsFound', 'error'); + Utils.showAlert(Utils.t("noColorsFound"), "error"); + updateUI("noColorsFound", "error"); return; } - + state.colorsChecked = true; uploadBtn.disabled = false; selectPosBtn.disabled = false; - initBotBtn.style.display = 'none'; - - updateUI('colorsFound', 'success', { count: state.availableColors.length }); + initBotBtn.style.display = "none"; + + updateUI("colorsFound", "success", { + count: state.availableColors.length, + }); updateStats(); - } catch { - updateUI('imageError', 'error'); + updateUI("imageError", "error"); } }); - - uploadBtn.addEventListener('click', async () => { + + uploadBtn.addEventListener("click", async () => { try { - updateUI('loadingImage', 'default'); + updateUI("loadingImage", "default"); const imageSrc = await Utils.createImageUploader(); - + const processor = new ImageProcessor(imageSrc); await processor.load(); - + const { width, height } = processor.getDimensions(); const pixels = processor.getPixelData(); - + let totalValidPixels = 0; for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { @@ -948,96 +1009,97 @@ const g = pixels[idx + 1]; const b = pixels[idx + 2]; const alpha = pixels[idx + 3]; - + if (alpha < CONFIG.TRANSPARENCY_THRESHOLD) continue; if (Utils.isWhitePixel(r, g, b)) continue; - + totalValidPixels++; } } - + state.imageData = { width, height, pixels, totalPixels: totalValidPixels, - processor + processor, }; - + state.totalPixels = totalValidPixels; state.paintedPixels = 0; state.imageLoaded = true; state.lastPosition = { x: 0, y: 0 }; - + resizeBtn.disabled = false; - + if (state.startPosition) { startBtn.disabled = false; } - + updateStats(); - updateUI('imageLoaded', 'success', { count: totalValidPixels }); + updateUI("imageLoaded", "success", { count: totalValidPixels }); } catch { - updateUI('imageError', 'error'); + updateUI("imageError", "error"); } }); - - resizeBtn.addEventListener('click', () => { + + resizeBtn.addEventListener("click", () => { if (state.imageLoaded && state.imageData.processor) { showResizeDialog(state.imageData.processor); } }); - - selectPosBtn.addEventListener('click', async () => { + + selectPosBtn.addEventListener("click", async () => { if (state.selectingPosition) return; - + state.selectingPosition = true; state.startPosition = null; state.region = null; startBtn.disabled = true; - - Utils.showAlert(Utils.t('selectPositionAlert'), 'info'); - updateUI('waitingPosition', 'default'); - + + Utils.showAlert(Utils.t("selectPositionAlert"), "info"); + updateUI("waitingPosition", "default"); + const originalFetch = window.fetch; - + window.fetch = async (url, options) => { - if (typeof url === 'string' && - url.includes('https://backend.wplace.live/s0/pixel/') && - options?.method?.toUpperCase() === 'POST') { - + if ( + typeof url === "string" && + url.includes("https://backend.wplace.live/s0/pixel/") && + options?.method?.toUpperCase() === "POST" + ) { try { const response = await originalFetch(url, options); const clonedResponse = response.clone(); const data = await clonedResponse.json(); - + if (data?.painted === 1) { const regionMatch = url.match(/\/pixel\/(\d+)\/(\d+)/); if (regionMatch && regionMatch.length >= 3) { state.region = { x: parseInt(regionMatch[1]), - y: parseInt(regionMatch[2]) + y: parseInt(regionMatch[2]), }; } - + const payload = JSON.parse(options.body); if (payload?.coords && Array.isArray(payload.coords)) { state.startPosition = { x: payload.coords[0], - y: payload.coords[1] + y: payload.coords[1], }; state.lastPosition = { x: 0, y: 0 }; - + if (state.imageLoaded) { startBtn.disabled = false; } - + window.fetch = originalFetch; state.selectingPosition = false; - updateUI('positionSet', 'success'); + updateUI("positionSet", "success"); } } - + return response; } catch { return originalFetch(url, options); @@ -1045,29 +1107,28 @@ } return originalFetch(url, options); }; - + setTimeout(() => { if (state.selectingPosition) { window.fetch = originalFetch; state.selectingPosition = false; - updateUI('positionTimeout', 'error'); - Utils.showAlert(Utils.t('positionTimeout'), 'error'); + updateUI("positionTimeout", "error"); + Utils.showAlert(Utils.t("positionTimeout"), "error"); } }, 120000); }); - - startBtn.addEventListener('click', async () => { - // Before starting, check for all requirements, including the token. + + startBtn.addEventListener("click", async () => { if (!state.imageLoaded || !state.startPosition || !state.region) { - updateUI('missingRequirements', 'error'); + updateUI("missingRequirements", "error"); return; } if (!capturedCaptchaToken) { - updateUI('captchaNeeded', 'error'); - Utils.showAlert(Utils.t('captchaNeeded'), 'error'); + updateUI("captchaNeeded", "error"); + Utils.showAlert(Utils.t("captchaNeeded"), "error"); return; } - + state.running = true; state.stopFlag = false; startBtn.disabled = true; @@ -1075,17 +1136,17 @@ uploadBtn.disabled = true; selectPosBtn.disabled = true; resizeBtn.disabled = true; - - updateUI('startPaintingMsg', 'success'); - + + updateUI("startPaintingMsg", "success"); + try { await processImage(); } catch { - updateUI('paintingError', 'error'); + updateUI("paintingError", "error"); } finally { state.running = false; stopBtn.disabled = true; - + if (!state.stopFlag) { startBtn.disabled = true; uploadBtn.disabled = false; @@ -1096,12 +1157,12 @@ } } }); - - stopBtn.addEventListener('click', () => { + + stopBtn.addEventListener("click", () => { state.stopFlag = true; state.running = false; stopBtn.disabled = true; - updateUI('paintingStopped', 'warning'); + updateUI("paintingStopped", "warning"); }); } @@ -1109,42 +1170,30 @@ const { width, height, pixels } = state.imageData; const { x: startX, y: startY } = state.startPosition; const { x: regionX, y: regionY } = state.region; - - // If resuming, start from the last stopped position + let startRow = state.lastPosition.y || 0; let startCol = state.lastPosition.x || 0; - // Track painted pixels to avoid repainting if (!state.paintedMap) { - // Create a 2D array to mark painted pixels - state.paintedMap = Array(height).fill().map(() => Array(width).fill(false)); + state.paintedMap = Array(height) + .fill() + .map(() => Array(width).fill(false)); } - // Setup interval to refresh getCharges every 30s while painting - if (window._chargesInterval) clearInterval(window._chargesInterval); - window._chargesInterval = setInterval(() => { - // Don't use await in setInterval directly, use then() - if (state.running && !state.stopFlag) { - WPlaceService.getCharges().then(chargeUpdate => { - state.currentCharges = chargeUpdate.charges; - state.cooldown = chargeUpdate.cooldown; - updateStats(); - }); - } - }, 30000); + let pixelBatch = []; try { - outerLoop: - for (let y = startRow; y < height; y++) { - for (let x = (y === startRow ? startCol : 0); x < width; x++) { - // If stop is requested, save the last position and exit + outerLoop: for (let y = startRow; y < height; y++) { + for (let x = y === startRow ? startCol : 0; x < width; x++) { if (state.stopFlag) { + if (pixelBatch.length > 0) { + await sendPixelBatch(pixelBatch, regionX, regionY); + } state.lastPosition = { x, y }; - updateUI('paintingPaused', 'warning', { x, y }); + updateUI("paintingPaused", "warning", { x, y }); break outerLoop; } - // Skip pixel if already painted (for resume) if (state.paintedMap[y][x]) continue; const idx = (y * width + x) * 4; @@ -1153,79 +1202,133 @@ const b = pixels[idx + 2]; const alpha = pixels[idx + 3]; - // Skip transparent or white pixels if (alpha < CONFIG.TRANSPARENCY_THRESHOLD) continue; if (Utils.isWhitePixel(r, g, b)) continue; const rgb = [r, g, b]; const colorId = findClosestColor(rgb, state.availableColors); - - if (state.currentCharges < 1) { - updateUI('noCharges', 'warning', { time: Utils.formatTime(state.cooldown) }); - await Utils.sleep(state.cooldown); - - const chargeUpdate = await WPlaceService.getCharges(); - state.currentCharges = chargeUpdate.charges; - state.cooldown = chargeUpdate.cooldown; - } - const pixelX = startX + x; const pixelY = startY + y; - const success = await WPlaceService.paintPixelInRegion( - regionX, - regionY, - pixelX, - pixelY, - colorId - ); + pixelBatch.push({ + x: pixelX, + y: pixelY, + color: colorId, + localX: x, + localY: y, + }); - // If CAPTCHA/token error, stop and notify - if (success === 'token_error') { + if (pixelBatch.length >= 60) { + const success = await sendPixelBatch(pixelBatch, regionX, regionY); + + if (success === "token_error") { state.stopFlag = true; - updateUI('captchaNeeded', 'error'); - Utils.showAlert(Utils.t('captchaNeeded'), 'error'); + updateUI("captchaNeeded", "error"); + Utils.showAlert(Utils.t("captchaNeeded"), "error"); break outerLoop; - } + } - if (success) { - state.paintedPixels++; - state.currentCharges--; - state.paintedMap[y][x] = true; // Mark pixel as painted - - state.estimatedTime = Utils.calculateEstimatedTime( - state.totalPixels - state.paintedPixels, - state.currentCharges, - state.cooldown - ); - - if (state.paintedPixels % CONFIG.LOG_INTERVAL === 0) { - updateStats(); - updateUI('paintingProgress', 'default', { - painted: state.paintedPixels, - total: state.totalPixels + if (success) { + pixelBatch.forEach((pixel) => { + state.paintedMap[pixel.localY][pixel.localX] = true; + state.paintedPixels++; }); + + state.currentCharges -= pixelBatch.length; + updateStats(); + updateUI("paintingProgress", "default", { + painted: state.paintedPixels, + total: state.totalPixels, + }); + } + + pixelBatch = []; + + if (state.currentCharges < 60) { + updateUI("noCharges", "warning", { + time: Utils.formatTime(state.cooldown), + }); + await Utils.sleep(state.cooldown); + + const chargeUpdate = await WPlaceService.getCharges(); + state.currentCharges = chargeUpdate.charges; + state.cooldown = chargeUpdate.cooldown; } } } } + + if (pixelBatch.length > 0 && !state.stopFlag) { + const success = await sendPixelBatch(pixelBatch, regionX, regionY); + if (success) { + pixelBatch.forEach((pixel) => { + state.paintedMap[pixel.localY][pixel.localX] = true; + state.paintedPixels++; + }); + state.currentCharges -= pixelBatch.length; + } + } } finally { - // Always clear the interval when painting stops or finishes if (window._chargesInterval) clearInterval(window._chargesInterval); window._chargesInterval = null; } - // If stopped, keep last position for resume; if finished, reset lastPosition and paintedMap if (state.stopFlag) { - updateUI('paintingStopped', 'warning'); + updateUI("paintingStopped", "warning"); } else { - updateUI('paintingComplete', 'success', { count: state.paintedPixels }); + updateUI("paintingComplete", "success", { count: state.paintedPixels }); state.lastPosition = { x: 0, y: 0 }; - state.paintedMap = null; // Reset painted map for next image + state.paintedMap = null; } updateStats(); } + async function sendPixelBatch(pixelBatch, regionX, regionY) { + if (!capturedCaptchaToken) { + return "token_error"; + } + + const coords = []; + const colors = []; + + pixelBatch.forEach((pixel) => { + coords.push(pixel.x, pixel.y); + colors.push(pixel.color); + }); + + try { + const payload = { + coords: coords, + colors: colors, + t: capturedCaptchaToken, + }; + + const res = await fetch( + `https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, + { + method: "POST", + headers: { "Content-Type": "text/plain;charset=UTF-8" }, + credentials: "include", + body: JSON.stringify(payload), + } + ); + + if (res.status === 403) { + console.error( + "❌ 403 Forbidden. CAPTCHA token might be invalid or expired." + ); + capturedCaptchaToken = null; + return "token_error"; + } + + const data = await res.json(); + return data?.painted === pixelBatch.length; + } catch (e) { + console.error("Batch paint request failed:", e); + return false; + } + } + createUI(); -})(); \ No newline at end of file +})(); From 3ceec4c186ec177de119f5907cf4ff1db90aa7ac Mon Sep 17 00:00:00 2001 From: Sophie Calixto <156002567+datapanthom@users.noreply.github.com> Date: Wed, 13 Aug 2025 09:27:54 -0300 Subject: [PATCH 2/2] fix: use user charges on req --- Auto-Image.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Auto-Image.js b/Auto-Image.js index 5dfe2c9..f7e0f17 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -1218,7 +1218,7 @@ localY: y, }); - if (pixelBatch.length >= 60) { + if (pixelBatch.length >= Math.floor(state.currentCharges)) { const success = await sendPixelBatch(pixelBatch, regionX, regionY); if (success === "token_error") { @@ -1244,7 +1244,7 @@ pixelBatch = []; - if (state.currentCharges < 60) { + if (state.currentCharges < 1) { updateUI("noCharges", "warning", { time: Utils.formatTime(state.cooldown), });