diff --git a/Auto-Image.js b/Auto-Image.js
index b1f2352..f7e0f17 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 = `
`;
-
- 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 >= Math.floor(state.currentCharges)) {
+ 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 < 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;
}
}
}
}
+
+ 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
+})();