mirror of
https://github.com/tiennm99/WPlace-AutoBOT.git
synced 2026-10-13 04:14:49 +00:00
Update Auto-Image.js
This commit is contained in:
1 parent
60ba9d9d2f
commit
4d72113a1a
1 file changed
+253
-66
+253
-66
@@ -5,6 +5,9 @@
|
||||
TRANSPARENCY_THRESHOLD: 100,
|
||||
WHITE_THRESHOLD: 250,
|
||||
LOG_INTERVAL: 10,
|
||||
TOKEN_REFRESH_INTERVAL: 300000, // Refresh token every 5 minutes
|
||||
MAX_RETRIES: 3,
|
||||
RETRY_DELAY: 5000,
|
||||
THEME: {
|
||||
primary: '#000000',
|
||||
secondary: '#111111',
|
||||
@@ -52,7 +55,9 @@
|
||||
waitingInit: "Aguardando inicialização...",
|
||||
resizeSuccess: "✅ Imagem redimensionada para {width}x{height}",
|
||||
paintingPaused: "⏸️ Pintura pausada na posição X: {x}, Y: {y}",
|
||||
captchaNeeded: "❗ Token CAPTCHA necessário. Pinte um pixel manualmente para continuar."
|
||||
captchaNeeded: "❗ Token CAPTCHA necessário. Pinte um pixel manualmente para continuar.",
|
||||
tokenRefreshed: "🔄 Token atualizado automaticamente",
|
||||
retryingRequest: "🔄 Tentando novamente... ({retry}/{max})"
|
||||
},
|
||||
en: {
|
||||
title: "WPlace Auto-Image",
|
||||
@@ -87,7 +92,9 @@
|
||||
waitingInit: "Waiting for initialization...",
|
||||
resizeSuccess: "✅ Image resized to {width}x{height}",
|
||||
paintingPaused: "⏸️ Painting paused at position X: {x}, Y: {y}",
|
||||
captchaNeeded: "❗ CAPTCHA token needed. Paint one pixel manually to continue."
|
||||
captchaNeeded: "❗ CAPTCHA token needed. Paint one pixel manually to continue.",
|
||||
tokenRefreshed: "🔄 Token refreshed automatically",
|
||||
retryingRequest: "🔄 Retrying... ({retry}/{max})"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -110,36 +117,134 @@
|
||||
minimized: false,
|
||||
lastPosition: { x: 0, y: 0 },
|
||||
estimatedTime: 0,
|
||||
language: 'en'
|
||||
language: 'en',
|
||||
tokenLastRefreshed: Date.now(),
|
||||
failedAttempts: 0,
|
||||
sessionHeaders: null
|
||||
};
|
||||
|
||||
// Global variable to store the captured CAPTCHA token.
|
||||
// Enhanced token management
|
||||
let capturedCaptchaToken = null;
|
||||
let capturedHeaders = null;
|
||||
let tokenRefreshTimer = null;
|
||||
|
||||
// Intercept the original window.fetch function to "listen" for network requests.
|
||||
// Enhanced request interceptor with header capture
|
||||
const originalFetch = window.fetch;
|
||||
const originalXHROpen = XMLHttpRequest.prototype.open;
|
||||
const originalXHRSend = XMLHttpRequest.prototype.send;
|
||||
const originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
||||
|
||||
// Intercept XMLHttpRequest for better header capture
|
||||
XMLHttpRequest.prototype.open = function(method, url) {
|
||||
this._url = url;
|
||||
this._method = method;
|
||||
this._requestHeaders = {};
|
||||
return originalXHROpen.apply(this, arguments);
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
|
||||
this._requestHeaders[header] = value;
|
||||
return originalXHRSetRequestHeader.apply(this, arguments);
|
||||
};
|
||||
|
||||
XMLHttpRequest.prototype.send = function(body) {
|
||||
if (this._url && this._url.includes('backend.wplace.live/s0/pixel/')) {
|
||||
try {
|
||||
const payload = JSON.parse(body);
|
||||
if (payload.t) {
|
||||
console.log("✅ CAPTCHA Token Captured via XHR:", payload.t);
|
||||
capturedCaptchaToken = payload.t;
|
||||
capturedHeaders = {...this._requestHeaders};
|
||||
state.tokenLastRefreshed = Date.now();
|
||||
|
||||
if (document.querySelector('#statusText')?.textContent.includes('CAPTCHA')) {
|
||||
Utils.showAlert("Token captured successfully! You can start the bot now.", "success");
|
||||
updateUI('colorsFound', 'success', { count: state.availableColors.length });
|
||||
}
|
||||
|
||||
// Reset failed attempts counter
|
||||
state.failedAttempts = 0;
|
||||
|
||||
// Start token refresh timer
|
||||
startTokenRefreshTimer();
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
return originalXHRSend.apply(this, arguments);
|
||||
};
|
||||
|
||||
// Enhanced fetch interceptor
|
||||
window.fetch = async (url, options) => {
|
||||
// Check if the request is for painting a pixel on wplace.
|
||||
if (typeof url === 'string' && url.includes('https://backend.wplace.live/s0/pixel/')) {
|
||||
if (typeof url === 'string' && url.includes('backend.wplace.live/s0/pixel/')) {
|
||||
try {
|
||||
const payload = JSON.parse(options.body);
|
||||
// If the request body contains the 't' field, it's our CAPTCHA token.
|
||||
if (payload.t) {
|
||||
console.log("✅ CAPTCHA Token Captured:", payload.t);
|
||||
// Store the token for our bot to use.
|
||||
console.log("✅ CAPTCHA Token Captured via Fetch:", payload.t);
|
||||
capturedCaptchaToken = payload.t;
|
||||
// Notify the user that the token is captured and they can start the bot.
|
||||
if(document.querySelector('#statusText')?.textContent.includes('CAPTCHA')){
|
||||
Utils.showAlert("Token captured successfully! You can start the bot now.", "success");
|
||||
updateUI('colorsFound', 'success', { count: state.availableColors.length });
|
||||
capturedHeaders = options.headers ? {...options.headers} : null;
|
||||
state.tokenLastRefreshed = Date.now();
|
||||
|
||||
if (document.querySelector('#statusText')?.textContent.includes('CAPTCHA')) {
|
||||
Utils.showAlert("Token captured successfully! You can start the bot now.", "success");
|
||||
updateUI('colorsFound', 'success', { count: state.availableColors.length });
|
||||
}
|
||||
|
||||
state.failedAttempts = 0;
|
||||
startTokenRefreshTimer();
|
||||
}
|
||||
} catch (e) { /* Ignore errors if the request body isn't valid JSON */ }
|
||||
} catch (e) {}
|
||||
}
|
||||
// Finally, execute the original request, whether we inspected it or not.
|
||||
return originalFetch(url, options);
|
||||
};
|
||||
|
||||
// Token refresh mechanism
|
||||
function startTokenRefreshTimer() {
|
||||
if (tokenRefreshTimer) {
|
||||
clearInterval(tokenRefreshTimer);
|
||||
}
|
||||
|
||||
tokenRefreshTimer = setInterval(async () => {
|
||||
if (state.running && capturedCaptchaToken) {
|
||||
const timeSinceRefresh = Date.now() - state.tokenLastRefreshed;
|
||||
if (timeSinceRefresh >= CONFIG.TOKEN_REFRESH_INTERVAL) {
|
||||
console.log("⏰ Token refresh time reached, requesting manual paint...");
|
||||
updateUI('captchaNeeded', 'warning');
|
||||
Utils.showAlert("Please paint a pixel manually to refresh the token", "warning");
|
||||
}
|
||||
}
|
||||
}, 60000); // Check every minute
|
||||
}
|
||||
|
||||
// Auto-recovery mechanism
|
||||
async function attemptAutoRecovery() {
|
||||
console.log("🔧 Attempting auto-recovery...");
|
||||
|
||||
// Try to trigger a Cloudflare challenge solve
|
||||
try {
|
||||
// Method 1: Try to fetch the main page to trigger CF challenge
|
||||
const mainPageResponse = await fetch('https://wplace.live/', {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'User-Agent': navigator.userAgent
|
||||
}
|
||||
});
|
||||
|
||||
// Method 2: Try to get user info which might refresh session
|
||||
const meResponse = await fetch('https://backend.wplace.live/me', {
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (meResponse.ok) {
|
||||
console.log("✅ Session appears to be valid");
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Auto-recovery failed:", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function detectLanguage() {
|
||||
try {
|
||||
@@ -262,36 +367,80 @@
|
||||
}
|
||||
};
|
||||
|
||||
// WPLACE API SERVICE
|
||||
// Enhanced WPLACE API SERVICE with retry logic
|
||||
const WPlaceService = {
|
||||
async paintPixelInRegion(regionX, regionY, pixelX, pixelY, color) {
|
||||
try {
|
||||
// Construct the payload including the captured 't' token.
|
||||
const payload = {
|
||||
coords: [pixelX, pixelY],
|
||||
colors: [color],
|
||||
t: capturedCaptchaToken
|
||||
};
|
||||
const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(payload)
|
||||
async paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount = 0) {
|
||||
try {
|
||||
const payload = {
|
||||
coords: [pixelX, pixelY],
|
||||
colors: [color],
|
||||
t: capturedCaptchaToken
|
||||
};
|
||||
|
||||
// Build headers based on captured headers
|
||||
const headers = {
|
||||
'Content-Type': 'text/plain;charset=UTF-8',
|
||||
...(capturedHeaders || {})
|
||||
};
|
||||
|
||||
const res = await fetch(`https://backend.wplace.live/s0/pixel/${regionX}/${regionY}`, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
// Handle different response codes
|
||||
if (res.status === 403) {
|
||||
console.error("❌ 403 Forbidden. Token expired or invalid.");
|
||||
state.failedAttempts++;
|
||||
|
||||
// Try auto-recovery first
|
||||
if (retryCount < CONFIG.MAX_RETRIES) {
|
||||
updateUI('retryingRequest', 'warning', {
|
||||
retry: retryCount + 1,
|
||||
max: CONFIG.MAX_RETRIES
|
||||
});
|
||||
|
||||
// If we get a 403 Forbidden error, our token is likely expired.
|
||||
if (res.status === 403) {
|
||||
console.error("❌ 403 Forbidden. CAPTCHA token might be invalid or expired.");
|
||||
capturedCaptchaToken = null; // Invalidate our stored token.
|
||||
return 'token_error'; // Return a special status to stop the bot.
|
||||
|
||||
const recovered = await attemptAutoRecovery();
|
||||
if (recovered) {
|
||||
await Utils.sleep(CONFIG.RETRY_DELAY);
|
||||
return await this.paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount + 1);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
return data?.painted === 1;
|
||||
} catch(e) {
|
||||
console.error("Paint request failed:", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
capturedCaptchaToken = null;
|
||||
return 'token_error';
|
||||
}
|
||||
|
||||
if (res.status === 429) {
|
||||
console.warn("⚠️ Rate limited. Waiting before retry...");
|
||||
await Utils.sleep(CONFIG.RETRY_DELAY * 2);
|
||||
if (retryCount < CONFIG.MAX_RETRIES) {
|
||||
return await this.paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
// Reset failed attempts on success
|
||||
if (data?.painted === 1) {
|
||||
state.failedAttempts = 0;
|
||||
}
|
||||
|
||||
return data?.painted === 1;
|
||||
} catch(e) {
|
||||
console.error("Paint request failed:", e);
|
||||
|
||||
// Retry on network errors
|
||||
if (retryCount < CONFIG.MAX_RETRIES) {
|
||||
await Utils.sleep(CONFIG.RETRY_DELAY);
|
||||
return await this.paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount + 1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
async getCharges() {
|
||||
@@ -374,6 +523,7 @@
|
||||
}, { color: palette[0], distance: Utils.colorDistance(rgb, palette[0].rgb) }).color.id;
|
||||
}
|
||||
|
||||
// Create UI function with enhanced token status display
|
||||
async function createUI() {
|
||||
await detectLanguage();
|
||||
|
||||
@@ -397,7 +547,7 @@
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 300px;
|
||||
width: 320px;
|
||||
background: ${CONFIG.THEME.primary};
|
||||
border: 1px solid ${CONFIG.THEME.accent};
|
||||
border-radius: 8px;
|
||||
@@ -543,22 +693,25 @@
|
||||
background: rgba(255, 165, 0, 0.1);
|
||||
color: orange;
|
||||
}
|
||||
#paintEffect {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.position-info {
|
||||
font-size: 13px;
|
||||
margin-top: 5px;
|
||||
.token-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 5px;
|
||||
background: ${CONFIG.THEME.secondary};
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.token-status-indicator {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: ${CONFIG.THEME.error};
|
||||
}
|
||||
.token-status-indicator.active {
|
||||
background: ${CONFIG.THEME.success};
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
.wplace-minimized .wplace-content {
|
||||
display: none;
|
||||
@@ -667,6 +820,11 @@
|
||||
<div id="statusText" class="wplace-status status-default">
|
||||
${Utils.t('waitingInit')}
|
||||
</div>
|
||||
|
||||
<div class="token-status">
|
||||
<div id="tokenIndicator" class="token-status-indicator"></div>
|
||||
<span id="tokenStatusText">Token: Not captured</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -676,26 +834,26 @@
|
||||
<h3 style="margin-top: 0; color: ${CONFIG.THEME.text}">${Utils.t('resizeImage')}</h3>
|
||||
<div class="resize-controls">
|
||||
<label style="color: ${CONFIG.THEME.text}">
|
||||
${Utils.t('width')}: <span id="widthValue">0</span>px
|
||||
Width: <span id="widthValue">0</span>px
|
||||
<input type="range" id="widthSlider" class="resize-slider" min="10" max="500" value="100">
|
||||
</label>
|
||||
<label style="color: ${CONFIG.THEME.text}">
|
||||
${Utils.t('height')}: <span id="heightValue">0</span>px
|
||||
Height: <span id="heightValue">0</span>px
|
||||
<input type="range" id="heightSlider" class="resize-slider" min="10" max="500" value="100">
|
||||
</label>
|
||||
<label style="color: ${CONFIG.THEME.text}">
|
||||
<input type="checkbox" id="keepAspect" checked>
|
||||
${Utils.t('keepAspect')}
|
||||
Keep Aspect Ratio
|
||||
</label>
|
||||
<img id="resizePreview" class="resize-preview" src="">
|
||||
<div class="resize-buttons">
|
||||
<button id="confirmResize" class="wplace-btn wplace-btn-primary">
|
||||
<i class="fas fa-check"></i>
|
||||
<span>${Utils.t('apply')}</span>
|
||||
<span>Apply</span>
|
||||
</button>
|
||||
<button id="cancelResize" class="wplace-btn wplace-btn-stop">
|
||||
<i class="fas fa-times"></i>
|
||||
<span>${Utils.t('cancel')}</span>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -708,6 +866,7 @@
|
||||
document.body.appendChild(resizeOverlay);
|
||||
document.body.appendChild(resizeContainer);
|
||||
|
||||
// Drag functionality
|
||||
const header = container.querySelector('.wplace-header');
|
||||
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
|
||||
|
||||
@@ -738,6 +897,7 @@
|
||||
document.onmousemove = null;
|
||||
}
|
||||
|
||||
// Get UI elements
|
||||
const initBotBtn = container.querySelector('#initBotBtn');
|
||||
const uploadBtn = container.querySelector('#uploadBtn');
|
||||
const resizeBtn = container.querySelector('#resizeBtn');
|
||||
@@ -748,7 +908,8 @@
|
||||
const statusText = container.querySelector('#statusText');
|
||||
const progressBar = container.querySelector('#progressBar');
|
||||
const statsArea = container.querySelector('#statsArea');
|
||||
const content = container.querySelector('.wplace-content');
|
||||
const tokenIndicator = container.querySelector('#tokenIndicator');
|
||||
const tokenStatusText = container.querySelector('#tokenStatusText');
|
||||
|
||||
const widthSlider = resizeContainer.querySelector('#widthSlider');
|
||||
const heightSlider = resizeContainer.querySelector('#heightSlider');
|
||||
@@ -759,6 +920,22 @@
|
||||
const confirmResize = resizeContainer.querySelector('#confirmResize');
|
||||
const cancelResize = resizeContainer.querySelector('#cancelResize');
|
||||
|
||||
// Update token status display
|
||||
const updateTokenStatus = () => {
|
||||
if (capturedCaptchaToken) {
|
||||
tokenIndicator.classList.add('active');
|
||||
const timeElapsed = Date.now() - state.tokenLastRefreshed;
|
||||
const timeRemaining = Math.max(0, CONFIG.TOKEN_REFRESH_INTERVAL - timeElapsed);
|
||||
tokenStatusText.textContent = `Token: Active (${Utils.formatTime(timeRemaining)} left)`;
|
||||
} else {
|
||||
tokenIndicator.classList.remove('active');
|
||||
tokenStatusText.textContent = 'Token: Not captured';
|
||||
}
|
||||
};
|
||||
|
||||
// Update token status every second
|
||||
setInterval(updateTokenStatus, 1000);
|
||||
|
||||
minimizeBtn.addEventListener('click', () => {
|
||||
state.minimized = !state.minimized;
|
||||
if (state.minimized) {
|
||||
@@ -904,6 +1081,7 @@
|
||||
resizeContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
initBotBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
updateUI('checkingColors', 'default');
|
||||
@@ -1057,7 +1235,6 @@
|
||||
});
|
||||
|
||||
startBtn.addEventListener('click', async () => {
|
||||
// Before starting, check for all requirements, including the token.
|
||||
if (!state.imageLoaded || !state.startPosition || !state.region) {
|
||||
updateUI('missingRequirements', 'error');
|
||||
return;
|
||||
@@ -1154,12 +1331,14 @@
|
||||
colorId
|
||||
);
|
||||
|
||||
// Check for the special 'token_error' status.
|
||||
if (success === 'token_error') {
|
||||
state.stopFlag = true; // Stop the process.
|
||||
state.stopFlag = true;
|
||||
updateUI('captchaNeeded', 'error');
|
||||
Utils.showAlert(Utils.t('captchaNeeded'), 'error');
|
||||
break outerLoop; // Exit the loop immediately.
|
||||
|
||||
// Wait for manual token refresh
|
||||
capturedCaptchaToken = null;
|
||||
break outerLoop;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
@@ -1180,6 +1359,14 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check if token needs refresh
|
||||
const timeSinceRefresh = Date.now() - state.tokenLastRefreshed;
|
||||
if (timeSinceRefresh >= CONFIG.TOKEN_REFRESH_INTERVAL - 30000) {
|
||||
// Token is about to expire in 30 seconds
|
||||
updateUI('captchaNeeded', 'warning');
|
||||
Utils.showAlert("Token expiring soon. Please paint manually to refresh.", "warning");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user