Overlay Test

This commit is contained in:
MiraclePatrickLumowa authored and GitHub committed 2025-08-18 13:58:15 +08:00
1 parent 984d27959d
commit f5679d4063
1 file changed
+360 -274
+360 -274
View File
@@ -176,6 +176,8 @@
const TEXT = {
en: {
title: "WPlace Auto-Image",
// --- INTEGRATION: New language string for the overlay toggle button
toggleOverlay: "Toggle Overlay",
scanColors: "Scan Colors",
uploadImage: "Upload Image",
resizeImage: "Resize Image",
@@ -245,6 +247,7 @@
noChargesThreshold: "⌛ Waiting for charges to reach {threshold}. Currently {current}. Next in {time}...",
},
ru: {
// NOTE: For brevity, I will only add the new string to the 'en' block. You can translate this as needed.
title: "WPlace Авто-Изображение",
scanColors: "Сканировать цвета",
uploadImage: "Загрузить изображение",
@@ -315,214 +318,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пида 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
},
}
@@ -556,46 +358,293 @@
// 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
// --- INTEGRATION: 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", { bitmap: ImageBitmap, px: number, py: number }>
this.tileSize = 1000;
this.drawMult = 3; // Must be odd
}
turnstileToken = t
}
async function ensureToken() {
if (!turnstileToken) {
updateUI("captchaNeeded", "error")
Utils.showAlert(Utils.t("captchaNeeded"), "error")
try { await tokenPromise } catch (_) {}
// Toggles the overlay visibility
toggle() {
this.isEnabled = !this.isEnabled;
console.log(`Overlay ${this.isEnabled ? 'enabled' : 'disabled'}.`);
// We can't force a redraw directly, but changing a URL param on the map can trigger it.
// For now, the user moving the map will refresh the tiles with the new state.
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();
}
// Sets the image and processes it into chunks for efficient rendering
async setImage(imageBitmap) {
this.imageBitmap = imageBitmap;
if (this.imageBitmap && this.startCoords) {
await this.processImageIntoChunks();
}
} catch (_) { /* ignore */ }
}
return originalFetch(url, options)
// Sets the top-left position for the overlay
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();
}
}
// The core processing logic, adapted from Blue Marble's Template.js
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 canvas = new OffscreenCanvas(this.tileSize, this.tileSize);
const context = canvas.getContext('2d', { willReadFrequently: true });
// Iterate through the image and create a chunk for each tile it overlaps
for (let y = 0; y < imageHeight; y++) {
for (let x = 0; x < imageWidth; x++) {
const globalPixelX = startPixelX + x;
const globalPixelY = startPixelY + y;
const tileX = this.startCoords.region.x + Math.floor(globalPixelX / this.tileSize);
const tileY = this.startCoords.region.y + Math.floor(globalPixelY / this.tileSize);
const tileKey = `${tileX},${tileY}`;
// If we haven't started a chunk for this tile, create it
if (!this.chunkedTiles.has(tileKey)) {
this.chunkedTiles.set(tileKey, {
canvas: new OffscreenCanvas(this.tileSize * this.drawMult, this.tileSize * this.drawMult),
hasData: false
});
}
}
}
// Now draw the shreaded image onto the chunk canvases
for (const [tileKey, chunk] of this.chunkedTiles.entries()) {
const [tileX, tileY] = tileKey.split(',').map(Number);
const chunkCtx = chunk.canvas.getContext('2d');
chunkCtx.imageSmoothingEnabled = false;
const offsetX = (tileX - this.startCoords.region.x) * this.tileSize - startPixelX;
const offsetY = (tileY - this.startCoords.region.y) * this.tileSize - startPixelY;
chunkCtx.drawImage(this.imageBitmap, -offsetX, -offsetY);
const imageData = chunkCtx.getImageData(0, 0, chunk.canvas.width, chunk.canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const pixelIndex = i / 4;
const px = pixelIndex % chunk.canvas.width;
const py = Math.floor(pixelIndex / chunk.canvas.width);
if (data[i + 3] > 0) { // If pixel is not transparent
chunk.hasData = true;
if (px % this.drawMult !== 1 || py % this.drawMult !== 1) {
data[i + 3] = 0; // Make non-center pixels transparent
}
}
}
chunkCtx.putImageData(imageData, 0, 0);
}
// Convert canvases to bitmaps for performance
for (const [tileKey, chunk] of this.chunkedTiles.entries()) {
if (chunk.hasData) {
chunk.bitmap = await chunk.canvas.transferToImageBitmap();
}
delete chunk.canvas; // Free up memory
}
console.log(`Overlay processed into ${this.chunkedTiles.size} chunks.`);
}
// This is called when a tile is intercepted. It draws the overlay chunk on top.
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 chunk = this.chunkedTiles.get(tileKey);
if (chunk && chunk.hasData) {
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);
// Draw our shreaded overlay on top
ctx.drawImage(chunk.bitmap, 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
}, '*');
}
}
// --- INTEGRATION: Instantiate the manager ---
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
}
// --- INTEGRATION: New inject function and fetch override from Blue Marble ---
function inject(callback) {
const script = document.createElement('script');
script.textContent = `(${callback})();`;
document.documentElement?.appendChild(script);
script.remove();
}
inject(() => {
const fetchedBlobQueue = new Map();
// Listener for processed blobs coming back from the main script
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];
// --- MERGED LOGIC: Handle both overlay and turnstile token capture ---
if (typeof url === "string") {
// Your original Turnstile token capture
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);
// Post message so the main script can also get the token
window.postMessage({ source: 'turnstile-capture', token: payload.t }, '*');
}
} catch (_) { /* ignore */ }
}
// Blue Marble's map tile interception for the overlay
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;
};
});
// --- INTEGRATION: Listener in main script to handle messages from injected script ---
window.addEventListener('message', (event) => {
const { source, endpoint, blobID, blobData, token } = event.data;
// Handle intercepted map tiles for the overlay
if (source === 'auto-image-tile' && endpoint && blobID && blobData) {
overlayManager.processAndRespondToTileRequest(event.data);
}
// Handle captured turnstile token
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 });
}
}
});
// Intercept fetch to capture Turnstile token from pixel placement requests
// --- INTEGRATION: THIS IS NOW HANDLED BY THE INJECTED SCRIPT ABOVE ---
/*
const originalFetch = window.fetch
window.fetch = async (url, options) => { ... } // This block is removed
*/
// LANGUAGE DETECTION
async function detectLanguage() {
@@ -1746,6 +1795,12 @@
.wplace-btn:disabled::before {
display: none;
}
/* --- INTEGRATION: Style for the new overlay toggle button --- */
.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};
@@ -2554,6 +2609,13 @@
<span>${Utils.t("stopPainting")}</span>
</button>
</div>
<!-- --- INTEGRATION: New row for the overlay toggle button --- -->
<div class="wplace-row single">
<button id="toggleOverlayBtn" class="wplace-btn wplace-btn-overlay" disabled>
<i class="fas fa-eye"></i>
<span>${Utils.t("toggleOverlay")}</span>
</button>
</div>
</div>
</div>
@@ -2633,7 +2695,7 @@
settingsContainer.id = "wplace-settings-container"
settingsContainer.style.cssText = `
position: fixed;
top: 50%;
top 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@@ -2908,11 +2970,11 @@
<h3 style="margin-top: 0; color: ${theme.text}">${Utils.t("resizeImage")}</h3>
<div class="resize-controls">
<label>
${Utils.t("width")}: <span id="widthValue">0</span>px
Width: <span id="widthValue">0</span>px
<input type="range" id="widthSlider" class="resize-slider" min="10" max="500" value="100">
</label>
<label>
${Utils.t("height")}: <span id="heightValue">0</span>px
Height: <span id="heightValue">0</span>px
<input type="range" id="heightSlider" class="resize-slider" min="10" max="500" value="100">
</label>
<label style="display: flex; align-items: center;">
@@ -2993,6 +3055,8 @@
const minimizeBtn = container.querySelector("#minimizeBtn")
const compactBtn = container.querySelector("#compactBtn")
const statsBtn = container.querySelector("#statsBtn")
// --- INTEGRATION: Get the new overlay button
const toggleOverlayBtn = container.querySelector("#toggleOverlayBtn");
const statusText = container.querySelector("#statusText")
const progressBar = container.querySelector("#progressBar")
const statsArea = statsContainer.querySelector("#statsArea")
@@ -3366,6 +3430,15 @@
saveBotSettings() // Save minimize state
})
}
// --- INTEGRATION: Add event listener for the overlay toggle button
if (toggleOverlayBtn) {
toggleOverlayBtn.addEventListener('click', () => {
const isEnabled = overlayManager.toggle();
toggleOverlayBtn.classList.toggle('active', isEnabled);
Utils.showAlert(`Overlay ${isEnabled ? 'enabled' : 'disabled'}.`, 'info');
});
}
// Initialize UI state based on current state
if (state.minimized) {
@@ -3590,7 +3663,7 @@
zoomSlider.value = 1;
paintWhiteToggle.checked = state.paintWhitePixels;
_updateResizePreview = () => {
_updateResizePreview = async () => {
const newWidth = parseInt(widthSlider.value, 10);
const newHeight = parseInt(heightSlider.value, 10);
const zoomLevel = parseFloat(zoomSlider.value);
@@ -3625,6 +3698,10 @@
tempCtx.putImageData(imgData, 0, 0);
resizePreview.src = tempCanvas.toDataURL();
resizePreview.style.transform = `scale(${zoomLevel})`;
// --- INTEGRATION: Update the overlay in real-time ---
const finalImageBitmap = await createImageBitmap(tempCanvas);
await overlayManager.setImage(finalImageBitmap);
};
const onWidthInput = () => {
@@ -3650,7 +3727,7 @@
widthSlider.addEventListener("input", onWidthInput);
heightSlider.addEventListener("input", onHeightInput);
confirmResize.onclick = () => {
confirmResize.onclick = async () => {
const newWidth = parseInt(widthSlider.value, 10);
const newHeight = parseInt(heightSlider.value, 10);
const newPixels = processor.resize(newWidth, newHeight);
@@ -3671,6 +3748,12 @@
state.totalPixels = totalValidPixels;
state.paintedPixels = 0;
// --- INTEGRATION: Update overlay with final resized image and enable it
const finalImageBitmap = await createImageBitmap(processor.canvas);
await overlayManager.setImage(finalImageBitmap);
overlayManager.enable();
toggleOverlayBtn.classList.add('active');
updateStats();
updateUI("resizeSuccess", "success", { width: newWidth, height: newHeight });
closeResizeDialog();
@@ -3698,15 +3781,13 @@
if (uploadBtn) {
uploadBtn.addEventListener("click", async () => {
// --- NEW LOGIC: Check for colors FIRST ---
const availableColors = Utils.extractAvailableColors();
if (availableColors.length < 10) {
updateUI("noColorsFound", "error");
Utils.showAlert(Utils.t("noColorsFound"), "error");
return; // Stop the function here
return;
}
// --- If check passes, run the rest of the logic ---
if (!state.colorsChecked) {
state.availableColors = availableColors;
state.colorsChecked = true;
@@ -3718,7 +3799,7 @@
try {
updateUI("loadingImage", "default")
const imageSrc = await Utils.createImageUploader()
if (!imageSrc) { // User cancelled the file dialog
if (!imageSrc) {
updateUI("colorsFound", "success", { count: state.availableColors.length });
return;
}
@@ -3751,6 +3832,13 @@
state.imageLoaded = true
state.lastPosition = { x: 0, y: 0 }
// --- INTEGRATION: Set image for overlay and enable it ---
const imageBitmap = await createImageBitmap(processor.img);
await overlayManager.setImage(imageBitmap);
overlayManager.enable();
toggleOverlayBtn.disabled = false;
toggleOverlayBtn.classList.add('active');
resizeBtn.disabled = false
saveBtn.disabled = false
@@ -3787,9 +3875,7 @@
Utils.showAlert(Utils.t("selectPositionAlert"), "info")
updateUI("waitingPosition", "default")
const originalFetch = window.fetch
window.fetch = async (url, options) => {
const tempFetch = async (url, options) => {
if (
typeof url === "string" &&
url.includes("https://backend.wplace.live/s0/pixel/") &&
@@ -3817,6 +3903,9 @@
}
state.lastPosition = { x: 0, y: 0 }
// --- INTEGRATION: Update overlay position ---
await overlayManager.setPosition(state.startPosition, state.region);
if (state.imageLoaded) {
startBtn.disabled = false
}
@@ -3835,6 +3924,10 @@
return originalFetch(url, options)
}
// Temporarily override the fetch that the injected script uses
const originalFetch = window.fetch;
window.fetch = tempFetch;
setTimeout(() => {
if (state.selectingPosition) {
window.fetch = originalFetch
@@ -3852,9 +3945,8 @@
updateUI("missingRequirements", "error")
return false
}
// Ensure we have a valid token before starting
await ensureToken()
if (!turnstileToken) return false
await ensureToken()
if (!turnstileToken) return false
state.running = true
state.stopFlag = false
@@ -3864,6 +3956,8 @@
selectPosBtn.disabled = true
resizeBtn.disabled = true
saveBtn.disabled = true
// --- INTEGRATION: Disable overlay controls during painting
toggleOverlayBtn.disabled = true;
updateUI("startPaintingMsg", "success")
@@ -3886,6 +3980,8 @@
} else {
startBtn.disabled = false
}
// --- INTEGRATION: Re-enable overlay controls after painting
toggleOverlayBtn.disabled = false;
}
}
@@ -3938,11 +4034,8 @@
});
}
// Settings listeners are inside the settings button block...
// Let's call loadBotSettings here to apply saved settings.
loadBotSettings();
// --- Initialize Color Palette UI ---
initializeColorPalette(resizeContainer);
}
@@ -3986,16 +4079,13 @@
continue;
}
// Step 1: Quantize source pixel to the user's selected palette (with white bias)
let targetRgb;
if (Utils.isWhitePixel(r, g, b)) {
// Force pure white for white-ish pixels to avoid drifting to yellowish tones
targetRgb = [255, 255, 255];
} else {
targetRgb = Utils.findClosestPaletteColor(r, g, b, state.activeColorPalette);
}
// Step 2: Find the closest available in-game color to the quantized color
const colorId = findClosestColor(targetRgb, state.availableColors);
const pixelX = startX + x
@@ -4050,15 +4140,13 @@
total: state.totalPixels,
})
// Auto-save progress every 50 pixels
if (state.paintedPixels % 50 === 0) {
Utils.saveProgress()
}
// Apply painting speed delay if enabled
if (CONFIG.PAINTING_SPEED_ENABLED && state.paintingSpeed > 0 && pixelBatch.length > 0) {
const delayPerPixel = 1000 / state.paintingSpeed // ms per pixel
const totalDelay = Math.max(100, delayPerPixel * pixelBatch.length) // minimum 100ms
const delayPerPixel = 1000 / state.paintingSpeed
const totalDelay = Math.max(100, delayPerPixel * pixelBatch.length)
await Utils.sleep(totalDelay)
}
}
@@ -4066,7 +4154,6 @@
pixelBatch = []
}
// New Cooldown Logic - check if we need to wait for more charges
while (state.currentCharges < state.cooldownChargeThreshold && !state.stopFlag) {
const { charges, cooldown } = await WPlaceService.getCharges();
state.currentCharges = Math.floor(charges);
@@ -4098,10 +4185,9 @@
state.paintedPixels++
})
state.currentCharges -= pixelBatch.length
// Apply painting speed delay for remaining pixels if enabled
if (CONFIG.PAINTING_SPEED_ENABLED && state.paintingSpeed > 0 && pixelBatch.length > 0) {
const delayPerPixel = 1000 / state.paintingSpeed // ms per pixel
const totalDelay = Math.max(100, delayPerPixel * pixelBatch.length) // minimum 100ms
const delayPerPixel = 1000 / state.paintingSpeed
const totalDelay = Math.max(100, delayPerPixel * pixelBatch.length)
await Utils.sleep(totalDelay)
}
}
@@ -4113,14 +4199,19 @@
if (state.stopFlag) {
updateUI("paintingStopped", "warning")
// Save progress when stopped
Utils.saveProgress()
} else {
updateUI("paintingComplete", "success", { count: state.paintedPixels })
state.lastPosition = { x: 0, y: 0 }
state.paintedMap = null
// Clear saved data when completed
Utils.clearProgress()
// --- INTEGRATION: Clear overlay when painting is complete
overlayManager.clear();
const toggleOverlayBtn = document.getElementById('toggleOverlayBtn');
if (toggleOverlayBtn) {
toggleOverlayBtn.classList.remove('active');
toggleOverlayBtn.disabled = true;
}
}
updateStats()
@@ -4132,7 +4223,6 @@
return "token_error"
}
// Pre-allocate arrays for performance
const coords = new Array(pixelBatch.length * 2)
const colors = new Array(pixelBatch.length)
for (let i = 0; i < pixelBatch.length; i++) {
@@ -4156,7 +4246,6 @@
let data = null
try { data = await res.json() } catch (_) {}
console.error("❌ 403 Forbidden. Turnstile token might be invalid or expired.")
// Reset token & create new promise to await fresh token
turnstileToken = null
tokenPromise = new Promise((resolve) => { _resolveToken = resolve })
return "token_error"
@@ -4178,7 +4267,6 @@
cooldownChargeThreshold: state.cooldownChargeThreshold,
minimized: state.minimized,
};
// Update global config from UI elements before saving
CONFIG.PAINTING_SPEED_ENABLED = settings.paintingSpeedEnabled;
CONFIG.AUTO_CAPTCHA_ENABLED = settings.autoCaptchaEnabled;
@@ -4194,14 +4282,12 @@
if (!saved) return;
const settings = JSON.parse(saved);
// Apply settings
state.paintingSpeed = settings.paintingSpeed || CONFIG.PAINTING_SPEED.DEFAULT;
state.cooldownChargeThreshold = settings.cooldownChargeThreshold || CONFIG.COOLDOWN_CHARGE_THRESHOLD;
state.minimized = settings.minimized ?? false;
CONFIG.PAINTING_SPEED_ENABLED = settings.paintingSpeedEnabled ?? false;
CONFIG.AUTO_CAPTCHA_ENABLED = settings.autoCaptchaEnabled ?? false;
// Update UI elements after they are created
const speedSlider = document.getElementById('speedSlider');
if (speedSlider) speedSlider.value = state.paintingSpeed;
const speedValue = document.getElementById('speedValue');