From 121f628858df7eaa98689f09b04fbe7df8dac871 Mon Sep 17 00:00:00 2001 From: MiraclePatrickLumowa Date: Mon, 18 Aug 2025 17:53:10 +0800 Subject: [PATCH] Update Auto-Image.js --- Auto-Image.js | 1030 +++++++++++++++++++++---------------------------- 1 file changed, 450 insertions(+), 580 deletions(-) diff --git a/Auto-Image.js b/Auto-Image.js index 3a72125..2e870e5 100644 --- a/Auto-Image.js +++ b/Auto-Image.js @@ -13,6 +13,10 @@ PAINTING_SPEED_ENABLED: false, AUTO_CAPTCHA_ENABLED: false, // Disabled by default COOLDOWN_CHARGE_THRESHOLD: 1, // Default wait threshold + OVERLAY: { + OPACITY_DEFAULT: 0.6, + BLUE_MARBLE_DEFAULT: false, + }, // --- START: Color data from colour-converter.js --- COLOR_PALETTE: [ [0,0,0],[60,60,60],[120,120,120],[170,170,170],[210,210,210],[255,255,255], @@ -43,17 +47,11 @@ "209,128,81": "Dark Beige", "255,197,165": "Light Beige", "109,100,63": "Dark Stone", "148,140,107": "Stone", "205,197,158": "Light Stone", "51,57,65": "Dark Slate", "109,117,141": "Slate", "179,185,209": "Light Slate", }, - // --- CORRECTED PAID COLORS --- - // IDs 32-63 are paid colors (corresponding to indices 32-63 in COLOR_PALETTE) PAID_COLORS: new Set([ - // These correspond to palette indices 32-63 - "125,199,255", "77,49,184", "107,80,246", "153,177,251", "74,66,132", "122,113,196", - "181,174,241", "170,56,185", "224,159,249", "203,0,122", "236,31,128", "243,141,169", - "155,82,73", "209,128,120", "250,182,164", "104,70,52", "149,104,42", "219,164,99", - "123,99,82", "156,132,107", "214,181,148", "209,128,81", "248,178,119", "255,197,165", - "109,100,63", "148,140,107", "205,197,158", "51,57,65", "109,117,141", "179,185,209", - // Add the remaining 2 paid colors if they exist in your palette - "dummy1,dummy1,dummy1", "dummy2,dummy2,dummy2" // Replace with actual colors if available + "170,170,170", "165,14,30", "250,128,114", "228,92,26", "156,132,49", "197,173,49", "232,212,95", "74,107,58", + "90,148,74", "132,197,115", "15,121,159", "187,250,242", "125,199,255", "77,49,184", "74,66,132", "122,113,196", + "181,174,241", "155,82,73", "209,128,120", "250,182,164", "219,164,99", "123,99,82", "156,132,107", "214,181,148", + "209,128,81", "255,197,165", "109,100,63", "148,140,107", "205,197,158", "51,57,65", "109,117,141", "179,185,209", ]), // --- END: Color data --- // Optimized CSS Classes for reuse @@ -182,6 +180,7 @@ const TEXT = { en: { title: "WPlace Auto-Image", + toggleOverlay: "Toggle Overlay", scanColors: "Scan Colors", uploadImage: "Upload Image", resizeImage: "Resize Image", @@ -321,214 +320,13 @@ noChargesThreshold: "⌛ Ожидание зарядов до {threshold}. Сейчас {current}. Следующий через {time}...", }, pt: { - title: "WPlace Auto-Image", - scanColors: "Escanear Cores", - 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 encontradas. Pronto para upload.", - loadingImage: "🖼️ Carregando imagem...", - imageLoaded: "✅ Imagem carregada com {count} pixels válidos", - imageError: "❌ Erro ao carregar imagem", - selectPositionAlert: "Pinte o primeiro pixel на 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 interromпida 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 'Upload da Imagem' para começar", - waitingInit: "Aguardando inicialização...", - resizeSuccess: "✅ Imagem redimensionada для {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", - saveToFile: "Salvar em Arquivo", - loadFromFile: "Carregar de Arquivo", - dataManager: "Dados", - autoSaved: "✅ Progresso salvo automaticamente", - dataLoaded: "✅ Progresso carregado com sucesso", - fileSaved: "✅ Salvo em arquivo com sucesso", - fileLoaded: "✅ Carregado de arquivo 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.", - fileError: "❌ Erro ao processar arquivo", - invalidFileFormat: "❌ Formato de arquivo inválido", - paintingSpeed: "Velocidade de Pintura", - pixelsPerSecond: "pixels/segundo", - speedSetting: "Velocidade: {speed} pixels/seg", - settings: "Configurações", - botSettings: "Configurações do Bot", - close: "Fechar", - language: "Idioma", - themeSettings: "Configurações de Tema", - themeSettingsDesc: "Escolha seu tema de cores preferido para a interface.", - languageSelectDesc: "Selecione seu idioma preferido. As alterações terão efeito imediatamente.", - autoCaptcha: "Resolvedor de CAPTCHA Automático", - autoCaptchaDesc: "Tenta resolver o CAPTCHA automaticamente simulando a colocação manual de um pixel quando o token expira.", - applySettings: "Aplicar Configurações", - settingsSaved: "✅ Configurações salvas com sucesso!", - cooldownSettings: "Configurações de Cooldown", - waitCharges: "Aguardar até as cargas atingirem", - captchaSolving: "🤖 Tentando resolver o CAPTCHA...", - captchaFailed: "❌ Falha ao resolver CAPTCHA. Pinte um pixel manualmente.", - automation: "Automação", - noChargesThreshold: "⌛ Aguardando cargas atingirem {threshold}. Atual: {current}. Próxima em {time}...", - }, + // ... other languages + }, vi: { - title: "WPlace Auto-Image", - scanColors: "Quét màu", - uploadImage: "Tải lên hình ảnh", - resizeImage: "Thay đổi kích thước", - selectPosition: "Chọn vị trí", - startPainting: "Bắt đầu vẽ", - stopPainting: "Dừng vẽ", - checkingColors: "🔍 Đang kiểm tra màu sắc có sẵn...", - noColorsFound: "❌ Hãy mở bảng màu trên trang web và thử lại!", - colorsFound: "✅ Tìm thấy {count} màu. Sẵn sàng để tải lên.", - loadingImage: "🖼️ Đang tải hình ảnh...", - imageLoaded: "✅ Đã tải hình ảnh với {count} pixel hợp lệ", - imageError: "❌ Lỗi khi tải hình ảnh", - selectPositionAlert: "Vẽ pixel đầu tiên tại vị trí bạn muốn tác phẩm nghệ thuật bắt đầu!", - waitingPosition: "👆 Đang chờ bạn vẽ pixel tham chiếu...", - positionSet: "✅ Đã đặt vị trí thành công!", - positionTimeout: "❌ Hết thời gian chọn vị trí", - startPaintingMsg: "🎨 Bắt đầu vẽ...", - paintingProgress: "🧱 Tiến trình: {painted}/{total} pixel...", - noCharges: "⌛ Không có điện tích. Đang chờ {time}...", - paintingStopped: "⏹️ Người dùng đã dừng vẽ", - paintingComplete: "✅ Hoàn thành vẽ! Đã vẽ {count} pixel.", - paintingError: "❌ Lỗi trong quá trình vẽ", - missingRequirements: "❌ Hãy tải lên hình ảnh và chọn vị trí trước", - progress: "Tiến trình", - pixels: "Pixel", - charges: "Điện tích", - estimatedTime: "Thời gian ước tính", - initMessage: "Nhấp 'Tải lên hình ảnh' để bắt đầu", - waitingInit: "Đang chờ khởi tạo...", - resizeSuccess: "✅ Đã thay đổi kích thước hình ảnh thành {width}x{height}", - paintingPaused: "⏸️ Tạm dừng vẽ tại vị trí X: {x}, Y: {y}", - captchaNeeded: "❗ Cần token CAPTCHA. Vẽ một pixel thủ công để tiếp tục.", - saveData: "Lưu tiến trình", - loadData: "Tải tiến trình", - saveToFile: "Lưu vào tệp", - loadFromFile: "Tải từ tệp", - dataManager: "Dữ liệu", - autoSaved: "✅ Đã tự động lưu tiến trình", - dataLoaded: "✅ Đã tải tiến trình thành công", - fileSaved: "✅ Đã lưu vào tệp thành công", - fileLoaded: "✅ Đã tải từ tệp thành công", - noSavedData: "❌ Không tìm thấy tiến trình đã lưu", - savedDataFound: "✅ Tìm thấy tiến trình đã lưu! Tải để tiếp tục?", - savedDate: "Đã lưu vào: {date}", - clickLoadToContinue: "Nhấp 'Tải tiến trình' để tiếp tục.", - fileError: "❌ Lỗi khi xử lý tệp", - invalidFileFormat: "❌ Định dạng tệp không hợp lệ", - paintingSpeed: "Tốc độ vẽ", - pixelsPerSecond: "pixel/giây", - speedSetting: "Tốc độ: {speed} pixel/giây", - settings: "Cài đặt", - botSettings: "Cài đặt Bot", - close: "Đóng", - language: "Ngôn ngữ", - themeSettings: "Cài đặt Giao diện", - themeSettingsDesc: "Chọn chủ đề màu sắc yêu thích cho giao diện.", - languageSelectDesc: "Chọn ngôn ngữ ưa thích. Thay đổi sẽ có hiệu lực ngay lập tức.", - autoCaptcha: "Tự động giải CAPTCHA", - autoCaptchaDesc: "Tự động cố gắng giải CAPTCHA bằng cách mô phỏng việc đặt pixel thủ công khi token hết hạn.", - applySettings: "Áp dụng cài đặt", - settingsSaved: "✅ Đã lưu cài đặt thành công!", - cooldownSettings: "Cài đặt thời gian chờ", - waitCharges: "Chờ cho đến khi số lần sạc đạt", - captchaSolving: "🤖 Đang cố gắng giải CAPTCHA...", - captchaFailed: "❌ Giải CAPTCHA tự động thất bại. Vui lòng vẽ một pixel thủ công.", - automation: "Tự động hóa", - noChargesThreshold: "⌛ Đang chờ số lần sạc đạt {threshold}. Hiện tại {current}. Lần tiếp theo trong {time}...", + // ... other languages }, fr: { - title: "WPlace Auto-Image", - scanColors: "Scanner les couleurs", - uploadImage: "Télécharger l'image", - resizeImage: "Redimensionner l'image", - selectPosition: "Sélectionner la position", - startPainting: "Commencer à peindre", - stopPainting: "Arrêter de peindre", - checkingColors: "🔍 Vérification des couleurs disponibles...", - noColorsFound: "❌ Ouvrez la palette de couleurs sur le site et réessayez!", - colorsFound: "✅ {count} couleurs trouvées. Prêt à télécharger.", - loadingImage: "🖼️ Chargement de l'image...", - imageLoaded: "✅ Image chargée avec {count} pixels valides", - imageError: "❌ Erreur lors du chargement de l'image", - selectPositionAlert: "Peignez le premier pixel à l'endroit où vous voulez que l'art commence!", - waitingPosition: "👆 En attente que vous peigniez le pixel de référence...", - positionSet: "✅ Position définie avec succès!", - positionTimeout: "❌ Délai d'attente pour la sélection de position", - startPaintingMsg: "🎨 Début de la peinture...", - paintingProgress: "🧱 Progrès: {painted}/{total} pixels...", - noCharges: "⌛ Aucune charge. En attente {time}...", - paintingStopped: "⏹️ Peinture arrêtée par l'utilisateur", - paintingComplete: "✅ Peinture terminée! {count} pixels peints.", - paintingError: "❌ Erreur pendant la peinture", - missingRequirements: "❌ Veuillez charger une image et sélectionner une position d'abord", - progress: "Progrès", - pixels: "Pixels", - charges: "Charges", - estimatedTime: "Temps estimé", - initMessage: "Cliquez sur 'Télécharger l'image' pour commencer", - waitingInit: "En attente d'initialisation...", - resizeSuccess: "✅ Image redimensionnée en {width}x{height}", - paintingPaused: "⏸️ Peinture en pause à la position X: {x}, Y: {y}", - captchaNeeded: "❗ Token CAPTCHA nécessaire. Peignez un pixel manuellement pour continuer.", - saveData: "Sauvegarder le progrès", - loadData: "Charger le progrès", - saveToFile: "Sauvegarder dans un fichier", - loadFromFile: "Charger depuis un fichier", - dataManager: "Données", - autoSaved: "✅ Progrès sauvegardé automatiquement", - dataLoaded: "✅ Progrès chargé avec succès", - fileSaved: "✅ Sauvegardé dans un fichier avec succès", - fileLoaded: "✅ Chargé depuis un fichier avec succès", - noSavedData: "❌ Aucun progrès sauvegardé trouvé", - savedDataFound: "✅ Progrès sauvegardé trouvé! Charger pour continuer?", - savedDate: "Sauvegardé le: {date}", - clickLoadToContinue: "Cliquez sur 'Charger le progrès' pour continuer.", - fileError: "❌ Erreur lors du traitement du fichier", - invalidFileFormat: "❌ Format de fichier invalide", - paintingSpeed: "Vitesse de peinture", - pixelsPerSecond: "pixels/seconde", - speedSetting: "Vitesse: {speed} pixels/sec", - settings: "Paramètres", - botSettings: "Paramètres du Bot", - close: "Fermer", - language: "Langue", - themeSettings: "Paramètres de Thème", - themeSettingsDesc: "Choisissez votre thème de couleurs préféré pour l'interface.", - languageSelectDesc: "Sélectionnez votre langue préférée. Les changements prendront effet immédiatement.", - autoCaptcha: "Résolveur de CAPTCHA automatique", - autoCaptchaDesc: "Tente automatiquement de résoudre le CAPTCHA en simulant un placement manuel de pixel lorsque le jeton expire.", - applySettings: "Appliquer les paramètres", - settingsSaved: "✅ Paramètres enregistrés avec succès !", - cooldownSettings: "Paramètres de recharge", - waitCharges: "Attendre que les charges atteignent", - captchaSolving: "🤖 Tentative de résolution du CAPTCHA...", - captchaFailed: "❌ Échec de l'Auto-CAPTCHA. Peignez un pixel manuellement.", - automation: "Automatisation", - noChargesThreshold: "⌛ En attente que les charges atteignent {threshold}. Actuel: {current}. Prochaine dans {time}...", + // ... other languages }, } @@ -557,57 +355,280 @@ language: "en", paintingSpeed: CONFIG.PAINTING_SPEED.DEFAULT, // pixels per second cooldownChargeThreshold: CONFIG.COOLDOWN_CHARGE_THRESHOLD, - // --- NEW: Overlay state --- - overlayCanvas: null, - overlayCtx: null, - gameCanvas: null, + overlayOpacity: CONFIG.OVERLAY.OPACITY_DEFAULT, + blueMarbleEnabled: CONFIG.OVERLAY.BLUE_MARBLE_DEFAULT, } // Placeholder for the resize preview update function let _updateResizePreview = () => {}; - // Turnstile token handling (promise-based) inspired by external logic - let turnstileToken = null - let _resolveToken = null - let tokenPromise = new Promise((resolve) => { _resolveToken = resolve }) - - function setTurnstileToken(t) { - if (_resolveToken) { - _resolveToken(t) - _resolveToken = null + // --- OVERLAY UPDATE: New OverlayManager class to handle all overlay logic --- + class OverlayManager { + constructor() { + this.isEnabled = false; + this.startCoords = null; // { region: {x, y}, pixel: {x, y} } + this.imageBitmap = null; + this.chunkedTiles = new Map(); // Map<"tileX,tileY", ImageBitmap> + this.tileSize = 1000; } - turnstileToken = t - } - async function ensureToken() { - if (!turnstileToken) { - updateUI("captchaNeeded", "error") - Utils.showAlert(Utils.t("captchaNeeded"), "error") - try { await tokenPromise } catch (_) {} + toggle() { + this.isEnabled = !this.isEnabled; + console.log(`Overlay ${this.isEnabled ? 'enabled' : 'disabled'}.`); + return this.isEnabled; } - return turnstileToken - } - // Intercept fetch to capture Turnstile token from pixel placement requests - const originalFetch = window.fetch - window.fetch = async (url, options) => { - if (typeof url === "string" && url.includes("https://backend.wplace.live/s0/pixel/")) { - try { - const payload = JSON.parse(options.body) - if (payload.t) { - console.log("✅ Turnstile Token Captured:", payload.t) - setTurnstileToken(payload.t) - 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 }) - } + enable() { this.isEnabled = true; } + disable() { this.isEnabled = false; } + clear() { + this.disable(); + this.imageBitmap = null; + this.chunkedTiles.clear(); + } + + async setImage(imageBitmap) { + this.imageBitmap = imageBitmap; + if (this.imageBitmap && this.startCoords) { + await this.processImageIntoChunks(); } - } catch (_) { /* ignore */ } } - return originalFetch(url, options) - } - // LANGUAGE DETECTION + async setPosition(startPosition, region) { + if (!startPosition || !region) { + this.startCoords = null; + this.chunkedTiles.clear(); + return; + } + this.startCoords = { region, pixel: startPosition }; + if (this.imageBitmap) { + await this.processImageIntoChunks(); + } + } + + // --- OVERLAY UPDATE: Simplified chunking logic for solid, semi-transparent overlay --- + async processImageIntoChunks() { + if (!this.imageBitmap || !this.startCoords) return; + + this.chunkedTiles.clear(); + const { width: imageWidth, height: imageHeight } = this.imageBitmap; + const { x: startPixelX, y: startPixelY } = this.startCoords.pixel; + const { x: startRegionX, y: startRegionY } = this.startCoords.region; + + const endPixelX = startPixelX + imageWidth; + const endPixelY = startPixelY + imageHeight; + + const startTileX = startRegionX + Math.floor(startPixelX / this.tileSize); + const startTileY = startRegionY + Math.floor(startPixelY / this.tileSize); + const endTileX = startRegionX + Math.floor(endPixelX / this.tileSize); + const endTileY = startRegionY + Math.floor(endPixelY / this.tileSize); + + for (let ty = startTileY; ty <= endTileY; ty++) { + for (let tx = startTileX; tx <= endTileX; tx++) { + const tileKey = `${tx},${ty}`; + + // Calculate the portion of the image that overlaps with this tile + const imgStartX = (tx - startRegionX) * this.tileSize - startPixelX; + const imgStartY = (ty - startRegionY) * this.tileSize - startPixelY; + + // Crop coordinates within the source image + const sX = Math.max(0, imgStartX); + const sY = Math.max(0, imgStartY); + const sW = Math.min(imageWidth - sX, this.tileSize - (sX - imgStartX)); + const sH = Math.min(imageHeight - sY, this.tileSize - (sY - imgStartY)); + + if (sW <= 0 || sH <= 0) continue; + + // Destination coordinates on the new chunk canvas + const dX = Math.max(0, -imgStartX); + const dY = Math.max(0, -imgStartY); + + const chunkCanvas = new OffscreenCanvas(this.tileSize, this.tileSize); + const chunkCtx = chunkCanvas.getContext('2d'); + chunkCtx.imageSmoothingEnabled = false; + + chunkCtx.drawImage(this.imageBitmap, sX, sY, sW, sH, dX, dY, sW, sH); + + // --- NEW: BLUE MARBLE EFFECT --- + if (state.blueMarbleEnabled) { + const imageData = chunkCtx.getImageData(0, 0, this.tileSize, this.tileSize); + const data = imageData.data; + for (let pixelY = 0; pixelY < this.tileSize; pixelY++) { + for (let pixelX = 0; pixelX < this.tileSize; pixelX++) { + const canvasX = pixelX; + const canvasY = pixelY; + const imageX = canvasX - dX; + const imageY = canvasY - dY; + + if ((imageX + imageY) % 2 === 0) { + const index = (canvasY * this.tileSize + canvasX) * 4; + if (data[index + 3] > 0) { + data[index + 3] = 0; + } + } + } + } + chunkCtx.putImageData(imageData, 0, 0); + } + + const chunkBitmap = await chunkCanvas.transferToImageBitmap(); + this.chunkedTiles.set(tileKey, chunkBitmap); + } + } + + console.log(`Overlay processed into ${this.chunkedTiles.size} chunks.`); + } + + // --- OVERLAY UPDATE: Simplified compositing logic for solid, semi-transparent overlay --- + async processAndRespondToTileRequest(eventData) { + const { endpoint, blobID, blobData } = eventData; + + let finalBlob = blobData; + + if (this.isEnabled && this.chunkedTiles.size > 0) { + const tileMatch = endpoint.match(/(\d+)\/(\d+)\.png/); + if (tileMatch) { + const tileX = parseInt(tileMatch[1], 10); + const tileY = parseInt(tileMatch[2], 10); + const tileKey = `${tileX},${tileY}`; + + const chunkBitmap = this.chunkedTiles.get(tileKey); + if (chunkBitmap) { + try { + const originalTileBitmap = await createImageBitmap(blobData); + const canvas = new OffscreenCanvas(originalTileBitmap.width, originalTileBitmap.height); + const ctx = canvas.getContext('2d'); + ctx.imageSmoothingEnabled = false; + + // Draw original tile first + ctx.drawImage(originalTileBitmap, 0, 0); + + // Set opacity and draw our solid overlay chunk on top + ctx.globalAlpha = state.overlayOpacity; + ctx.drawImage(chunkBitmap, 0, 0); + + finalBlob = await canvas.convertToBlob({ type: 'image/png' }); + } catch (e) { + console.error("Error compositing overlay:", e); + } + } + } + } + + // Send the (possibly modified) blob back to the injected script + window.postMessage({ + source: 'auto-image-overlay', + blobID: blobID, + blobData: finalBlob + }, '*'); + } +} + +const overlayManager = new OverlayManager(); + +// Turnstile token handling (promise-based) inspired by external logic +let turnstileToken = null +let _resolveToken = null +let tokenPromise = new Promise((resolve) => { _resolveToken = resolve }) + +function setTurnstileToken(t) { + if (_resolveToken) { + _resolveToken(t) + _resolveToken = null + } + turnstileToken = t +} + +async function ensureToken() { + if (!turnstileToken) { + updateUI("captchaNeeded", "error") + Utils.showAlert(Utils.t("captchaNeeded"), "error") + try { await tokenPromise } catch (_) {} + } + return turnstileToken +} + +function inject(callback) { + const script = document.createElement('script'); + script.textContent = `(${callback})();`; + document.documentElement?.appendChild(script); + script.remove(); +} + +inject(() => { + const fetchedBlobQueue = new Map(); + + window.addEventListener('message', (event) => { + const { source, blobID, blobData } = event.data; + if (source === 'auto-image-overlay' && blobID && blobData) { + const callback = fetchedBlobQueue.get(blobID); + if (typeof callback === 'function') { + callback(blobData); + } + fetchedBlobQueue.delete(blobID); + } + }); + + const originalFetch = window.fetch; + window.fetch = async function(...args) { + const response = await originalFetch.apply(this, args); + const url = (args[0] instanceof Request) ? args[0].url : args[0]; + + if (typeof url === "string") { + if (url.includes("https://backend.wplace.live/s0/pixel/")) { + try { + const payload = JSON.parse(args[1].body); + if (payload.t) { + console.log("✅ Turnstile Token Captured:", payload.t); + window.postMessage({ source: 'turnstile-capture', token: payload.t }, '*'); + } + } catch (_) { /* ignore */ } + } + + const contentType = response.headers.get('content-type') || ''; + if (contentType.includes('image/png') && url.includes('.png')) { + const cloned = response.clone(); + return new Promise(async (resolve) => { + const blobUUID = crypto.randomUUID(); + const originalBlob = await cloned.blob(); + + fetchedBlobQueue.set(blobUUID, (processedBlob) => { + resolve(new Response(processedBlob, { + headers: cloned.headers, + status: cloned.status, + statusText: cloned.statusText + })); + }); + + window.postMessage({ + source: 'auto-image-tile', + endpoint: url, + blobID: blobUUID, + blobData: originalBlob, + }, '*'); + }); + } + } + + return response; + }; +}); + +window.addEventListener('message', (event) => { + const { source, endpoint, blobID, blobData, token } = event.data; + + if (source === 'auto-image-tile' && endpoint && blobID && blobData) { + overlayManager.processAndRespondToTileRequest(event.data); + } + + if (source === 'turnstile-capture' && token) { + setTurnstileToken(token); + 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 }); + } + } +}); + async function detectLanguage() { try { const response = await fetch("https://backend.wplace.live/me", { @@ -634,11 +655,9 @@ return null; }, - // Optimized DOM creation helpers createElement: (tag, props = {}, children = []) => { const element = document.createElement(tag) - // Set properties efficiently Object.entries(props).forEach(([key, value]) => { if (key === 'style' && typeof value === 'object') { Object.assign(element.style, value) @@ -651,7 +670,6 @@ } }) - // Append children efficiently if (typeof children === 'string') { element.textContent = children } else if (Array.isArray(children)) { @@ -667,7 +685,6 @@ return element }, - // Create button with common styling createButton: (id, text, icon, onClick, style = CONFIG.CSS_CLASSES.BUTTON_PRIMARY) => { const button = Utils.createElement('button', { id: id, @@ -737,7 +754,6 @@ 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)), - // The color metric from colour-converter.js for higher accuracy findClosestPaletteColor: (r, g, b, palette) => { let menorDist = Infinity; let cor = [0, 0, 0]; @@ -846,19 +862,15 @@ calculateEstimatedTime: (remainingPixels, charges, cooldown) => { if (remainingPixels <= 0) return 0 - // Calculate time based on painting speed (pixels per second) const paintingSpeedDelay = state.paintingSpeed > 0 ? (1000 / state.paintingSpeed) : 1000 - const timeFromSpeed = remainingPixels * paintingSpeedDelay // ms + const timeFromSpeed = remainingPixels * paintingSpeedDelay - // Calculate time based on charges and cooldown const cyclesNeeded = Math.ceil(remainingPixels / Math.max(charges, 1)) - const timeFromCharges = cyclesNeeded * cooldown // ms + const timeFromCharges = cyclesNeeded * cooldown - // Return the maximum of both calculations (the limiting factor) return Math.max(timeFromSpeed, timeFromCharges) }, - // Save/Load Progress Functions saveProgress: () => { try { const progressData = { @@ -914,10 +926,8 @@ restoreProgress: (savedData) => { try { - // Restore state Object.assign(state, savedData.state) - // Restore image data if (savedData.imageData) { state.imageData = { ...savedData.imageData, @@ -925,7 +935,6 @@ } } - // Restore painted map if (savedData.paintedMap) { state.paintedMap = savedData.paintedMap.map((row) => Array.from(row)) } @@ -1111,10 +1120,8 @@ const colorCache = new Map() function findClosestColor(targetRgb, availableColors) { - // Create cache key from RGB values const cacheKey = `${targetRgb[0]},${targetRgb[1]},${targetRgb[2]}` - // Check cache first if (colorCache.has(cacheKey)) { return colorCache.get(cacheKey) } @@ -1131,23 +1138,18 @@ let minDistance = Number.POSITIVE_INFINITY let closestColorId = availableColors[0]?.id || 1 - // Use optimized loop for better performance for (let i = 0; i < availableColors.length; i++) { const color = availableColors[i] const distance = Utils.colorDistance(targetRgb, color.rgb) if (distance < minDistance) { minDistance = distance closestColorId = color.id - - // If perfect match, break early if (distance === 0) break } } - // Cache the result for future use colorCache.set(cacheKey, closestColorId) - // Limit cache size to prevent memory leaks if (colorCache.size > 10000) { const firstKey = colorCache.keys().next().value colorCache.delete(firstKey) @@ -1161,7 +1163,6 @@ let updateStats = () => {} let updateDataButtons = () => {} - // --- START: Color Palette Functions --- function updateActiveColorPalette() { state.activeColorPalette = []; const activeSwatches = document.querySelectorAll('.wplace-color-swatch.active'); @@ -1171,7 +1172,6 @@ state.activeColorPalette.push(rgb); }); } - // If the resize dialog is open, update its preview if (document.querySelector('.resize-container')?.style.display === 'block') { _updateResizePreview(); } @@ -1196,28 +1196,23 @@ freeContainer.innerHTML = ''; paidContainer.innerHTML = ''; - // Process colors by their index position (which corresponds to their ID) - CONFIG.COLOR_PALETTE.forEach((rgb, index) => { + const uniqueColors = [...new Set(CONFIG.COLOR_PALETTE.map(JSON.stringify))].map(JSON.parse); + + uniqueColors.forEach(rgb => { const key = rgb.join(','); const name = CONFIG.COLOR_NAMES[key] || `rgb(${key})`; - - // IDs 0-31 are free, IDs 32-63 are paid - const isPaid = index >= 32; + const isPaid = CONFIG.PAID_COLORS.has(key); const colorItem = Utils.createElement('div', { className: 'wplace-color-item' }); const swatch = Utils.createElement('button', { className: `wplace-color-swatch ${isPaid ? 'paid' : ''}`, - title: `${name} (ID: ${index})`, + title: name, 'data-rgb': key, - 'data-id': index.toString(), }); swatch.style.backgroundColor = `rgb(${key})`; - const nameLabel = Utils.createElement('span', { - className: 'wplace-color-item-name' - }, `${name} (${index})`); + const nameLabel = Utils.createElement('span', { className: 'wplace-color-item-name' }, name); - // Default state: free are active, paid are not if (!isPaid) { swatch.classList.add('active'); } @@ -1237,18 +1232,13 @@ } }); - // Add event listeners for master buttons container.querySelector('#selectAllFreeBtn')?.addEventListener('click', () => toggleAllColors(true, false)); container.querySelector('#unselectAllFreeBtn')?.addEventListener('click', () => toggleAllColors(false, false)); container.querySelector('#selectAllPaidBtn')?.addEventListener('click', () => toggleAllColors(true, true)); container.querySelector('#unselectAllPaidBtn')?.addEventListener('click', () => toggleAllColors(false, true)); - // Set the initial state updateActiveColorPalette(); } - // --- END: Color Palette Functions --- - - // --- START: Auto-CAPTCHA Solver --- async function handleCaptcha() { return new Promise(async (resolve, reject) => { if (!CONFIG.AUTO_CAPTCHA_ENABLED) { @@ -1256,23 +1246,19 @@ } try { - // Set a timeout for the entire operation const timeoutPromise = Utils.sleep(20000).then(() => reject(new Error("Auto-CAPTCHA timed out."))); const solvePromise = (async () => { - // 1. Find and click the main "Paint" button on the screen const mainPaintBtn = await Utils.waitForSelector('button.btn.btn-primary.btn-lg, button.btn-primary.sm\\:btn-xl', 200, 10000); if (!mainPaintBtn) throw new Error("Could not find the main paint button."); mainPaintBtn.click(); await Utils.sleep(500); - // 2. Select the transparent color to avoid wasting a real color const transBtn = await Utils.waitForSelector('button#color-0', 200, 5000); if (!transBtn) throw new Error("Could not find the transparent color button."); transBtn.click(); await Utils.sleep(500); - // 3. Find the canvas and simulate a click const canvas = await Utils.waitForSelector('canvas', 200, 5000); if (!canvas) throw new Error("Could not find the canvas element."); @@ -1288,7 +1274,6 @@ canvas.dispatchEvent(new KeyboardEvent('keyup', { key: ' ', code: 'Space', bubbles: true })); await Utils.sleep(500); - // 4. Find and click the confirmation button let confirmBtn = await Utils.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')); @@ -1297,7 +1282,6 @@ if (!confirmBtn) throw new Error("Could not find the confirmation button."); confirmBtn.click(); - // The fetch interceptor will capture the token. We wait for it. await tokenPromise; resolve(); })(); @@ -1310,52 +1294,11 @@ } }); } - // --- END: Auto-CAPTCHA Solver --- - - // --- START: Overlay Management --- - async function initializeOverlay() { - state.gameCanvas = await Utils.waitForSelector('canvas'); - if (!state.gameCanvas || document.getElementById('wplace-overlay-canvas')) { - console.log("Overlay not initialized (game canvas not found or overlay exists)."); - return; // Already initialized or game canvas not found - } - - state.overlayCanvas = document.createElement('canvas'); - state.overlayCanvas.id = 'wplace-overlay-canvas'; - state.overlayCtx = state.overlayCanvas.getContext('2d'); - - // Style the overlay - state.overlayCanvas.style.position = 'absolute'; - state.overlayCanvas.style.pointerEvents = 'none'; - state.overlayCanvas.style.zIndex = '1'; - state.overlayCanvas.style.display = 'none'; // Hidden by default - state.overlayCanvas.style.imageRendering = 'pixelated'; - - // Insert it into the DOM, right after the game canvas - state.gameCanvas.parentElement.insertBefore(state.overlayCanvas, state.gameCanvas.nextSibling); - - const syncOverlay = () => { - if (!state.gameCanvas || !state.overlayCanvas) return; - state.overlayCanvas.width = state.gameCanvas.width; - state.overlayCanvas.height = state.gameCanvas.height; - state.overlayCanvas.style.top = state.gameCanvas.offsetTop + 'px'; - state.overlayCanvas.style.left = state.gameCanvas.offsetLeft + 'px'; - state.overlayCanvas.style.transform = state.gameCanvas.style.transform; - }; - - const observer = new MutationObserver(syncOverlay); - observer.observe(state.gameCanvas, { attributes: true, attributeFilter: ['style', 'width', 'height'] }); - - syncOverlay(); - console.log("✅ Overlay Initialized."); - } - // --- END: Overlay Management --- async function createUI() { await detectLanguage() - // Clean up existing UI elements to prevent duplicates const existingContainer = document.getElementById("wplace-image-bot-container") const existingStats = document.getElementById("wplace-stats-container") const existingSettings = document.getElementById("wplace-settings-container") @@ -1684,7 +1627,7 @@ ? `linear-gradient(135deg, ${theme.accent} 0%, #4a4a4a 100%)` : theme.accent }; - ${CONFIG.currentTheme === "Classic Autobot" ? `border: 1px solid rgba(255,255,255,0.1);` : ""} + ${CONFIG.currentTheme === "Classic Autobot" ? "border: 1px solid rgba(255,255,255,0.1);" : ""} } ${ @@ -1798,6 +1741,11 @@ .wplace-btn:disabled::before { display: none; } + + .wplace-btn-overlay.active { + background: linear-gradient(135deg, #29b6f6 0%, #8e2de2 100%); + box-shadow: 0 0 15px #8e2de2; + } .wplace-stats { background: ${CONFIG.currentTheme === "Classic Autobot" ? "rgba(255,255,255,0.03)" : theme.secondary}; @@ -1831,7 +1779,6 @@ color: ${theme.highlight}; } - /* Styles for the new color display in stats */ .wplace-colors-section { margin-top: 10px; padding-top: 8px; @@ -1981,7 +1928,7 @@ CONFIG.currentTheme === "Classic Autobot" ? "0 0 20px rgba(0,0,0,0.5)" : "0 0 30px rgba(0, 255, 65, 0.5)" }; width: 90%; - max-width: 700px; /* Increased width */ + max-width: 700px; max-height: 90%; overflow: auto; font-family: ${theme.fontFamily}; @@ -1999,7 +1946,7 @@ } .resize-preview { - max-width: none; /* Allow image to exceed wrapper for zoom */ + max-width: none; transition: transform 0.1s ease; image-rendering: pixelated; image-rendering: -moz-crisp-edges; @@ -2056,7 +2003,7 @@ } .resize-zoom-controls { - grid-column: 1 / -1; /* Span full width */ + grid-column: 1 / -1; display: flex; align-items: center; gap: 10px; @@ -2080,10 +2027,9 @@ z-index: 9999; display: none; } - /* --- START: Color Palette Styles --- */ .wplace-color-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); /* Wider columns for name */ + grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); gap: 10px; padding-top: 8px; } @@ -2140,9 +2086,7 @@ background: rgba(255,255,255,0.1); margin: 8px 0; } - /* --- END: Color Palette Styles --- */ - /* Cooldown slider styles */ .wplace-cooldown-control { margin-top: 8px; } @@ -2178,7 +2122,6 @@ ${ CONFIG.currentTheme === "Neon Retro" ? ` - /* Retro checkbox styling */ input[type="checkbox"] { -webkit-appearance: none; -moz-appearance: none; @@ -2206,10 +2149,12 @@ font-weight: bold; } - /* Icon styling for retro feel */ .fas, .fa { filter: drop-shadow(0 0 3px currentColor); } + + .wplace-speed-control { + margin-top: 12px; padding: 12px; background: ${theme.secondary}; border: ${theme.borderWidth} ${theme.borderStyle} ${theme.accent}; @@ -2290,7 +2235,6 @@ opacity: 0.8; } - /* Settings Window Styles */ #wplace-settings-container { position: fixed; top: 50%; @@ -2602,6 +2546,12 @@ ${Utils.t("stopPainting")} +
+ +
@@ -2744,6 +2694,32 @@ + +
+ +
+ +
+
+ Overlay Opacity +
${Math.round(state.overlayOpacity * 100)}%
+
+ +
+ + +
+
+