mirror of
https://github.com/tiennm99/WPlace-AutoBOT.git
synced 2026-10-11 12:08:55 +00:00
11 files changed
+6570
-5524
No files matched your search
@@ -110,7 +110,7 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
const overlayManagerCode = await overlayManagerResponse.text();
|
||||
console.log('🎨 Overlay manager loaded, injecting third...');
|
||||
|
||||
// Execute overlay manager third
|
||||
// Execute overlay manager thirds
|
||||
await chrome.scripting.executeScript({
|
||||
target: { tabId: tabId },
|
||||
world: "MAIN",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "WPlace AutoBOT Script Launcher",
|
||||
"version": "2.0.2",
|
||||
"version": "2.0.4",
|
||||
"description": "Launch and manage automation scripts for WPlace",
|
||||
"icons": {
|
||||
"16": "icons/icon16.png",
|
||||
|
||||
@@ -335,7 +335,7 @@ main {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.section-header h4 {
|
||||
@@ -351,6 +351,9 @@ main {
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
|
||||
+19
-11
@@ -30,16 +30,24 @@
|
||||
<div class="account-section">
|
||||
<div class="section-header">
|
||||
<h4>Account Manager</h4>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-primary btn-small" id="refreshAccountBtn" title="Add Current Account">
|
||||
<span class="icon">🔄</span>
|
||||
Refresh
|
||||
</button>
|
||||
<button class="btn btn-primary btn-small" id="addAccountBtn" title="Add Account Manually">
|
||||
<span class="icon">➕</span>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-primary btn-small" id="refreshAccountBtn" title="Add Current Account">
|
||||
<span class="icon">🔄</span>
|
||||
Refresh
|
||||
</button>
|
||||
<button class="btn btn-primary btn-small" id="addAccountBtn" title="Add Account Manually">
|
||||
<span class="icon">➕</span>
|
||||
Add
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-small" id="exportAccountsBtn" title="Export All Accounts">
|
||||
<span class="icon">📤</span>
|
||||
Export
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-small" id="importAccountsBtn" title="Import Accounts">
|
||||
<span class="icon">📥</span>
|
||||
Import
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="accounts-container" id="accountsContainer">
|
||||
@@ -105,7 +113,7 @@
|
||||
|
||||
<footer>
|
||||
<div class="footer-info">
|
||||
<span>v2.0.2</span>
|
||||
<span>v2.0.4</span>
|
||||
<a href="#" id="settingsBtn">Settings</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -39,6 +39,7 @@ const AVAILABLE_SCRIPTS = [
|
||||
let statusDot, statusText, scriptsList, accountsContainer, accountPlaceholder;
|
||||
let addAccountModal, accountNameInput, accountTokenInput;
|
||||
let refreshAccountBtn, addAccountBtn, saveAccountBtn, cancelAccountBtn, closeModalBtn;
|
||||
let exportAccountsBtn, importAccountsBtn;
|
||||
|
||||
// Global state
|
||||
let accounts = [];
|
||||
@@ -75,6 +76,8 @@ function initializeElements() {
|
||||
saveAccountBtn = document.getElementById('saveAccountBtn');
|
||||
cancelAccountBtn = document.getElementById('cancelAccountBtn');
|
||||
closeModalBtn = document.getElementById('closeModalBtn');
|
||||
exportAccountsBtn = document.getElementById('exportAccountsBtn');
|
||||
importAccountsBtn = document.getElementById('importAccountsBtn');
|
||||
}
|
||||
|
||||
async function loadScripts() {
|
||||
@@ -126,6 +129,8 @@ function setupEventListeners() {
|
||||
saveAccountBtn.addEventListener('click', handleSaveAccount);
|
||||
cancelAccountBtn.addEventListener('click', closeAddAccountModal);
|
||||
closeModalBtn.addEventListener('click', closeAddAccountModal);
|
||||
exportAccountsBtn.addEventListener('click', handleExportAccounts);
|
||||
importAccountsBtn.addEventListener('click', handleImportAccounts);
|
||||
|
||||
// Modal close on overlay click
|
||||
addAccountModal.addEventListener('click', (e) => {
|
||||
@@ -663,6 +668,194 @@ async function loadStartupScript() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleExportAccounts() {
|
||||
try {
|
||||
if (isLoading) return;
|
||||
|
||||
setLoading(true);
|
||||
showNotification('Exporting accounts...', 'info');
|
||||
|
||||
console.log('📤 Starting account export process');
|
||||
|
||||
// Get accounts from storage
|
||||
const result = await chrome.storage.local.get('infoAccounts');
|
||||
const accounts = result.infoAccounts || [];
|
||||
|
||||
if (accounts.length === 0) {
|
||||
showNotification('No accounts to export', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create export data structure
|
||||
const exportData = {
|
||||
version: '1.0',
|
||||
exportDate: new Date().toISOString(),
|
||||
accountCount: accounts.length,
|
||||
accounts: accounts.map(account => ({
|
||||
name: account.name,
|
||||
token: account.token,
|
||||
ID: account.ID,
|
||||
// Include static metadata only (exclude dynamic status like Charges, Max, Droplets)
|
||||
...(account.lastActive && { lastActive: account.lastActive }),
|
||||
...(account.level && { level: account.level }),
|
||||
...(account.totalPixelsPainted && { totalPixelsPainted: account.totalPixelsPainted }),
|
||||
...(account.allianceName && { allianceName: account.allianceName }),
|
||||
...(account.allianceRole && { allianceRole: account.allianceRole })
|
||||
}))
|
||||
};
|
||||
|
||||
// Create filename with timestamp
|
||||
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
|
||||
const filename = `wplace-accounts-${timestamp}.json`;
|
||||
|
||||
// Create and download file
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
console.log(`✅ Exported ${accounts.length} accounts to ${filename}`);
|
||||
showNotification(`Exported ${accounts.length} accounts successfully`, 'success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error exporting accounts:', error);
|
||||
showNotification('Failed to export accounts', 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleImportAccounts() {
|
||||
try {
|
||||
if (isLoading) return;
|
||||
|
||||
setLoading(true);
|
||||
showNotification('Importing accounts...', 'info');
|
||||
|
||||
console.log('📥 Starting account import process');
|
||||
|
||||
// Create file input
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
|
||||
input.onchange = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const text = await file.text();
|
||||
const importData = JSON.parse(text);
|
||||
|
||||
// Validate import data structure
|
||||
if (!importData.accounts || !Array.isArray(importData.accounts)) {
|
||||
throw new Error('Invalid file format: missing accounts array');
|
||||
}
|
||||
|
||||
// Validate each account has required fields
|
||||
for (const account of importData.accounts) {
|
||||
if (!account.name || !account.token) {
|
||||
throw new Error('Invalid account data: missing name or token');
|
||||
}
|
||||
}
|
||||
|
||||
// Get existing accounts
|
||||
const result = await chrome.storage.local.get('infoAccounts');
|
||||
let existingAccounts = result.infoAccounts || [];
|
||||
|
||||
let addedCount = 0;
|
||||
let updatedCount = 0;
|
||||
let skippedCount = 0;
|
||||
|
||||
// Process each imported account
|
||||
for (const importAccount of importData.accounts) {
|
||||
// Check if account already exists (by token or ID)
|
||||
const existingIndex = existingAccounts.findIndex(existing =>
|
||||
existing.token === importAccount.token ||
|
||||
(existing.ID && importAccount.ID && existing.ID === importAccount.ID)
|
||||
);
|
||||
|
||||
if (existingIndex > -1) {
|
||||
// Account exists - ask user what to do
|
||||
const shouldUpdate = confirm(
|
||||
`Account "${importAccount.name}" already exists. Update it?\n\n` +
|
||||
`Click OK to update, Cancel to skip.`
|
||||
);
|
||||
|
||||
if (shouldUpdate) {
|
||||
// Update existing account
|
||||
existingAccounts[existingIndex] = {
|
||||
...existingAccounts[existingIndex],
|
||||
...importAccount,
|
||||
lastImported: new Date().toISOString()
|
||||
};
|
||||
updatedCount++;
|
||||
console.log(`🔄 Updated account: ${importAccount.name}`);
|
||||
} else {
|
||||
skippedCount++;
|
||||
console.log(`⏭️ Skipped account: ${importAccount.name}`);
|
||||
}
|
||||
} else {
|
||||
// New account - add it
|
||||
existingAccounts.push({
|
||||
...importAccount,
|
||||
lastImported: new Date().toISOString(),
|
||||
// Set default values for missing fields
|
||||
Charges: importAccount.Charges || 0,
|
||||
Max: importAccount.Max || 100,
|
||||
Droplets: importAccount.Droplets || 0
|
||||
});
|
||||
addedCount++;
|
||||
console.log(`➕ Added new account: ${importAccount.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Save updated accounts
|
||||
await chrome.storage.local.set({ infoAccounts: existingAccounts });
|
||||
|
||||
// Update the accounts array for compatibility
|
||||
const accountTokens = existingAccounts.map(account => account.token);
|
||||
await chrome.storage.local.set({ accounts: accountTokens });
|
||||
|
||||
// Reload the accounts display
|
||||
await loadAccounts();
|
||||
|
||||
// Show summary
|
||||
const summary = [
|
||||
`Import completed:`,
|
||||
addedCount > 0 ? `${addedCount} added` : '',
|
||||
updatedCount > 0 ? `${updatedCount} updated` : '',
|
||||
skippedCount > 0 ? `${skippedCount} skipped` : ''
|
||||
].filter(Boolean).join(', ');
|
||||
|
||||
console.log(`✅ Import summary: ${summary}`);
|
||||
showNotification(summary, 'success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error importing accounts:', error);
|
||||
showNotification(`Import failed: ${error.message}`, 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
input.click();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error starting import:', error);
|
||||
showNotification('Failed to start import', 'error');
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced account validation
|
||||
async function validateAccountToken(token) {
|
||||
try {
|
||||
|
||||
@@ -40,7 +40,7 @@ function getText(key, params) {
|
||||
window.WPLACE_ART_EXTRACTOR_LOADED = true;
|
||||
|
||||
console.log('%c🎨 WPlace Art Extractor Starting...', 'color: #ff6b35; font-weight: bold; font-size: 16px;');
|
||||
console.log('%c✨ Now featuring Auto-Guard style interactive pixel capture!', 'color: #26de81; font-weight: bold;');
|
||||
console.log('%c✨ Interactive pixel capture for area extraction!', 'color: #26de81; font-weight: bold;');
|
||||
|
||||
// Immediate visual confirmation that script is running
|
||||
try {
|
||||
@@ -66,9 +66,9 @@ function getText(key, params) {
|
||||
// CONFIGURATION CONSTANTS
|
||||
const CONFIG = {
|
||||
OVERLAY_OPACITY: 0.7,
|
||||
SELECTION_COLOR: { r: 50, g: 200, b: 50, a: 180 }, // Green extraction overlay (different from Auto-Guard's red)
|
||||
CORNER_MARKER_COLOR: { r: 255, g: 255, b: 0, a: 255 }, // Yellow corners like Auto-Guard
|
||||
CORNER_MARKER_SIZE: 12, // Larger markers like Auto-Guard
|
||||
SELECTION_COLOR: { r: 50, g: 200, b: 50, a: 180 }, // Green extraction overlay
|
||||
CORNER_MARKER_COLOR: { r: 255, g: 255, b: 0, a: 255 }, // Yellow corner markers
|
||||
CORNER_MARKER_SIZE: 12, // Corner marker size
|
||||
MIN_AREA_SIZE: 1,
|
||||
MAX_AREA_SIZE: 2500,
|
||||
COLOR_MAP: {
|
||||
@@ -179,12 +179,12 @@ function getText(key, params) {
|
||||
// Use Auto-Image's overlay system - no custom overlay manager needed
|
||||
// We'll integrate with the global overlay manager that Auto-Image uses
|
||||
|
||||
// Monitor WPlace paint events to capture pixel coordinates using Auto-Guard's fetch interception method
|
||||
// Monitor WPlace paint events to capture pixel coordinates using fetch interception
|
||||
let originalFetch = null;
|
||||
let fetchInterceptionActive = false;
|
||||
|
||||
function setupPixelCapture() {
|
||||
console.log('🎯 Setting up Auto-Guard style pixel paint monitoring...');
|
||||
console.log('🎯 Setting up pixel paint monitoring...');
|
||||
|
||||
try {
|
||||
// Store original fetch if not already stored
|
||||
@@ -192,7 +192,7 @@ function getText(key, params) {
|
||||
originalFetch = window.fetch;
|
||||
}
|
||||
|
||||
// Install fetch interceptor (Auto-Guard method)
|
||||
// Install fetch interceptor for pixel capture
|
||||
if (!fetchInterceptionActive) {
|
||||
window.fetch = async (url, options) => {
|
||||
// Check if this is a pixel painting request
|
||||
@@ -256,13 +256,13 @@ function getText(key, params) {
|
||||
};
|
||||
|
||||
fetchInterceptionActive = true;
|
||||
console.log('✅ Auto-Guard style fetch interception enabled');
|
||||
console.log('✅ Fetch interception enabled');
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Could not set up Auto-Guard style paint monitoring:', error);
|
||||
console.warn('⚠️ Could not set up paint monitoring:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -360,7 +360,7 @@ function getText(key, params) {
|
||||
// If user clicked cancel (coordStr === null), do nothing
|
||||
}
|
||||
|
||||
// Add a captured pixel (Auto-Guard style with enhanced validation)
|
||||
// Add a captured pixel with enhanced validation
|
||||
function addCapturedPixel(worldX, worldY) {
|
||||
if (!state.isCapturing) {
|
||||
console.log('Not in capture mode, ignoring pixel');
|
||||
@@ -400,7 +400,7 @@ function getText(key, params) {
|
||||
|
||||
updateUI();
|
||||
|
||||
// Auto-Guard style progress messages
|
||||
// Progress messages
|
||||
if (state.capturedPixels.length === 1) {
|
||||
showAlert(`✅ Upper-left corner captured: (${worldX}, ${worldY})`, 'success');
|
||||
setTimeout(() => {
|
||||
@@ -419,7 +419,7 @@ function getText(key, params) {
|
||||
}
|
||||
}
|
||||
|
||||
// Complete area capture when we have 2 pixels (Auto-Guard style with validation)
|
||||
// Complete area capture when we have 2 pixels with validation
|
||||
function completeAreaCapture() {
|
||||
state.isCapturing = false;
|
||||
restoreFetch(); // Restore original fetch function
|
||||
@@ -436,7 +436,7 @@ function getText(key, params) {
|
||||
const maxX = Math.max(...pixels.map(p => p.worldX));
|
||||
const maxY = Math.max(...pixels.map(p => p.worldY));
|
||||
|
||||
// Auto-Guard style validation: ensure upper-left is actually upper-left
|
||||
// Validation: ensure upper-left is actually upper-left
|
||||
if (minX >= maxX || minY >= maxY) {
|
||||
showAlert('❌ Invalid area: upper-left corner must be less than lower-right corner', 'error');
|
||||
// Reset capture to try again
|
||||
@@ -460,7 +460,7 @@ function getText(key, params) {
|
||||
}
|
||||
|
||||
state.selectionArea = {
|
||||
x1: minX, // Auto-Guard style naming
|
||||
x1: minX, // Coordinate naming
|
||||
y1: minY,
|
||||
x2: maxX,
|
||||
y2: maxY,
|
||||
@@ -502,7 +502,7 @@ function getText(key, params) {
|
||||
const canvas = new OffscreenCanvas(width, height);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Fill with extraction area overlay (different color from Auto-Guard's protection overlay)
|
||||
// Fill with extraction area overlay
|
||||
ctx.fillStyle = `rgba(${CONFIG.SELECTION_COLOR.r}, ${CONFIG.SELECTION_COLOR.g}, ${CONFIG.SELECTION_COLOR.b}, 0.3)`;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
@@ -511,7 +511,7 @@ function getText(key, params) {
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(1, 1, width - 2, height - 2);
|
||||
|
||||
// Add corner markers (like Auto-Guard)
|
||||
// Add corner markers
|
||||
const cornerSize = CONFIG.CORNER_MARKER_SIZE;
|
||||
ctx.fillStyle = `rgba(${CONFIG.CORNER_MARKER_COLOR.r}, ${CONFIG.CORNER_MARKER_COLOR.g}, ${CONFIG.CORNER_MARKER_COLOR.b}, 1)`;
|
||||
|
||||
@@ -558,14 +558,14 @@ function getText(key, params) {
|
||||
// Continue without overlay - not critical for extraction functionality
|
||||
}
|
||||
}
|
||||
// Auto-Guard style tile-based pixel scanning for area extraction
|
||||
// Tile-based pixel scanning for area extraction
|
||||
async function scanSelectedArea() {
|
||||
if (!state.selectionArea) {
|
||||
showAlert('No area selected', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🔍 Starting Auto-Guard style area scan...');
|
||||
console.log('🔍 Starting area scan...');
|
||||
state.isScanning = true;
|
||||
state.scannedPixels = [];
|
||||
updateUI();
|
||||
@@ -577,7 +577,7 @@ function getText(key, params) {
|
||||
|
||||
console.log(`🔍 Analyzing area ${areaWidth}x${areaHeight} from (${x1},${y1}) to (${x2},${y2})`);
|
||||
|
||||
// Auto-Guard style tile calculation
|
||||
// Tile calculation
|
||||
const startTileX = Math.floor(x1 / 1000);
|
||||
const startTileY = Math.floor(y1 / 1000);
|
||||
const endTileX = Math.floor(x2 / 1000);
|
||||
@@ -586,20 +586,20 @@ function getText(key, params) {
|
||||
state.totalPixels = areaWidth * areaHeight;
|
||||
let processedPixels = 0;
|
||||
|
||||
// Process each tile that intersects with our area (Auto-Guard method)
|
||||
// Process each tile that intersects with our area
|
||||
for (let tileY = startTileY; tileY <= endTileY; tileY++) {
|
||||
for (let tileX = startTileX; tileX <= endTileX; tileX++) {
|
||||
try {
|
||||
console.log(`📄 Processing tile (${tileX}, ${tileY})...`);
|
||||
|
||||
// Download tile data using Auto-Guard's getTileImage method
|
||||
// Download tile data
|
||||
const tileBlob = await getTileImage(tileX, tileY);
|
||||
if (!tileBlob) {
|
||||
console.warn(`⚠️ Could not download tile ${tileX},${tileY}, skipping...`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process tile data like Auto-Guard
|
||||
// Process tile data
|
||||
const tileImageData = await processTileBlob(tileBlob);
|
||||
if (!tileImageData) {
|
||||
console.warn(`⚠️ Could not process tile ${tileX},${tileY}, skipping...`);
|
||||
@@ -617,14 +617,14 @@ function getText(key, params) {
|
||||
const intersectEndX = Math.min(x2 + 1, tileEndX);
|
||||
const intersectEndY = Math.min(y2 + 1, tileEndY);
|
||||
|
||||
// Extract pixels from intersection area (Auto-Guard style)
|
||||
// Extract pixels from intersection area
|
||||
for (let globalY = intersectStartY; globalY < intersectEndY; globalY++) {
|
||||
for (let globalX = intersectStartX; globalX < intersectEndX; globalX++) {
|
||||
// Convert to tile-local coordinates
|
||||
const localX = globalX - tileStartX;
|
||||
const localY = globalY - tileStartY;
|
||||
|
||||
// Normalize coordinates (Auto-Guard normalization)
|
||||
// Normalize coordinates
|
||||
const normalizedX = (localX % 1000 + 1000) % 1000;
|
||||
const normalizedY = (localY % 1000 + 1000) % 1000;
|
||||
|
||||
@@ -633,7 +633,7 @@ function getText(key, params) {
|
||||
normalizedX < tileImageData.width &&
|
||||
normalizedY < tileImageData.height) {
|
||||
|
||||
// Extract RGBA values (Auto-Guard method)
|
||||
// Extract RGBA values
|
||||
const pixelIndex = (normalizedY * tileImageData.width + normalizedX) * 4;
|
||||
const r = tileImageData.data[pixelIndex];
|
||||
const g = tileImageData.data[pixelIndex + 1];
|
||||
@@ -641,7 +641,7 @@ function getText(key, params) {
|
||||
const a = tileImageData.data[pixelIndex + 3];
|
||||
|
||||
if (a > 0) { // Skip transparent pixels
|
||||
// Find closest color in WPlace palette (Auto-Guard method)
|
||||
// Find closest color in WPlace palette
|
||||
const closestColor = findClosestColor(r, g, b);
|
||||
|
||||
if (closestColor) {
|
||||
@@ -675,7 +675,7 @@ function getText(key, params) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Auto-Guard style scan complete: ${state.scannedPixels.length} pixels extracted`);
|
||||
console.log(`✅ Scan complete: ${state.scannedPixels.length} pixels extracted`);
|
||||
showAlert(`✅ Extracted ${state.scannedPixels.length} pixels from area`, 'success');
|
||||
|
||||
} catch (error) {
|
||||
@@ -687,7 +687,7 @@ function getText(key, params) {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-Guard's getTileImage function for downloading tiles
|
||||
// Download tile images
|
||||
async function getTileImage(tileX, tileY) {
|
||||
try {
|
||||
const tileUrl = `https://backend.wplace.live/files/s0/tiles/${tileX}/${tileY}.png`;
|
||||
@@ -702,7 +702,7 @@ function getText(key, params) {
|
||||
}
|
||||
}
|
||||
|
||||
// Process tile blob into ImageData like Auto-Guard
|
||||
// Process tile blob into ImageData
|
||||
async function processTileBlob(blob) {
|
||||
try {
|
||||
const img = new Image();
|
||||
@@ -726,14 +726,14 @@ function getText(key, params) {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-Guard style color matching using LAB color space for better accuracy
|
||||
// Color matching using LAB color space for better accuracy
|
||||
function findClosestColor(r, g, b) {
|
||||
if (!CONFIG.COLOR_MAP) return null;
|
||||
|
||||
let closestColor = null;
|
||||
let minDistance = Infinity;
|
||||
|
||||
// Convert input RGB to LAB color space (Auto-Guard method)
|
||||
// Convert input RGB to LAB color space
|
||||
const inputLab = rgbToLab(r, g, b);
|
||||
|
||||
for (const [index, colorInfo] of Object.entries(CONFIG.COLOR_MAP)) {
|
||||
@@ -744,7 +744,7 @@ function getText(key, params) {
|
||||
// Convert palette color to LAB
|
||||
const paletteLab = rgbToLab(cr, cg, cb);
|
||||
|
||||
// Calculate Delta-E distance (Auto-Guard method)
|
||||
// Calculate Delta-E distance
|
||||
const deltaE = calculateDeltaE(inputLab, paletteLab);
|
||||
|
||||
if (deltaE < minDistance) {
|
||||
@@ -756,7 +756,7 @@ function getText(key, params) {
|
||||
return closestColor;
|
||||
}
|
||||
|
||||
// Auto-Guard's RGB to LAB color space conversion
|
||||
// RGB to LAB color space conversion
|
||||
function rgbToLab(r, g, b) {
|
||||
// Normalize RGB values
|
||||
r = r / 255;
|
||||
@@ -789,7 +789,7 @@ function getText(key, params) {
|
||||
return { l, a, b: bLab };
|
||||
}
|
||||
|
||||
// Auto-Guard's Delta-E color difference calculation
|
||||
// Delta-E color difference calculation
|
||||
function calculateDeltaE(lab1, lab2) {
|
||||
const deltaL = lab1.l - lab2.l;
|
||||
const deltaA = lab1.a - lab2.a;
|
||||
@@ -1483,7 +1483,7 @@ function getText(key, params) {
|
||||
|
||||
// Control functions
|
||||
function startPixelCapture() {
|
||||
console.log('🎯 Starting Auto-Guard style pixel capture mode...');
|
||||
console.log('🎯 Starting pixel capture mode...');
|
||||
|
||||
state.isCapturing = true;
|
||||
state.capturedPixels = [];
|
||||
@@ -1495,7 +1495,7 @@ function getText(key, params) {
|
||||
state.overlayManager.disable();
|
||||
}
|
||||
|
||||
// Set up Auto-Guard style pixel paint monitoring with fetch interception
|
||||
// Set up pixel paint monitoring with fetch interception
|
||||
const setupSuccess = setupPixelCapture();
|
||||
|
||||
if (setupSuccess) {
|
||||
|
||||
+897
-446
File diff suppressed because it is too large.
Load diff
+3331
-3331
File diff suppressed because it is too large.
Load diff
@@ -39,8 +39,23 @@ class TokenManager {
|
||||
// Retry counter
|
||||
this.retryCount = 0;
|
||||
|
||||
// Widget pooling for faster token generation
|
||||
this.widgetPool = [];
|
||||
this.poolQueue = []; // Resolvers waiting for free widgets
|
||||
this.poolSize = 2; // Default pool size
|
||||
this.poolInitialized = false;
|
||||
this.poolMetrics = {
|
||||
tokensGenerated: 0,
|
||||
avgCreationTime: 0,
|
||||
poolHits: 0,
|
||||
poolMisses: 0
|
||||
};
|
||||
|
||||
// Initialize message listener for token capture
|
||||
this._initializeTokenCapture();
|
||||
|
||||
// Initialize widget pool after a short delay to ensure DOM is ready
|
||||
setTimeout(() => this.initPool(this.poolSize), 100);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,7 +349,7 @@ class TokenManager {
|
||||
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
script.onload = () => {
|
||||
script.onload = async () => {
|
||||
this.turnstileLoaded = true;
|
||||
console.log('✅ Turnstile script loaded successfully');
|
||||
resolve();
|
||||
@@ -411,6 +426,60 @@ class TokenManager {
|
||||
async executeTurnstile(sitekey, action = 'paint') {
|
||||
await this.loadTurnstile();
|
||||
|
||||
// Initialize widget pool lazily on first use
|
||||
if (!this.poolInitialized && window.turnstile && false) { // Temporarily disabled
|
||||
try {
|
||||
console.log('🏊 Initializing widget pool on first use...');
|
||||
// Use the current sitekey if available, otherwise skip pool for this request
|
||||
if (sitekey && sitekey.length > 10 && sitekey.startsWith('0x')) {
|
||||
this._cachedSitekey = sitekey; // Cache the valid sitekey
|
||||
await this.initPool();
|
||||
} else {
|
||||
console.warn('⚠️ Invalid sitekey, skipping pool initialization for now');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Failed to initialize widget pool:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip widget pool for now (temporarily disabled)
|
||||
// Try widget pool first for faster generation (only if we have a valid pool)
|
||||
if (false && this.poolInitialized && this.widgetPool.length > 0 && sitekey && sitekey.length > 10) {
|
||||
try {
|
||||
console.log('🏊 Using widget pool for token generation...');
|
||||
const poolStartTime = performance.now();
|
||||
|
||||
const token = await this.withWidget(async (widget) => {
|
||||
if (!widget) {
|
||||
throw new Error('No widget available from pool');
|
||||
}
|
||||
|
||||
// Reset the widget for fresh token generation
|
||||
if (window.turnstile?.reset) {
|
||||
window.turnstile.reset(widget.widgetId);
|
||||
}
|
||||
|
||||
// Generate token using pooled widget with built-in timeout
|
||||
const token = await widget.createNewTokenPromise();
|
||||
|
||||
return token;
|
||||
}, 12000); // Increased widget borrow timeout
|
||||
|
||||
if (token && token.length > 20) {
|
||||
const poolTime = performance.now() - poolStartTime;
|
||||
console.log(`✅ Token generated via pool in ${poolTime.toFixed(2)}ms`);
|
||||
return token;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('⚠️ Widget pool failed, falling back to direct creation:', error.message);
|
||||
// Don't try pool again for this sitekey if it's failing
|
||||
if (error.message.includes('401') || error.message.includes('unauthorized')) {
|
||||
console.log('🚫 Sitekey might be invalid, disabling pool for this session');
|
||||
this.poolInitialized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try reusing existing widget first if sitekey matches
|
||||
if (this._turnstileWidgetId && this._lastSitekey === sitekey && window.turnstile?.execute) {
|
||||
try {
|
||||
@@ -829,6 +898,328 @@ class TokenManager {
|
||||
)
|
||||
.join('');
|
||||
}
|
||||
|
||||
// ============ WIDGET POOLING METHODS ============
|
||||
|
||||
/**
|
||||
* Initialize widget pool for faster token generation
|
||||
* @param {number} size - Pool size (default 2)
|
||||
*/
|
||||
async initPool(size = 2) {
|
||||
if (this.poolInitialized) return;
|
||||
|
||||
console.log(`🏊 Initializing Turnstile widget pool with ${size} widgets...`);
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// Ensure Turnstile is loaded first
|
||||
await this.loadTurnstile();
|
||||
|
||||
// Use a fallback sitekey for pool initialization to avoid circular dependency
|
||||
let sitekey = this._cachedSitekey || '0x4AAAAAABpqJe8FO0N84q0F';
|
||||
|
||||
// Validate sitekey format
|
||||
if (!sitekey || sitekey.length < 10 || !sitekey.startsWith('0x')) {
|
||||
console.warn('⚠️ Invalid cached sitekey, using fallback');
|
||||
sitekey = '0x4AAAAAABpqJe8FO0N84q0F';
|
||||
}
|
||||
|
||||
console.log('🔍 Using sitekey for pool initialization:', sitekey);
|
||||
|
||||
// Create pool entries
|
||||
const promises = [];
|
||||
for (let i = 0; i < size; i++) {
|
||||
promises.push(this._createWidgetEntry(sitekey, i));
|
||||
}
|
||||
|
||||
const results = await Promise.allSettled(promises);
|
||||
|
||||
// Process results
|
||||
results.forEach((result, index) => {
|
||||
if (result.status === 'fulfilled' && result.value) {
|
||||
this.widgetPool.push(result.value);
|
||||
console.log(`✅ Pool widget ${index} created successfully`);
|
||||
} else {
|
||||
console.warn(`❌ Pool widget ${index} failed:`, result.reason);
|
||||
}
|
||||
});
|
||||
|
||||
const initTime = performance.now() - startTime;
|
||||
this.poolMetrics.avgCreationTime = initTime / this.widgetPool.length;
|
||||
this.poolInitialized = true;
|
||||
|
||||
console.log(`🏊 Widget pool initialized: ${this.widgetPool.length}/${size} widgets ready (${initTime.toFixed(2)}ms)`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Widget pool initialization failed:', error);
|
||||
this.poolInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single widget entry for the pool
|
||||
* @param {string} sitekey - Turnstile sitekey
|
||||
* @param {number} index - Widget index for identification
|
||||
* @returns {Promise<Object>} Widget entry object
|
||||
*/
|
||||
async _createWidgetEntry(sitekey, index = 0) {
|
||||
const startTime = performance.now();
|
||||
|
||||
try {
|
||||
// Create hidden container
|
||||
const container = document.createElement('div');
|
||||
container.id = `turnstile-pool-widget-${index}-${this._randStr(8)}`;
|
||||
container.style.cssText = `
|
||||
position: fixed !important;
|
||||
left: -9999px !important;
|
||||
top: -9999px !important;
|
||||
width: 300px !important;
|
||||
height: 65px !important;
|
||||
visibility: hidden !important;
|
||||
pointer-events: none !important;
|
||||
z-index: -1 !important;
|
||||
`;
|
||||
|
||||
document.body.appendChild(container);
|
||||
|
||||
// Create widget
|
||||
let widgetId = null;
|
||||
let tokenPromise = null;
|
||||
|
||||
const widgetPromise = new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Widget creation timeout'));
|
||||
}, 10000);
|
||||
|
||||
try {
|
||||
widgetId = window.turnstile.render(container, {
|
||||
sitekey: sitekey,
|
||||
size: 'normal',
|
||||
theme: 'light',
|
||||
callback: (token) => {
|
||||
clearTimeout(timeout);
|
||||
if (tokenPromise) {
|
||||
tokenPromise.resolve(token);
|
||||
}
|
||||
},
|
||||
'error-callback': (error) => {
|
||||
clearTimeout(timeout);
|
||||
if (tokenPromise) {
|
||||
tokenPromise.reject(new Error(`Turnstile error: ${error}`));
|
||||
} else {
|
||||
reject(new Error(`Turnstile error: ${error}`));
|
||||
}
|
||||
},
|
||||
'expired-callback': () => {
|
||||
console.log(`🔄 Pool widget ${index} token expired`);
|
||||
}
|
||||
});
|
||||
|
||||
if (widgetId) {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
} else {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error('Failed to create widget'));
|
||||
}
|
||||
} catch (error) {
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
await widgetPromise;
|
||||
|
||||
const entry = {
|
||||
id: `pool-widget-${index}`,
|
||||
index,
|
||||
container,
|
||||
widgetId,
|
||||
sitekey,
|
||||
state: 'idle', // 'idle' | 'in-use' | 'error' | 'initializing'
|
||||
createdAt: Date.now(),
|
||||
lastUsedAt: 0,
|
||||
generationCount: 0,
|
||||
createNewTokenPromise() {
|
||||
tokenPromise = {};
|
||||
tokenPromise.promise = new Promise((resolve, reject) => {
|
||||
tokenPromise.resolve = resolve;
|
||||
tokenPromise.reject = reject;
|
||||
});
|
||||
return tokenPromise.promise;
|
||||
}
|
||||
};
|
||||
|
||||
const creationTime = performance.now() - startTime;
|
||||
console.log(`✅ Pool widget ${index} created in ${creationTime.toFixed(2)}ms`);
|
||||
|
||||
return entry;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to create pool widget ${index}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an available widget from the pool
|
||||
* @param {number} timeoutMs - Timeout in milliseconds
|
||||
* @returns {Promise<Object>} Widget entry
|
||||
*/
|
||||
async getWidget(timeoutMs = 5000) {
|
||||
// Try to find an idle widget
|
||||
const idleWidget = this.widgetPool.find(entry => entry.state === 'idle');
|
||||
if (idleWidget) {
|
||||
idleWidget.state = 'in-use';
|
||||
idleWidget.lastUsedAt = Date.now();
|
||||
this.poolMetrics.poolHits++;
|
||||
return idleWidget;
|
||||
}
|
||||
|
||||
// No idle widget available, add to queue and wait
|
||||
this.poolMetrics.poolMisses++;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
// Remove from queue
|
||||
const index = this.poolQueue.findIndex(item => item.resolve === resolve);
|
||||
if (index > -1) {
|
||||
this.poolQueue.splice(index, 1);
|
||||
}
|
||||
reject(new Error('Widget pool timeout: no widget available'));
|
||||
}, timeoutMs);
|
||||
|
||||
this.poolQueue.push({
|
||||
resolve: (widget) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(widget);
|
||||
},
|
||||
reject: (error) => {
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a widget back to the pool
|
||||
* @param {Object} entry - Widget entry to release
|
||||
*/
|
||||
releaseWidget(entry) {
|
||||
if (!entry || entry.state !== 'in-use') {
|
||||
console.warn('⚠️ Attempted to release widget that is not in use');
|
||||
return;
|
||||
}
|
||||
|
||||
entry.state = 'idle';
|
||||
entry.generationCount++;
|
||||
|
||||
// Check if widget needs rotation (after many uses)
|
||||
if (entry.generationCount > 50) {
|
||||
console.log(`🔄 Rotating pool widget ${entry.index} after ${entry.generationCount} uses`);
|
||||
this.destroyWidget(entry);
|
||||
// Recreate widget in background
|
||||
this._createWidgetEntry(entry.sitekey, entry.index).then(newEntry => {
|
||||
if (newEntry) {
|
||||
this.widgetPool.push(newEntry);
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Failed to recreate rotated widget:', error);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Serve next waiting request if any
|
||||
if (this.poolQueue.length > 0) {
|
||||
const waiter = this.poolQueue.shift();
|
||||
entry.state = 'in-use';
|
||||
entry.lastUsedAt = Date.now();
|
||||
waiter.resolve(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a widget and remove from pool
|
||||
* @param {Object} entry - Widget entry to destroy
|
||||
*/
|
||||
destroyWidget(entry) {
|
||||
if (!entry) return;
|
||||
|
||||
try {
|
||||
// Reset widget if possible
|
||||
if (entry.widgetId && window.turnstile?.reset) {
|
||||
window.turnstile.reset(entry.widgetId);
|
||||
}
|
||||
|
||||
// Remove container
|
||||
if (entry.container && entry.container.parentNode) {
|
||||
entry.container.parentNode.removeChild(entry.container);
|
||||
}
|
||||
|
||||
// Remove from pool
|
||||
const index = this.widgetPool.findIndex(w => w.id === entry.id);
|
||||
if (index > -1) {
|
||||
this.widgetPool.splice(index, 1);
|
||||
}
|
||||
|
||||
console.log(`🗑️ Destroyed pool widget ${entry.index}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error destroying widget:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a function with a pooled widget (automatic borrow/return)
|
||||
* @param {Function} fn - Async function that receives widget entry
|
||||
* @param {number} timeoutMs - Widget acquisition timeout
|
||||
* @returns {Promise} Result of the function
|
||||
*/
|
||||
async withWidget(fn, timeoutMs = 5000) {
|
||||
let widget = null;
|
||||
|
||||
try {
|
||||
// Fallback to direct creation if pool not ready
|
||||
if (!this.poolInitialized || this.widgetPool.length === 0) {
|
||||
console.log('📝 Pool not ready, falling back to direct widget creation');
|
||||
return await fn(null); // Let the function handle direct creation
|
||||
}
|
||||
|
||||
widget = await this.getWidget(timeoutMs);
|
||||
const result = await fn(widget);
|
||||
|
||||
this.poolMetrics.tokensGenerated++;
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error in withWidget:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
if (widget) {
|
||||
this.releaseWidget(widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pool statistics
|
||||
* @returns {Object} Pool metrics and status
|
||||
*/
|
||||
getPoolStats() {
|
||||
const idleCount = this.widgetPool.filter(w => w.state === 'idle').length;
|
||||
const inUseCount = this.widgetPool.filter(w => w.state === 'in-use').length;
|
||||
|
||||
return {
|
||||
initialized: this.poolInitialized,
|
||||
totalWidgets: this.widgetPool.length,
|
||||
idleWidgets: idleCount,
|
||||
inUseWidgets: inUseCount,
|
||||
queueLength: this.poolQueue.length,
|
||||
metrics: { ...this.poolMetrics },
|
||||
hitRate: this.poolMetrics.poolHits / Math.max(1, this.poolMetrics.poolHits + this.poolMetrics.poolMisses)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Create global instance
|
||||
|
||||
+815
-815
File diff suppressed because it is too large.
Load diff
+881
-881
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user