mirror of
https://github.com/tiennm99/WPlace-AutoBOT.git
synced 2026-10-13 12:14:09 +00:00
Overlay Test
This commit is contained in:
1 parent
984d27959d
commit
f5679d4063
1 file changed
+360
-274
+360
-274
@@ -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');
|
||||
|
||||
Reference in new issue
Block a user