From c6e292a0dc3c40151b717412ebc6227e747ec111 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C5=A9ng?= <99890979+maiminhdung@users.noreply.github.com>
Date: Thu, 14 Aug 2025 17:57:02 +0700
Subject: [PATCH] Add Import/Export data file + New UI
---
Auto-Image.js | 1201 +++++++++++++++++++++++++++++++++++----------
FR.md | 1 +
PT.md | 1 +
test-save-load.md | 38 --
4 files changed, 949 insertions(+), 292 deletions(-)
delete mode 100644 test-save-load.md
diff --git a/Auto-Image.js b/Auto-Image.js
index 925c2ae..20cee08 100644
--- a/Auto-Image.js
+++ b/Auto-Image.js
@@ -57,13 +57,20 @@
captchaNeeded:
"❗ Token CAPTCHA necessário. Pinte um pixel manualmente para continuar.",
saveData: "Salvar Progresso",
- loadData: "Carregar 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",
},
en: {
title: "WPlace Auto-Image",
@@ -103,12 +110,19 @@
"❗ CAPTCHA token needed. Paint one pixel manually to continue.",
saveData: "Save Progress",
loadData: "Load Progress",
+ saveToFile: "Save to File",
+ loadFromFile: "Load from File",
+ dataManager: "Data Manager",
autoSaved: "✅ Progress saved automatically",
dataLoaded: "✅ Progress loaded successfully",
+ fileSaved: "✅ Progress saved to file successfully",
+ fileLoaded: "✅ Progress loaded from file successfully",
noSavedData: "❌ No saved progress found",
savedDataFound: "✅ Saved progress found! Load to continue?",
savedDate: "Saved on: {date}",
clickLoadToContinue: "Click 'Load Progress' to continue.",
+ fileError: "❌ Error processing file",
+ invalidFileFormat: "❌ Invalid file format",
},
};
@@ -152,6 +166,7 @@
console.log("✅ CAPTCHA Token Captured:", payload.t);
// Store the token for our bot to use.
capturedCaptchaToken = payload.t;
+
// Notify the user that the token is captured and they can start the bot.
if (
document
@@ -209,6 +224,44 @@
input.click();
}),
+ createFileDownloader: (data, filename) => {
+ const blob = new Blob([data], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ },
+
+ createFileUploader: () =>
+ new Promise((resolve, reject) => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = ".json";
+ input.onchange = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = () => {
+ try {
+ const data = JSON.parse(reader.result);
+ resolve(data);
+ } catch (error) {
+ reject(new Error('Invalid JSON file'));
+ }
+ };
+ reader.onerror = () => reject(new Error('File reading error'));
+ reader.readAsText(file);
+ } else {
+ reject(new Error('No file selected'));
+ }
+ };
+ input.click();
+ }),
+
extractAvailableColors: () => {
const colorElements = document.querySelectorAll('[id^="color-"]');
return Array.from(colorElements)
@@ -270,6 +323,38 @@
}
},
+ saveProgressToFile: () => {
+ try {
+ const progressData = {
+ timestamp: Date.now(),
+ version: "1.0",
+ appName: "WPlace Auto-Image",
+ state: {
+ imageLoaded: state.imageLoaded,
+ totalPixels: state.totalPixels,
+ paintedPixels: state.paintedPixels,
+ lastPosition: state.lastPosition,
+ startPosition: state.startPosition,
+ region: state.region,
+ paintedMap: state.paintedMap,
+ imageData: state.imageData,
+ availableColors: state.availableColors,
+ language: state.language
+ }
+ };
+
+ const filename = `wplace-progress-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`;
+ const dataString = JSON.stringify(progressData, null, 2);
+ Utils.createFileDownloader(dataString, filename);
+
+ console.log('✅ Progress saved to file');
+ return true;
+ } catch (error) {
+ console.error('❌ Error saving progress to file:', error);
+ return false;
+ }
+ },
+
loadProgress: () => {
try {
const savedData = localStorage.getItem('wplace-auto-image-progress');
@@ -361,6 +446,7 @@
// Update stats to show current progress
updateStats();
+ updateDataButtons();
return true;
} catch (error) {
console.error('❌ Error restoring progress:', error);
@@ -383,6 +469,132 @@
return localStorage.getItem('wplace-auto-image-progress') !== null;
},
+ saveProgressToFile: () => {
+ try {
+ updateUI("default", "default", {});
+
+ const progressData = {
+ timestamp: Date.now(),
+ version: "1.0",
+ appName: "WPlace Auto-Image",
+ state: {
+ imageLoaded: state.imageLoaded,
+ totalPixels: state.totalPixels,
+ paintedPixels: state.paintedPixels,
+ lastPosition: state.lastPosition,
+ startPosition: state.startPosition,
+ region: state.region,
+ paintedMap: state.paintedMap,
+ imageData: state.imageData,
+ availableColors: state.availableColors,
+ language: state.language
+ }
+ };
+
+ const filename = `wplace-progress-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`;
+ const dataString = JSON.stringify(progressData, null, 2);
+ Utils.createFileDownloader(dataString, filename);
+
+ console.log('✅ Progress saved to file');
+ return true;
+ } catch (error) {
+ console.error('❌ Error saving progress to file:', error);
+ return false;
+ }
+ },
+
+ loadProgressFromFile: async () => {
+ try {
+ updateUI("default", "default", {});
+
+ const fileData = await Utils.createFileUploader();
+
+ // Validate file format
+ if (!fileData.state || !fileData.timestamp) {
+ throw new Error('Invalid file format - missing required fields');
+ }
+
+ if (!fileData.appName || fileData.appName !== "WPlace Auto-Image") {
+ throw new Error('Invalid file format - not a WPlace Auto-Image file');
+ }
+
+ const savedState = fileData.state;
+
+ // Restore state
+ state.imageLoaded = savedState.imageLoaded;
+ state.totalPixels = savedState.totalPixels;
+ state.paintedPixels = savedState.paintedPixels;
+ state.lastPosition = savedState.lastPosition || { x: 0, y: 0 };
+ state.startPosition = savedState.startPosition;
+ state.region = savedState.region;
+ state.paintedMap = savedState.paintedMap;
+ state.imageData = savedState.imageData;
+ state.availableColors = savedState.availableColors;
+ state.language = savedState.language;
+ state.colorsChecked = savedState.availableColors && savedState.availableColors.length > 0;
+
+ // Update UI to reflect restored state
+ if (state.imageLoaded) {
+ const initBotBtn = document.querySelector("#initBotBtn");
+ const uploadBtn = document.querySelector("#uploadBtn");
+ const resizeBtn = document.querySelector("#resizeBtn");
+ const selectPosBtn = document.querySelector("#selectPosBtn");
+ const startBtn = document.querySelector("#startBtn");
+ const saveBtn = document.querySelector("#saveBtn");
+ const progressBar = document.querySelector("#progressBar");
+
+ // Show/hide appropriate buttons based on state
+ if (state.colorsChecked) {
+ initBotBtn.style.display = "none";
+ uploadBtn.disabled = false;
+ selectPosBtn.disabled = false;
+ } else {
+ initBotBtn.style.display = "block";
+ uploadBtn.disabled = true;
+ selectPosBtn.disabled = true;
+ }
+
+ resizeBtn.disabled = false;
+ saveBtn.disabled = false;
+
+ if (state.startPosition && state.region) {
+ selectPosBtn.disabled = false;
+ startBtn.disabled = false;
+ }
+
+ // Update progress bar
+ const progress = state.totalPixels > 0 ? Math.round((state.paintedPixels / state.totalPixels) * 100) : 0;
+ progressBar.style.width = `${progress}%`;
+
+ // Update status message based on progress
+ if (state.paintedPixels > 0) {
+ if (state.lastPosition.x > 0 || state.lastPosition.y > 0) {
+ updateUI("paintingPaused", "warning", {
+ x: state.lastPosition.x,
+ y: state.lastPosition.y
+ });
+ } else {
+ updateUI("paintingProgress", "default", {
+ painted: state.paintedPixels,
+ total: state.totalPixels,
+ });
+ }
+ } else {
+ updateUI("imageLoaded", "success", { count: state.totalPixels });
+ }
+ }
+
+ // Update stats to show current progress
+ updateStats();
+ updateDataButtons();
+ console.log('✅ Progress loaded from file');
+ return true;
+ } catch (error) {
+ console.error('❌ Error loading progress from file:', error);
+ return false;
+ }
+ },
+
showAlert: (message, type = "info") => {
const alert = document.createElement("div");
alert.style.position = "fixed";
@@ -583,143 +795,332 @@
100% { box-shadow: 0 0 0 0 rgba(0, 255, 0, 0); }
}
@keyframes slideIn {
- from { transform: translateY(20px); opacity: 0; }
+ from { transform: translateY(-10px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
#wplace-image-bot-container {
position: fixed;
top: 20px;
right: 20px;
- width: 300px;
- background: ${CONFIG.THEME.primary};
+ width: 280px;
+ max-height: calc(100vh - 40px);
+ background: linear-gradient(135deg, ${CONFIG.THEME.primary} 0%, #1a1a1a 100%);
border: 1px solid ${CONFIG.THEME.accent};
- border-radius: 8px;
+ border-radius: 12px;
padding: 0;
- box-shadow: 0 5px 15px rgba(0,0,0,0.5);
+ box-shadow: 0 8px 32px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.1);
z-index: 9998;
font-family: 'Segoe UI', Roboto, sans-serif;
color: ${CONFIG.THEME.text};
animation: slideIn 0.4s ease-out;
overflow: hidden;
+ backdrop-filter: blur(10px);
+ transition: all 0.3s ease;
+ user-select: none;
+ }
+ #wplace-image-bot-container.wplace-dragging {
+ transition: none;
+ box-shadow: 0 12px 40px rgba(0,0,0,0.8), 0 0 0 2px rgba(255,255,255,0.2);
+ transform: scale(1.02);
+ z-index: 9999;
+ }
+ #wplace-image-bot-container.wplace-minimized {
+ width: 200px;
+ height: auto;
+ }
+ #wplace-image-bot-container.wplace-compact {
+ width: 240px;
+ }
+ #wplace-image-bot-container.wplace-compact .wplace-content {
+ padding: 12px;
+ }
+ #wplace-image-bot-container.wplace-compact .wplace-tab {
+ padding: 8px 10px;
+ font-size: 10px;
+ }
+ #wplace-image-bot-container.wplace-compact .wplace-btn {
+ padding: 6px 12px;
+ font-size: 11px;
+ gap: 4px;
+ }
+ #wplace-image-bot-container.wplace-compact .wplace-stats {
+ padding: 8px;
+ }
+ #wplace-image-bot-container.wplace-compact .wplace-stat-item {
+ font-size: 11px;
+ padding: 4px 0;
+ }
+
+ /* Stats Container */
+ #wplace-stats-container {
+ position: fixed;
+ top: 20px;
+ left: 20px;
+ width: 280px;
+ max-height: calc(100vh - 40px);
+ background: linear-gradient(135deg, ${CONFIG.THEME.primary} 0%, #1a1a1a 100%);
+ border: 1px solid ${CONFIG.THEME.accent};
+ border-radius: 12px;
+ padding: 0;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.1);
+ z-index: 9997;
+ font-family: 'Segoe UI', Roboto, sans-serif;
+ color: ${CONFIG.THEME.text};
+ animation: slideIn 0.4s ease-out;
+ overflow: hidden;
+ backdrop-filter: blur(10px);
+ transition: all 0.3s ease;
+ user-select: none;
+ }
+ #wplace-stats-container.wplace-dragging {
+ transition: none;
+ box-shadow: 0 12px 40px rgba(0,0,0,0.8), 0 0 0 2px rgba(255,255,255,0.2);
+ transform: scale(1.02);
+ z-index: 9999;
}
.wplace-header {
- padding: 12px 15px;
- background: ${CONFIG.THEME.secondary};
+ padding: 8px 12px;
+ background: linear-gradient(135deg, ${CONFIG.THEME.secondary} 0%, #2a2a2a 100%);
color: ${CONFIG.THEME.highlight};
- font-size: 16px;
- font-weight: 600;
+ font-size: 13px;
+ font-weight: 700;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
user-select: none;
+ border-bottom: 1px solid rgba(255,255,255,0.1);
+ text-shadow: 0 1px 2px rgba(0,0,0,0.5);
+ transition: background 0.2s ease;
+ }
+ .wplace-header:hover {
+ background: linear-gradient(135deg, ${CONFIG.THEME.secondary} 10%, #3a3a3a 100%);
+ }
+ .wplace-header:active {
+ background: linear-gradient(135deg, ${CONFIG.THEME.secondary} 20%, #4a4a4a 100%);
+ cursor: grabbing;
}
.wplace-header-title {
display: flex;
align-items: center;
- gap: 8px;
+ gap: 6px;
}
.wplace-header-controls {
display: flex;
- gap: 10px;
+ gap: 6px;
}
.wplace-header-btn {
- background: none;
+ background: rgba(255,255,255,0.1);
border: none;
color: ${CONFIG.THEME.text};
cursor: pointer;
- opacity: 0.7;
- transition: opacity 0.2s;
+ border-radius: 4px;
+ width: 18px;
+ height: 18px;
+ font-size: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s;
}
.wplace-header-btn:hover {
- opacity: 1;
+ background: ${CONFIG.THEME.accent};
+ transform: scale(1.1);
}
.wplace-content {
- padding: 15px;
+ padding: 12px;
display: block;
}
+ .wplace-content.wplace-hidden {
+ display: none;
+ }
+ .wplace-status-section {
+ margin-bottom: 12px;
+ padding: 8px;
+ background: rgba(255,255,255,0.03);
+ border-radius: 6px;
+ border: 1px solid rgba(255,255,255,0.1);
+ }
+ .wplace-section {
+ margin-bottom: 12px;
+ padding: 12px;
+ background: rgba(255,255,255,0.03);
+ border-radius: 6px;
+ border: 1px solid rgba(255,255,255,0.1);
+ }
+ .wplace-section-title {
+ font-size: 11px;
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: ${CONFIG.THEME.highlight};
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ }
+ .wplace-section {
+ margin-bottom: 12px;
+ padding: 12px;
+ background: rgba(255,255,255,0.03);
+ border-radius: 6px;
+ border: 1px solid rgba(255,255,255,0.1);
+ }
+ .wplace-section-title {
+ font-size: 11px;
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: ${CONFIG.THEME.highlight};
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ }
.wplace-controls {
display: flex;
flex-direction: column;
- gap: 10px;
- margin-bottom: 15px;
+ gap: 8px;
+ }
+ .wplace-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px;
+ }
+ .wplace-row.single {
+ grid-template-columns: 1fr;
}
.wplace-btn {
- padding: 10px;
+ padding: 8px 12px;
border: none;
border-radius: 6px;
- font-weight: 600;
+ font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
- gap: 8px;
- transition: all 0.2s;
+ gap: 6px;
+ font-size: 11px;
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ background: linear-gradient(135deg, ${CONFIG.THEME.accent} 0%, #4a4a4a 100%);
+ border: 1px solid rgba(255,255,255,0.1);
}
- .wplace-btn:hover {
- transform: translateY(-2px);
+ .wplace-btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
+ transition: left 0.5s ease;
+ }
+ .wplace-btn:hover:not(:disabled)::before {
+ left: 100%;
+ }
+ .wplace-btn:hover:not(:disabled) {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(0,0,0,0.4);
+ }
+ .wplace-btn:active:not(:disabled) {
+ transform: translateY(0);
}
.wplace-btn-primary {
- background: ${CONFIG.THEME.accent};
+ background: linear-gradient(135deg, ${CONFIG.THEME.accent} 0%, #6a5acd 100%);
color: white;
}
.wplace-btn-upload {
- background: ${CONFIG.THEME.secondary};
+ background: linear-gradient(135deg, ${CONFIG.THEME.secondary} 0%, #4a4a4a 100%);
color: white;
- border: 1px dashed ${CONFIG.THEME.text};
+ border: 1px dashed ${CONFIG.THEME.highlight};
}
.wplace-btn-start {
- background: ${CONFIG.THEME.success};
+ background: linear-gradient(135deg, ${CONFIG.THEME.success} 0%, #228b22 100%);
color: white;
}
.wplace-btn-stop {
- background: ${CONFIG.THEME.error};
+ background: linear-gradient(135deg, ${CONFIG.THEME.error} 0%, #dc143c 100%);
color: white;
}
.wplace-btn-select {
- background: ${CONFIG.THEME.highlight};
- color: black;
+ background: linear-gradient(135deg, ${CONFIG.THEME.highlight} 0%, #9370db 100%);
+ color: white;
+ }
+ .wplace-btn-file {
+ background: linear-gradient(135deg, #ff8c00 0%, #ff7f50 100%);
+ color: white;
}
.wplace-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none !important;
}
+ .wplace-btn:disabled::before {
+ display: none;
+ }
.wplace-stats {
- background: ${CONFIG.THEME.secondary};
- padding: 12px;
+ background: rgba(255,255,255,0.03);
+ padding: 8px;
border-radius: 6px;
- margin-bottom: 15px;
+ margin-bottom: 8px;
+ border: 1px solid rgba(255,255,255,0.1);
}
.wplace-stat-item {
display: flex;
justify-content: space-between;
- padding: 6px 0;
- font-size: 14px;
+ padding: 4px 0;
+ font-size: 11px;
+ border-bottom: 1px solid rgba(255,255,255,0.05);
+ }
+ .wplace-stat-item:last-child {
+ border-bottom: none;
}
.wplace-stat-label {
display: flex;
align-items: center;
gap: 6px;
- opacity: 0.8;
+ opacity: 0.9;
+ font-size: 10px;
+ }
+ .wplace-stat-value {
+ font-weight: 600;
+ color: ${CONFIG.THEME.highlight};
}
.wplace-progress {
width: 100%;
- background: ${CONFIG.THEME.secondary};
+ background: rgba(0,0,0,0.3);
border-radius: 4px;
- margin: 10px 0;
+ margin: 8px 0;
overflow: hidden;
+ border: 1px solid rgba(255,255,255,0.1);
}
.wplace-progress-bar {
- height: 10px;
- background: ${CONFIG.THEME.highlight};
- transition: width 0.3s;
+ height: 6px;
+ background: linear-gradient(135deg, ${CONFIG.THEME.highlight} 0%, #9370db 100%);
+ transition: width 0.5s ease;
+ position: relative;
+ }
+ .wplace-progress-bar::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
+ animation: shimmer 2s infinite;
+ }
+ @keyframes shimmer {
+ 0% { transform: translateX(-100%); }
+ 100% { transform: translateX(100%); }
}
.wplace-status {
- padding: 8px;
+ padding: 6px;
border-radius: 4px;
text-align: center;
- font-size: 13px;
+ font-size: 11px;
}
.status-default {
background: rgba(255,255,255,0.1);
@@ -812,6 +1213,12 @@
${Utils.t("title")}
-
-
-
- ${Utils.t("initBot")}
-
-
-
- ${Utils.t("uploadImage")}
-
-
-
- ${Utils.t("resizeImage")}
-
-
-
- ${Utils.t("selectPosition")}
-
-
-
- ${Utils.t("startPainting")}
-
-
-
- ${Utils.t("stopPainting")}
-
-
-
- ${Utils.t("saveData")}
-
-
-
- ${Utils.t("loadData")}
+
+
+
+ ${Utils.t("waitingInit")}
+
+
+
+
+
+
+
🤖 Bot Setup
+
+
+
+ ${Utils.t("initBot")}
+
+
+
+
+
+
+
🖼️ Image Management
+
+
+
+
+ ${Utils.t("uploadImage")}
+
+
+
+ ${Utils.t("resizeImage")}
+
+
+
+
+
+ ${Utils.t("selectPosition")}
+
+
+
+
+
+
+
+
🎮 Painting Control
+
+
+
+
+ ${Utils.t("startPainting")}
+
+
+
+ ${Utils.t("stopPainting")}
+
+
+
+
+
+
+
+
💾 Data Management
+
+
+
+
+ ${Utils.t("saveData")}
+
+
+
+ ${Utils.t("loadData")}
+
+
+
+
+
+ ${Utils.t("saveToFile")}
+
+
+
+ ${Utils.t("loadFromFile")}
+
+
+
+
+
+ `;
+
+ // Stats Window - Separate UI
+ const statsContainer = document.createElement("div");
+ statsContainer.id = "wplace-stats-container";
+ statsContainer.style.display = "none";
+ statsContainer.innerHTML = `
+
+
-
-
- ${Utils.t("waitingInit")}
-
`;
@@ -914,6 +1385,46 @@
document.body.appendChild(container);
document.body.appendChild(resizeOverlay);
document.body.appendChild(resizeContainer);
+ document.body.appendChild(statsContainer);
+
+ // Query all UI elements after appending to DOM
+ const initBotBtn = container.querySelector("#initBotBtn");
+ const uploadBtn = container.querySelector("#uploadBtn");
+ const resizeBtn = container.querySelector("#resizeBtn");
+ const selectPosBtn = container.querySelector("#selectPosBtn");
+ const startBtn = container.querySelector("#startBtn");
+ const stopBtn = container.querySelector("#stopBtn");
+ const saveBtn = container.querySelector("#saveBtn");
+ const loadBtn = container.querySelector("#loadBtn");
+ const saveToFileBtn = container.querySelector("#saveToFileBtn");
+ const loadFromFileBtn = container.querySelector("#loadFromFileBtn");
+ const minimizeBtn = container.querySelector("#minimizeBtn");
+ const compactBtn = container.querySelector("#compactBtn");
+ const statsBtn = container.querySelector("#statsBtn");
+ const statusText = container.querySelector("#statusText");
+ const progressBar = container.querySelector("#progressBar");
+ const statsArea = statsContainer.querySelector("#statsArea");
+ const content = container.querySelector(".wplace-content");
+ const closeStatsBtn = statsContainer.querySelector("#closeStatsBtn");
+
+ // Check if all elements are found
+ if (!initBotBtn || !uploadBtn || !selectPosBtn || !startBtn || !stopBtn) {
+ console.error("Some UI elements not found:", {
+ initBotBtn: !!initBotBtn,
+ uploadBtn: !!uploadBtn,
+ selectPosBtn: !!selectPosBtn,
+ startBtn: !!startBtn,
+ stopBtn: !!stopBtn
+ });
+ }
+
+ if (!statsContainer || !statsArea || !closeStatsBtn) {
+ console.error("Stats UI elements not found:", {
+ statsContainer: !!statsContainer,
+ statsArea: !!statsArea,
+ closeStatsBtn: !!closeStatsBtn
+ });
+ }
const header = container.querySelector(".wplace-header");
let pos1 = 0,
@@ -929,8 +1440,12 @@
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
+ container.classList.add("wplace-dragging");
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
+
+ // Prevent text selection during drag
+ document.body.style.userSelect = "none";
}
function elementDrag(e) {
@@ -939,28 +1454,105 @@
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
- container.style.top = container.offsetTop - pos2 + "px";
- container.style.left = container.offsetLeft - pos1 + "px";
+
+ let newTop = container.offsetTop - pos2;
+ let newLeft = container.offsetLeft - pos1;
+
+ // Boundary checking to keep UI within viewport
+ const rect = container.getBoundingClientRect();
+ const maxTop = window.innerHeight - rect.height;
+ const maxLeft = window.innerWidth - rect.width;
+
+ newTop = Math.max(0, Math.min(newTop, maxTop));
+ newLeft = Math.max(0, Math.min(newLeft, maxLeft));
+
+ container.style.top = newTop + "px";
+ container.style.left = newLeft + "px";
}
function closeDragElement() {
+ container.classList.remove("wplace-dragging");
document.onmouseup = null;
document.onmousemove = null;
+ document.body.style.userSelect = "";
}
- const initBotBtn = container.querySelector("#initBotBtn");
- const uploadBtn = container.querySelector("#uploadBtn");
- const resizeBtn = container.querySelector("#resizeBtn");
- const selectPosBtn = container.querySelector("#selectPosBtn");
- const startBtn = container.querySelector("#startBtn");
- const stopBtn = container.querySelector("#stopBtn");
- const saveBtn = container.querySelector("#saveBtn");
- const loadBtn = container.querySelector("#loadBtn");
- const minimizeBtn = container.querySelector("#minimizeBtn");
- const statusText = container.querySelector("#statusText");
- const progressBar = container.querySelector("#progressBar");
- const statsArea = container.querySelector("#statsArea");
- const content = container.querySelector(".wplace-content");
+ function makeDraggable(element) {
+ let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
+ const header = element.querySelector(".wplace-header");
+ header.onmousedown = dragMouseDown;
+
+ function dragMouseDown(e) {
+ if (e.target.closest(".wplace-header-btn")) return;
+
+ e.preventDefault();
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ element.classList.add("wplace-dragging");
+ document.onmouseup = closeDragElement;
+ document.onmousemove = elementDrag;
+
+ // Prevent text selection during drag
+ document.body.style.userSelect = "none";
+ }
+
+ function elementDrag(e) {
+ e.preventDefault();
+ pos1 = pos3 - e.clientX;
+ pos2 = pos4 - e.clientY;
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+
+ let newTop = element.offsetTop - pos2;
+ let newLeft = element.offsetLeft - pos1;
+
+ // Boundary checking to keep UI within viewport
+ const rect = element.getBoundingClientRect();
+ const maxTop = window.innerHeight - rect.height;
+ const maxLeft = window.innerWidth - rect.width;
+
+ newTop = Math.max(0, Math.min(newTop, maxTop));
+ newLeft = Math.max(0, Math.min(newLeft, maxLeft));
+
+ element.style.top = newTop + "px";
+ element.style.left = newLeft + "px";
+ }
+
+ function closeDragElement() {
+ element.classList.remove("wplace-dragging");
+ document.onmouseup = null;
+ document.onmousemove = null;
+ document.body.style.userSelect = "";
+ }
+ }
+
+ // Make stats container draggable
+ makeDraggable(statsContainer);
+
+ // Make main container draggable
+ makeDraggable(container);
+
+ // Stats window functionality
+ if (statsBtn && closeStatsBtn) {
+ statsBtn.addEventListener("click", () => {
+ const isVisible = statsContainer.style.display !== "none";
+ if (isVisible) {
+ statsContainer.style.display = "none";
+ statsBtn.innerHTML = '
';
+ statsBtn.title = "Show Stats";
+ } else {
+ statsContainer.style.display = "block";
+ statsBtn.innerHTML = '
';
+ statsBtn.title = "Hide Stats";
+ }
+ });
+
+ closeStatsBtn.addEventListener("click", () => {
+ statsContainer.style.display = "none";
+ statsBtn.innerHTML = '
';
+ statsBtn.title = "Show Stats";
+ });
+ }
const widthSlider = resizeContainer.querySelector("#widthSlider");
const heightSlider = resizeContainer.querySelector("#heightSlider");
@@ -971,16 +1563,142 @@
const confirmResize = resizeContainer.querySelector("#confirmResize");
const cancelResize = resizeContainer.querySelector("#cancelResize");
- minimizeBtn.addEventListener("click", () => {
- state.minimized = !state.minimized;
- if (state.minimized) {
- container.classList.add("wplace-minimized");
- minimizeBtn.innerHTML = '
';
- } else {
- container.classList.remove("wplace-minimized");
- minimizeBtn.innerHTML = '
';
- }
- });
+ // Compact mode functionality
+ if (compactBtn) {
+ compactBtn.addEventListener("click", () => {
+ container.classList.toggle("wplace-compact");
+ const isCompact = container.classList.contains("wplace-compact");
+
+ if (isCompact) {
+ compactBtn.innerHTML = '
';
+ compactBtn.title = "Expand Mode";
+ } else {
+ compactBtn.innerHTML = '
';
+ compactBtn.title = "Compact Mode";
+ }
+ });
+ }
+
+ // Minimize functionality
+ if (minimizeBtn) {
+ minimizeBtn.addEventListener("click", () => {
+ state.minimized = !state.minimized;
+ if (state.minimized) {
+ container.classList.add("wplace-minimized");
+ content.classList.add("wplace-hidden");
+ minimizeBtn.innerHTML = '
';
+ minimizeBtn.title = "Restore";
+ } else {
+ container.classList.remove("wplace-minimized");
+ content.classList.remove("wplace-hidden");
+ minimizeBtn.innerHTML = '
';
+ minimizeBtn.title = "Minimize";
+ }
+ });
+ }
+
+ // Save progress functionality
+ if (saveBtn) {
+ saveBtn.addEventListener("click", () => {
+ if (!state.imageLoaded) {
+ Utils.showAlert(Utils.t("missingRequirements"), "error");
+ return;
+ }
+
+ const success = Utils.saveProgress();
+ if (success) {
+ updateUI("autoSaved", "success");
+ Utils.showAlert(Utils.t("autoSaved"), "success");
+ } else {
+ Utils.showAlert("❌ Erro ao salvar progresso", "error");
+ }
+ });
+ }
+
+ // Load progress functionality
+ if (loadBtn) {
+ loadBtn.addEventListener("click", () => {
+ const savedData = Utils.loadProgress();
+ if (!savedData) {
+ updateUI("noSavedData", "warning");
+ Utils.showAlert(Utils.t("noSavedData"), "warning");
+ return;
+ }
+
+ // Show confirmation dialog
+ const confirmLoad = confirm(
+ `${Utils.t("savedDataFound")}\n\n` +
+ `Saved: ${new Date(savedData.timestamp).toLocaleString()}\n` +
+ `Progress: ${savedData.state.paintedPixels}/${savedData.state.totalPixels} pixels`
+ );
+
+ if (confirmLoad) {
+ const success = Utils.restoreProgress(savedData);
+ if (success) {
+ updateUI("dataLoaded", "success");
+ Utils.showAlert(Utils.t("dataLoaded"), "success");
+ updateDataButtons();
+
+ if (!state.colorsChecked) {
+ initBotBtn.style.display = "block";
+ }
+
+ if (state.imageLoaded && state.startPosition && state.region && state.colorsChecked) {
+ startBtn.disabled = false;
+ }
+ } else {
+ Utils.showAlert("❌ Erro ao carregar progresso", "error");
+ }
+ }
+ });
+ }
+
+ // Save to file functionality
+ if (saveToFileBtn) {
+ saveToFileBtn.addEventListener("click", () => {
+ const success = Utils.saveProgressToFile();
+ if (success) {
+ updateUI("fileSaved", "success");
+ Utils.showAlert(Utils.t("fileSaved"), "success");
+ } else {
+ Utils.showAlert(Utils.t("fileError"), "error");
+ }
+ });
+ }
+
+ // Load from file functionality
+ if (loadFromFileBtn) {
+ loadFromFileBtn.addEventListener("click", async () => {
+ try {
+ const success = await Utils.loadProgressFromFile();
+ if (success) {
+ updateUI("fileLoaded", "success");
+ Utils.showAlert(Utils.t("fileLoaded"), "success");
+ updateDataButtons();
+
+ // Auto-enable buttons after loading from file
+ if (state.colorsChecked) {
+ uploadBtn.disabled = false;
+ selectPosBtn.disabled = false;
+ resizeBtn.disabled = false;
+ initBotBtn.style.display = "none";
+ } else {
+ initBotBtn.style.display = "block";
+ }
+
+ if (state.imageLoaded && state.startPosition && state.region && state.colorsChecked) {
+ startBtn.disabled = false;
+ }
+ }
+ } catch (error) {
+ if (error.message === 'Invalid JSON file') {
+ Utils.showAlert(Utils.t("invalidFileFormat"), "error");
+ } else {
+ Utils.showAlert(Utils.t("fileError"), "error");
+ }
+ }
+ });
+ }
window.updateUI = (messageKey, type = "default", params = {}) => {
const message = Utils.t(messageKey, params);
@@ -1017,19 +1735,19 @@
${Utils.t(
"progress"
)}
-
${progress}%
+
${progress}%
${Utils.t(
"pixels"
)}
-
${state.paintedPixels}/${state.totalPixels}
+
${state.paintedPixels}/${state.totalPixels}
${Utils.t(
"charges"
)}
-
${Math.floor(state.currentCharges)}
+
${Math.floor(state.currentCharges)}
${
state.imageLoaded
@@ -1038,7 +1756,7 @@
${Utils.t(
"estimatedTime"
)}
- ${Utils.formatTime(state.estimatedTime)}
+ ${Utils.formatTime(state.estimatedTime)}
`
: ""
@@ -1046,6 +1764,16 @@
`;
};
+ // Helper function to update data management buttons
+ window.updateDataButtons = () => {
+ const hasImageData = state.imageLoaded && state.imageData;
+ saveBtn.disabled = !hasImageData;
+ saveToFileBtn.disabled = !hasImageData;
+ };
+
+ // Initialize data buttons state
+ updateDataButtons();
+
function showResizeDialog(processor) {
const { width, height } = processor.getDimensions();
const aspectRatio = width / height;
@@ -1133,42 +1861,45 @@
resizeContainer.style.display = "none";
}
- initBotBtn.addEventListener("click", async () => {
- try {
- updateUI("checkingColors", "default");
+ if (initBotBtn) {
+ initBotBtn.addEventListener("click", async () => {
+ try {
+ updateUI("checkingColors", "default");
- state.availableColors = Utils.extractAvailableColors();
+ state.availableColors = Utils.extractAvailableColors();
- if (state.availableColors.length === 0) {
- Utils.showAlert(Utils.t("noColorsFound"), "error");
- updateUI("noColorsFound", "error");
- return;
+ if (state.availableColors.length === 0) {
+ Utils.showAlert(Utils.t("noColorsFound"), "error");
+ updateUI("noColorsFound", "error");
+ return;
+ }
+
+ state.colorsChecked = true;
+ uploadBtn.disabled = false;
+ selectPosBtn.disabled = false;
+ initBotBtn.style.display = "none";
+
+ updateUI("colorsFound", "success", {
+ count: state.availableColors.length,
+ });
+ updateStats();
+ } catch {
+ updateUI("imageError", "error");
}
+ });
+ }
- state.colorsChecked = true;
- uploadBtn.disabled = false;
- selectPosBtn.disabled = false;
- initBotBtn.style.display = "none";
+ if (uploadBtn) {
+ uploadBtn.addEventListener("click", async () => {
+ try {
+ updateUI("loadingImage", "default");
+ const imageSrc = await Utils.createImageUploader();
- updateUI("colorsFound", "success", {
- count: state.availableColors.length,
- });
- updateStats();
- } catch {
- updateUI("imageError", "error");
- }
- });
+ const processor = new ImageProcessor(imageSrc);
+ await processor.load();
- uploadBtn.addEventListener("click", async () => {
- try {
- updateUI("loadingImage", "default");
- const imageSrc = await Utils.createImageUploader();
-
- const processor = new ImageProcessor(imageSrc);
- await processor.load();
-
- const { width, height } = processor.getDimensions();
- const pixels = processor.getPixelData();
+ const { width, height } = processor.getDimensions();
+ const pixels = processor.getPixelData();
let totalValidPixels = 0;
for (let y = 0; y < height; y++) {
@@ -1206,44 +1937,49 @@
startBtn.disabled = false;
}
- updateStats();
- updateUI("imageLoaded", "success", { count: totalValidPixels });
- } catch {
- updateUI("imageError", "error");
- }
- });
+ updateStats();
+ updateDataButtons();
+ updateUI("imageLoaded", "success", { count: totalValidPixels });
+ } catch {
+ updateUI("imageError", "error");
+ }
+ });
+ }
- resizeBtn.addEventListener("click", () => {
- if (state.imageLoaded && state.imageData.processor) {
- showResizeDialog(state.imageData.processor);
- }
- });
+ if (resizeBtn) {
+ resizeBtn.addEventListener("click", () => {
+ if (state.imageLoaded && state.imageData.processor) {
+ showResizeDialog(state.imageData.processor);
+ }
+ });
+ }
- selectPosBtn.addEventListener("click", async () => {
- if (state.selectingPosition) return;
+ if (selectPosBtn) {
+ selectPosBtn.addEventListener("click", async () => {
+ if (state.selectingPosition) return;
- state.selectingPosition = true;
- state.startPosition = null;
- state.region = null;
- startBtn.disabled = true;
+ state.selectingPosition = true;
+ state.startPosition = null;
+ state.region = null;
+ startBtn.disabled = true;
- Utils.showAlert(Utils.t("selectPositionAlert"), "info");
- updateUI("waitingPosition", "default");
+ Utils.showAlert(Utils.t("selectPositionAlert"), "info");
+ updateUI("waitingPosition", "default");
- const originalFetch = window.fetch;
+ const originalFetch = window.fetch;
- window.fetch = async (url, options) => {
- if (
- typeof url === "string" &&
- url.includes("https://backend.wplace.live/s0/pixel/") &&
- options?.method?.toUpperCase() === "POST"
- ) {
- try {
- const response = await originalFetch(url, options);
- const clonedResponse = response.clone();
- const data = await clonedResponse.json();
+ window.fetch = async (url, options) => {
+ if (
+ typeof url === "string" &&
+ url.includes("https://backend.wplace.live/s0/pixel/") &&
+ options?.method?.toUpperCase() === "POST"
+ ) {
+ try {
+ const response = await originalFetch(url, options);
+ const clonedResponse = response.clone();
+ const data = await clonedResponse.json();
- if (data?.painted === 1) {
+ if (data?.painted === 1) {
const regionMatch = url.match(/\/pixel\/(\d+)\/(\d+)/);
if (regionMatch && regionMatch.length >= 3) {
state.region = {
@@ -1278,25 +2014,27 @@
return originalFetch(url, options);
};
- setTimeout(() => {
- if (state.selectingPosition) {
- window.fetch = originalFetch;
- state.selectingPosition = false;
- updateUI("positionTimeout", "error");
- Utils.showAlert(Utils.t("positionTimeout"), "error");
- }
- }, 120000);
- });
+ setTimeout(() => {
+ if (state.selectingPosition) {
+ window.fetch = originalFetch;
+ state.selectingPosition = false;
+ updateUI("positionTimeout", "error");
+ Utils.showAlert(Utils.t("positionTimeout"), "error");
+ }
+ }, 120000);
+ });
+ }
- startBtn.addEventListener("click", async () => {
+ // Function to start painting (can be called programmatically)
+ async function startPainting() {
if (!state.imageLoaded || !state.startPosition || !state.region) {
updateUI("missingRequirements", "error");
- return;
+ return false;
}
if (!capturedCaptchaToken) {
updateUI("captchaNeeded", "error");
Utils.showAlert(Utils.t("captchaNeeded"), "error");
- return;
+ return false;
}
state.running = true;
@@ -1312,8 +2050,10 @@
try {
await processImage();
+ return true;
} catch {
updateUI("paintingError", "error");
+ return false;
} finally {
state.running = false;
stopBtn.disabled = true;
@@ -1328,74 +2068,27 @@
startBtn.disabled = false;
}
}
- });
+ }
- stopBtn.addEventListener("click", () => {
- state.stopFlag = true;
- state.running = false;
- stopBtn.disabled = true;
- updateUI("paintingStopped", "warning");
-
- // Auto save when stopping
- if (state.imageLoaded && state.paintedPixels > 0) {
- Utils.saveProgress();
- Utils.showAlert(Utils.t("autoSaved"), "success");
- }
- });
+ if (startBtn) {
+ startBtn.addEventListener("click", startPainting);
+ }
- saveBtn.addEventListener("click", () => {
- if (!state.imageLoaded) {
- Utils.showAlert(Utils.t("missingRequirements"), "error");
- return;
- }
-
- const success = Utils.saveProgress();
- if (success) {
- updateUI("autoSaved", "success");
- Utils.showAlert(Utils.t("autoSaved"), "success");
- } else {
- Utils.showAlert("❌ Erro ao salvar progresso", "error");
- }
- });
-
- loadBtn.addEventListener("click", () => {
- const savedData = Utils.loadProgress();
- if (!savedData) {
- updateUI("noSavedData", "warning");
- Utils.showAlert(Utils.t("noSavedData"), "warning");
- return;
- }
-
- // Show confirmation dialog
- const confirmLoad = confirm(
- `${Utils.t("savedDataFound")}\n\n` +
- `Saved: ${new Date(savedData.timestamp).toLocaleString()}\n` +
- `Progress: ${savedData.state.paintedPixels}/${savedData.state.totalPixels} pixels`
- );
-
- if (confirmLoad) {
- const success = Utils.restoreProgress(savedData);
- if (success) {
- updateUI("dataLoaded", "success");
- Utils.showAlert(Utils.t("dataLoaded"), "success");
-
- // Enable appropriate buttons
- saveBtn.disabled = false;
-
- // Show init button if colors not checked yet
- if (!state.colorsChecked) {
- initBotBtn.style.display = "block";
- }
-
- if (state.imageLoaded && state.startPosition && state.region && state.colorsChecked) {
- startBtn.disabled = false;
- }
- } else {
- Utils.showAlert("❌ Erro ao carregar progresso", "error");
+ if (stopBtn) {
+ stopBtn.addEventListener("click", () => {
+ state.stopFlag = true;
+ state.running = false;
+ stopBtn.disabled = true;
+ updateUI("paintingStopped", "warning");
+
+ // Auto save when stopping
+ if (state.imageLoaded && state.paintedPixels > 0) {
+ Utils.saveProgress();
+ Utils.showAlert(Utils.t("autoSaved"), "success");
}
- }
- });
-
+ });
+ }
+
// Check for saved progress on startup
const checkSavedProgress = () => {
const savedData = Utils.loadProgress();
@@ -1591,4 +2284,4 @@
}
createUI();
-})();
+})();
\ No newline at end of file
diff --git a/FR.md b/FR.md
index 5cae58f..bd48b47 100644
--- a/FR.md
+++ b/FR.md
@@ -2,6 +2,7 @@
WPlace AutoBOT
Ce projet est un fork de https://github.com/DarkModde/WPlace-AutoBOT
+Join our discord: https://discord.gg/CBB4abRmGM
Des scripts pratiques avec des menus intuitifs pour vous faciliter la vie sur WPlace !
diff --git a/PT.md b/PT.md
index be5d0b3..99e0bab 100644
--- a/PT.md
+++ b/PT.md
@@ -2,6 +2,7 @@
WPlace AutoBOT
Este projeto foi bifurcado de https://github.com/DarkModde/WPlace-AutoBOT
+Junte-se ao nosso discord: https://discord.gg/CBB4abRmGM
Scripts práticos com menus intuitivos para facilitar sua vida no WPlace !
diff --git a/test-save-load.md b/test-save-load.md
deleted file mode 100644
index a953055..0000000
--- a/test-save-load.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Test Save/Load Progress Feature
-
-## Các tính năng đã thêm:
-
-### Auto-Image.js:
-1. **Save Progress** - Nút lưu tiến độ thủ công
-2. **Load Progress** - Nút khôi phục tiến độ
-3. **Auto Save** - Tự động lưu mỗi 50 pixels và khi dừng
-4. **Smart UI Restore** - Khôi phục UI đúng trạng thái trước khi dừng
-5. **Progress Notification** - Thông báo khi khởi động nếu có progress đã lưu
-
-### Cách sử dụng:
-
-1. **Upload ảnh và chọn vị trí** như bình thường
-2. **Bắt đầu painting** - script sẽ tự động lưu tiến độ
-3. **Dừng script** khi cần - tiến độ sẽ được lưu tự động
-4. **Khởi động lại script** - sẽ có thông báo nếu có tiến độ đã lưu
-5. **Click "Load Progress"** để tiếp tục từ vị trí đã dừng
-6. **UI sẽ hiển thị** đúng trạng thái như trước khi dừng (progress bar, pixels painted, vị trí, etc.)
-
-### Dữ liệu được lưu:
-- Thông tin ảnh và pixels
-- Vị trí hiện tại trong painting
-- Số pixels đã paint
-- Map các pixels đã paint (để tránh paint lại)
-- Vị trí bắt đầu và region
-- Màu sắc available
-- Ngôn ngữ đã chọn
-
-### Auto Save:
-- Mỗi 50 pixels được paint
-- Khi user dừng script (Stop button)
-- Clear progress khi hoàn thành painting
-
-### Thông báo:
-- Tiếng Việt và English support
-- Hiển thị thời gian lưu và tiến độ
-- Xác nhận trước khi load