mirror of
https://github.com/tiennm99/WPlace-AutoBOT.git
synced 2026-05-30 06:20:10 +00:00
3333 lines
124 KiB
JavaScript
3333 lines
124 KiB
JavaScript
// eslint-disable-next-line prettier/prettier
|
||
; (async () => {
|
||
// CONFIGURATION CONSTANTS
|
||
const CONFIG = {
|
||
COOLDOWN_DEFAULT: 31000,
|
||
TRANSPARENCY_THRESHOLD: 100,
|
||
WHITE_THRESHOLD: 250,
|
||
LOG_INTERVAL: 10,
|
||
PAINTING_SPEED: {
|
||
MIN: 1,
|
||
MAX: 1000,
|
||
DEFAULT: 5,
|
||
},
|
||
TOKEN_SOURCE: 'generator', // "generator", "manual", or "hybrid"
|
||
AUTONOMOUS_MODE: true, // Enable autonomous operation
|
||
AUTO_TOKEN_REFRESH: true, // Automatically refresh tokens
|
||
TOKEN_PRELOAD_BUFFER: 60000, // Preload tokens 1 minute before expiry
|
||
MAX_RETRIES: 10,
|
||
RETRY_DELAY_BASE: 1000,
|
||
// Auto-batch configuration
|
||
AUTO_BATCH_ENABLED: true, // Enable auto pixel batch sizing
|
||
MAX_BATCH_SIZE: 500, // Maximum pixels per batch
|
||
MIN_BATCH_SIZE: 1, // Minimum pixels per batch
|
||
BATCH_OPTIMIZATION: true, // Optimize batch size based on detected damage
|
||
COLOR_MAP: {
|
||
0: { id: 1, name: 'Black', rgb: { r: 0, g: 0, b: 0 } },
|
||
1: { id: 2, name: 'Dark Gray', rgb: { r: 60, g: 60, b: 60 } },
|
||
2: { id: 3, name: 'Gray', rgb: { r: 120, g: 120, b: 120 } },
|
||
3: { id: 4, name: 'Light Gray', rgb: { r: 210, g: 210, b: 210 } },
|
||
4: { id: 5, name: 'White', rgb: { r: 255, g: 255, b: 255 } },
|
||
5: { id: 6, name: 'Deep Red', rgb: { r: 96, g: 0, b: 24 } },
|
||
6: { id: 7, name: 'Red', rgb: { r: 237, g: 28, b: 36 } },
|
||
7: { id: 8, name: 'Orange', rgb: { r: 255, g: 127, b: 39 } },
|
||
8: { id: 9, name: 'Gold', rgb: { r: 246, g: 170, b: 9 } },
|
||
9: { id: 10, name: 'Yellow', rgb: { r: 249, g: 221, b: 59 } },
|
||
10: { id: 11, name: 'Light Yellow', rgb: { r: 255, g: 250, b: 188 } },
|
||
11: { id: 12, name: 'Dark Green', rgb: { r: 14, g: 185, b: 104 } },
|
||
12: { id: 13, name: 'Green', rgb: { r: 19, g: 230, b: 123 } },
|
||
13: { id: 14, name: 'Light Green', rgb: { r: 135, g: 255, b: 94 } },
|
||
14: { id: 15, name: 'Dark Teal', rgb: { r: 12, g: 129, b: 110 } },
|
||
15: { id: 16, name: 'Teal', rgb: { r: 16, g: 174, b: 166 } },
|
||
16: { id: 17, name: 'Light Teal', rgb: { r: 19, g: 225, b: 190 } },
|
||
17: { id: 20, name: 'Cyan', rgb: { r: 96, g: 247, b: 242 } },
|
||
18: { id: 44, name: 'Light Cyan', rgb: { r: 187, g: 250, b: 242 } },
|
||
19: { id: 18, name: 'Dark Blue', rgb: { r: 40, g: 80, b: 158 } },
|
||
20: { id: 19, name: 'Blue', rgb: { r: 64, g: 147, b: 228 } },
|
||
21: { id: 21, name: 'Indigo', rgb: { r: 107, g: 80, b: 246 } },
|
||
22: { id: 22, name: 'Light Indigo', rgb: { r: 153, g: 177, b: 251 } },
|
||
23: { id: 23, name: 'Dark Purple', rgb: { r: 120, g: 12, b: 153 } },
|
||
24: { id: 24, name: 'Purple', rgb: { r: 170, g: 56, b: 185 } },
|
||
25: { id: 25, name: 'Light Purple', rgb: { r: 224, g: 159, b: 249 } },
|
||
26: { id: 26, name: 'Dark Pink', rgb: { r: 203, g: 0, b: 122 } },
|
||
27: { id: 27, name: 'Pink', rgb: { r: 236, g: 31, b: 128 } },
|
||
28: { id: 28, name: 'Light Pink', rgb: { r: 243, g: 141, b: 169 } },
|
||
29: { id: 29, name: 'Dark Brown', rgb: { r: 104, g: 70, b: 52 } },
|
||
30: { id: 30, name: 'Brown', rgb: { r: 149, g: 104, b: 42 } },
|
||
31: { id: 31, name: 'Beige', rgb: { r: 248, g: 178, b: 119 } },
|
||
32: { id: 52, name: 'Light Beige', rgb: { r: 255, g: 197, b: 165 } },
|
||
33: { id: 32, name: 'Medium Gray', rgb: { r: 170, g: 170, b: 170 } },
|
||
34: { id: 33, name: 'Dark Red', rgb: { r: 165, g: 14, b: 30 } },
|
||
35: { id: 34, name: 'Light Red', rgb: { r: 250, g: 128, b: 114 } },
|
||
36: { id: 35, name: 'Dark Orange', rgb: { r: 228, g: 92, b: 26 } },
|
||
37: { id: 37, name: 'Dark Goldenrod', rgb: { r: 156, g: 132, b: 49 } },
|
||
38: { id: 38, name: 'Goldenrod', rgb: { r: 197, g: 173, b: 49 } },
|
||
39: { id: 39, name: 'Light Goldenrod', rgb: { r: 232, g: 212, b: 95 } },
|
||
40: { id: 40, name: 'Dark Olive', rgb: { r: 74, g: 107, b: 58 } },
|
||
41: { id: 41, name: 'Olive', rgb: { r: 90, g: 148, b: 74 } },
|
||
42: { id: 42, name: 'Light Olive', rgb: { r: 132, g: 197, b: 115 } },
|
||
43: { id: 43, name: 'Dark Cyan', rgb: { r: 15, g: 121, b: 159 } },
|
||
44: { id: 45, name: 'Light Blue', rgb: { r: 125, g: 199, b: 255 } },
|
||
45: { id: 46, name: 'Dark Indigo', rgb: { r: 77, g: 49, b: 184 } },
|
||
46: { id: 47, name: 'Dark Slate Blue', rgb: { r: 74, g: 66, b: 132 } },
|
||
47: { id: 48, name: 'Slate Blue', rgb: { r: 122, g: 113, b: 196 } },
|
||
48: { id: 49, name: 'Light Slate Blue', rgb: { r: 181, g: 174, b: 241 } },
|
||
49: { id: 53, name: 'Dark Peach', rgb: { r: 155, g: 82, b: 73 } },
|
||
50: { id: 54, name: 'Peach', rgb: { r: 209, g: 128, b: 120 } },
|
||
51: { id: 55, name: 'Light Peach', rgb: { r: 250, g: 182, b: 164 } },
|
||
52: { id: 50, name: 'Light Brown', rgb: { r: 219, g: 164, b: 99 } },
|
||
53: { id: 56, name: 'Dark Tan', rgb: { r: 123, g: 99, b: 82 } },
|
||
54: { id: 57, name: 'Tan', rgb: { r: 156, g: 132, b: 107 } },
|
||
55: { id: 36, name: 'Light Tan', rgb: { r: 214, g: 181, b: 148 } },
|
||
56: { id: 51, name: 'Dark Beige', rgb: { r: 209, g: 128, b: 81 } },
|
||
57: { id: 61, name: 'Dark Stone', rgb: { r: 109, g: 100, b: 63 } },
|
||
58: { id: 62, name: 'Stone', rgb: { r: 148, g: 140, b: 107 } },
|
||
59: { id: 63, name: 'Light Stone', rgb: { r: 205, g: 197, b: 158 } },
|
||
60: { id: 58, name: 'Dark Slate', rgb: { r: 51, g: 57, b: 65 } },
|
||
61: { id: 59, name: 'Slate', rgb: { r: 109, g: 117, b: 141 } },
|
||
62: { id: 60, name: 'Light Slate', rgb: { r: 179, g: 185, b: 209 } },
|
||
63: { id: 0, name: 'Transparent', rgb: null },
|
||
},
|
||
};
|
||
|
||
// GLOBAL STATE
|
||
const state = {
|
||
running: false,
|
||
imageLoaded: false,
|
||
totalPixels: 0,
|
||
paintedPixels: 0,
|
||
availableColors: [],
|
||
displayCharges: 0,
|
||
maxCharges: 1,
|
||
cooldown: CONFIG.COOLDOWN_DEFAULT,
|
||
imageData: null,
|
||
stopFlag: false,
|
||
startPosition: null,
|
||
region: null,
|
||
paintWhitePixels: true,
|
||
paintTransparentPixels: true, // Changed to true to fix transparent pixel detection
|
||
autoRepairEnabled: false,
|
||
autoRepairInterval: 30,
|
||
autoRepairTimer: null,
|
||
debugLogs: [],
|
||
customTransparencyThreshold: CONFIG.TRANSPARENCY_THRESHOLD,
|
||
customWhiteThreshold: CONFIG.WHITE_THRESHOLD,
|
||
tokenSource: CONFIG.TOKEN_SOURCE,
|
||
autonomousMode: CONFIG.AUTONOMOUS_MODE,
|
||
autoTokenRefresh: CONFIG.AUTO_TOKEN_REFRESH,
|
||
tokenPreloadBuffer: CONFIG.TOKEN_PRELOAD_BUFFER,
|
||
retryCount: 0,
|
||
tokenRetryTimer: null,
|
||
tokenPreloadTimer: null,
|
||
windowMinimized: false,
|
||
lastAttackState: null,
|
||
initialSetupComplete: false,
|
||
// Auto-batch state
|
||
autoBatchEnabled: CONFIG.AUTO_BATCH_ENABLED,
|
||
currentBatchSize: CONFIG.MIN_BATCH_SIZE,
|
||
batchOptimization: CONFIG.BATCH_OPTIMIZATION,
|
||
};
|
||
|
||
// Random string generator
|
||
const randStr = (len, chars = 'abcdefghijklmnopqrstuvwxyz0123456789') =>
|
||
[...Array(len)].map(() => chars[(crypto?.getRandomValues?.(new Uint32Array(1))[0] % chars.length) || Math.floor(Math.random() * chars.length)]).join('');
|
||
|
||
const fpStr32 = randStr(32);
|
||
|
||
// Enhanced Turnstile token handling - Actualizado con la lógica de new.txt lol
|
||
let turnstileToken = null;
|
||
let tokenExpiryTime = 0;
|
||
let tokenGenerationInProgress = false;
|
||
let _resolveToken = null;
|
||
let tokenPromise = new Promise((resolve) => {
|
||
_resolveToken = resolve;
|
||
});
|
||
let retryCount = 0;
|
||
const MAX_RETRIES = 10;
|
||
const MAX_BATCH_RETRIES = 10;
|
||
const TOKEN_LIFETIME = 240000; // 4 minutes (tokens typically last 5 min, use 4 for safety)
|
||
|
||
function setTurnstileToken(token) {
|
||
if (_resolveToken) {
|
||
_resolveToken(token);
|
||
_resolveToken = null;
|
||
}
|
||
turnstileToken = token;
|
||
tokenExpiryTime = Date.now() + TOKEN_LIFETIME;
|
||
console.log('✅ Turnstile token set successfully');
|
||
Utils.addDebugLog('Token cached successfully', 'success');
|
||
}
|
||
|
||
function isTokenValid() {
|
||
return turnstileToken && Date.now() < tokenExpiryTime;
|
||
}
|
||
|
||
function invalidateToken() {
|
||
turnstileToken = null;
|
||
tokenExpiryTime = 0;
|
||
console.log('🗑️ Token invalidated, will force fresh generation');
|
||
Utils.addDebugLog('Token invalidated, will force fresh generation', 'warning');
|
||
}
|
||
|
||
async function ensureToken(forceRefresh = false) {
|
||
// Return cached token if still valid and not forcing refresh
|
||
if (isTokenValid() && !forceRefresh) {
|
||
return turnstileToken;
|
||
}
|
||
|
||
// Invalidate token if forcing refresh
|
||
if (forceRefresh) invalidateToken();
|
||
|
||
// Avoid multiple simultaneous token generations
|
||
if (tokenGenerationInProgress) {
|
||
console.log('🔄 Token generation already in progress, waiting...');
|
||
await Utils.sleep(2000);
|
||
return isTokenValid() ? turnstileToken : null;
|
||
}
|
||
|
||
tokenGenerationInProgress = true;
|
||
|
||
try {
|
||
console.log('🔄 Token expired or missing, generating new one...');
|
||
const token = await handleCaptchaWithRetry();
|
||
if (token && token.length > 20) {
|
||
setTurnstileToken(token);
|
||
console.log('✅ Token captured and cached successfully');
|
||
return token;
|
||
}
|
||
|
||
console.log('⚠️ Invisible Turnstile failed, forcing browser automation...');
|
||
const fallbackToken = await handleCaptchaFallback();
|
||
if (fallbackToken && fallbackToken.length > 20) {
|
||
setTurnstileToken(fallbackToken);
|
||
console.log('✅ Fallback token captured successfully');
|
||
return fallbackToken;
|
||
}
|
||
|
||
console.log('❌ All token generation methods failed');
|
||
return null;
|
||
} finally {
|
||
tokenGenerationInProgress = false;
|
||
}
|
||
}
|
||
|
||
async function handleCaptchaWithRetry() {
|
||
const startTime = performance.now();
|
||
|
||
try {
|
||
const { sitekey, token: preGeneratedToken } = await Utils.obtainSitekeyAndToken();
|
||
|
||
if (!sitekey) {
|
||
throw new Error('No valid sitekey found');
|
||
}
|
||
|
||
console.log('🔑 Using sitekey:', sitekey);
|
||
|
||
if (typeof window !== 'undefined' && window.navigator) {
|
||
console.log(
|
||
'🧭 UA:',
|
||
window.navigator.userAgent.substring(0, 50) + '...',
|
||
'Platform:',
|
||
window.navigator.platform
|
||
);
|
||
}
|
||
|
||
let token = null;
|
||
|
||
if (
|
||
preGeneratedToken &&
|
||
typeof preGeneratedToken === 'string' &&
|
||
preGeneratedToken.length > 20
|
||
) {
|
||
console.log('♻️ Reusing pre-generated Turnstile token');
|
||
token = preGeneratedToken;
|
||
} else {
|
||
if (isTokenValid()) {
|
||
console.log('♻️ Using existing cached token (from previous session)');
|
||
token = turnstileToken;
|
||
} else {
|
||
console.log('🔍 Generating new token with executeTurnstile...');
|
||
token = await Utils.executeTurnstile(sitekey, 'paint');
|
||
if (token) setTurnstileToken(token);
|
||
}
|
||
}
|
||
|
||
if (token && typeof token === 'string' && token.length > 20) {
|
||
const elapsed = Math.round(performance.now() - startTime);
|
||
console.log(`✅ Turnstile token generated successfully in ${elapsed}ms`);
|
||
return token;
|
||
} else {
|
||
throw new Error(`Invalid or empty token received - Length: ${token?.length || 0}`);
|
||
}
|
||
} catch (error) {
|
||
const elapsed = Math.round(performance.now() - startTime);
|
||
console.error(`❌ Turnstile token generation failed after ${elapsed}ms:`, error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async function handleCaptchaFallback() {
|
||
return new Promise(async (resolve, reject) => {
|
||
try {
|
||
// Ensure we have a fresh promise to await for a new token capture
|
||
if (!_resolveToken) {
|
||
tokenPromise = new Promise((res) => {
|
||
_resolveToken = res;
|
||
});
|
||
}
|
||
const timeoutPromise = Utils.sleep(20000).then(() =>
|
||
reject(new Error('Auto-CAPTCHA timed out.'))
|
||
);
|
||
|
||
const solvePromise = (async () => {
|
||
const mainPaintBtn = await Utils.waitForSelector(
|
||
'button.btn.btn-primary.btn-lg, button.btn-primary.sm\\:btn-xl',
|
||
200,
|
||
10000
|
||
);
|
||
if (!mainPaintBtn) throw new Error('Could not find the main paint button.');
|
||
mainPaintBtn.click();
|
||
await Utils.sleep(500);
|
||
|
||
const transBtn = await Utils.waitForSelector('button#color-0', 200, 5000);
|
||
if (!transBtn) throw new Error('Could not find the transparent color button.');
|
||
transBtn.click();
|
||
await Utils.sleep(500);
|
||
|
||
const canvas = await Utils.waitForSelector('canvas', 200, 5000);
|
||
if (!canvas) throw new Error('Could not find the canvas element.');
|
||
|
||
canvas.setAttribute('tabindex', '0');
|
||
canvas.focus();
|
||
const rect = canvas.getBoundingClientRect();
|
||
const centerX = Math.round(rect.left + rect.width / 2);
|
||
const centerY = Math.round(rect.top + rect.height / 2);
|
||
|
||
canvas.dispatchEvent(
|
||
new MouseEvent('mousemove', {
|
||
clientX: centerX,
|
||
clientY: centerY,
|
||
bubbles: true,
|
||
})
|
||
);
|
||
canvas.dispatchEvent(
|
||
new KeyboardEvent('keydown', {
|
||
key: ' ',
|
||
code: 'Space',
|
||
bubbles: true,
|
||
})
|
||
);
|
||
await Utils.sleep(50);
|
||
canvas.dispatchEvent(
|
||
new KeyboardEvent('keyup', {
|
||
key: ' ',
|
||
code: 'Space',
|
||
bubbles: true,
|
||
})
|
||
);
|
||
await Utils.sleep(500);
|
||
|
||
// 800ms delay before sending confirmation
|
||
await Utils.sleep(800);
|
||
|
||
// Keep confirming until token is captured
|
||
const confirmLoop = async () => {
|
||
while (!turnstileToken) {
|
||
let confirmBtn = await Utils.waitForSelector(
|
||
'button.btn.btn-primary.btn-lg, button.btn.btn-primary.sm\\:btn-xl'
|
||
);
|
||
if (!confirmBtn) {
|
||
const allPrimary = Array.from(document.querySelectorAll('button.btn-primary'));
|
||
confirmBtn = allPrimary.length ? allPrimary[allPrimary.length - 1] : null;
|
||
}
|
||
if (confirmBtn) {
|
||
confirmBtn.click();
|
||
}
|
||
await Utils.sleep(500); // 500ms delay between confirmation attempts
|
||
}
|
||
};
|
||
|
||
// Start confirmation loop and wait for token
|
||
confirmLoop();
|
||
const token = await tokenPromise;
|
||
await Utils.sleep(300); // small delay after token is captured
|
||
resolve(token);
|
||
})();
|
||
|
||
await Promise.race([solvePromise, timeoutPromise]);
|
||
} catch (error) {
|
||
console.error('Auto-CAPTCHA process failed:', error);
|
||
reject(error);
|
||
}
|
||
});
|
||
}
|
||
|
||
// NUEVA LÓGICA DE INYECCIÓN - ACTUALIZADA DESDE new.txt
|
||
function inject(callback) {
|
||
try {
|
||
const script = document.createElement('script');
|
||
script.textContent = `(${callback})();`;
|
||
document.documentElement?.appendChild(script);
|
||
script.remove();
|
||
} catch (error) {
|
||
console.error('❌ Injection error:', error);
|
||
}
|
||
}
|
||
|
||
inject(() => {
|
||
const fetchedBlobQueue = new Map();
|
||
|
||
window.addEventListener('message', (event) => {
|
||
const { source, blobID, blobData } = event.data;
|
||
if (source === 'auto-image-overlay' && blobID && blobData) {
|
||
const callback = fetchedBlobQueue.get(blobID);
|
||
if (typeof callback === 'function') {
|
||
callback(blobData);
|
||
}
|
||
fetchedBlobQueue.delete(blobID);
|
||
}
|
||
});
|
||
|
||
const originalFetch = window.fetch;
|
||
window.fetch = async function (...args) {
|
||
const response = await originalFetch.apply(this, args);
|
||
const url = args[0] instanceof Request ? args[0].url : args[0];
|
||
|
||
if (typeof url === 'string') {
|
||
if (url.includes('https://backend.wplace.live/s0/pixel/')) {
|
||
try {
|
||
const payload = JSON.parse(args[1].body);
|
||
if (payload.t) {
|
||
// 📊 Debug log
|
||
console.log(
|
||
`🔍✅ Turnstile Token Captured - Type: ${typeof payload.t}, Value: ${payload.t
|
||
? typeof payload.t === 'string'
|
||
? payload.t.length > 50
|
||
? payload.t.substring(0, 50) + '...'
|
||
: payload.t
|
||
: JSON.stringify(payload.t)
|
||
: 'null/undefined'
|
||
}, Length: ${payload.t?.length || 0}`
|
||
);
|
||
window.postMessage({ source: 'turnstile-capture', token: payload.t }, '*');
|
||
}
|
||
} catch (_) {
|
||
/* ignore */
|
||
}
|
||
}
|
||
|
||
const contentType = response.headers.get('content-type') || '';
|
||
if (contentType.includes('image/png') && url.includes('.png')) {
|
||
const cloned = response.clone();
|
||
return new Promise(async (resolve) => {
|
||
const blobUUID = crypto.randomUUID();
|
||
const originalBlob = await cloned.blob();
|
||
|
||
fetchedBlobQueue.set(blobUUID, (processedBlob) => {
|
||
resolve(
|
||
new Response(processedBlob, {
|
||
headers: cloned.headers,
|
||
status: cloned.status,
|
||
statusText: cloned.statusText,
|
||
})
|
||
);
|
||
});
|
||
|
||
window.postMessage(
|
||
{
|
||
source: 'auto-image-tile',
|
||
endpoint: url,
|
||
blobID: blobUUID,
|
||
blobData: originalBlob,
|
||
},
|
||
'*'
|
||
);
|
||
});
|
||
}
|
||
}
|
||
|
||
return response;
|
||
};
|
||
});
|
||
|
||
window.addEventListener('message', (event) => {
|
||
const { source, endpoint, blobID, blobData, token } = event.data;
|
||
|
||
if (source === 'auto-image-tile' && endpoint && blobID && blobData) {
|
||
overlayManager.processAndRespondToTileRequest(event.data);
|
||
}
|
||
|
||
if (source === 'turnstile-capture' && token) {
|
||
setTurnstileToken(token);
|
||
Utils.addDebugLog('Token captured from injection system', 'success');
|
||
updateTokenStatus();
|
||
}
|
||
});
|
||
|
||
// NUEVO SISTEMA WASM - ACTUALIZADO DESDE new.txt
|
||
var pawtect_chunk = null;
|
||
|
||
// Find module if pawtect_chunk is null
|
||
pawtect_chunk ??= await findTokenModule("pawtect_wasm_bg.wasm");
|
||
|
||
async function createWasmToken(regionX, regionY, payload) {
|
||
try {
|
||
// Load the Pawtect module and WASM
|
||
const mod = await import(new URL('/_app/immutable/chunks/'+pawtect_chunk, location.origin).href);
|
||
let wasm;
|
||
try {
|
||
wasm = await mod._();
|
||
console.log('✅ WASM initialized successfully');
|
||
} catch (wasmError) {
|
||
console.error('❌ WASM initialization failed:', wasmError);
|
||
return null;
|
||
}
|
||
try {
|
||
try {
|
||
const me = await fetch(`https://backend.wplace.live/me`, { credentials: 'include' }).then(r => r.ok ? r.json() : null);
|
||
if (me?.id) {
|
||
mod.i(me.id);
|
||
console.log('✅ user ID set:', me.id);
|
||
}
|
||
} catch { }
|
||
} catch (userIdError) {
|
||
console.log('⚠️ Error setting user ID:', userIdError.message);
|
||
}
|
||
try {
|
||
const testUrl = `https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`;
|
||
if (mod.r) {
|
||
mod.r(testUrl);
|
||
console.log('✅ Request URL set:', testUrl);
|
||
} else {
|
||
console.log('⚠️ request_url function (mod.r) not available');
|
||
}
|
||
} catch (urlError) {
|
||
console.log('⚠️ Error setting request URL:', urlError.message);
|
||
}
|
||
|
||
console.log('🔍 payload:', payload);
|
||
|
||
// Encode payload
|
||
const enc = new TextEncoder();
|
||
const dec = new TextDecoder();
|
||
const bodyStr = JSON.stringify(payload);
|
||
const bytes = enc.encode(bodyStr);
|
||
console.log('🔍 Payload size:', bytes.length, 'bytes');
|
||
console.log('🔄 Payload string:', bodyStr);
|
||
|
||
// Allocate WASM memory with validation
|
||
let inPtr;
|
||
try {
|
||
if (!wasm.__wbindgen_malloc) {
|
||
console.error('❌ __wbindgen_malloc function not found');
|
||
return null;
|
||
}
|
||
|
||
inPtr = wasm.__wbindgen_malloc(bytes.length, 1);
|
||
console.log('✅ WASM memory allocated, pointer:', inPtr);
|
||
|
||
// Copy data to WASM memory
|
||
const wasmBuffer = new Uint8Array(wasm.memory.buffer, inPtr, bytes.length);
|
||
wasmBuffer.set(bytes);
|
||
console.log('✅ Data copied to WASM memory');
|
||
} catch (memError) {
|
||
console.error('❌ Memory allocation error:', memError);
|
||
return null;
|
||
}
|
||
|
||
// Call the WASM function
|
||
console.log('🚀 Calling get_pawtected_endpoint_payload...');
|
||
let outPtr, outLen, token;
|
||
try {
|
||
const result = wasm.get_pawtected_endpoint_payload(inPtr, bytes.length);
|
||
console.log('✅ Function called, result type:', typeof result, result);
|
||
|
||
if (Array.isArray(result) && result.length === 2) {
|
||
[outPtr, outLen] = result;
|
||
console.log('✅ Got output pointer:', outPtr, 'length:', outLen);
|
||
|
||
// Decode the result
|
||
const outputBuffer = new Uint8Array(wasm.memory.buffer, outPtr, outLen);
|
||
token = dec.decode(outputBuffer);
|
||
console.log('✅ Token decoded successfully');
|
||
} else {
|
||
console.error('❌ Unexpected function result format:', result);
|
||
return null;
|
||
}
|
||
} catch (funcError) {
|
||
console.error('❌ Function call error:', funcError);
|
||
console.error('Stack trace:', funcError.stack);
|
||
return null;
|
||
}
|
||
|
||
// Cleanup memory
|
||
try {
|
||
if (wasm.__wbindgen_free && outPtr && outLen) {
|
||
wasm.__wbindgen_free(outPtr, outLen, 1);
|
||
console.log('✅ Output memory freed');
|
||
}
|
||
if (wasm.__wbindgen_free && inPtr) {
|
||
wasm.__wbindgen_free(inPtr, bytes.length, 1);
|
||
console.log('✅ Input memory freed');
|
||
}
|
||
} catch (cleanupError) {
|
||
console.log('⚠️ Cleanup warning:', cleanupError.message);
|
||
}
|
||
|
||
console.log('🎉 SUCCESS!');
|
||
console.log('🔑 Full token:', token);
|
||
return token;
|
||
} catch (error) {
|
||
console.error('❌ Failed to generate fp parameter:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function findTokenModule(str) {
|
||
console.log('🔎 Searching for wasm Module...');
|
||
const links = Array.from(document.querySelectorAll('link[rel="modulepreload"][href$=".js"]'));
|
||
|
||
for (const link of links) {
|
||
try {
|
||
const url = new URL(link.getAttribute("href"), location.origin).href;
|
||
const code = await fetch(url).then(r => r.text());
|
||
if (code.includes(str)) {
|
||
console.log('✅ Found wasm Module...');
|
||
return url.split('/').pop();
|
||
}
|
||
} catch (e) { /* ignore individual fetch errors */ }
|
||
}
|
||
console.error('❌ Could not find Pawtect chunk among preloaded modules');
|
||
return null;
|
||
}
|
||
|
||
// Audio notification system
|
||
function playNotificationSound() {
|
||
try {
|
||
const audio = new Audio('https://cdn.pixabay.com/download/audio/2025/03/21/audio_9bec51b17f.mp3?filename=glass-break-316720.mp3');
|
||
audio.volume = 0.5;
|
||
audio.play().catch(() => {
|
||
console.warn('Could not play notification sound');
|
||
});
|
||
} catch (error) {
|
||
console.warn('Audio notification failed:', error);
|
||
}
|
||
}
|
||
|
||
// Notification system for attacks and repairs
|
||
function showAttackNotification(type, count = 0) {
|
||
const existing = document.getElementById('attack-notification');
|
||
if (existing) existing.remove();
|
||
|
||
const notification = document.createElement('div');
|
||
notification.id = 'attack-notification';
|
||
notification.style.cssText = `
|
||
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
||
z-index: 15000; background: rgba(0,0,0,0.9); color: white;
|
||
border-radius: 15px; padding: 20px; text-align: center;
|
||
box-shadow: 0 10px 30px rgba(0,0,0,0.8);
|
||
border: 2px solid ${type === 'attack' ? '#ff4444' : '#44ff44'};
|
||
min-width: 350px; max-width: 450px;
|
||
`;
|
||
|
||
if (type === 'attack') {
|
||
notification.innerHTML = `
|
||
<div style="margin-bottom: 15px;">
|
||
<img src="https://i.imgur.com/uJ2FRUM.png"
|
||
alt="Attack"
|
||
style="width: 80px; height: 80px; border-radius: 10px; object-fit: cover;">
|
||
</div>
|
||
<h2 style="color: #ff4444; margin: 10px 0; font-size: 18px; font-weight: bold;">
|
||
PIXEL ART ATTACKED!
|
||
</h2>
|
||
<p style="margin: 5px 0; font-size: 14px;">
|
||
Detected ${count} damaged pixels!
|
||
</p>
|
||
`;
|
||
} else if (type === 'repaired') {
|
||
notification.innerHTML = `
|
||
<div style="margin-bottom: 15px;">
|
||
<img src="https://i.imgur.com/WXVkpjo.png"
|
||
alt="Repaired"
|
||
style="width: 80px; height: 80px; border-radius: 10px; object-fit: cover;">
|
||
</div>
|
||
<h2 style="color: #44ff44; margin: 10px 0; font-size: 18px; font-weight: bold;">
|
||
COMPLETELY REPAIRED!
|
||
</h2>
|
||
<p style="margin: 5px 0; font-size: 14px;">
|
||
All pixels have been restored!
|
||
</p>
|
||
`;
|
||
}
|
||
|
||
document.body.appendChild(notification);
|
||
|
||
setTimeout(() => {
|
||
if (notification.parentNode) {
|
||
notification.style.transition = 'opacity 0.5s ease';
|
||
notification.style.opacity = '0';
|
||
setTimeout(() => {
|
||
if (notification.parentNode) notification.remove();
|
||
}, 500);
|
||
}
|
||
}, 3000);
|
||
}
|
||
|
||
// Peaceful state display in main window
|
||
function showPeacefulState() {
|
||
const statusDiv = document.getElementById('status');
|
||
if (statusDiv && !state.running) {
|
||
statusDiv.innerHTML = `
|
||
<div style="display: flex; align-items: center; gap: 10px;">
|
||
<img src="https://c.tenor.com/Bo5TLHiee7QAAAAd/tenor.gif"
|
||
alt="Peaceful"
|
||
style="width: 32px; height: 32px; border-radius: 6px;">
|
||
<span style="color: #81c784; font-weight: 500;">No attacks detected - All secure</span>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// Typewriter effect for title
|
||
function createTypewriterTitle() {
|
||
const titleText = "WPlace Autonomous Repair Tool";
|
||
let currentIndex = 0;
|
||
const titleElement = document.getElementById('main-title');
|
||
|
||
if (!titleElement) return;
|
||
|
||
function typeNext() {
|
||
if (currentIndex < titleText.length) {
|
||
titleElement.textContent = titleText.substring(0, currentIndex + 1);
|
||
currentIndex++;
|
||
setTimeout(typeNext, 100);
|
||
} else {
|
||
setTimeout(() => {
|
||
currentIndex = 0;
|
||
titleElement.textContent = '';
|
||
setTimeout(typeNext, 1000);
|
||
}, 3000);
|
||
}
|
||
}
|
||
|
||
typeNext();
|
||
}
|
||
|
||
// Fallback translations
|
||
const TEXTS = {
|
||
title: 'WPlace Autonomous Repair Tool',
|
||
loadFromFile: 'Load Progress File',
|
||
repairPixels: 'Repair Pixels',
|
||
enableAutoRepair: 'Enable Auto Repair',
|
||
repairInterval: 'Check Interval (seconds)',
|
||
debug: 'Debug Console',
|
||
clearDebug: 'Clear Debug',
|
||
scanningForDamage: 'Scanning for damaged pixels...',
|
||
damageDetected: 'Damage detected: {count} pixels',
|
||
noDamageDetected: 'No damage found',
|
||
repairingPixels: 'Repairing {count} damaged pixels...',
|
||
repairComplete: 'Repair completed: {repaired} pixels fixed',
|
||
autoRepairStarted: 'Auto repair started (every {interval}s)',
|
||
autoRepairStopped: 'Auto repair stopped',
|
||
fileLoaded: 'Progress file loaded successfully',
|
||
invalidFile: 'Invalid file format',
|
||
noImageData: 'No image data found in file',
|
||
turnstileInstructions: 'Complete the verification',
|
||
hideTurnstileBtn: 'Hide',
|
||
tokenCapturedSuccess: 'Token captured successfully',
|
||
autonomousModeActive: 'Autonomous mode active',
|
||
tokenSystemReady: 'Advanced token system ready',
|
||
fileOperationsAvailable: 'File operations available',
|
||
initializingToken: 'Initializing token system...',
|
||
tokenReady: 'Token system ready',
|
||
};
|
||
|
||
// UTILIDADES ACTUALIZADAS CON NUEVA IMPLEMENTACIÓN TURNSTILE
|
||
const Utils = {
|
||
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
||
|
||
t: (key, params = {}) => {
|
||
let text = TEXTS[key] || key;
|
||
Object.keys(params).forEach((param) => {
|
||
text = text.replace(`{${param}}`, params[param]);
|
||
});
|
||
return text;
|
||
},
|
||
|
||
showAlert: (message, type = 'info') => {
|
||
const alertDiv = document.createElement('div');
|
||
alertDiv.style.cssText = `
|
||
position: fixed; top: 20px; right: 20px; z-index: 10000;
|
||
padding: 12px 16px; border-radius: 8px; color: white; font-weight: 500;
|
||
background: ${type === 'success' ? '#28a745' : type === 'error' ? '#dc3545' : type === 'warning' ? '#ffc107' : '#17a2b8'};
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||
max-width: 300px; word-wrap: break-word;
|
||
`;
|
||
alertDiv.textContent = message;
|
||
document.body.appendChild(alertDiv);
|
||
|
||
setTimeout(() => {
|
||
alertDiv.style.transition = 'opacity 0.3s ease';
|
||
alertDiv.style.opacity = '0';
|
||
setTimeout(() => {
|
||
if (alertDiv.parentNode) alertDiv.remove();
|
||
}, 300);
|
||
}, 4000);
|
||
},
|
||
|
||
addDebugLog: (message, type = 'info') => {
|
||
const timestamp = new Date().toLocaleTimeString();
|
||
state.debugLogs.push({ timestamp, message, type });
|
||
|
||
if (state.debugLogs.length > 150) {
|
||
state.debugLogs = state.debugLogs.slice(-150);
|
||
}
|
||
|
||
updateDebugConsole();
|
||
console.log(`[${timestamp}] ${message}`);
|
||
},
|
||
|
||
waitForSelector: async (selector, interval = 200, timeout = 5000) => {
|
||
const start = Date.now();
|
||
while (Date.now() - start < timeout) {
|
||
const el = document.querySelector(selector);
|
||
if (el) return el;
|
||
await Utils.sleep(interval);
|
||
}
|
||
return null;
|
||
},
|
||
|
||
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();
|
||
}),
|
||
|
||
isWhitePixel: (r, g, b) => {
|
||
const wt = state.customWhiteThreshold || CONFIG.WHITE_THRESHOLD;
|
||
return r >= wt && g >= wt && b >= wt;
|
||
},
|
||
|
||
resolveColor(targetRgb, availableColors) {
|
||
if (!availableColors || availableColors.length === 0) {
|
||
return { id: null, rgb: targetRgb };
|
||
}
|
||
|
||
let bestId = availableColors[0].id;
|
||
let bestRgb = [...availableColors[0].rgb];
|
||
let bestScore = Infinity;
|
||
|
||
for (let i = 0; i < availableColors.length; i++) {
|
||
const c = availableColors[i];
|
||
const [r, g, b] = c.rgb;
|
||
const rmean = (r + targetRgb[0]) / 2;
|
||
const rdiff = r - targetRgb[0];
|
||
const gdiff = g - targetRgb[1];
|
||
const bdiff = b - targetRgb[2];
|
||
const dist = Math.sqrt(
|
||
(((512 + rmean) * rdiff * rdiff) >> 8) +
|
||
4 * gdiff * gdiff +
|
||
(((767 - rmean) * bdiff * bdiff) >> 8)
|
||
);
|
||
if (dist < bestScore) {
|
||
bestScore = dist;
|
||
bestId = c.id;
|
||
bestRgb = [...c.rgb];
|
||
if (dist === 0) break;
|
||
}
|
||
}
|
||
|
||
return { id: bestId, rgb: bestRgb };
|
||
},
|
||
|
||
calculateTileRange(
|
||
startRegionX,
|
||
startRegionY,
|
||
startPixelX,
|
||
startPixelY,
|
||
width,
|
||
height,
|
||
tileSize = 1000
|
||
) {
|
||
const endPixelX = startPixelX + width;
|
||
const endPixelY = startPixelY + height;
|
||
|
||
return {
|
||
startTileX: startRegionX + Math.floor(startPixelX / tileSize),
|
||
startTileY: startRegionY + Math.floor(startPixelY / tileSize),
|
||
endTileX: startRegionX + Math.floor((endPixelX - 1) / tileSize),
|
||
endTileY: startRegionY + Math.floor((endPixelY - 1) / tileSize),
|
||
};
|
||
},
|
||
|
||
dynamicSleep: async function (tickAndGetRemainingMs) {
|
||
let remaining = Math.max(0, await tickAndGetRemainingMs());
|
||
while (remaining > 0) {
|
||
const interval = remaining > 5000 ? 2000 : remaining > 1000 ? 500 : 100;
|
||
await this.sleep(Math.min(interval, remaining));
|
||
remaining = Math.max(0, await tickAndGetRemainingMs());
|
||
}
|
||
},
|
||
|
||
// NUEVA IMPLEMENTACIÓN TURNSTILE COMPLETA - ACTUALIZADA DESDE new.txt
|
||
turnstileLoaded: false,
|
||
_turnstileContainer: null,
|
||
_turnstileOverlay: null,
|
||
_turnstileWidgetId: null,
|
||
_lastSitekey: null,
|
||
_cachedSitekey: null,
|
||
|
||
async loadTurnstile() {
|
||
if (window.turnstile) {
|
||
this.turnstileLoaded = true;
|
||
return Promise.resolve();
|
||
}
|
||
|
||
return new Promise((resolve, reject) => {
|
||
if (document.querySelector('script[src^="https://challenges.cloudflare.com/turnstile/v0/api.js"]')) {
|
||
const checkReady = () => {
|
||
if (window.turnstile) {
|
||
this.turnstileLoaded = true;
|
||
resolve();
|
||
} else {
|
||
setTimeout(checkReady, 100);
|
||
}
|
||
};
|
||
return checkReady();
|
||
}
|
||
|
||
const script = document.createElement('script');
|
||
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
|
||
script.async = true;
|
||
script.defer = true;
|
||
script.onload = () => {
|
||
this.turnstileLoaded = true;
|
||
Utils.addDebugLog('Turnstile script loaded successfully', 'success');
|
||
resolve();
|
||
};
|
||
script.onerror = () => {
|
||
Utils.addDebugLog('Failed to load Turnstile script', 'error');
|
||
reject(new Error('Failed to load Turnstile'));
|
||
};
|
||
document.head.appendChild(script);
|
||
});
|
||
},
|
||
|
||
ensureTurnstileContainer() {
|
||
if (!this._turnstileContainer || !document.body.contains(this._turnstileContainer)) {
|
||
if (this._turnstileContainer) {
|
||
this._turnstileContainer.remove();
|
||
}
|
||
|
||
this._turnstileContainer = document.createElement('div');
|
||
this._turnstileContainer.className = 'wplace-turnstile-hidden';
|
||
this._turnstileContainer.setAttribute('aria-hidden', 'true');
|
||
this._turnstileContainer.id = 'turnstile-widget-container';
|
||
document.body.appendChild(this._turnstileContainer);
|
||
}
|
||
return this._turnstileContainer;
|
||
},
|
||
|
||
ensureTurnstileOverlayContainer() {
|
||
if (this._turnstileOverlay && document.body.contains(this._turnstileOverlay)) {
|
||
return this._turnstileOverlay;
|
||
}
|
||
|
||
const overlay = document.createElement('div');
|
||
overlay.id = 'turnstile-overlay-container';
|
||
overlay.className = 'wplace-turnstile-overlay wplace-overlay-hidden';
|
||
|
||
const title = document.createElement('div');
|
||
title.textContent = Utils.t('turnstileInstructions');
|
||
title.className = 'wplace-turnstile-title';
|
||
|
||
const host = document.createElement('div');
|
||
host.id = 'turnstile-overlay-host';
|
||
host.className = 'wplace-turnstile-host';
|
||
|
||
const hideBtn = document.createElement('button');
|
||
hideBtn.textContent = Utils.t('hideTurnstileBtn');
|
||
hideBtn.className = 'wplace-turnstile-hide-btn';
|
||
hideBtn.addEventListener('click', () => overlay.remove());
|
||
|
||
overlay.appendChild(title);
|
||
overlay.appendChild(host);
|
||
overlay.appendChild(hideBtn);
|
||
document.body.appendChild(overlay);
|
||
|
||
this._turnstileOverlay = overlay;
|
||
return overlay;
|
||
},
|
||
|
||
async executeTurnstile(sitekey, action = 'paint') {
|
||
await this.loadTurnstile();
|
||
|
||
// Try reusing existing widget first if sitekey matches
|
||
if (this._turnstileWidgetId && this._lastSitekey === sitekey && window.turnstile?.execute) {
|
||
try {
|
||
console.log('🔄 Reusing existing Turnstile widget...');
|
||
const token = await Promise.race([
|
||
window.turnstile.execute(this._turnstileWidgetId, { action }),
|
||
new Promise((_, reject) =>
|
||
setTimeout(() => reject(new Error('Execute timeout')), 15000)
|
||
),
|
||
]);
|
||
if (token && token.length > 20) {
|
||
console.log('✅ Token generated via widget reuse');
|
||
return token;
|
||
}
|
||
} catch (error) {
|
||
console.log(' Widget reuse failed, will create a fresh widget:', error.message);
|
||
}
|
||
}
|
||
|
||
// Try invisible widget first
|
||
const invisibleToken = await this.createTurnstileWidget(sitekey, action);
|
||
if (invisibleToken && invisibleToken.length > 20) {
|
||
return invisibleToken;
|
||
}
|
||
|
||
console.log(' Falling back to interactive Turnstile (visible).');
|
||
return await this.createTurnstileWidgetInteractive(sitekey, action);
|
||
},
|
||
|
||
async createTurnstileWidget(sitekey, action) {
|
||
return new Promise((resolve) => {
|
||
try {
|
||
// Force cleanup of any existing widget
|
||
if (this._turnstileWidgetId && window.turnstile?.remove) {
|
||
try {
|
||
window.turnstile.remove(this._turnstileWidgetId);
|
||
console.log('🧹 Cleaned up existing Turnstile widget');
|
||
} catch (e) {
|
||
console.warn('⚠️ Widget cleanup warning:', e.message);
|
||
}
|
||
}
|
||
|
||
const container = this.ensureTurnstileContainer();
|
||
container.innerHTML = '';
|
||
|
||
// Verify Turnstile is available
|
||
if (!window.turnstile?.render) {
|
||
console.error('❌ Turnstile not available for rendering');
|
||
resolve(null);
|
||
return;
|
||
}
|
||
|
||
console.log('🔧 Creating invisible Turnstile widget...');
|
||
const widgetId = window.turnstile.render(container, {
|
||
sitekey,
|
||
action,
|
||
size: 'invisible',
|
||
retry: 'auto',
|
||
'retry-interval': 8000,
|
||
callback: (token) => {
|
||
console.log('✅ Invisible Turnstile callback');
|
||
resolve(token);
|
||
},
|
||
'error-callback': () => resolve(null),
|
||
'timeout-callback': () => resolve(null),
|
||
});
|
||
|
||
this._turnstileWidgetId = widgetId;
|
||
this._lastSitekey = sitekey;
|
||
|
||
if (!widgetId) {
|
||
return resolve(null);
|
||
}
|
||
|
||
// Execute the widget and race with timeout
|
||
Promise.race([
|
||
window.turnstile.execute(widgetId, { action }),
|
||
new Promise((_, reject) =>
|
||
setTimeout(() => reject(new Error('Invisible execute timeout')), 12000)
|
||
),
|
||
])
|
||
.then(resolve)
|
||
.catch(() => resolve(null));
|
||
} catch (e) {
|
||
console.error('❌ Invisible Turnstile creation failed:', e);
|
||
resolve(null);
|
||
}
|
||
});
|
||
},
|
||
|
||
async createTurnstileWidgetInteractive(sitekey, action) {
|
||
// Create a visible widget that users can interact with if needed
|
||
console.log('🔄 Creating interactive Turnstile widget (visible)');
|
||
|
||
return new Promise((resolve) => {
|
||
try {
|
||
// Force cleanup of any existing widget
|
||
if (this._turnstileWidgetId && window.turnstile?.remove) {
|
||
try {
|
||
window.turnstile.remove(this._turnstileWidgetId);
|
||
} catch (e) {
|
||
console.warn('⚠️ Widget cleanup warning:', e.message);
|
||
}
|
||
}
|
||
|
||
const overlay = this.ensureTurnstileOverlayContainer();
|
||
overlay.classList.remove('wplace-overlay-hidden');
|
||
overlay.style.display = 'block';
|
||
|
||
const host = overlay.querySelector('#turnstile-overlay-host');
|
||
host.innerHTML = '';
|
||
|
||
// Set a timeout for interactive mode
|
||
const timeout = setTimeout(() => {
|
||
console.warn('⏰ Interactive Turnstile widget timeout');
|
||
overlay.classList.add('wplace-overlay-hidden');
|
||
overlay.style.display = 'none';
|
||
resolve(null);
|
||
}, 60000); // 60 seconds for user interaction
|
||
|
||
const widgetId = window.turnstile.render(host, {
|
||
sitekey,
|
||
action,
|
||
size: 'normal',
|
||
theme: 'light',
|
||
callback: (token) => {
|
||
clearTimeout(timeout);
|
||
overlay.classList.add('wplace-overlay-hidden');
|
||
overlay.style.display = 'none';
|
||
console.log('✅ Interactive Turnstile completed successfully');
|
||
|
||
if (typeof token === 'string' && token.length > 20) {
|
||
resolve(token);
|
||
} else {
|
||
console.warn('❌ Invalid token from interactive widget');
|
||
resolve(null);
|
||
}
|
||
},
|
||
'error-callback': (error) => {
|
||
clearTimeout(timeout);
|
||
overlay.classList.add('wplace-overlay-hidden');
|
||
overlay.style.display = 'none';
|
||
console.warn('❌ Interactive Turnstile error:', error);
|
||
resolve(null);
|
||
},
|
||
});
|
||
|
||
this._turnstileWidgetId = widgetId;
|
||
this._lastSitekey = sitekey;
|
||
|
||
if (!widgetId) {
|
||
clearTimeout(timeout);
|
||
overlay.classList.add('wplace-overlay-hidden');
|
||
overlay.style.display = 'none';
|
||
console.warn('❌ Failed to create interactive Turnstile widget');
|
||
resolve(null);
|
||
} else {
|
||
console.log('✅ Interactive Turnstile widget created, waiting for user interaction...');
|
||
}
|
||
} catch (e) {
|
||
console.error('❌ Interactive Turnstile creation failed:', e);
|
||
resolve(null);
|
||
}
|
||
});
|
||
},
|
||
|
||
cleanupTurnstile() {
|
||
if (this._turnstileWidgetId && window.turnstile?.remove) {
|
||
try {
|
||
window.turnstile.remove(this._turnstileWidgetId);
|
||
} catch (e) {
|
||
console.warn('Failed to cleanup Turnstile widget:', e);
|
||
}
|
||
}
|
||
|
||
if (this._turnstileContainer && document.body.contains(this._turnstileContainer)) {
|
||
this._turnstileContainer.remove();
|
||
}
|
||
|
||
if (this._turnstileOverlay && document.body.contains(this._turnstileOverlay)) {
|
||
this._turnstileOverlay.remove();
|
||
}
|
||
|
||
this._turnstileWidgetId = null;
|
||
this._turnstileContainer = null;
|
||
this._turnstileOverlay = null;
|
||
this._lastSitekey = null;
|
||
},
|
||
|
||
// DETECCIÓN DE SITEKEY MEJORADA - ACTUALIZADA DESDE new.txt
|
||
async obtainSitekeyAndToken(fallback = '0x4AAAAAABpqJe8FO0N84q0F') {
|
||
// Cache sitekey to avoid repeated DOM queries
|
||
if (this._cachedSitekey) {
|
||
console.log('🔍 Using cached sitekey:', this._cachedSitekey);
|
||
|
||
return isTokenValid()
|
||
? {
|
||
sitekey: this._cachedSitekey,
|
||
token: turnstileToken,
|
||
}
|
||
: { sitekey: this._cachedSitekey, token: null };
|
||
}
|
||
|
||
// List of potential sitekeys to try
|
||
const potentialSitekeys = [
|
||
'0x4AAAAAABpqJe8FO0N84q0F', // WPlace common sitekey
|
||
'0x4AAAAAAAJ7xjKAp6Mt_7zw', // Alternative WPlace sitekey
|
||
'0x4AAAAAADm5QWx6Ov2LNF2g', // Another common sitekey
|
||
];
|
||
const trySitekey = async (sitekey, source) => {
|
||
if (!sitekey || sitekey.length < 10) return null;
|
||
|
||
console.log(`🔍 Testing sitekey from ${source}:`, sitekey);
|
||
const token = await this.executeTurnstile(sitekey);
|
||
|
||
if (token && token.length >= 20) {
|
||
console.log(`✅ Valid token generated from ${source} sitekey`);
|
||
setTurnstileToken(token);
|
||
this._cachedSitekey = sitekey;
|
||
return { sitekey, token };
|
||
} else {
|
||
console.log(`❌ Failed to get token from ${source} sitekey`);
|
||
return null;
|
||
}
|
||
};
|
||
|
||
try {
|
||
// 1️⃣ data-sitekey attribute
|
||
const sitekeySel = document.querySelector('[data-sitekey]');
|
||
if (sitekeySel) {
|
||
const sitekey = sitekeySel.getAttribute('data-sitekey');
|
||
const result = await trySitekey(sitekey, 'data attribute');
|
||
if (result) {
|
||
return result;
|
||
}
|
||
}
|
||
|
||
// 2️⃣ Turnstile element
|
||
const turnstileEl = document.querySelector('.cf-turnstile');
|
||
if (turnstileEl?.dataset?.sitekey) {
|
||
const sitekey = turnstileEl.dataset.sitekey;
|
||
const result = await trySitekey(sitekey, 'turnstile element');
|
||
if (result) {
|
||
return result;
|
||
}
|
||
}
|
||
|
||
// 3️⃣ Meta tags
|
||
const metaTags = document.querySelectorAll(
|
||
'meta[name*="turnstile"], meta[property*="turnstile"]'
|
||
);
|
||
for (const meta of metaTags) {
|
||
const content = meta.getAttribute('content');
|
||
const result = await trySitekey(content, 'meta tag');
|
||
if (result) {
|
||
return result;
|
||
}
|
||
}
|
||
|
||
// 4️⃣ Global variable
|
||
if (window.__TURNSTILE_SITEKEY) {
|
||
const result = await trySitekey(window.__TURNSTILE_SITEKEY, 'global variable');
|
||
if (result) {
|
||
return result;
|
||
}
|
||
}
|
||
|
||
// 5️⃣ Script tags
|
||
const scripts = document.querySelectorAll('script');
|
||
for (const script of scripts) {
|
||
const content = script.textContent || script.innerHTML;
|
||
const match = content.match(
|
||
/(?:sitekey|data-sitekey)['"\s\[\]:\=\(]*['"]?([0-9a-zA-Z_-]{20,})['"]?/i
|
||
);
|
||
if (match && match[1]) {
|
||
const extracted = match[1].replace(/['"]/g, '');
|
||
const result = await trySitekey(extracted, 'script content');
|
||
if (result) {
|
||
return result;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 6️⃣ Known potential sitekeys
|
||
console.log('🔍 Testing known potential sitekeys...');
|
||
for (const testSitekey of potentialSitekeys) {
|
||
const result = await trySitekey(testSitekey, 'known list');
|
||
if (result) {
|
||
return result;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.warn('⚠️ Error during sitekey detection:', error);
|
||
}
|
||
|
||
// 7️⃣ Fallback
|
||
console.log('🔧 Trying fallback sitekey:', fallback);
|
||
const fallbackResult = await trySitekey(fallback, 'fallback');
|
||
if (fallbackResult) {
|
||
return fallbackResult;
|
||
}
|
||
|
||
console.error('❌ No working sitekey or token found.');
|
||
return { sitekey: null, token: null };
|
||
},
|
||
};
|
||
|
||
// Enhanced Overlay Manager for pixel detection with autonomous capabilities
|
||
class OverlayManager {
|
||
constructor() {
|
||
this.originalTiles = new Map();
|
||
this.originalTilesData = new Map();
|
||
this.tileSize = 1000;
|
||
this.loadingPromises = new Map();
|
||
this.autonomousMode = state.autonomousMode;
|
||
}
|
||
|
||
async processAndRespondToTileRequest(eventData) {
|
||
const { endpoint, blobID, blobData } = eventData;
|
||
|
||
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}`;
|
||
|
||
try {
|
||
const originalBitmap = await createImageBitmap(blobData);
|
||
this.originalTiles.set(tileKey, originalBitmap);
|
||
|
||
try {
|
||
let canvas, ctx;
|
||
if (typeof OffscreenCanvas !== 'undefined') {
|
||
canvas = new OffscreenCanvas(originalBitmap.width, originalBitmap.height);
|
||
ctx = canvas.getContext('2d');
|
||
} else {
|
||
canvas = document.createElement('canvas');
|
||
canvas.width = originalBitmap.width;
|
||
canvas.height = originalBitmap.height;
|
||
ctx = canvas.getContext('2d');
|
||
}
|
||
ctx.imageSmoothingEnabled = false;
|
||
ctx.drawImage(originalBitmap, 0, 0);
|
||
const imgData = ctx.getImageData(0, 0, originalBitmap.width, originalBitmap.height);
|
||
|
||
this.originalTilesData.set(tileKey, {
|
||
w: originalBitmap.width,
|
||
h: originalBitmap.height,
|
||
data: new Uint8ClampedArray(imgData.data),
|
||
});
|
||
|
||
if (this.autonomousMode) {
|
||
Utils.addDebugLog(`Auto-cached tile: ${tileKey} (${originalBitmap.width}x${originalBitmap.height})`, 'info');
|
||
}
|
||
} catch (e) {
|
||
Utils.addDebugLog(`Failed to cache tile ImageData: ${tileKey} - ${e.message}`, 'warning');
|
||
}
|
||
} catch (e) {
|
||
Utils.addDebugLog(`Failed to create tile bitmap: ${tileKey} - ${e.message}`, 'error');
|
||
}
|
||
}
|
||
|
||
window.postMessage(
|
||
{
|
||
source: 'auto-image-overlay',
|
||
blobID: blobID,
|
||
blobData: blobData,
|
||
},
|
||
'*'
|
||
);
|
||
}
|
||
|
||
async getTilePixelColor(tileX, tileY, pixelX, pixelY) {
|
||
const tileKey = `${tileX},${tileY}`;
|
||
const alphaThresh = state.customTransparencyThreshold || CONFIG.TRANSPARENCY_THRESHOLD;
|
||
|
||
const cached = this.originalTilesData.get(tileKey);
|
||
if (cached && cached.data && cached.w > 0 && cached.h > 0) {
|
||
const x = Math.max(0, Math.min(cached.w - 1, pixelX));
|
||
const y = Math.max(0, Math.min(cached.h - 1, pixelY));
|
||
const idx = (y * cached.w + x) * 4;
|
||
const d = cached.data;
|
||
const a = d[idx + 3];
|
||
|
||
// Always return pixel data, let caller decide about transparency
|
||
return [d[idx], d[idx + 1], d[idx + 2], a];
|
||
}
|
||
|
||
const bitmap = this.originalTiles.get(tileKey);
|
||
if (!bitmap) {
|
||
if (this.autonomousMode) {
|
||
Utils.addDebugLog(`Tile ${tileKey} not available, requesting load...`, 'warning');
|
||
}
|
||
return null;
|
||
}
|
||
|
||
try {
|
||
let canvas, ctx;
|
||
if (typeof OffscreenCanvas !== 'undefined') {
|
||
canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
|
||
ctx = canvas.getContext('2d');
|
||
} else {
|
||
canvas = document.createElement('canvas');
|
||
canvas.width = bitmap.width;
|
||
canvas.height = bitmap.height;
|
||
ctx = canvas.getContext('2d');
|
||
}
|
||
ctx.imageSmoothingEnabled = false;
|
||
ctx.drawImage(bitmap, 0, 0);
|
||
|
||
const x = Math.max(0, Math.min(bitmap.width - 1, pixelX));
|
||
const y = Math.max(0, Math.min(bitmap.height - 1, pixelY));
|
||
const data = ctx.getImageData(x, y, 1, 1).data;
|
||
const a = data[3];
|
||
|
||
// Always return pixel data, let caller decide about transparency
|
||
return [data[0], data[1], data[2], a];
|
||
} catch (e) {
|
||
Utils.addDebugLog(`Error reading pixel from tile ${tileKey}: ${e.message}`, 'error');
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async waitForTiles(startRegionX, startRegionY, pixelWidth, pixelHeight, startPixelX = 0, startPixelY = 0, timeoutMs = 15000) {
|
||
const { startTileX, startTileY, endTileX, endTileY } = Utils.calculateTileRange(
|
||
startRegionX,
|
||
startRegionY,
|
||
startPixelX,
|
||
startPixelY,
|
||
pixelWidth,
|
||
pixelHeight,
|
||
this.tileSize
|
||
);
|
||
|
||
const requiredTiles = [];
|
||
for (let ty = startTileY; ty <= endTileY; ty++) {
|
||
for (let tx = startTileX; tx <= endTileX; tx++) {
|
||
requiredTiles.push(`${tx},${ty}`);
|
||
}
|
||
}
|
||
|
||
if (requiredTiles.length === 0) return true;
|
||
|
||
Utils.addDebugLog(`Waiting for ${requiredTiles.length} tiles (autonomous: ${this.autonomousMode})...`, 'info');
|
||
|
||
const startTime = Date.now();
|
||
let lastProgress = 0;
|
||
|
||
while (Date.now() - startTime < timeoutMs) {
|
||
if (state.stopFlag) {
|
||
Utils.addDebugLog('waitForTiles: stopped by user', 'warning');
|
||
return false;
|
||
}
|
||
|
||
const loaded = requiredTiles.filter((k) => this.originalTiles.has(k)).length;
|
||
const progress = Math.round((loaded / requiredTiles.length) * 100);
|
||
|
||
if (progress !== lastProgress && progress % 20 === 0) {
|
||
Utils.addDebugLog(`Tile loading progress: ${loaded}/${requiredTiles.length} (${progress}%)`, 'info');
|
||
lastProgress = progress;
|
||
}
|
||
|
||
if (loaded === requiredTiles.length) {
|
||
Utils.addDebugLog(`All ${requiredTiles.length} required tiles are loaded`, 'success');
|
||
return true;
|
||
}
|
||
|
||
await Utils.sleep(this.autonomousMode ? 500 : 1000);
|
||
}
|
||
|
||
const loaded = requiredTiles.filter((k) => this.originalTiles.has(k)).length;
|
||
Utils.addDebugLog(`Timeout waiting for tiles: ${loaded}/${requiredTiles.length} loaded`, 'warning');
|
||
|
||
if (this.autonomousMode && loaded > requiredTiles.length * 0.8) {
|
||
Utils.addDebugLog(`Autonomous mode: proceeding with ${loaded}/${requiredTiles.length} tiles (80%+ loaded)`, 'warning');
|
||
return true;
|
||
}
|
||
|
||
return loaded > 0;
|
||
}
|
||
}
|
||
|
||
const overlayManager = new OverlayManager();
|
||
|
||
// Enhanced WPlace API Service with auto-batch functionality
|
||
const WPlaceService = {
|
||
async paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount = 0) {
|
||
try {
|
||
await ensureToken();
|
||
if (!turnstileToken) {
|
||
Utils.addDebugLog('No valid token available for paint request', 'error');
|
||
return 'token_error';
|
||
}
|
||
|
||
const payload = {
|
||
coords: [pixelX, pixelY],
|
||
colors: [color],
|
||
t: turnstileToken,
|
||
fp: fpStr32,
|
||
};
|
||
|
||
const wasmToken = await createWasmToken(regionX, regionY, payload);
|
||
if (!wasmToken) {
|
||
Utils.addDebugLog('Failed to generate WASM token', 'error');
|
||
return false;
|
||
}
|
||
|
||
const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'text/plain;charset=UTF-8',
|
||
'x-pawtect-token': wasmToken
|
||
},
|
||
credentials: 'include',
|
||
body: JSON.stringify(payload),
|
||
});
|
||
|
||
if (res.status === 403) {
|
||
Utils.addDebugLog(`403 Forbidden for pixel (${pixelX},${pixelY}). Token invalid/expired.`, 'error');
|
||
invalidateToken();
|
||
|
||
if (retryCount < 2 && state.autonomousMode) {
|
||
Utils.addDebugLog(`Autonomous retry ${retryCount + 1}/2 for pixel (${pixelX},${pixelY})`, 'warning');
|
||
await Utils.sleep(1000);
|
||
return await this.paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount + 1);
|
||
}
|
||
|
||
return 'token_error';
|
||
}
|
||
|
||
if (!res.ok) {
|
||
Utils.addDebugLog(`Paint request failed with status ${res.status}`, 'error');
|
||
return false;
|
||
}
|
||
|
||
const data = await res.json();
|
||
const success = data?.painted === 1;
|
||
|
||
if (success) {
|
||
Utils.addDebugLog(`Paint SUCCESS for (${pixelX},${pixelY}) with color ${color}`, 'success');
|
||
} else {
|
||
Utils.addDebugLog(`Paint FAILED for (${pixelX},${pixelY}) - server response: ${JSON.stringify(data)}`, 'error');
|
||
}
|
||
|
||
return success;
|
||
} catch (e) {
|
||
Utils.addDebugLog(`Paint request error for (${pixelX},${pixelY}): ${e.message}`, 'error');
|
||
return false;
|
||
}
|
||
},
|
||
|
||
// NEW: Auto-batch pixel painting functionality
|
||
async paintPixelBatchInRegion(regionX, regionY, pixelBatch, retryCount = 0) {
|
||
try {
|
||
await ensureToken();
|
||
if (!turnstileToken) {
|
||
Utils.addDebugLog('No valid token available for batch paint request', 'error');
|
||
return 'token_error';
|
||
}
|
||
|
||
// Prepare batch coordinates and colors
|
||
const coords = [];
|
||
const colors = [];
|
||
|
||
for (const pixel of pixelBatch) {
|
||
coords.push(pixel.pixelX, pixel.pixelY);
|
||
colors.push(pixel.color);
|
||
}
|
||
|
||
const payload = {
|
||
coords: coords,
|
||
colors: colors,
|
||
t: turnstileToken,
|
||
fp: fpStr32,
|
||
};
|
||
|
||
const wasmToken = await createWasmToken(regionX, regionY, payload);
|
||
if (!wasmToken) {
|
||
Utils.addDebugLog('Failed to generate WASM token for batch', 'error');
|
||
return false;
|
||
}
|
||
|
||
const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'text/plain;charset=UTF-8',
|
||
'x-pawtect-token': wasmToken
|
||
},
|
||
credentials: 'include',
|
||
body: JSON.stringify(payload),
|
||
});
|
||
|
||
if (res.status === 403) {
|
||
Utils.addDebugLog(`403 Forbidden for batch (${pixelBatch.length} pixels). Token invalid/expired.`, 'error');
|
||
invalidateToken();
|
||
|
||
if (retryCount < 2 && state.autonomousMode) {
|
||
Utils.addDebugLog(`Autonomous batch retry ${retryCount + 1}/2 for ${pixelBatch.length} pixels`, 'warning');
|
||
await Utils.sleep(1000);
|
||
return await this.paintPixelBatchInRegion(regionX, regionY, pixelBatch, retryCount + 1);
|
||
}
|
||
|
||
return 'token_error';
|
||
}
|
||
|
||
if (!res.ok) {
|
||
Utils.addDebugLog(`Batch paint request failed with status ${res.status}`, 'error');
|
||
return false;
|
||
}
|
||
|
||
const data = await res.json();
|
||
const successCount = data?.painted || 0;
|
||
|
||
Utils.addDebugLog(`Batch paint result: ${successCount}/${pixelBatch.length} pixels painted successfully`,
|
||
successCount === pixelBatch.length ? 'success' : 'warning');
|
||
|
||
return {
|
||
success: successCount > 0,
|
||
painted: successCount,
|
||
total: pixelBatch.length
|
||
};
|
||
|
||
} catch (e) {
|
||
Utils.addDebugLog(`Batch paint request error for ${pixelBatch.length} pixels: ${e.message}`, 'error');
|
||
return false;
|
||
}
|
||
},
|
||
|
||
async getCharges() {
|
||
try {
|
||
const res = await fetch('https://backend.wplace.live/me', {
|
||
credentials: 'include',
|
||
});
|
||
if (!res.ok) return { charges: 0, max: 1, cooldown: CONFIG.COOLDOWN_DEFAULT };
|
||
const data = await res.json();
|
||
return {
|
||
charges: data.charges?.count ?? 0,
|
||
max: data.charges?.max ?? 1,
|
||
cooldown: data.charges?.cooldownMs ?? CONFIG.COOLDOWN_DEFAULT,
|
||
};
|
||
} catch (e) {
|
||
Utils.addDebugLog(`Error fetching charges: ${e.message}`, 'warning');
|
||
return { charges: 0, max: 1, cooldown: CONFIG.COOLDOWN_DEFAULT };
|
||
}
|
||
},
|
||
};
|
||
|
||
// Enhanced anti-grief repair system with improved transparent pixel detection
|
||
async function scanForDamage() {
|
||
if (!state.imageData || !state.startPosition || !state.region) {
|
||
Utils.addDebugLog('No image data or position for scanning', 'warning');
|
||
return [];
|
||
}
|
||
|
||
Utils.addDebugLog('Starting enhanced damage scan with transparent pixel detection...', 'info');
|
||
updateStatus(Utils.t('scanningForDamage'));
|
||
|
||
const damagedPixels = [];
|
||
const { width, height, pixels } = state.imageData;
|
||
|
||
// Check if we're working with restored data - skip tile waiting for restored saves
|
||
const isRestoredData = state.availableColors && state.availableColors.length > 0 && state.colorsChecked;
|
||
|
||
if (!isRestoredData) {
|
||
const ready = await overlayManager.waitForTiles(
|
||
state.region.x,
|
||
state.region.y,
|
||
width,
|
||
height,
|
||
state.startPosition.x,
|
||
state.startPosition.y,
|
||
state.autonomousMode ? 20000 : 15000
|
||
);
|
||
|
||
if (!ready) {
|
||
Utils.addDebugLog('Failed to load required tiles for scanning', 'error');
|
||
if (state.autonomousMode) {
|
||
Utils.addDebugLog('Autonomous mode: will retry scan in 30 seconds', 'warning');
|
||
setTimeout(() => {
|
||
if (state.autoRepairEnabled && !state.stopFlag) {
|
||
scanForDamage();
|
||
}
|
||
}, 30000);
|
||
}
|
||
return [];
|
||
}
|
||
} else {
|
||
Utils.addDebugLog('Using restored data - skipping tile wait for damage scan', 'info');
|
||
}
|
||
|
||
Utils.addDebugLog(`Scanning ${width}x${height} image for damage (transparent detection: ${state.paintTransparentPixels})...`, 'info');
|
||
|
||
let scannedPixels = 0;
|
||
let transparentPixelsDetected = 0;
|
||
let wrongColorPixelsDetected = 0;
|
||
let lastProgressUpdate = Date.now();
|
||
|
||
for (let y = 0; y < height; y++) {
|
||
for (let x = 0; x < width; x++) {
|
||
if (state.stopFlag) break;
|
||
|
||
const idx = (y * width + x) * 4;
|
||
const originalR = pixels[idx];
|
||
const originalG = pixels[idx + 1];
|
||
const originalB = pixels[idx + 2];
|
||
const originalA = pixels[idx + 3];
|
||
|
||
// Enhanced transparent pixel detection
|
||
const isOriginalTransparent = originalA < state.customTransparencyThreshold;
|
||
|
||
// Skip white pixels if configured
|
||
if (!state.paintWhitePixels && Utils.isWhitePixel(originalR, originalG, originalB)) {
|
||
continue;
|
||
}
|
||
|
||
scannedPixels++;
|
||
|
||
const absX = state.startPosition.x + x;
|
||
const absY = state.startPosition.y + y;
|
||
const tileX = state.region.x + Math.floor(absX / 1000);
|
||
const tileY = state.region.y + Math.floor(absY / 1000);
|
||
const pixelX = absX % 1000;
|
||
const pixelY = absY % 1000;
|
||
|
||
try {
|
||
const currentPixel = await overlayManager.getTilePixelColor(tileX, tileY, pixelX, pixelY);
|
||
|
||
// Handle missing current pixel data (could indicate transparent area)
|
||
if (!currentPixel) {
|
||
if (state.paintTransparentPixels && !isOriginalTransparent) {
|
||
// Original pixel is not transparent but current is missing/transparent
|
||
const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors);
|
||
damagedPixels.push({
|
||
x,
|
||
y,
|
||
originalColor: targetColor,
|
||
currentColor: { id: 63, name: 'Transparent', rgb: [0, 0, 0] }, // Transparent color
|
||
originalRgb: [originalR, originalG, originalB],
|
||
currentRgb: [0, 0, 0],
|
||
isDamagedTransparent: true
|
||
});
|
||
transparentPixelsDetected++;
|
||
|
||
if (!state.autonomousMode || transparentPixelsDetected <= 10) {
|
||
Utils.addDebugLog(`Missing/Transparent pixel at (${x},${y}): expected color ${targetColor.id}, found transparent/missing`, 'warning');
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
|
||
const currentIsTransparent = currentPixel[3] < state.customTransparencyThreshold;
|
||
|
||
// Case 1: Original is not transparent, but current is transparent (damaged)
|
||
if (!isOriginalTransparent && currentIsTransparent && state.paintTransparentPixels) {
|
||
const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors);
|
||
damagedPixels.push({
|
||
x,
|
||
y,
|
||
originalColor: targetColor,
|
||
currentColor: { id: 63, name: 'Transparent', rgb: [0, 0, 0] },
|
||
originalRgb: [originalR, originalG, originalB],
|
||
currentRgb: currentPixel.slice(0, 3),
|
||
isDamagedTransparent: true
|
||
});
|
||
transparentPixelsDetected++;
|
||
|
||
if (!state.autonomousMode || transparentPixelsDetected <= 10) {
|
||
Utils.addDebugLog(`Transparent damage at (${x},${y}): expected color ${targetColor.id}, found transparent`, 'warning');
|
||
}
|
||
continue;
|
||
}
|
||
|
||
// Case 2: Original is transparent, current is not transparent (wrong placement)
|
||
if (isOriginalTransparent && !currentIsTransparent && state.paintTransparentPixels) {
|
||
damagedPixels.push({
|
||
x,
|
||
y,
|
||
originalColor: { id: 63, name: 'Transparent', rgb: null },
|
||
currentColor: Utils.resolveColor(currentPixel.slice(0, 3), state.availableColors),
|
||
originalRgb: [originalR, originalG, originalB],
|
||
currentRgb: currentPixel.slice(0, 3),
|
||
isDamagedTransparent: true
|
||
});
|
||
transparentPixelsDetected++;
|
||
|
||
if (!state.autonomousMode || transparentPixelsDetected <= 10) {
|
||
Utils.addDebugLog(`Wrong placement at (${x},${y}): expected transparent, found color`, 'warning');
|
||
}
|
||
continue;
|
||
}
|
||
|
||
// Case 3: Both are not transparent, check color match (normal damage detection)
|
||
if (!isOriginalTransparent && !currentIsTransparent) {
|
||
const targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors);
|
||
const currentColor = Utils.resolveColor(currentPixel.slice(0, 3), state.availableColors);
|
||
|
||
if (targetColor.id === currentColor.id) {
|
||
// Pixel is already correctly painted - skip it
|
||
continue;
|
||
}
|
||
|
||
// Pixel has wrong color - mark as damaged
|
||
damagedPixels.push({
|
||
x,
|
||
y,
|
||
originalColor: targetColor,
|
||
currentColor: currentColor,
|
||
originalRgb: [originalR, originalG, originalB],
|
||
currentRgb: currentPixel.slice(0, 3),
|
||
isDamagedTransparent: false
|
||
});
|
||
wrongColorPixelsDetected++;
|
||
|
||
if (!state.autonomousMode || wrongColorPixelsDetected <= 10) {
|
||
Utils.addDebugLog(`Color damage at (${x},${y}): expected color ${targetColor.id}, found color ${currentColor.id}`, 'warning');
|
||
}
|
||
}
|
||
|
||
} catch (e) {
|
||
if (!state.autonomousMode) {
|
||
Utils.addDebugLog(`Error checking pixel (${x},${y}): ${e.message}`, 'error');
|
||
}
|
||
}
|
||
}
|
||
|
||
if (state.autonomousMode && Date.now() - lastProgressUpdate > 5000) {
|
||
Utils.addDebugLog(`Scan progress: ${y}/${height} rows (${Math.round((y / height) * 100)}%), found ${damagedPixels.length} damaged (${transparentPixelsDetected} transparent, ${wrongColorPixelsDetected} wrong color)`, 'info');
|
||
lastProgressUpdate = Date.now();
|
||
} else if (!state.autonomousMode && y % 10 === 0) {
|
||
Utils.addDebugLog(`Scan progress: ${y}/${height} rows (${Math.round((y / height) * 100)}%)`, 'info');
|
||
}
|
||
}
|
||
|
||
const logLevel = damagedPixels.length > 0 ? 'warning' : 'success';
|
||
Utils.addDebugLog(`Enhanced scan complete. Checked ${scannedPixels} pixels, found ${damagedPixels.length} damaged (${transparentPixelsDetected} transparent issues, ${wrongColorPixelsDetected} wrong colors)`, logLevel);
|
||
|
||
// Smart auto-adjust batch size: if 6 pixels detected, batch size = 6
|
||
if (state.autoBatchEnabled && state.batchOptimization && damagedPixels.length > 0) {
|
||
const newBatchSize = Math.min(CONFIG.MAX_BATCH_SIZE, Math.max(CONFIG.MIN_BATCH_SIZE, damagedPixels.length));
|
||
if (newBatchSize !== state.currentBatchSize) {
|
||
state.currentBatchSize = newBatchSize;
|
||
Utils.addDebugLog(`Smart batch: Auto-adjusted batch size to ${newBatchSize} to match ${damagedPixels.length} detected damaged pixels`, 'info');
|
||
updateBatchInfo(); // Update UI display
|
||
}
|
||
}
|
||
|
||
return damagedPixels;
|
||
}
|
||
|
||
async function repairDamagedPixels(damagedPixels) {
|
||
if (damagedPixels.length === 0) {
|
||
updateStatus(Utils.t('noDamageDetected'));
|
||
return 0;
|
||
}
|
||
|
||
Utils.addDebugLog(`Starting enhanced repair of ${damagedPixels.length} pixels with auto-batch (batch size: ${state.currentBatchSize})`, 'info');
|
||
updateStatus(Utils.t('repairingPixels', { count: damagedPixels.length }));
|
||
|
||
let repairedCount = 0;
|
||
let consecutiveFailures = 0;
|
||
const maxConsecutiveFailures = state.autonomousMode ? 5 : 3;
|
||
|
||
// Group pixels by region for batch processing
|
||
const pixelsByRegion = new Map();
|
||
|
||
for (const pixel of damagedPixels) {
|
||
const absX = state.startPosition.x + pixel.x;
|
||
const absY = state.startPosition.y + pixel.y;
|
||
const regionX = state.region.x + Math.floor(absX / 1000);
|
||
const regionY = state.region.y + Math.floor(absY / 1000);
|
||
const regionKey = `${regionX},${regionY}`;
|
||
|
||
if (!pixelsByRegion.has(regionKey)) {
|
||
pixelsByRegion.set(regionKey, []);
|
||
}
|
||
|
||
pixelsByRegion.get(regionKey).push({
|
||
...pixel,
|
||
regionX,
|
||
regionY,
|
||
pixelX: absX % 1000,
|
||
pixelY: absY % 1000,
|
||
color: pixel.isDamagedTransparent && pixel.originalColor.id === 63 ? 63 : pixel.originalColor.id
|
||
});
|
||
}
|
||
|
||
Utils.addDebugLog(`Grouped ${damagedPixels.length} pixels into ${pixelsByRegion.size} regions for batch processing`, 'info');
|
||
|
||
// Process each region
|
||
for (const [regionKey, regionPixels] of pixelsByRegion) {
|
||
if (state.stopFlag) {
|
||
Utils.addDebugLog('Repair stopped by user request', 'warning');
|
||
break;
|
||
}
|
||
|
||
const [regionX, regionY] = regionKey.split(',').map(Number);
|
||
Utils.addDebugLog(`Processing region ${regionKey} with ${regionPixels.length} pixels`, 'info');
|
||
|
||
// Process pixels in batches
|
||
for (let i = 0; i < regionPixels.length; i += state.currentBatchSize) {
|
||
if (state.stopFlag) break;
|
||
|
||
const batch = regionPixels.slice(i, i + state.currentBatchSize);
|
||
const actualBatchSize = Math.min(batch.length, state.currentBatchSize);
|
||
|
||
// Enhanced charge management for batch processing
|
||
await updateCharges();
|
||
let chargeWaitAttempts = 0;
|
||
const maxChargeWaitAttempts = state.autonomousMode ? 20 : 10;
|
||
|
||
while (state.displayCharges < actualBatchSize && !state.stopFlag && chargeWaitAttempts < maxChargeWaitAttempts) {
|
||
chargeWaitAttempts++;
|
||
const waitTime = state.autonomousMode ? Math.min(state.cooldown, 10000) : state.cooldown;
|
||
|
||
if (chargeWaitAttempts === 1) {
|
||
Utils.addDebugLog(`Waiting for charges... (need ${actualBatchSize}, have ${state.displayCharges}/${state.maxCharges})`, 'info');
|
||
}
|
||
|
||
await Utils.dynamicSleep(() => {
|
||
if (state.displayCharges >= actualBatchSize) return 0;
|
||
if (state.stopFlag) return 0;
|
||
return waitTime;
|
||
});
|
||
await updateCharges();
|
||
}
|
||
|
||
if (state.stopFlag) break;
|
||
|
||
if (state.displayCharges < actualBatchSize) {
|
||
Utils.addDebugLog(`Insufficient charges for batch (need ${actualBatchSize}, have ${state.displayCharges}), falling back to single pixel repair`, 'warning');
|
||
|
||
// Fall back to single pixel repair
|
||
for (const pixel of batch) {
|
||
if (state.stopFlag) break;
|
||
|
||
await updateCharges();
|
||
if (state.displayCharges < 1) {
|
||
Utils.addDebugLog(`No charges available, skipping pixel (${pixel.x},${pixel.y})`, 'warning');
|
||
continue;
|
||
}
|
||
|
||
const success = await repairSinglePixel(pixel);
|
||
if (success) {
|
||
repairedCount++;
|
||
consecutiveFailures = 0;
|
||
} else {
|
||
consecutiveFailures++;
|
||
}
|
||
|
||
if (consecutiveFailures >= maxConsecutiveFailures) break;
|
||
await Utils.sleep(100);
|
||
}
|
||
} else {
|
||
// Use batch repair
|
||
const batchResult = await repairPixelBatch(regionX, regionY, batch);
|
||
|
||
if (batchResult && batchResult.success) {
|
||
repairedCount += batchResult.painted;
|
||
consecutiveFailures = 0;
|
||
|
||
Utils.addDebugLog(`Batch repair: ${batchResult.painted}/${batchResult.total} pixels repaired successfully [Total: ${repairedCount}/${damagedPixels.length}]`, 'success');
|
||
} else {
|
||
consecutiveFailures++;
|
||
Utils.addDebugLog(`Batch repair failed for ${batch.length} pixels [${consecutiveFailures}/${maxConsecutiveFailures} consecutive failures]`, 'error');
|
||
|
||
if (consecutiveFailures >= maxConsecutiveFailures) {
|
||
Utils.addDebugLog(`Too many consecutive batch failures (${consecutiveFailures}), pausing repair`, 'error');
|
||
if (state.autonomousMode) {
|
||
Utils.addDebugLog('Autonomous mode: will retry repair in 60 seconds', 'warning');
|
||
setTimeout(() => {
|
||
if (state.autoRepairEnabled && !state.stopFlag) {
|
||
performRepairCheck();
|
||
}
|
||
}, 60000);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
await updateCharges();
|
||
|
||
// Dynamic delay based on mode and success rate
|
||
const baseDelay = state.autonomousMode ? 100 : 200;
|
||
const adaptiveDelay = consecutiveFailures > 0 ? baseDelay * (consecutiveFailures + 1) : baseDelay;
|
||
await Utils.sleep(adaptiveDelay);
|
||
}
|
||
|
||
if (consecutiveFailures >= maxConsecutiveFailures) break;
|
||
}
|
||
|
||
const message = Utils.t('repairComplete', { repaired: repairedCount });
|
||
updateStatus(message);
|
||
Utils.addDebugLog(`${message} (${damagedPixels.length - repairedCount} remaining) - Used auto-batch with size ${state.currentBatchSize}`, 'success');
|
||
|
||
return repairedCount;
|
||
}
|
||
|
||
async function repairPixelBatch(regionX, regionY, pixelBatch) {
|
||
try {
|
||
Utils.addDebugLog(`Attempting batch repair of ${pixelBatch.length} pixels in region ${regionX},${regionY}`, 'info');
|
||
|
||
const result = await WPlaceService.paintPixelBatchInRegion(regionX, regionY, pixelBatch);
|
||
|
||
if (result === 'token_error') {
|
||
Utils.addDebugLog('Token error during batch repair, refreshing token...', 'warning');
|
||
await ensureToken(true);
|
||
await Utils.sleep(state.autonomousMode ? 500 : 1000);
|
||
|
||
// Retry once with new token
|
||
const retryResult = await WPlaceService.paintPixelBatchInRegion(regionX, regionY, pixelBatch);
|
||
return retryResult;
|
||
}
|
||
|
||
return result;
|
||
} catch (e) {
|
||
Utils.addDebugLog(`Error during batch repair: ${e.message}`, 'error');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
async function repairSinglePixel(pixel) {
|
||
const { x, y, originalColor, regionX, regionY, pixelX, pixelY, color } = pixel;
|
||
|
||
try {
|
||
const result = await WPlaceService.paintPixelInRegion(
|
||
regionX,
|
||
regionY,
|
||
pixelX,
|
||
pixelY,
|
||
color || originalColor.id
|
||
);
|
||
|
||
if (result === 'token_error') {
|
||
Utils.addDebugLog('Token error during repair, refreshing token...', 'warning');
|
||
await ensureToken(true);
|
||
await Utils.sleep(state.autonomousMode ? 500 : 1000);
|
||
|
||
// Retry once with new token
|
||
const retryResult = await WPlaceService.paintPixelInRegion(regionX, regionY, pixelX, pixelY, color || originalColor.id);
|
||
return retryResult === true;
|
||
}
|
||
|
||
if (result === true) {
|
||
if (pixel.isDamagedTransparent) {
|
||
Utils.addDebugLog(`Repaired transparent pixel (${x},${y}) with color ${color || originalColor.id}`, 'success');
|
||
}
|
||
}
|
||
|
||
return result === true;
|
||
} catch (e) {
|
||
Utils.addDebugLog(`Error repairing pixel (${x},${y}): ${e.message}`, 'error');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
async function performRepairCheck() {
|
||
if (state.running) {
|
||
Utils.addDebugLog('Repair check skipped - manual repair in progress', 'info');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
Utils.addDebugLog('Autonomous repair check triggered', 'info');
|
||
|
||
// Ensure we have a valid token before starting
|
||
if (!isTokenValid()) {
|
||
Utils.addDebugLog('No valid token for autonomous repair, generating...', 'warning');
|
||
await ensureToken(true);
|
||
if (!isTokenValid()) {
|
||
Utils.addDebugLog('Failed to generate token for autonomous repair, will retry next cycle', 'error');
|
||
return;
|
||
}
|
||
}
|
||
|
||
const damagedPixels = await scanForDamage();
|
||
|
||
if (damagedPixels.length > 0) {
|
||
Utils.addDebugLog(`Autonomous repair: Found ${damagedPixels.length} damaged pixels, starting repair`, 'warning');
|
||
updateStatus(Utils.t('damageDetected', { count: damagedPixels.length }));
|
||
|
||
// Show attack notification
|
||
showAttackNotification('attack', damagedPixels.length);
|
||
state.lastAttackState = 'attacked';
|
||
|
||
const repairedCount = await repairDamagedPixels(damagedPixels);
|
||
|
||
// Show repair complete notification if all pixels were repaired
|
||
if (repairedCount === damagedPixels.length) {
|
||
showAttackNotification('repaired');
|
||
state.lastAttackState = 'repaired';
|
||
}
|
||
} else {
|
||
updateStatus(Utils.t('noDamageDetected'));
|
||
|
||
// Show peaceful state if we're not under attack
|
||
if (state.lastAttackState !== 'peaceful') {
|
||
showPeacefulState();
|
||
state.lastAttackState = 'peaceful';
|
||
}
|
||
|
||
if (!state.autonomousMode) {
|
||
Utils.addDebugLog('Autonomous repair: No damage detected', 'success');
|
||
}
|
||
}
|
||
} catch (error) {
|
||
Utils.addDebugLog(`Autonomous repair error: ${error.message}`, 'error');
|
||
|
||
if (state.autonomousMode) {
|
||
Utils.addDebugLog('Autonomous mode: will retry repair check in 120 seconds due to error', 'warning');
|
||
setTimeout(() => {
|
||
if (state.autoRepairEnabled && !state.stopFlag) {
|
||
performRepairCheck();
|
||
}
|
||
}, 120000);
|
||
}
|
||
}
|
||
}
|
||
|
||
function startAutoRepair() {
|
||
if (state.autoRepairTimer) {
|
||
clearInterval(state.autoRepairTimer);
|
||
}
|
||
|
||
const intervalMs = state.autoRepairInterval * 1000;
|
||
state.autoRepairTimer = setInterval(performRepairCheck, intervalMs);
|
||
|
||
const message = Utils.t('autoRepairStarted', { interval: state.autoRepairInterval });
|
||
Utils.addDebugLog(`${message} (autonomous: ${state.autonomousMode})`, 'info');
|
||
Utils.showAlert(message, 'success');
|
||
|
||
// Perform first check immediately
|
||
setTimeout(performRepairCheck, 2000);
|
||
}
|
||
|
||
function stopAutoRepair() {
|
||
if (state.autoRepairTimer) {
|
||
clearInterval(state.autoRepairTimer);
|
||
state.autoRepairTimer = null;
|
||
}
|
||
|
||
const message = Utils.t('autoRepairStopped');
|
||
Utils.addDebugLog(`${message}`, 'info');
|
||
Utils.showAlert(message, 'info');
|
||
}
|
||
|
||
// FUNCIONES DE UI MEJORADAS CON GESTIÓN DE CONFIGURACIÓN INICIAL
|
||
function enableFileOperations() {
|
||
state.initialSetupComplete = true;
|
||
|
||
const loadBtn = document.querySelector('#loadFileBtn');
|
||
if (loadBtn) {
|
||
loadBtn.disabled = false;
|
||
loadBtn.title = '';
|
||
loadBtn.style.animation = 'pulse 0.6s ease-in-out';
|
||
setTimeout(() => {
|
||
if (loadBtn) loadBtn.style.animation = '';
|
||
}, 600);
|
||
Utils.addDebugLog('Load Progress button enabled after initial setup', 'success');
|
||
}
|
||
|
||
Utils.showAlert(Utils.t('fileOperationsAvailable'), 'success');
|
||
}
|
||
|
||
async function initializeTokenGenerator() {
|
||
if (isTokenValid()) {
|
||
Utils.addDebugLog('Valid token already available, skipping initialization', 'success');
|
||
updateTokenStatus();
|
||
enableFileOperations();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
Utils.addDebugLog('Initializing Turnstile token generator...', 'info');
|
||
updateStatus(Utils.t('initializingToken'));
|
||
|
||
await Utils.loadTurnstile();
|
||
Utils.addDebugLog('Turnstile script loaded successfully', 'success');
|
||
|
||
if (state.autoTokenRefresh) {
|
||
Utils.addDebugLog('Pre-generating token for autonomous operations...', 'info');
|
||
await ensureToken();
|
||
}
|
||
|
||
state.initialSetupComplete = true;
|
||
Utils.addDebugLog('Enhanced token generator initialization complete', 'success');
|
||
updateStatus(Utils.t('tokenReady'));
|
||
enableFileOperations();
|
||
|
||
} catch (error) {
|
||
Utils.addDebugLog('Token system initialization failed: ' + error.message, 'warning');
|
||
Utils.addDebugLog('Manual token generation will be required', 'info');
|
||
enableFileOperations();
|
||
}
|
||
}
|
||
|
||
function updateStatus(message) {
|
||
const statusEl = document.getElementById('status');
|
||
if (statusEl) {
|
||
if (typeof message === 'string') {
|
||
statusEl.innerHTML = message;
|
||
} else {
|
||
statusEl.textContent = message;
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateDebugConsole() {
|
||
const debugConsole = document.getElementById('debugConsole');
|
||
if (!debugConsole) return;
|
||
|
||
const logsHtml = state.debugLogs.map(log => {
|
||
const colorClass = log.type === 'error' ? 'neon-text danger' :
|
||
log.type === 'warning' ? 'neon-text warning' :
|
||
log.type === 'success' ? 'neon-text success' :
|
||
log.type === 'info' ? 'neon-text info' : 'neon-text';
|
||
|
||
return `<div class="${colorClass}" style="margin: 2px 0; font-size: 7px; line-height: 1.4;">
|
||
<span style="opacity: 0.7;">[${log.timestamp}]</span> ${log.message}
|
||
</div>`;
|
||
}).join('');
|
||
|
||
debugConsole.innerHTML = logsHtml;
|
||
debugConsole.scrollTop = debugConsole.scrollHeight;
|
||
}
|
||
|
||
async function updateCharges() {
|
||
try {
|
||
const { charges, max, cooldown } = await WPlaceService.getCharges();
|
||
state.displayCharges = Math.floor(charges);
|
||
state.maxCharges = Math.max(1, Math.floor(max));
|
||
state.cooldown = cooldown;
|
||
|
||
updateChargesDisplay();
|
||
} catch (error) {
|
||
if (!state.autonomousMode) {
|
||
Utils.addDebugLog(`Error updating charges: ${error.message}`, 'error');
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateChargesDisplay() {
|
||
const chargesEl = document.getElementById('chargesInfo');
|
||
if (chargesEl) {
|
||
chargesEl.textContent = `Charges: ${state.displayCharges}/${state.maxCharges} (cooldown: ${Math.round(state.cooldown / 1000)}s)`;
|
||
}
|
||
}
|
||
|
||
function updateTokenStatus() {
|
||
const tokenEl = document.getElementById('tokenInfo');
|
||
if (tokenEl) {
|
||
// Remove existing classes first
|
||
tokenEl.className = '';
|
||
|
||
if (isTokenValid()) {
|
||
const remaining = Math.round((tokenExpiryTime - Date.now()) / 1000);
|
||
tokenEl.textContent = `Token: Valid (expires in ${remaining}s)`;
|
||
tokenEl.className = 'neon-text success';
|
||
} else {
|
||
tokenEl.textContent = 'Token: Not generated or expired';
|
||
tokenEl.className = 'neon-text warning';
|
||
}
|
||
}
|
||
}
|
||
|
||
// Update batch information display for new UI
|
||
function updateBatchInfo() {
|
||
const batchSizeEl = document.getElementById('batchSize');
|
||
const batchStatusEl = document.getElementById('batchStatus');
|
||
const smartModeEl = document.getElementById('smartBatchMode');
|
||
const manualModeEl = document.getElementById('manualBatchMode');
|
||
const manualControlsEl = document.getElementById('manualBatchControls');
|
||
|
||
// Update radio button states
|
||
if (smartModeEl && manualModeEl) {
|
||
smartModeEl.checked = state.autoBatchEnabled && state.batchOptimization;
|
||
manualModeEl.checked = !state.autoBatchEnabled || !state.batchOptimization;
|
||
}
|
||
|
||
// Update manual controls visibility
|
||
if (manualControlsEl) {
|
||
manualControlsEl.style.display = (!state.autoBatchEnabled || !state.batchOptimization) ? 'flex' : 'none';
|
||
}
|
||
|
||
// Update batch size input
|
||
if (batchSizeEl) {
|
||
batchSizeEl.value = state.currentBatchSize;
|
||
}
|
||
|
||
// Update status display
|
||
if (batchStatusEl) {
|
||
if (state.autoBatchEnabled && state.batchOptimization) {
|
||
batchStatusEl.textContent = `🧠 Smart Batch: AUTO (Current: ${state.currentBatchSize})`;
|
||
batchStatusEl.className = 'status-info';
|
||
} else {
|
||
batchStatusEl.textContent = `✋ Manual Batch: ${state.currentBatchSize} pixels fixed`;
|
||
batchStatusEl.className = 'status-warning';
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateSystemStatus() {
|
||
const systemEl = document.getElementById('systemInfo');
|
||
if (systemEl) {
|
||
const autonomousStatus = state.autonomousMode ? 'AUTONOMOUS' : 'MANUAL';
|
||
const tokenMode = state.tokenSource.toUpperCase();
|
||
systemEl.textContent = `System: ${autonomousStatus} | Token: ${tokenMode} | Auto-refresh: ${state.autoTokenRefresh ? 'ON' : 'OFF'}`;
|
||
}
|
||
}
|
||
|
||
// Window dragging functionality
|
||
function makeWindowDraggable() {
|
||
const container = document.getElementById('wplace-repair-tool');
|
||
const header = document.getElementById('window-header');
|
||
|
||
let isDragging = false;
|
||
let currentX;
|
||
let currentY;
|
||
let initialX;
|
||
let initialY;
|
||
let xOffset = 0;
|
||
let yOffset = 0;
|
||
|
||
function dragStart(e) {
|
||
if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') {
|
||
return;
|
||
}
|
||
|
||
initialX = e.clientX - xOffset;
|
||
initialY = e.clientY - yOffset;
|
||
|
||
if (e.target === header || header.contains(e.target)) {
|
||
isDragging = true;
|
||
container.style.cursor = 'grabbing';
|
||
}
|
||
}
|
||
|
||
function dragEnd() {
|
||
initialX = currentX;
|
||
initialY = currentY;
|
||
isDragging = false;
|
||
container.style.cursor = 'default';
|
||
}
|
||
|
||
function drag(e) {
|
||
if (isDragging) {
|
||
e.preventDefault();
|
||
currentX = e.clientX - initialX;
|
||
currentY = e.clientY - initialY;
|
||
xOffset = currentX;
|
||
yOffset = currentY;
|
||
|
||
container.style.transform = `translate(${currentX}px, ${currentY}px)`;
|
||
}
|
||
}
|
||
|
||
header.addEventListener('mousedown', dragStart);
|
||
document.addEventListener('mouseup', dragEnd);
|
||
document.addEventListener('mousemove', drag);
|
||
}
|
||
|
||
// Enhanced UI Creation with NEW IMPLEMENTATION
|
||
function createUI() {
|
||
const existing = document.getElementById('wplace-repair-tool');
|
||
if (existing) existing.remove();
|
||
|
||
const container = document.createElement('div');
|
||
container.id = 'wplace-repair-tool';
|
||
container.style.cssText = `
|
||
position: fixed; top: 20px; left: 20px; z-index: 10000;
|
||
background: #1a1a2e;
|
||
border: 3px solid #00ff41;
|
||
border-radius: 0;
|
||
min-width: 600px; max-width: 700px;
|
||
box-shadow:
|
||
0 0 30px rgb(0 255 65 / 50%),
|
||
inset 0 0 30px rgb(0 255 65 / 10%),
|
||
0 0 0 1px #00ff41;
|
||
color: #00ff41;
|
||
font-family: 'Press Start 2P', monospace, 'Courier New';
|
||
resize: both; overflow: hidden;
|
||
animation: neon-pulse 2s ease-in-out infinite alternate;
|
||
`;
|
||
|
||
// Add Neon theme styles
|
||
const neonStyles = document.createElement('style');
|
||
neonStyles.id = 'auto-repair-neon-styles';
|
||
neonStyles.textContent = `
|
||
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
|
||
|
||
#wplace-repair-tool::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 2px;
|
||
background: linear-gradient(90deg, transparent, #00ff41, transparent);
|
||
z-index: 1;
|
||
pointer-events: none;
|
||
animation: scanline 3s linear infinite;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.neon-btn {
|
||
background: #16213e !important;
|
||
border: 2px solid #00ff41 !important;
|
||
border-radius: 0 !important;
|
||
color: #00ff41 !important;
|
||
padding: 8px 15px !important;
|
||
font-family: 'Press Start 2P', monospace !important;
|
||
font-size: 8px !important;
|
||
text-transform: uppercase !important;
|
||
cursor: pointer !important;
|
||
transition: all 0.3s ease !important;
|
||
text-shadow: 0 0 8px #00ff41 !important;
|
||
letter-spacing: 1px !important;
|
||
margin: 4px !important;
|
||
}
|
||
|
||
.neon-btn:hover {
|
||
background: rgba(0,255,65,0.1) !important;
|
||
box-shadow: 0 0 20px rgb(0 255 65 / 60%) !important;
|
||
animation: pixel-blink 0.5s infinite !important;
|
||
}
|
||
|
||
.neon-btn:disabled {
|
||
background: #0d1117 !important;
|
||
border-color: #555 !important;
|
||
color: #555 !important;
|
||
text-shadow: none !important;
|
||
cursor: not-allowed !important;
|
||
}
|
||
|
||
.neon-btn.danger {
|
||
border-color: #ff073a !important;
|
||
color: #ff073a !important;
|
||
text-shadow: 0 0 8px #ff073a !important;
|
||
}
|
||
|
||
.neon-btn.danger:hover {
|
||
background: rgba(255, 7, 58, 0.1) !important;
|
||
box-shadow: 0 0 20px rgb(255 7 58 / 60%) !important;
|
||
}
|
||
|
||
.neon-btn.warning {
|
||
border-color: #ff6b35 !important;
|
||
color: #ff6b35 !important;
|
||
text-shadow: 0 0 8px #ff6b35 !important;
|
||
}
|
||
|
||
.neon-btn.warning:hover {
|
||
background: rgba(255, 107, 53, 0.1) !important;
|
||
box-shadow: 0 0 20px rgb(255 107 53 / 60%) !important;
|
||
}
|
||
|
||
.neon-input {
|
||
background: #16213e !important;
|
||
border: 2px solid #00ff41 !important;
|
||
border-radius: 0 !important;
|
||
color: #00ff41 !important;
|
||
padding: 6px 10px !important;
|
||
font-family: 'Press Start 2P', monospace !important;
|
||
font-size: 8px !important;
|
||
text-shadow: 0 0 5px #00ff41 !important;
|
||
}
|
||
|
||
.neon-input:focus {
|
||
outline: none !important;
|
||
box-shadow: 0 0 15px rgb(0 255 65 / 60%) !important;
|
||
animation: pixel-blink 0.5s infinite !important;
|
||
}
|
||
|
||
.neon-select {
|
||
background: #16213e !important;
|
||
border: 2px solid #00ff41 !important;
|
||
border-radius: 0 !important;
|
||
color: #00ff41 !important;
|
||
padding: 4px 8px !important;
|
||
font-family: 'Press Start 2P', monospace !important;
|
||
font-size: 7px !important;
|
||
text-shadow: 0 0 5px #00ff41 !important;
|
||
}
|
||
|
||
.neon-checkbox {
|
||
accent-color: #00ff41 !important;
|
||
transform: scale(1.2) !important;
|
||
margin-right: 8px !important;
|
||
}
|
||
|
||
/* Enhanced Batch Control Styles */
|
||
.batch-control-group {
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
gap: 8px !important;
|
||
margin: 5px 0 !important;
|
||
padding: 8px !important;
|
||
background: rgba(0, 255, 65, 0.05) !important;
|
||
border: 1px solid rgba(0, 255, 65, 0.3) !important;
|
||
border-radius: 3px !important;
|
||
}
|
||
|
||
.batch-control-header {
|
||
text-align: center !important;
|
||
margin-bottom: 10px !important;
|
||
padding: 5px !important;
|
||
border-bottom: 1px solid rgba(0, 255, 65, 0.3) !important;
|
||
}
|
||
|
||
.batch-control-label {
|
||
color: #00ff41 !important;
|
||
font-family: 'Press Start 2P', monospace !important;
|
||
font-size: 6px !important;
|
||
text-shadow: 0 0 3px #00ff41 !important;
|
||
white-space: nowrap !important;
|
||
}
|
||
|
||
.batch-mode-selection {
|
||
margin: 10px 0 !important;
|
||
}
|
||
|
||
.batch-mode-option {
|
||
margin: 8px 0 !important;
|
||
padding: 6px !important;
|
||
background: rgba(0, 255, 65, 0.03) !important;
|
||
border: 1px solid rgba(0, 255, 65, 0.2) !important;
|
||
border-radius: 3px !important;
|
||
transition: all 0.3s ease !important;
|
||
}
|
||
|
||
.batch-mode-option:hover {
|
||
background: rgba(0, 255, 65, 0.08) !important;
|
||
border-color: rgba(0, 255, 65, 0.4) !important;
|
||
}
|
||
|
||
.batch-mode-label {
|
||
color: #00ff41 !important;
|
||
font-family: 'Press Start 2P', monospace !important;
|
||
font-size: 6px !important;
|
||
text-shadow: 0 0 3px #00ff41 !important;
|
||
margin-left: 8px !important;
|
||
}
|
||
|
||
.neon-radio {
|
||
accent-color: #00ff41 !important;
|
||
transform: scale(1.2) !important;
|
||
margin-right: 8px !important;
|
||
}
|
||
|
||
.batch-manual-controls {
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
gap: 8px !important;
|
||
margin: 8px 0 !important;
|
||
padding: 8px !important;
|
||
background: rgba(0, 255, 65, 0.05) !important;
|
||
border: 1px solid rgba(0, 255, 65, 0.3) !important;
|
||
border-radius: 3px !important;
|
||
}
|
||
|
||
.batch-size-control {
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
gap: 2px !important;
|
||
border: 1px solid #00ff41 !important;
|
||
border-radius: 3px !important;
|
||
background: #16213e !important;
|
||
overflow: hidden !important;
|
||
}
|
||
|
||
.batch-adjust-btn {
|
||
width: 20px !important;
|
||
height: 24px !important;
|
||
background: #16213e !important;
|
||
border: none !important;
|
||
color: #00ff41 !important;
|
||
font-family: 'Press Start 2P', monospace !important;
|
||
font-size: 8px !important;
|
||
cursor: pointer !important;
|
||
transition: all 0.3s ease !important;
|
||
text-shadow: 0 0 3px #00ff41 !important;
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
justify-content: center !important;
|
||
}
|
||
|
||
.batch-adjust-btn:hover {
|
||
background: rgba(0, 255, 65, 0.1) !important;
|
||
box-shadow: 0 0 5px rgba(0, 255, 65, 0.3) !important;
|
||
}
|
||
|
||
.batch-adjust-btn:active {
|
||
background: rgba(0, 255, 65, 0.2) !important;
|
||
transform: scale(0.95) !important;
|
||
}
|
||
|
||
.batch-size-input {
|
||
width: 50px !important;
|
||
height: 24px !important;
|
||
background: #16213e !important;
|
||
border: none !important;
|
||
color: #00ff41 !important;
|
||
padding: 0 4px !important;
|
||
font-family: 'Press Start 2P', monospace !important;
|
||
font-size: 6px !important;
|
||
text-align: center !important;
|
||
text-shadow: 0 0 3px #00ff41 !important;
|
||
outline: none !important;
|
||
/* Hide browser default spinner buttons */
|
||
-webkit-appearance: none !important;
|
||
-moz-appearance: textfield !important;
|
||
}
|
||
|
||
/* Hide Chrome spinner buttons */
|
||
.batch-size-input::-webkit-outer-spin-button,
|
||
.batch-size-input::-webkit-inner-spin-button {
|
||
-webkit-appearance: none !important;
|
||
margin: 0 !important;
|
||
}
|
||
|
||
/* Hide Firefox spinner buttons */
|
||
.batch-size-input[type=number] {
|
||
-moz-appearance: textfield !important;
|
||
}
|
||
|
||
.status-info {
|
||
color: #00ff41 !important;
|
||
}
|
||
|
||
.status-warning {
|
||
color: #ff6b35 !important;
|
||
}
|
||
|
||
.neon-panel {
|
||
background: rgba(22, 33, 62, 0.3) !important;
|
||
border: 1px solid #00ff41 !important;
|
||
border-radius: 0 !important;
|
||
padding: 15px !important;
|
||
margin: 10px 0 !important;
|
||
box-shadow: inset 0 0 15px rgba(0, 255, 65, 0.1) !important;
|
||
}
|
||
|
||
.neon-text {
|
||
color: #00ff41 !important;
|
||
text-shadow: 0 0 5px #00ff41 !important;
|
||
font-family: 'Press Start 2P', monospace !important;
|
||
}
|
||
|
||
.neon-text.success {
|
||
color: #39ff14 !important;
|
||
text-shadow: 0 0 5px #39ff14 !important;
|
||
}
|
||
|
||
.neon-text.warning {
|
||
color: #ff6b35 !important;
|
||
text-shadow: 0 0 5px #ff6b35 !important;
|
||
}
|
||
|
||
.neon-text.danger {
|
||
color: #ff073a !important;
|
||
text-shadow: 0 0 5px #ff073a !important;
|
||
}
|
||
|
||
.neon-text.info {
|
||
color: #00ccff !important;
|
||
text-shadow: 0 0 5px #00ccff !important;
|
||
}
|
||
|
||
/* Custom scrollbar for debug console */
|
||
#debugConsole::-webkit-scrollbar {
|
||
width: 12px;
|
||
}
|
||
|
||
#debugConsole::-webkit-scrollbar-track {
|
||
background: #16213e;
|
||
border: 1px solid #00ff41;
|
||
}
|
||
|
||
#debugConsole::-webkit-scrollbar-thumb {
|
||
background: #00ff41;
|
||
border-radius: 0;
|
||
box-shadow: 0 0 10px #00ff41;
|
||
}
|
||
|
||
#debugConsole::-webkit-scrollbar-thumb:hover {
|
||
background: #39ff14;
|
||
box-shadow: 0 0 15px #39ff14;
|
||
}
|
||
|
||
/* Animations */
|
||
@keyframes neon-pulse {
|
||
0% { box-shadow: 0 0 30px rgb(0 255 65 / 50%), inset 0 0 30px rgb(0 255 65 / 10%), 0 0 0 1px #00ff41; }
|
||
100% { box-shadow: 0 0 40px rgb(0 255 65 / 70%), inset 0 0 40px rgb(0 255 65 / 15%), 0 0 0 1px #00ff41; }
|
||
}
|
||
|
||
@keyframes pixel-blink {
|
||
0%, 50% { opacity: 1; }
|
||
51%, 100% { opacity: 0.7; }
|
||
}
|
||
|
||
@keyframes scanline {
|
||
0% { transform: translateY(-100%); }
|
||
100% { transform: translateY(400px); }
|
||
}
|
||
|
||
@keyframes text-glow {
|
||
0% { text-shadow: 0 0 15px #00ff41; }
|
||
100% { text-shadow: 0 0 25px #00ff41, 0 0 35px #00ff41; }
|
||
}
|
||
`;
|
||
|
||
if (!document.getElementById('auto-repair-neon-styles')) {
|
||
document.head.appendChild(neonStyles);
|
||
}
|
||
|
||
container.innerHTML = `
|
||
<div id="window-header" style="
|
||
background: #16213e; padding: 15px 20px;
|
||
border-bottom: 2px solid #00ff41; cursor: grab; user-select: none;
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
position: relative;
|
||
">
|
||
<div style="display: flex; align-items: center; gap: 15px;">
|
||
<div style="
|
||
width: 32px; height: 32px; background: #00ff41;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 16px; animation: pixel-blink 3s infinite;
|
||
box-shadow: 0 0 15px rgba(0, 255, 65, 0.4);
|
||
">🔧</div>
|
||
<h3 id="main-title" class="neon-text" style="
|
||
margin: 0; font-size: 12px; min-height: 22px;
|
||
text-transform: uppercase; letter-spacing: 2px;
|
||
animation: text-glow 2s ease-in-out infinite alternate;
|
||
">
|
||
WPlace Autonomous Repair Tool
|
||
</h3>
|
||
</div>
|
||
<div style="display: flex; gap: 8px;">
|
||
<button id="minimizeBtn" class="neon-btn warning" style="
|
||
width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
|
||
font-size: 12px; padding: 0;
|
||
">−</button>
|
||
<button id="closeBtn" class="neon-btn danger" style="
|
||
width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
|
||
font-size: 12px; padding: 0;
|
||
">×</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="window-content" style="padding: 20px;">
|
||
<div class="neon-panel">
|
||
<div id="status" class="neon-text" style="font-size: 10px; margin-bottom: 10px; min-height: 20px;">
|
||
${Utils.t('autonomousModeActive')}
|
||
</div>
|
||
<div id="chargesInfo" class="neon-text info" style="font-size: 8px;">
|
||
Charges: 0/1 (cooldown: 31s)
|
||
</div>
|
||
<div id="tokenInfo" class="neon-text success" style="font-size: 8px; margin-top: 5px;">
|
||
Token: Initializing...
|
||
</div>
|
||
<div id="systemInfo" class="neon-text warning" style="font-size: 7px; margin-top: 3px;">
|
||
System: AUTONOMOUS | Token: GENERATOR | Auto-refresh: ON
|
||
</div>
|
||
<div id="batchStatus" class="neon-text info" style="font-size: 7px; margin-top: 2px;">
|
||
Auto-Batch: ON | Size: 5 | Opt: ON
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 15px; display: flex; flex-wrap: wrap; gap: 5px;">
|
||
<button id="loadFileBtn" class="neon-btn" disabled title="Waiting for initial setup to complete...">
|
||
📁 ${Utils.t('loadFromFile')}
|
||
</button>
|
||
|
||
<button id="repairBtn" class="neon-btn" disabled>
|
||
🔧 ${Utils.t('repairPixels')}
|
||
</button>
|
||
|
||
<button id="generateTokenBtn" class="neon-btn">
|
||
🎯 Force Token
|
||
</button>
|
||
</div>
|
||
|
||
<div class="neon-panel">
|
||
<div style="margin-bottom: 10px;">
|
||
<label style="display: flex; align-items: center; cursor: pointer;" class="neon-text">
|
||
<input type="checkbox" id="autoRepairEnabled" class="neon-checkbox">
|
||
<span style="font-size: 9px;">${Utils.t('enableAutoRepair')} (Enhanced)</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;">
|
||
<label class="neon-text" style="font-size: 8px;">${Utils.t('repairInterval')}:</label>
|
||
<input type="number" id="repairInterval" value="30" min="10" max="3600"
|
||
class="neon-input" style="width: 80px;">
|
||
<span class="neon-text" style="font-size: 7px;">seconds</span>
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 10px; margin-bottom: 10px; flex-wrap: wrap;">
|
||
<label style="display: flex; align-items: center; cursor: pointer;" class="neon-text">
|
||
<input type="checkbox" id="autoTokenRefresh" ${state.autoTokenRefresh ? 'checked' : ''} class="neon-checkbox">
|
||
<span style="font-size: 8px;">Auto Token Refresh</span>
|
||
</label>
|
||
<select id="tokenSourceSelect" class="neon-select">
|
||
<option value="generator" ${state.tokenSource === 'generator' ? 'selected' : ''}>Generator</option>
|
||
<option value="hybrid" ${state.tokenSource === 'hybrid' ? 'selected' : ''}>Hybrid</option>
|
||
<option value="manual" ${state.tokenSource === 'manual' ? 'selected' : ''}>Manual</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- NEW: Enhanced Pixel Batch Controls -->
|
||
<div style="border-top: 1px solid #00ff41; margin-top: 15px; padding-top: 10px;">
|
||
<div class="batch-control-header">
|
||
<span class="batch-control-label">🎯 Pixel Batch Mode</span>
|
||
</div>
|
||
|
||
<!-- Batch Mode Selection -->
|
||
<div class="batch-mode-selection">
|
||
<div class="batch-mode-option">
|
||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||
<input type="radio" name="batchMode" id="smartBatchMode" ${state.autoBatchEnabled && state.batchOptimization ? 'checked' : ''} class="neon-radio">
|
||
<span class="batch-mode-label">🧠 Smart Batch (Auto-adjust to damage)</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="batch-mode-option">
|
||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||
<input type="radio" name="batchMode" id="manualBatchMode" ${!state.autoBatchEnabled || !state.batchOptimization ? 'checked' : ''} class="neon-radio">
|
||
<span class="batch-mode-label">✋ Manual Batch (Fixed size)</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Manual Batch Size Controls -->
|
||
<div class="batch-manual-controls" id="manualBatchControls" style="display: ${!state.autoBatchEnabled || !state.batchOptimization ? 'flex' : 'none'};">
|
||
<span class="batch-control-label">Batch Size:</span>
|
||
<div class="batch-size-control">
|
||
<button type="button" id="batchSizeDecrease" class="batch-adjust-btn">-</button>
|
||
<input type="number" id="batchSize" value="${state.currentBatchSize}" min="1" max="${CONFIG.MAX_BATCH_SIZE}"
|
||
class="batch-size-input">
|
||
<button type="button" id="batchSizeIncrease" class="batch-adjust-btn">+</button>
|
||
</div>
|
||
<span class="batch-control-label">pixels</span>
|
||
</div> <!-- Transparent Pixel Control -->
|
||
<div class="batch-control-group">
|
||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||
<input type="checkbox" id="paintTransparentPixels" ${state.paintTransparentPixels ? 'checked' : ''} class="neon-checkbox">
|
||
<span class="batch-control-label">🔧 Repair Transparent Pixels</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top: 10px; font-size: 8px;" class="neon-text info">
|
||
<div>Image: <span id="imageInfo">Not loaded</span></div>
|
||
<div>Position: <span id="positionInfo">Not set</span></div>
|
||
<div>Colors: <span id="colorsInfo">0 available</span></div>
|
||
<div>Batch Mode: <span id="batchInfo">Auto (${state.currentBatchSize} pixels)</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top: 15px;">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||
<h4 class="neon-text" style="margin: 0; font-size: 10px; text-transform: uppercase;">
|
||
📋 ${Utils.t('debug')} (Enhanced System)
|
||
</h4>
|
||
<button id="clearDebugBtn" class="neon-btn warning" style="font-size: 7px; padding: 4px 8px;">
|
||
${Utils.t('clearDebug')}
|
||
</button>
|
||
</div>
|
||
|
||
<div id="debugConsole" style="
|
||
background: rgba(0,0,0,0.6); border: 2px solid #00ff41;
|
||
border-radius: 0; padding: 10px; height: 300px; overflow-y: auto;
|
||
font-family: 'Press Start 2P', monospace; font-size: 7px;
|
||
color: #00ff41; line-height: 1.4;
|
||
box-shadow: inset 0 0 15px rgba(0, 255, 65, 0.2);
|
||
">
|
||
<div class="neon-text info">[Ready] WPlace Autonomous Repair Tool v3.0</div>
|
||
<div class="neon-text success">[Info] NEW Turnstile implementation active</div>
|
||
<div class="neon-text warning">[Info] Enhanced token management with preloading</div>
|
||
<div class="neon-text">[Info] ${Utils.t('tokenSystemReady')}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(container);
|
||
makeWindowDraggable();
|
||
setupEventListeners();
|
||
|
||
// Start typewriter effect
|
||
setTimeout(() => {
|
||
createTypewriterTitle();
|
||
}, 1000);
|
||
|
||
// Start autonomous systems
|
||
initializeAutonomousSystems();
|
||
}
|
||
|
||
function initializeAutonomousSystems() {
|
||
// Update charges periodically
|
||
updateCharges();
|
||
setInterval(updateCharges, 10000);
|
||
|
||
// Update token status
|
||
updateTokenStatus();
|
||
setInterval(updateTokenStatus, 5000);
|
||
|
||
// Update system status
|
||
updateSystemStatus();
|
||
setInterval(updateSystemStatus, 15000);
|
||
|
||
// Update batch information
|
||
updateBatchInfo();
|
||
|
||
// Initialize token generator
|
||
initializeTokenGenerator();
|
||
}
|
||
|
||
function setupEventListeners() {
|
||
// Window controls
|
||
document.getElementById('minimizeBtn').addEventListener('click', () => {
|
||
const content = document.getElementById('window-content');
|
||
state.windowMinimized = !state.windowMinimized;
|
||
|
||
if (state.windowMinimized) {
|
||
content.style.display = 'none';
|
||
document.getElementById('minimizeBtn').textContent = '+';
|
||
} else {
|
||
content.style.display = 'block';
|
||
document.getElementById('minimizeBtn').textContent = '−';
|
||
}
|
||
});
|
||
|
||
document.getElementById('closeBtn').addEventListener('click', () => {
|
||
const container = document.getElementById('wplace-repair-tool');
|
||
if (container) {
|
||
container.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||
container.style.opacity = '0';
|
||
container.style.transform = 'scale(0.8)';
|
||
setTimeout(() => {
|
||
container.remove();
|
||
cleanup();
|
||
}, 300);
|
||
}
|
||
});
|
||
|
||
// Load file
|
||
document.getElementById('loadFileBtn').addEventListener('click', async () => {
|
||
try {
|
||
Utils.addDebugLog('Loading progress file...', 'info');
|
||
const data = await Utils.createFileUploader();
|
||
|
||
if (!data || !data.state || !data.imageData) {
|
||
throw new Error(Utils.t('invalidFile'));
|
||
}
|
||
|
||
const migratedData = migrateProgressData(data);
|
||
|
||
Object.assign(state, migratedData.state);
|
||
state.imageData = {
|
||
...migratedData.imageData,
|
||
pixels: new Uint8ClampedArray(migratedData.imageData.pixels),
|
||
};
|
||
|
||
state.imageLoaded = true;
|
||
document.getElementById('repairBtn').disabled = false;
|
||
|
||
// Update info displays
|
||
document.getElementById('imageInfo').textContent = `${state.imageData.width}x${state.imageData.height}`;
|
||
document.getElementById('positionInfo').textContent = state.startPosition ?
|
||
`(${state.startPosition.x}, ${state.startPosition.y}) in region (${state.region.x}, ${state.region.y})` : 'Not set';
|
||
document.getElementById('colorsInfo').textContent = `${state.availableColors?.length || 0} available`;
|
||
|
||
updateStatus(Utils.t('fileLoaded'));
|
||
Utils.addDebugLog(`Loaded image: ${state.imageData.width}x${state.imageData.height}`, 'success');
|
||
Utils.addDebugLog(`Position: (${state.startPosition?.x}, ${state.startPosition?.y})`, 'info');
|
||
Utils.addDebugLog(`Region: (${state.region?.x}, ${state.region?.y})`, 'info');
|
||
Utils.addDebugLog(`Available colors: ${state.availableColors?.length || 0}`, 'info');
|
||
|
||
if (migratedData.state.paintWhitePixels !== undefined) {
|
||
state.paintWhitePixels = migratedData.state.paintWhitePixels;
|
||
Utils.addDebugLog(`Paint white pixels: ${state.paintWhitePixels}`, 'info');
|
||
}
|
||
if (migratedData.state.paintTransparentPixels !== undefined) {
|
||
state.paintTransparentPixels = migratedData.state.paintTransparentPixels;
|
||
Utils.addDebugLog(`Paint transparent pixels: ${state.paintTransparentPixels}`, 'info');
|
||
}
|
||
|
||
Utils.showAlert(Utils.t('fileLoaded'), 'success');
|
||
|
||
// If autonomous mode and auto repair is enabled, start it
|
||
if (state.autonomousMode && document.getElementById('autoRepairEnabled').checked) {
|
||
Utils.addDebugLog('Autonomous mode: starting auto repair after file load', 'info');
|
||
setTimeout(() => {
|
||
state.autoRepairEnabled = true;
|
||
startAutoRepair();
|
||
}, 5000);
|
||
}
|
||
|
||
} catch (error) {
|
||
Utils.addDebugLog(`Load error: ${error.message}`, 'error');
|
||
Utils.showAlert(error.message, 'error');
|
||
}
|
||
});
|
||
|
||
// Generate Token button
|
||
document.getElementById('generateTokenBtn').addEventListener('click', async () => {
|
||
const btn = document.getElementById('generateTokenBtn');
|
||
const originalText = btn.textContent;
|
||
|
||
try {
|
||
btn.disabled = true;
|
||
btn.textContent = '🔄 Generating...';
|
||
btn.style.background = 'linear-gradient(135deg, #ffa726 0%, #ff9800 100%)';
|
||
|
||
Utils.addDebugLog('Manual token generation requested', 'info');
|
||
|
||
const token = await ensureToken(true);
|
||
|
||
if (token) {
|
||
Utils.addDebugLog('Token generation succeeded!', 'success');
|
||
Utils.showAlert('Token generated successfully!', 'success');
|
||
updateTokenStatus();
|
||
} else {
|
||
throw new Error('Token generation failed');
|
||
}
|
||
} catch (error) {
|
||
Utils.addDebugLog(`Token generation failed: ${error.message}`, 'error');
|
||
Utils.showAlert('Token generation failed', 'error');
|
||
playNotificationSound();
|
||
} finally {
|
||
btn.disabled = false;
|
||
btn.textContent = originalText;
|
||
btn.style.background = 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)';
|
||
}
|
||
});
|
||
|
||
// Manual repair
|
||
let repairRunning = false;
|
||
document.getElementById('repairBtn').addEventListener('click', async () => {
|
||
if (!state.imageLoaded) {
|
||
Utils.showAlert('Please load a progress file first', 'warning');
|
||
return;
|
||
}
|
||
|
||
if (repairRunning) {
|
||
state.stopFlag = true;
|
||
Utils.addDebugLog('Manual repair stop requested', 'warning');
|
||
return;
|
||
}
|
||
|
||
repairRunning = true;
|
||
state.running = true;
|
||
state.stopFlag = false;
|
||
const repairBtn = document.getElementById('repairBtn');
|
||
repairBtn.textContent = '⏸️ Stop Repair';
|
||
repairBtn.style.background = 'linear-gradient(135deg, #ff6b35 0%, #f7931e 100%)';
|
||
|
||
try {
|
||
Utils.addDebugLog('Starting manual repair with enhanced autonomous system...', 'info');
|
||
|
||
if (!isTokenValid()) {
|
||
Utils.addDebugLog('No valid token, generating...', 'info');
|
||
await ensureToken(true);
|
||
}
|
||
|
||
const damagedPixels = await scanForDamage();
|
||
|
||
if (damagedPixels.length > 0) {
|
||
updateStatus(Utils.t('damageDetected', { count: damagedPixels.length }));
|
||
showAttackNotification('attack', damagedPixels.length);
|
||
|
||
const repairedCount = await repairDamagedPixels(damagedPixels);
|
||
|
||
if (repairedCount === damagedPixels.length) {
|
||
showAttackNotification('repaired');
|
||
}
|
||
} else {
|
||
updateStatus(Utils.t('noDamageDetected'));
|
||
showPeacefulState();
|
||
}
|
||
} catch (error) {
|
||
Utils.addDebugLog(`Repair error: ${error.message}`, 'error');
|
||
Utils.showAlert('Repair failed', 'error');
|
||
} finally {
|
||
repairRunning = false;
|
||
state.running = false;
|
||
state.stopFlag = false;
|
||
repairBtn.textContent = '🔧 ' + Utils.t('repairPixels');
|
||
repairBtn.style.background = 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)';
|
||
}
|
||
});
|
||
|
||
// Auto repair toggle
|
||
document.getElementById('autoRepairEnabled').addEventListener('change', (e) => {
|
||
state.autoRepairEnabled = e.target.checked;
|
||
|
||
if (state.autoRepairEnabled) {
|
||
if (!state.imageLoaded) {
|
||
Utils.showAlert('Please load a progress file first', 'warning');
|
||
e.target.checked = false;
|
||
state.autoRepairEnabled = false;
|
||
return;
|
||
}
|
||
startAutoRepair();
|
||
} else {
|
||
stopAutoRepair();
|
||
}
|
||
});
|
||
|
||
// Repair interval change
|
||
document.getElementById('repairInterval').addEventListener('change', (e) => {
|
||
const value = parseInt(e.target.value) || 30;
|
||
state.autoRepairInterval = Math.max(10, Math.min(3600, value));
|
||
e.target.value = state.autoRepairInterval;
|
||
|
||
Utils.addDebugLog(`Repair interval changed to ${state.autoRepairInterval} seconds`, 'info');
|
||
|
||
if (state.autoRepairEnabled && state.imageLoaded) {
|
||
Utils.addDebugLog('Restarting auto repair with new interval', 'info');
|
||
startAutoRepair();
|
||
}
|
||
});
|
||
|
||
// Auto token refresh toggle
|
||
document.getElementById('autoTokenRefresh').addEventListener('change', (e) => {
|
||
state.autoTokenRefresh = e.target.checked;
|
||
Utils.addDebugLog(`Auto token refresh ${state.autoTokenRefresh ? 'enabled' : 'disabled'}`, 'info');
|
||
updateSystemStatus();
|
||
|
||
if (state.autoTokenRefresh && state.autonomousMode && !isTokenValid()) {
|
||
ensureToken();
|
||
}
|
||
});
|
||
|
||
// Token source selection
|
||
document.getElementById('tokenSourceSelect').addEventListener('change', (e) => {
|
||
state.tokenSource = e.target.value;
|
||
Utils.addDebugLog(`Token source changed to: ${state.tokenSource}`, 'info');
|
||
updateSystemStatus();
|
||
|
||
// Invalidate current token to force new generation with new method
|
||
if (isTokenValid()) {
|
||
Utils.addDebugLog('Invalidating current token due to source change', 'info');
|
||
invalidateToken();
|
||
}
|
||
});
|
||
|
||
// NEW: Enhanced batch mode controls
|
||
document.getElementById('smartBatchMode').addEventListener('change', (e) => {
|
||
if (e.target.checked) {
|
||
state.autoBatchEnabled = true;
|
||
state.batchOptimization = true;
|
||
document.getElementById('manualBatchControls').style.display = 'none';
|
||
Utils.addDebugLog('Smart batch mode enabled - will auto-adjust to damage count', 'info');
|
||
updateBatchInfo();
|
||
}
|
||
});
|
||
|
||
document.getElementById('manualBatchMode').addEventListener('change', (e) => {
|
||
if (e.target.checked) {
|
||
state.autoBatchEnabled = false;
|
||
state.batchOptimization = false;
|
||
document.getElementById('manualBatchControls').style.display = 'flex';
|
||
Utils.addDebugLog('Manual batch mode enabled - using fixed batch size', 'info');
|
||
updateBatchInfo();
|
||
}
|
||
});
|
||
|
||
// Manual batch size controls with +/- buttons
|
||
document.getElementById('batchSizeIncrease').addEventListener('click', () => {
|
||
const newSize = Math.min(CONFIG.MAX_BATCH_SIZE, state.currentBatchSize + 1);
|
||
if (newSize !== state.currentBatchSize) {
|
||
state.currentBatchSize = newSize;
|
||
document.getElementById('batchSize').value = state.currentBatchSize;
|
||
Utils.addDebugLog(`Increased batch size to ${state.currentBatchSize} pixels`, 'info');
|
||
updateBatchInfo();
|
||
}
|
||
});
|
||
|
||
document.getElementById('batchSizeDecrease').addEventListener('click', () => {
|
||
const newSize = Math.max(CONFIG.MIN_BATCH_SIZE, state.currentBatchSize - 1);
|
||
if (newSize !== state.currentBatchSize) {
|
||
state.currentBatchSize = newSize;
|
||
document.getElementById('batchSize').value = state.currentBatchSize;
|
||
Utils.addDebugLog(`Decreased batch size to ${state.currentBatchSize} pixels`, 'info');
|
||
updateBatchInfo();
|
||
}
|
||
});
|
||
|
||
// Manual input allows direct typing for precise control
|
||
document.getElementById('batchSize').addEventListener('input', (e) => {
|
||
const value = parseInt(e.target.value);
|
||
if (!isNaN(value)) {
|
||
const clampedValue = Math.max(CONFIG.MIN_BATCH_SIZE, Math.min(CONFIG.MAX_BATCH_SIZE, value));
|
||
if (clampedValue !== state.currentBatchSize) {
|
||
state.currentBatchSize = clampedValue;
|
||
if (clampedValue !== value) {
|
||
e.target.value = state.currentBatchSize;
|
||
}
|
||
Utils.addDebugLog(`Manual batch size set to ${state.currentBatchSize} pixels`, 'info');
|
||
updateBatchInfo();
|
||
}
|
||
}
|
||
});
|
||
|
||
document.getElementById('paintTransparentPixels').addEventListener('change', (e) => {
|
||
state.paintTransparentPixels = e.target.checked;
|
||
Utils.addDebugLog(`Transparent pixel repair ${state.paintTransparentPixels ? 'enabled' : 'disabled'}`, 'info');
|
||
});
|
||
|
||
// Clear debug
|
||
document.getElementById('clearDebugBtn').addEventListener('click', () => {
|
||
state.debugLogs = [];
|
||
updateDebugConsole();
|
||
Utils.addDebugLog('Debug console cleared', 'info');
|
||
Utils.addDebugLog('Enhanced WPlace Autonomous Repair Tool ready', 'info');
|
||
});
|
||
}
|
||
|
||
// Enhanced data migration helper
|
||
function migrateProgressData(data) {
|
||
if (!data.version || data.version < '2.0') {
|
||
Utils.addDebugLog('Migrating old progress data format...', 'info');
|
||
|
||
if (!data.state) data.state = {};
|
||
if (!data.imageData) data.imageData = {};
|
||
|
||
data.state.paintWhitePixels = data.state.paintWhitePixels ?? true;
|
||
data.state.paintTransparentPixels = data.state.paintTransparentPixels ?? false;
|
||
|
||
data.version = '2.0';
|
||
}
|
||
|
||
// Additional migrations for autonomous features
|
||
if (data.version === '2.0') {
|
||
data.state.autonomousMode = data.state.autonomousMode ?? CONFIG.AUTONOMOUS_MODE;
|
||
data.state.autoTokenRefresh = data.state.autoTokenRefresh ?? CONFIG.AUTO_TOKEN_REFRESH;
|
||
data.state.tokenSource = data.state.tokenSource ?? CONFIG.TOKEN_SOURCE;
|
||
data.version = '3.0';
|
||
Utils.addDebugLog('Migrated to autonomous features v3.0', 'info');
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
// Main initialization function
|
||
async function initialize() {
|
||
Utils.addDebugLog('Starting WPlace Autonomous Repair Tool v3.0...', 'info');
|
||
|
||
// Create UI first
|
||
createUI();
|
||
|
||
Utils.addDebugLog('NEW Enhanced Features Initialized:', 'info');
|
||
Utils.addDebugLog('• NEW Turnstile implementation with widget reuse', 'info');
|
||
Utils.addDebugLog('• Enhanced sitekey detection from multiple sources', 'info');
|
||
Utils.addDebugLog('• Sophisticated token management with preloading', 'info');
|
||
Utils.addDebugLog('• Autonomous operation with smart retry logic', 'info');
|
||
Utils.addDebugLog('• Enhanced tile interception and caching', 'info');
|
||
Utils.addDebugLog('• Adaptive repair algorithms with failure handling', 'info');
|
||
Utils.addDebugLog('• Visual notifications and audio alerts', 'info');
|
||
Utils.addDebugLog('• Draggable window with minimize/close controls', 'info');
|
||
|
||
if (state.autonomousMode) {
|
||
Utils.addDebugLog('AUTONOMOUS MODE ACTIVE - Tool will operate independently', 'success');
|
||
Utils.addDebugLog('Instructions for autonomous operation:', 'info');
|
||
Utils.addDebugLog('1. Wait for initial setup to complete', 'info');
|
||
Utils.addDebugLog('2. Load a progress file using "Load Progress File"', 'info');
|
||
Utils.addDebugLog('3. Enable "Auto Repair" to start autonomous monitoring', 'info');
|
||
Utils.addDebugLog('4. Tool will automatically scan and repair damage', 'info');
|
||
Utils.addDebugLog('5. Tokens will be generated and refreshed automatically', 'info');
|
||
Utils.addDebugLog('6. Monitor debug console for autonomous operation logs', 'info');
|
||
} else {
|
||
Utils.addDebugLog('Manual mode active - use controls for manual operation', 'info');
|
||
}
|
||
|
||
// Show autonomous mode status
|
||
updateStatus(state.autonomousMode ? Utils.t('autonomousModeActive') : 'Manual mode ready - Load file and enable auto repair to start');
|
||
|
||
Utils.addDebugLog('WPlace Autonomous Repair Tool ready for operation', 'success');
|
||
}
|
||
|
||
// Enhanced cleanup function
|
||
function cleanup() {
|
||
Utils.addDebugLog('Cleaning up autonomous systems...', 'info');
|
||
|
||
// Clear all timers
|
||
if (state.autoRepairTimer) {
|
||
clearInterval(state.autoRepairTimer);
|
||
state.autoRepairTimer = null;
|
||
}
|
||
|
||
if (state.tokenRetryTimer) {
|
||
clearTimeout(state.tokenRetryTimer);
|
||
state.tokenRetryTimer = null;
|
||
}
|
||
|
||
if (state.tokenPreloadTimer) {
|
||
clearTimeout(state.tokenPreloadTimer);
|
||
state.tokenPreloadTimer = null;
|
||
}
|
||
|
||
// Cleanup Turnstile
|
||
Utils.cleanupTurnstile();
|
||
|
||
// Set stop flag
|
||
state.stopFlag = true;
|
||
|
||
Utils.addDebugLog('Cleanup completed', 'info');
|
||
}
|
||
|
||
// Add cleanup on page unload
|
||
window.addEventListener('beforeunload', cleanup);
|
||
|
||
// Handle page visibility changes for autonomous mode
|
||
document.addEventListener('visibilitychange', () => {
|
||
if (state.autonomousMode) {
|
||
if (document.hidden) {
|
||
Utils.addDebugLog('Page hidden - autonomous systems continue running', 'info');
|
||
} else {
|
||
Utils.addDebugLog('Page visible - autonomous systems active', 'info');
|
||
// Refresh status when page becomes visible
|
||
updateCharges();
|
||
updateTokenStatus();
|
||
}
|
||
}
|
||
});
|
||
|
||
// Enhanced error handling for autonomous mode
|
||
window.addEventListener('error', (event) => {
|
||
if (state.autonomousMode) {
|
||
Utils.addDebugLog(`Global error in autonomous mode: ${event.error?.message || 'Unknown error'}`, 'error');
|
||
}
|
||
});
|
||
|
||
window.addEventListener('unhandledrejection', (event) => {
|
||
if (state.autonomousMode) {
|
||
Utils.addDebugLog(`Unhandled promise rejection in autonomous mode: ${event.reason}`, 'error');
|
||
}
|
||
});
|
||
|
||
// Start the enhanced autonomous application
|
||
initialize().catch(error => {
|
||
console.error('Failed to initialize autonomous repair tool:', error);
|
||
Utils.addDebugLog(`Initialization failed: ${error.message}`, 'error');
|
||
});
|
||
|
||
})();
|