diff --git a/Auto-Image.js b/Auto-Image.js index 44ec6df..925c2ae 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -1,307 +1,584 @@ (async () => { + // CONFIGURATION CONSTANTS const CONFIG = { - START_X: 742, - START_Y: 1148, - PIXELS_PER_LINE: 100, - DELAY: 1000, + COOLDOWN_DEFAULT: 31000, + TRANSPARENCY_THRESHOLD: 100, + WHITE_THRESHOLD: 250, + LOG_INTERVAL: 10, THEME: { - primary: '#000000', - secondary: '#111111', - accent: '#222222', - text: '#ffffff', - highlight: '#775ce3', - success: '#00ff00', - error: '#ff0000' - } + primary: "#000000", + secondary: "#111111", + accent: "#222222", + text: "#ffffff", + highlight: "#775ce3", + success: "#00ff00", + error: "#ff0000", + warning: "#ffaa00", + }, }; + // BILINGUAL TEXT STRINGS + const TEXTS = { + pt: { + title: "WPlace Auto-Image", + initBot: "Iniciar Auto-BOT", + uploadImage: "Upload da Imagem", + resizeImage: "Redimensionar Imagem", + selectPosition: "Selecionar Posição", + startPainting: "Iniciar Pintura", + stopPainting: "Parar Pintura", + checkingColors: "🔍 Verificando cores disponíveis...", + noColorsFound: "❌ Abra a paleta de cores no site e tente novamente!", + colorsFound: "✅ {count} cores disponíveis encontradas", + 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!", + waitingPosition: "👆 Aguardando você pintar o pixel de referência...", + positionSet: "✅ Posição definida com sucesso!", + positionTimeout: "❌ Tempo esgotado para selecionar posição", + startPaintingMsg: "🎨 Iniciando pintura...", + paintingProgress: "🧱 Progresso: {painted}/{total} pixels...", + noCharges: "⌛ Sem cargas. Aguardando {time}...", + 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", + progress: "Progresso", + pixels: "Pixels", + charges: "Cargas", + estimatedTime: "Tempo estimado", + initMessage: "Clique em 'Iniciar Auto-BOT' para começar", + 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.", + saveData: "Salvar Progresso", + loadData: "Carregar Progresso", + autoSaved: "✅ Progresso salvo automaticamente", + dataLoaded: "✅ Progresso carregado com sucesso", + noSavedData: "❌ Nenhum progresso salvo encontrado", + savedDataFound: "✅ Progresso salvo encontrado! Carregar para continuar?", + savedDate: "Salvo em: {date}", + clickLoadToContinue: "Clique em 'Carregar Progresso' para continuar.", + }, + en: { + title: "WPlace Auto-Image", + initBot: "Start Auto-BOT", + uploadImage: "Upload Image", + resizeImage: "Resize Image", + selectPosition: "Select Position", + startPainting: "Start Painting", + stopPainting: "Stop Painting", + checkingColors: "🔍 Checking available colors...", + noColorsFound: "❌ Open the color palette on the site and try again!", + colorsFound: "✅ {count} available colors found", + 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!", + waitingPosition: "👆 Waiting for you to paint the reference pixel...", + positionSet: "✅ Position set successfully!", + positionTimeout: "❌ Timeout for position selection", + startPaintingMsg: "🎨 Starting painting...", + paintingProgress: "🧱 Progress: {painted}/{total} pixels...", + noCharges: "⌛ No charges. Waiting {time}...", + paintingStopped: "⏹️ Painting stopped by user", + paintingComplete: "✅ Painting complete! {count} pixels painted.", + paintingError: "❌ Error during painting", + missingRequirements: "❌ Load an image and select a position first", + progress: "Progress", + pixels: "Pixels", + charges: "Charges", + estimatedTime: "Estimated time", + initMessage: "Click 'Start Auto-BOT' to begin", + 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.", + saveData: "Save Progress", + loadData: "Load Progress", + autoSaved: "✅ Progress saved automatically", + dataLoaded: "✅ Progress loaded successfully", + noSavedData: "❌ No saved progress found", + savedDataFound: "✅ Saved progress found! Load to continue?", + savedDate: "Saved on: {date}", + clickLoadToContinue: "Click 'Load Progress' to continue.", + }, + }; + + // GLOBAL STATE const state = { running: false, - paintedCount: 0, - charges: { count: 0, max: 80, cooldownMs: 30000 }, - userInfo: null, - lastPixel: null, + imageLoaded: false, + processing: false, + totalPixels: 0, + paintedPixels: 0, + availableColors: [], + currentCharges: 0, + cooldown: CONFIG.COOLDOWN_DEFAULT, + imageData: null, + stopFlag: false, + colorsChecked: false, + startPosition: null, + selectingPosition: false, + region: null, minimized: false, - menuOpen: false, - language: 'en', - autoRefresh: true, - pausedForManual: false + lastPosition: { x: 0, y: 0 }, + estimatedTime: 0, + language: "en", }; - const sleep = ms => new Promise(r => setTimeout(r, ms)); - const waitForSelector = async (selector, interval = 200, timeout = 5000) => { - const start = Date.now(); - while (Date.now() - start < timeout) { - const el = document.querySelector(selector); - if (el) return el; - await sleep(interval); - } - return null; - }; - - const originalFetch = window.fetch; + // Global variable to store the captured CAPTCHA token. let capturedCaptchaToken = null; - window.fetch = async (url, options = {}) => { - if (typeof url === 'string' && url.includes('https://backend.wplace.live/s0/pixel/')) { + + // Intercept the original window.fetch function to "listen" for network requests. + 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/") + ) { try { - const payload = JSON.parse(options.body || '{}'); + const payload = JSON.parse(options.body); + // If the request body contains the 't' field, it's our CAPTCHA token. if (payload.t) { - console.log('✅ CAPTCHA Token Captured:', payload.t); + console.log("✅ CAPTCHA Token Captured:", payload.t); + // Store the token for our bot to use. capturedCaptchaToken = payload.t; - if (state.pausedForManual) { - state.pausedForManual = false; - state.running = true; - updateUI( - state.language === 'pt' ? '🚀 Pintura reiniciada!' : '🚀 Painting resumed!', - 'success' + // 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" ); - paintLoop(); + updateUI("colorsFound", "success", { + count: state.availableColors.length, + }); } } } 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); }; - const fetchAPI = async (url, options = {}) => { + async function detectLanguage() { try { - const res = await fetch(url, { - credentials: 'include', - ...options - }); - return await res.json(); - } catch (e) { - return null; - } - }; - - const getRandomPosition = () => ({ - x: Math.floor(Math.random() * CONFIG.PIXELS_PER_LINE), - y: Math.floor(Math.random() * CONFIG.PIXELS_PER_LINE) - }); - - const paintPixel = async (x, y) => { - const randomColor = Math.floor(Math.random() * 31) + 1; - const url = `https://backend.wplace.live/s0/pixel/${CONFIG.START_X}/${CONFIG.START_Y}`; - const payload = JSON.stringify({ coords: [x, y], colors: [randomColor], t: capturedCaptchaToken }); - try { - const res = await originalFetch(url, { - method: 'POST', - headers: { 'Content-Type': 'text/plain;charset=UTF-8' }, - credentials: 'include', - body: payload - }); - if (res.status === 403) { - console.error('❌ 403 Forbidden. CAPTCHA token might be invalid or expired.'); - capturedCaptchaToken = null; - stoppedForToken = true; - return 'token_error'; - } - const data = await res.json(); - return data; - } catch (e) { - return null; - } - }; - - const getCharge = async () => { - const data = await fetchAPI('https://backend.wplace.live/me'); - if (data) { - state.userInfo = data; - state.charges = { - count: Math.floor(data.charges.count), - max: Math.floor(data.charges.max), - cooldownMs: data.charges.cooldownMs - }; - if (state.userInfo.level) { - state.userInfo.level = Math.floor(state.userInfo.level); - } - } - return state.charges; - }; - - const detectUserLocation = async () => { - try { - const response = await fetch('https://ipapi.co/json/'); + const response = await fetch("https://ipapi.co/json/"); const data = await response.json(); - if (data.country === 'BR') { - state.language = 'pt'; - } else if (data.country === 'US') { - state.language = 'en'; - } else { - state.language = 'en'; - } + state.language = data.country === "BR" ? "pt" : "en"; } catch { - state.language = 'en'; + state.language = "en"; } - }; + } - const paintLoop = async () => { - while (state.running) { - const { count, cooldownMs } = state.charges; - - if (count < 1 && state.autoRefresh) { - if (!state.pausedForManual) { - updateUI( - state.language === 'pt' - ? '⚡ Aguardando pelo menos 2 cargas para auto-refresh...' - : 'Waiting for at least 2 charges for auto-refresh...', - 'status' - ); - state.pausedForManual = true; - } - while (state.charges.count < 2) { - await sleep(60000); - await getCharge(); - updateStats(); - } - state.pausedForManual = false; - updateUI( - state.language === 'pt' ? '🔋 Cargas suficientes. Retomando...' : 'Sufficient charges. Resuming...', - 'success' - ); - continue; - } else if (count < 1) { - // manual mode fallback - updateUI(state.language === 'pt' ? `⌛ Sem cargas. Esperando ${Math.ceil(cooldownMs/1000)}s...` : `⌛ No charges. Waiting ${Math.ceil(cooldownMs/1000)}s...`, 'status'); - await sleep(cooldownMs); - await getCharge(); - continue; - } + // UTILITY FUNCTIONS + const Utils = { + sleep: (ms) => new Promise((r) => setTimeout(r, ms)), - const randomPos = getRandomPosition(); - const paintResult = await paintPixel(randomPos.x, randomPos.y); - if (paintResult === 'token_error') { - if (state.autoRefresh) { - await getCharge(); - if (state.charges.count < 2) { - if (!state.pausedForManual) { - updateUI( - state.language === 'pt' - ? '⚡ Aguardando pelo menos 2 cargas para auto-refresh...' - : 'Waiting for at least 2 charges for auto-refresh...', - 'status' - ); - state.pausedForManual = true; - } - while (state.charges.count < 2) { - await sleep(60000); - await getCharge(); - updateStats(); - } - state.pausedForManual = false; - } - updateUI( - state.language === 'pt' - ? '❌ Token expirado. Aguardando elemento Paint...' - : '❌ CAPTCHA token expired. Waiting for Paint button...', - 'error' - ); - const mainPaintBtn = await waitForSelector('button.btn.btn-primary.btn-lg, button.btn-primary.sm\\:btn-xl'); - if (mainPaintBtn) mainPaintBtn.click(); - await sleep(500); - updateUI( - state.language === 'pt' ? 'Selecionando transparente...' : 'Selecting transparent...', - 'status' - ); - const transBtn = await waitForSelector('button#color-0'); - if (transBtn) transBtn.click(); - await sleep(500); - const canvas = await waitForSelector('canvas'); - if (canvas) { - canvas.setAttribute('tabindex', '0'); - canvas.focus(); - const rect = canvas.getBoundingClientRect(); - const centerX = Math.round(rect.left + rect.width / 2); - const centerY = Math.round(rect.top + rect.height / 2); - const moveEvt = new MouseEvent('mousemove', { - clientX: centerX, - clientY: centerY, - bubbles: true - }); - canvas.dispatchEvent(moveEvt); - const keyDown = new KeyboardEvent('keydown', { key: ' ', code: 'Space', bubbles: true }); - const keyUp = new KeyboardEvent('keyup', { key: ' ', code: 'Space', bubbles: true }); - canvas.dispatchEvent(keyDown); - canvas.dispatchEvent(keyUp); - } - await sleep(500); - updateUI( - state.language === 'pt' ? 'Confirmando pintura...' : 'Confirming paint...', - 'status' - ); - let confirmBtn = await waitForSelector( - 'button.btn.btn-primary.btn-lg, button.btn.btn-primary.sm\\:btn-xl' - ); - if (!confirmBtn) { - const allPrimary = Array.from(document.querySelectorAll('button.btn-primary')); - confirmBtn = allPrimary.length ? allPrimary[allPrimary.length - 1] : null; - } - confirmBtn?.click(); - } else { - // insufficient charges or auto-refresh disabled - if (state.autoRefresh && state.charges.count < 2) { - updateUI( - state.language === 'pt' - ? '⚡ Cargas insuficientes para auto-refresh. Por favor, clique manualmente.' - : 'Insufficient charges for auto-refresh. Please click manually.', - 'error' - ); - } - if (!state.pausedForManual) { - updateUI( - state.language === 'pt' - ? 'Auto-refresh desativado. Por favor, clique no botão pintura manualmente.' - : 'Auto-refresh disabled. Please click the Paint button manually.', - 'status' - ); - state.pausedForManual = true; - } - state.running = false; - return; - } - await sleep(1000); - continue; - } - - if (paintResult?.painted === 1) { - state.paintedCount++; - state.lastPixel = { - x: CONFIG.START_X + randomPos.x, - y: CONFIG.START_Y + randomPos.y, - time: new Date() + 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]); }; - state.charges.count--; - - document.getElementById('paintEffect').style.animation = 'pulse 0.5s'; - setTimeout(() => { - document.getElementById('paintEffect').style.animation = ''; - }, 500); - - updateUI(state.language === 'pt' ? '✅ Pixel pintado!' : '✅ Pixel painted!', 'success'); - } else { - updateUI(state.language === 'pt' ? '❌ Falha ao pintar' : '❌ Failed to paint', 'error'); - } + input.click(); + }), - await sleep(CONFIG.DELAY); - updateStats(); - } + 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-", "")); + return id !== 0 && id !== 5; + }) + .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) => { + 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 = ""; + 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; + }, + + // Save/Load Progress Functions + saveProgress: () => { + try { + const progressData = { + version: "1.0", + timestamp: Date.now(), + state: { + imageLoaded: state.imageLoaded, + totalPixels: state.totalPixels, + paintedPixels: state.paintedPixels, + lastPosition: state.lastPosition, + startPosition: state.startPosition, + region: state.region, + paintedMap: state.paintedMap, + imageData: state.imageData, + availableColors: state.availableColors, + colorsChecked: state.colorsChecked, + language: state.language + } + }; + + localStorage.setItem('wplace-auto-image-progress', JSON.stringify(progressData)); + console.log('✅ Progress saved successfully'); + return true; + } catch (error) { + console.error('❌ Error saving progress:', error); + return false; + } + }, + + loadProgress: () => { + try { + const savedData = localStorage.getItem('wplace-auto-image-progress'); + if (!savedData) { + return null; + } + + const progressData = JSON.parse(savedData); + + // Validate data structure + if (!progressData.version || !progressData.state) { + return null; + } + + return progressData; + } catch (error) { + console.error('❌ Error loading progress:', error); + return null; + } + }, + + restoreProgress: (progressData) => { + try { + const savedState = progressData.state; + + // Restore state + state.imageLoaded = savedState.imageLoaded; + state.totalPixels = savedState.totalPixels; + state.paintedPixels = savedState.paintedPixels; + state.lastPosition = savedState.lastPosition || { x: 0, y: 0 }; + state.startPosition = savedState.startPosition; + state.region = savedState.region; + state.paintedMap = savedState.paintedMap; + state.imageData = savedState.imageData; + state.availableColors = savedState.availableColors; + state.language = savedState.language; + state.colorsChecked = savedState.availableColors && savedState.availableColors.length > 0; + + // Update UI to reflect restored state + if (state.imageLoaded) { + const initBotBtn = document.querySelector("#initBotBtn"); + const uploadBtn = document.querySelector("#uploadBtn"); + const resizeBtn = document.querySelector("#resizeBtn"); + const selectPosBtn = document.querySelector("#selectPosBtn"); + const startBtn = document.querySelector("#startBtn"); + const saveBtn = document.querySelector("#saveBtn"); + const progressBar = document.querySelector("#progressBar"); + + // Show/hide appropriate buttons based on state + if (state.colorsChecked) { + initBotBtn.style.display = "none"; + uploadBtn.disabled = false; + selectPosBtn.disabled = false; + } else { + initBotBtn.style.display = "block"; + uploadBtn.disabled = true; + selectPosBtn.disabled = true; + } + + resizeBtn.disabled = false; + saveBtn.disabled = false; + + if (state.startPosition && state.region) { + selectPosBtn.disabled = false; + startBtn.disabled = false; + } + + // Update progress bar + const progress = state.totalPixels > 0 ? Math.round((state.paintedPixels / state.totalPixels) * 100) : 0; + progressBar.style.width = `${progress}%`; + + // Update status message based on progress + if (state.paintedPixels > 0) { + if (state.lastPosition.x > 0 || state.lastPosition.y > 0) { + updateUI("paintingPaused", "warning", { + x: state.lastPosition.x, + y: state.lastPosition.y + }); + } else { + updateUI("paintingProgress", "default", { + painted: state.paintedPixels, + total: state.totalPixels, + }); + } + } else { + updateUI("imageLoaded", "success", { count: state.totalPixels }); + } + } + + // Update stats to show current progress + updateStats(); + return true; + } catch (error) { + console.error('❌ Error restoring progress:', error); + return false; + } + }, + + clearProgress: () => { + try { + localStorage.removeItem('wplace-auto-image-progress'); + console.log('✅ Progress data cleared'); + return true; + } catch (error) { + console.error('❌ Error clearing progress:', error); + return false; + } + }, + + hasSavedProgress: () => { + return localStorage.getItem('wplace-auto-image-progress') !== null; + }, + + 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"; + + const icons = { + 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"; + 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; + }, + + isWhitePixel: (r, g, b) => { + 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; + }, }; - const createUI = () => { - if (state.menuOpen) return; - state.menuOpen = true; + // 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), + } + ); - 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'; + // 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 data = await res.json(); + 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"); + } + + async load() { + return new Promise((resolve, reject) => { + this.img.onload = () => { + this.canvas.width = this.img.width; + this.canvas.height = this.img.height; + this.ctx.drawImage(this.img, 0, 0); + resolve(); + }; + this.img.onerror = reject; + this.img.src = this.imageSrc; + }); + } + + getPixelData() { + 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"); + tempCanvas.width = newWidth; + tempCanvas.height = newHeight; + 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; + this.previewCtx.imageSmoothingEnabled = false; + this.previewCtx.drawImage(this.img, 0, 0, newWidth, newHeight); + return this.previewCanvas.toDataURL(); + } + } + + 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; + } + + 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"; 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); }n + 0% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0.7); } 70% { box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); } 100% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0); } } @@ -309,17 +586,17 @@ from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } - .wplace-bot-panel { + #wplace-image-bot-container { position: fixed; top: 20px; right: 20px; - width: 250px; + width: 300px; background: ${CONFIG.THEME.primary}; border: 1px solid ${CONFIG.THEME.accent}; border-radius: 8px; padding: 0; box-shadow: 0 5px 15px rgba(0,0,0,0.5); - z-index: 9999; + z-index: 9998; font-family: 'Segoe UI', Roboto, sans-serif; color: ${CONFIG.THEME.text}; animation: slideIn 0.4s ease-out; @@ -359,15 +636,15 @@ } .wplace-content { padding: 15px; - display: ${state.minimized ? 'none' : 'block'}; + display: block; } .wplace-controls { display: flex; + flex-direction: column; gap: 10px; margin-bottom: 15px; } .wplace-btn { - flex: 1; padding: 10px; border: none; border-radius: 6px; @@ -386,10 +663,28 @@ background: ${CONFIG.THEME.accent}; color: white; } + .wplace-btn-upload { + background: ${CONFIG.THEME.secondary}; + color: white; + border: 1px dashed ${CONFIG.THEME.text}; + } + .wplace-btn-start { + background: ${CONFIG.THEME.success}; + color: white; + } .wplace-btn-stop { background: ${CONFIG.THEME.error}; color: white; } + .wplace-btn-select { + background: ${CONFIG.THEME.highlight}; + color: black; + } + .wplace-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none !important; + } .wplace-stats { background: ${CONFIG.THEME.secondary}; padding: 12px; @@ -408,6 +703,18 @@ gap: 6px; opacity: 0.8; } + .wplace-progress { + width: 100%; + background: ${CONFIG.THEME.secondary}; + border-radius: 4px; + margin: 10px 0; + overflow: hidden; + } + .wplace-progress-bar { + height: 10px; + background: ${CONFIG.THEME.highlight}; + transition: width 0.3s; + } .wplace-status { padding: 8px; border-radius: 4px; @@ -425,6 +732,10 @@ background: rgba(255, 0, 0, 0.1); color: ${CONFIG.THEME.error}; } + .status-warning { + background: rgba(255, 165, 0, 0.1); + color: orange; + } #paintEffect { position: absolute; top: 0; @@ -434,213 +745,850 @@ pointer-events: none; border-radius: 8px; } + .position-info { + font-size: 13px; + margin-top: 5px; + padding: 5px; + background: ${CONFIG.THEME.secondary}; + border-radius: 4px; + text-align: center; + } + .wplace-minimized .wplace-content { + display: none; + } + .resize-container { + display: none; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: ${CONFIG.THEME.primary}; + padding: 20px; + border-radius: 8px; + z-index: 10000; + box-shadow: 0 0 20px rgba(0,0,0,0.5); + max-width: 90%; + max-height: 90%; + overflow: auto; + } + .resize-preview { + max-width: 100%; + max-height: 300px; + margin: 10px 0; + border: 1px solid ${CONFIG.THEME.accent}; + } + .resize-controls { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 15px; + } + .resize-slider { + width: 100%; + } + .resize-buttons { + display: flex; + gap: 10px; + } + .resize-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.7); + z-index: 9999; + display: none; + } `; document.head.appendChild(style); - const translations = { - pt: { - title: "WPlace Auto-Farm", - start: "Iniciar", - stop: "Parar", - ready: "Pronto para começar", - user: "Usuário", - pixels: "Pixels", - charges: "Cargas", - level: "Level" - }, - en: { - title: "WPlace Auto-Farm", - start: "Start", - stop: "Stop", - ready: "Ready to start", - user: "User", - pixels: "Pixels", - charges: "Charges", - level: "Level" - } - }; - - const t = translations[state.language] || translations.en; - - const panel = document.createElement('div'); - panel.className = 'wplace-bot-panel'; - panel.innerHTML = ` -
+ const container = document.createElement("div"); + container.id = "wplace-image-bot-container"; + container.innerHTML = `