feat: full migration to extension
No files matched your search
@@ -34,6 +34,13 @@
|
||||
description: 'Automated repair and maintenance tasks',
|
||||
icon: '🔧',
|
||||
category: 'utility'
|
||||
},
|
||||
{
|
||||
name: 'Acc-Switch.js',
|
||||
displayName: '🔄 Account Switcher',
|
||||
description: 'Temporary account switching script',
|
||||
icon: '🔄',
|
||||
category: 'automation'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -1920,3 +1920,162 @@
|
||||
color: var(--wplace-icon-palette);
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* Account Management Styles */
|
||||
.wplace-account-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
background: var(--wplace-secondary);
|
||||
border: 1px solid var(--wplace-border-color);
|
||||
border-radius: var(--wplace-radius);
|
||||
}
|
||||
|
||||
.wplace-account-current {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--wplace-text);
|
||||
}
|
||||
|
||||
.wplace-account-current i {
|
||||
color: var(--wplace-highlight);
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.wplace-account-current strong {
|
||||
color: var(--wplace-success);
|
||||
}
|
||||
|
||||
.wplace-account-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.wplace-toggle-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.wplace-toggle-switch input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wplace-toggle-slider {
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
background: var(--wplace-accent);
|
||||
border: 1px solid var(--wplace-border-color);
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.wplace-toggle-slider:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: var(--wplace-text);
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.wplace-toggle-switch input:checked + .wplace-toggle-slider {
|
||||
background: var(--wplace-success);
|
||||
border-color: var(--wplace-success);
|
||||
}
|
||||
|
||||
.wplace-toggle-switch input:checked + .wplace-toggle-slider:before {
|
||||
transform: translateX(20px);
|
||||
background: white;
|
||||
}
|
||||
|
||||
.wplace-toggle-label {
|
||||
font-size: 14px;
|
||||
color: var(--wplace-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.wplace-account-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--wplace-border-color);
|
||||
border-radius: var(--wplace-radius);
|
||||
background: var(--wplace-secondary);
|
||||
}
|
||||
|
||||
.wplace-account-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--wplace-border-color);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.wplace-account-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.wplace-account-item:hover {
|
||||
background: var(--wplace-accent);
|
||||
}
|
||||
|
||||
.wplace-account-item.current {
|
||||
background: var(--wplace-success)20;
|
||||
border-left: 3px solid var(--wplace-success);
|
||||
}
|
||||
|
||||
.wplace-account-info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.wplace-account-number {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: var(--wplace-highlight);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wplace-account-name {
|
||||
font-size: 14px;
|
||||
color: var(--wplace-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.wplace-account-status {
|
||||
font-size: 12px;
|
||||
color: var(--wplace-text-secondary);
|
||||
}
|
||||
|
||||
.wplace-account-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 20px;
|
||||
color: var(--wplace-text-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.wplace-account-placeholder i {
|
||||
color: var(--wplace-warning);
|
||||
}
|
||||
@@ -48,7 +48,7 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
} else {
|
||||
scriptUrl = chrome.runtime.getURL(`scripts/${scriptName}`);
|
||||
}
|
||||
|
||||
|
||||
const response = await fetch(scriptUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -67,38 +67,38 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
world: "MAIN", // Key: executes in page context, not extension context
|
||||
func: (code, name, themeCSS, languages) => {
|
||||
console.log(`%c🚀 Executing ${name}...`, 'color: #4ade80; font-weight: bold; font-size: 14px;');
|
||||
|
||||
|
||||
// Create detailed resource report
|
||||
console.group(`%c📊 WPlace AutoBOT Resource Report for ${name}`, 'color: #3b82f6; font-weight: bold; font-size: 16px;');
|
||||
|
||||
|
||||
// Debug: Log what resources we received
|
||||
console.log(`%c📦 Raw Resources Received:`, 'color: #8b5cf6; font-weight: bold;');
|
||||
console.log(` - Themes object:`, themeCSS);
|
||||
console.log(` - Languages object:`, languages);
|
||||
console.log(` - Theme count: ${Object.keys(themeCSS || {}).length}`);
|
||||
console.log(` - Language count: ${Object.keys(languages || {}).length}`);
|
||||
|
||||
|
||||
// Inject CSS themes if available
|
||||
if (themeCSS && Object.keys(themeCSS).length > 0) {
|
||||
console.group(`%c🎨 Theme Processing`, 'color: #8b5cf6; font-weight: bold;');
|
||||
console.log(`%c📁 Loading ${Object.keys(themeCSS).length} theme files from extension local storage...`, 'color: #8b5cf6;');
|
||||
|
||||
|
||||
// Create a global themes object
|
||||
window.AUTOBOT_THEMES = themeCSS;
|
||||
|
||||
|
||||
// Log detailed theme information
|
||||
Object.entries(themeCSS).forEach(([filename, content]) => {
|
||||
console.log(`%c📄 Theme File: ${filename}`, 'color: #8b5cf6; font-weight: bold;');
|
||||
console.log(` 📏 Size: ${content.length.toLocaleString()} characters`);
|
||||
console.log(` 📍 Source: Extension local file (chrome-extension://)`);
|
||||
console.log(` 🔗 Full path: themes/${filename}`);
|
||||
|
||||
|
||||
// Show first few lines as preview
|
||||
const preview = content.substring(0, 200).split('\\n').slice(0, 3).join('\\n');
|
||||
console.log(` 👀 Preview: ${preview}${content.length > 200 ? '...' : ''}`);
|
||||
console.log(` ✅ Status: Loaded successfully`);
|
||||
});
|
||||
|
||||
|
||||
// Inject auto-image-styles.css if available
|
||||
if (themeCSS['auto-image-styles.css']) {
|
||||
const autoImageContent = themeCSS['auto-image-styles.css'];
|
||||
@@ -106,7 +106,7 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
styleElement.id = 'autobot-auto-image-styles';
|
||||
styleElement.textContent = autoImageContent;
|
||||
document.head.appendChild(styleElement);
|
||||
|
||||
|
||||
console.log(`%c✨ AUTO-INJECTED: auto-image-styles.css`, 'color: #10b981; font-weight: bold;');
|
||||
console.log(` 📏 Injected size: ${autoImageContent.length.toLocaleString()} characters`);
|
||||
console.log(` 📍 Source: Extension local file`);
|
||||
@@ -122,14 +122,14 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
console.log(` 📋 Expected files: auto-image-styles.css, acrylic.css, classic.css, etc.`);
|
||||
window.AUTOBOT_THEMES = {};
|
||||
}
|
||||
|
||||
|
||||
// Inject language data if available
|
||||
if (languages && Object.keys(languages).length > 0) {
|
||||
console.group(`%c🌍 Language Processing`, 'color: #06b6d4; font-weight: bold;');
|
||||
console.log(`%c📁 Loading ${Object.keys(languages).length} language files from extension local storage...`, 'color: #06b6d4;');
|
||||
|
||||
|
||||
window.AUTOBOT_LANGUAGES = languages;
|
||||
|
||||
|
||||
// Log detailed language information
|
||||
Object.entries(languages).forEach(([filename, content]) => {
|
||||
console.log(`%c📄 Language File: ${filename}`, 'color: #06b6d4; font-weight: bold;');
|
||||
@@ -137,18 +137,18 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
console.log(` 📏 Keys count: ${Object.keys(content).length.toLocaleString()}`);
|
||||
console.log(` 📍 Source: Extension local file (chrome-extension://)`);
|
||||
console.log(` 🔗 Full path: lang/${filename}`);
|
||||
|
||||
|
||||
// Show some sample keys
|
||||
const sampleKeys = Object.keys(content).slice(0, 5);
|
||||
console.log(` 🔑 Sample keys: ${sampleKeys.join(', ')}${Object.keys(content).length > 5 ? '...' : ''}`);
|
||||
console.log(` ✅ Status: Loaded successfully`);
|
||||
});
|
||||
|
||||
|
||||
// Helper function to get language data with detailed logging
|
||||
window.getLanguage = function(lang = 'en') {
|
||||
window.getLanguage = function (lang = 'en') {
|
||||
const langFile = lang + '.json';
|
||||
const result = window.AUTOBOT_LANGUAGES[langFile] || window.AUTOBOT_LANGUAGES['en.json'] || {};
|
||||
|
||||
|
||||
console.group(`%c🔤 Language Access: ${lang.toUpperCase()}`, 'color: #06b6d4; font-weight: bold;');
|
||||
console.log(` 📋 Requested: ${lang}`);
|
||||
console.log(` 📄 File: ${langFile}`);
|
||||
@@ -157,10 +157,10 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
console.log(` ✅ Success: ${window.AUTOBOT_LANGUAGES[langFile] ? 'Found exact match' : 'Fallback to English'}`);
|
||||
console.log(` 📝 Data preview:`, result);
|
||||
console.groupEnd();
|
||||
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
console.log(`%c🔤 Available languages: ${Object.keys(languages).map(f => f.replace('.json', '')).join(', ')}`, 'color: #06b6d4;');
|
||||
console.groupEnd();
|
||||
} else {
|
||||
@@ -168,16 +168,16 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
console.log(` 📁 Expected source: Extension local files`);
|
||||
console.log(` 📋 Expected files: en.json, de.json, fr.json, etc.`);
|
||||
window.AUTOBOT_LANGUAGES = {};
|
||||
window.getLanguage = function() {
|
||||
window.getLanguage = function () {
|
||||
console.warn(`%c⚠️ getLanguage() called but no languages available`, 'color: #f59e0b;');
|
||||
return {};
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Helper function to apply theme with detailed logging
|
||||
window.applyTheme = function(themeName) {
|
||||
window.applyTheme = function (themeName) {
|
||||
console.group(`%c🎨 Theme Application: ${themeName}`, 'color: #8b5cf6; font-weight: bold;');
|
||||
|
||||
|
||||
if (!window.AUTOBOT_THEMES || Object.keys(window.AUTOBOT_THEMES).length === 0) {
|
||||
console.error(`%c❌ No themes available in extension`, 'color: #ef4444; font-weight: bold;');
|
||||
console.log(` 📁 Expected source: Extension local files`);
|
||||
@@ -185,38 +185,38 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
console.groupEnd();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const themeFile = themeName + '.css';
|
||||
console.log(` 📋 Requested theme: ${themeName}`);
|
||||
console.log(` 📄 Looking for file: ${themeFile}`);
|
||||
console.log(` 📁 Available themes: ${Object.keys(window.AUTOBOT_THEMES).join(', ')}`);
|
||||
|
||||
|
||||
if (window.AUTOBOT_THEMES[themeFile]) {
|
||||
const themeContent = window.AUTOBOT_THEMES[themeFile];
|
||||
|
||||
|
||||
// Remove existing theme
|
||||
const existing = document.getElementById('autobot-theme');
|
||||
if (existing) {
|
||||
console.log(` 🗑️ Removing previous theme element`);
|
||||
existing.remove();
|
||||
}
|
||||
|
||||
|
||||
// Apply new theme
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.id = 'autobot-theme';
|
||||
styleElement.textContent = themeContent;
|
||||
document.head.appendChild(styleElement);
|
||||
|
||||
|
||||
console.log(`%c✅ Theme applied successfully: ${themeName}`, 'color: #10b981; font-weight: bold;');
|
||||
console.log(` 📏 Content size: ${themeContent.length.toLocaleString()} characters`);
|
||||
console.log(` 📍 Source: Extension local file`);
|
||||
console.log(` 🎯 Target: <head> as <style> element`);
|
||||
console.log(` 🆔 Element ID: autobot-theme`);
|
||||
|
||||
|
||||
// Show preview of applied CSS
|
||||
const preview = themeContent.substring(0, 150).split('\\n').slice(0, 2).join('\\n');
|
||||
console.log(` 👀 CSS Preview: ${preview}...`);
|
||||
|
||||
|
||||
console.groupEnd();
|
||||
return true;
|
||||
} else {
|
||||
@@ -227,7 +227,7 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Final resource summary
|
||||
console.group(`%c📋 Resource Summary`, 'color: #10b981; font-weight: bold;');
|
||||
console.log(`%c🎨 Themes loaded: ${Object.keys(window.AUTOBOT_THEMES || {}).length}`, 'color: #8b5cf6;');
|
||||
@@ -237,15 +237,15 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
console.log(` - getLanguage(lang) - Get language translations`);
|
||||
console.log(`%c📍 All resources loaded from: Extension local files`, 'color: #10b981;');
|
||||
console.groupEnd();
|
||||
|
||||
|
||||
console.groupEnd(); // End main resource report
|
||||
|
||||
|
||||
// Create script element to execute the code
|
||||
const script = document.createElement('script');
|
||||
script.textContent = code;
|
||||
document.head.appendChild(script);
|
||||
script.remove(); // Clean up after execution
|
||||
|
||||
|
||||
console.log(`%c✅ ${name} executed successfully with full resource access`, 'color: #4ade80; font-weight: bold;');
|
||||
},
|
||||
args: [scriptCode, scriptName, resources.themes, resources.languages]
|
||||
@@ -262,7 +262,7 @@ async function executeLocalScript(scriptName, tabId) {
|
||||
async function loadExtensionResources() {
|
||||
console.group('%c🔧 WPlace AutoBOT Resource Loading System', 'color: #3b82f6; font-weight: bold; font-size: 16px;');
|
||||
const startTime = performance.now();
|
||||
|
||||
|
||||
const resources = {
|
||||
themes: {},
|
||||
languages: {}
|
||||
@@ -270,13 +270,13 @@ async function loadExtensionResources() {
|
||||
|
||||
try {
|
||||
console.log('%c� Starting resource loading from extension directory...', 'color: #3b82f6; font-weight: bold;');
|
||||
|
||||
|
||||
// Load theme files
|
||||
console.group('%c🎨 Theme Files Loading', 'color: #8b5cf6; font-weight: bold;');
|
||||
const themeFiles = [
|
||||
'auto-image-styles.css',
|
||||
'themes/acrylic.css',
|
||||
'themes/classic-light.css',
|
||||
'themes/classic-light.css',
|
||||
'themes/classic.css',
|
||||
'themes/neon.css'
|
||||
];
|
||||
@@ -286,34 +286,34 @@ async function loadExtensionResources() {
|
||||
console.log(`%c� Loading theme: ${themeFile}`, 'color: #8b5cf6;');
|
||||
console.log(` 📍 Source path: ${themeFile}`);
|
||||
console.log(` 🔗 Full URL: chrome-extension://${chrome.runtime.id}/${themeFile}`);
|
||||
|
||||
|
||||
const themeUrl = chrome.runtime.getURL(themeFile);
|
||||
console.log(` 🌐 Resolved URL: ${themeUrl}`);
|
||||
|
||||
|
||||
const response = await fetch(themeUrl);
|
||||
console.log(` 📡 Fetch response status: ${response.status} ${response.statusText}`);
|
||||
console.log(` 📋 Response headers:`, Object.fromEntries(response.headers.entries()));
|
||||
|
||||
|
||||
if (response.ok) {
|
||||
const content = await response.text();
|
||||
const fileName = themeFile.split('/').pop();
|
||||
resources.themes[fileName] = content;
|
||||
|
||||
|
||||
console.log(`%c✅ ${fileName} loaded successfully`, 'color: #10b981; font-weight: bold;');
|
||||
console.log(` 📏 File size: ${content.length.toLocaleString()} characters`);
|
||||
console.log(` 📊 File size: ${(content.length / 1024).toFixed(2)} KB`);
|
||||
console.log(` 🔍 Content type: CSS stylesheet`);
|
||||
|
||||
|
||||
// Show content preview
|
||||
const firstLine = content.split('\n')[0];
|
||||
const lastLine = content.split('\n').slice(-1)[0];
|
||||
console.log(` 👀 First line: ${firstLine.substring(0, 100)}${firstLine.length > 100 ? '...' : ''}`);
|
||||
console.log(` 👀 Last line: ${lastLine.substring(0, 100)}${lastLine.length > 100 ? '...' : ''}`);
|
||||
|
||||
|
||||
// Count CSS rules
|
||||
const ruleCount = (content.match(/\{[^}]*\}/g) || []).length;
|
||||
console.log(` 📝 Estimated CSS rules: ${ruleCount.toLocaleString()}`);
|
||||
|
||||
|
||||
} else {
|
||||
console.error(`%c❌ Failed to load ${themeFile}`, 'color: #ef4444; font-weight: bold;');
|
||||
console.error(` 📡 Status: ${response.status} ${response.statusText}`);
|
||||
@@ -367,35 +367,35 @@ async function loadExtensionResources() {
|
||||
console.log(` 🌐 Language: ${langFile.replace('lang/', '').replace('.json', '').toUpperCase()}`);
|
||||
console.log(` 📍 Source path: ${langFile}`);
|
||||
console.log(` 🔗 Full URL: chrome-extension://${chrome.runtime.id}/${langFile}`);
|
||||
|
||||
|
||||
const langUrl = chrome.runtime.getURL(langFile);
|
||||
console.log(` 🌐 Resolved URL: ${langUrl}`);
|
||||
|
||||
|
||||
const response = await fetch(langUrl);
|
||||
console.log(` 📡 Fetch response status: ${response.status} ${response.statusText}`);
|
||||
|
||||
|
||||
if (response.ok) {
|
||||
const text = await response.text();
|
||||
console.log(` 📏 Raw text size: ${text.length.toLocaleString()} characters`);
|
||||
|
||||
|
||||
const parsed = JSON.parse(text);
|
||||
const fileName = langFile.split('/').pop();
|
||||
resources.languages[fileName] = parsed;
|
||||
|
||||
|
||||
console.log(`%c✅ ${fileName} loaded successfully`, 'color: #10b981; font-weight: bold;');
|
||||
console.log(` 📏 JSON size: ${text.length.toLocaleString()} characters`);
|
||||
console.log(` 📊 File size: ${(text.length / 1024).toFixed(2)} KB`);
|
||||
console.log(` 🔑 Translation keys: ${Object.keys(parsed).length.toLocaleString()}`);
|
||||
console.log(` 🔍 Content type: JSON translation data`);
|
||||
|
||||
|
||||
// Show some sample keys
|
||||
const sampleKeys = Object.keys(parsed).slice(0, 5);
|
||||
console.log(` 🎯 Sample keys: ${sampleKeys.join(', ')}${Object.keys(parsed).length > 5 ? '...' : ''}`);
|
||||
|
||||
|
||||
// Show sample translations
|
||||
const samples = sampleKeys.map(key => `${key}: "${parsed[key]}"`).slice(0, 3);
|
||||
console.log(` 📝 Sample translations: ${samples.join(', ')}`);
|
||||
|
||||
|
||||
} else {
|
||||
console.error(`%c❌ Failed to load ${langFile}`, 'color: #ef4444; font-weight: bold;');
|
||||
console.error(` 📡 Status: ${response.status} ${response.statusText}`);
|
||||
@@ -413,28 +413,28 @@ async function loadExtensionResources() {
|
||||
console.groupEnd();
|
||||
|
||||
const loadTime = performance.now() - startTime;
|
||||
|
||||
|
||||
// Final summary with detailed statistics
|
||||
console.group('%c� Resource Loading Summary', 'color: #10b981; font-weight: bold;');
|
||||
console.log(`%c⏱️ Total loading time: ${loadTime.toFixed(2)}ms`, 'color: #10b981; font-weight: bold;');
|
||||
console.log(`%c🎨 Themes loaded: ${Object.keys(resources.themes).length}/${themeFiles.length}`, 'color: #8b5cf6; font-weight: bold;');
|
||||
console.log(`%c🌍 Languages loaded: ${Object.keys(resources.languages).length}/${languageFiles.length}`, 'color: #06b6d4; font-weight: bold;');
|
||||
|
||||
|
||||
// Calculate total size
|
||||
const themeSize = Object.values(resources.themes).reduce((sum, content) => sum + content.length, 0);
|
||||
const langSize = Object.values(resources.languages).reduce((sum, content) => sum + JSON.stringify(content).length, 0);
|
||||
const totalSize = themeSize + langSize;
|
||||
|
||||
|
||||
console.log(`%c📊 Total data loaded: ${(totalSize / 1024).toFixed(2)} KB`, 'color: #10b981; font-weight: bold;');
|
||||
console.log(` 🎨 Themes: ${(themeSize / 1024).toFixed(2)} KB`);
|
||||
console.log(` 🌍 Languages: ${(langSize / 1024).toFixed(2)} KB`);
|
||||
|
||||
|
||||
console.log(`%c📁 Resource sources:`, 'color: #10b981; font-weight: bold;');
|
||||
console.log(` 📍 Extension ID: ${chrome.runtime.id}`);
|
||||
console.log(` 🔗 Base URL: chrome-extension://${chrome.runtime.id}/`);
|
||||
console.log(` 📂 Themes folder: chrome-extension://${chrome.runtime.id}/themes/`);
|
||||
console.log(` 📂 Languages folder: chrome-extension://${chrome.runtime.id}/lang/`);
|
||||
|
||||
|
||||
// List successful loads
|
||||
if (Object.keys(resources.themes).length > 0) {
|
||||
console.log(`%c✅ Loaded themes: ${Object.keys(resources.themes).join(', ')}`, 'color: #8b5cf6;');
|
||||
@@ -443,7 +443,7 @@ async function loadExtensionResources() {
|
||||
const langs = Object.keys(resources.languages).map(f => f.replace('.json', '')).join(', ');
|
||||
console.log(`%c✅ Loaded languages: ${langs}`, 'color: #06b6d4;');
|
||||
}
|
||||
|
||||
|
||||
console.log(`%c🚀 Resources ready for injection into scripts`, 'color: #10b981; font-weight: bold;');
|
||||
console.groupEnd();
|
||||
console.groupEnd();
|
||||
@@ -460,3 +460,331 @@ async function loadExtensionResources() {
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
const cookieDomain = ".backend.wplace.live";
|
||||
|
||||
async function preserveAndResetJ() {
|
||||
let savedValue = null;
|
||||
|
||||
try {
|
||||
const oldJ = await chrome.cookies.get({
|
||||
url: "https://backend.wplace.live/",
|
||||
name: "j",
|
||||
});
|
||||
|
||||
if (oldJ && oldJ.value) {
|
||||
savedValue = oldJ.value.trim();
|
||||
console.log("[bg] Saved j cookie:", savedValue);
|
||||
|
||||
const accountInfo = await checkTokenAndGetInfo(savedValue);
|
||||
|
||||
if (accountInfo) {
|
||||
const store = await chrome.storage.local.get("infoAccounts");
|
||||
let infoAccounts = store.infoAccounts || [];
|
||||
|
||||
const existingIndex = infoAccounts.findIndex(account => account.ID === accountInfo.ID);
|
||||
|
||||
if (existingIndex > -1) {
|
||||
// Merge existing data with new data to preserve any additional info
|
||||
const existingAccount = infoAccounts[existingIndex];
|
||||
infoAccounts[existingIndex] = {
|
||||
...existingAccount, // Keep existing additional data
|
||||
...accountInfo, // Update with fresh data from API
|
||||
token: accountInfo.token, // Ensure token is updated
|
||||
name: accountInfo.name, // Ensure name is updated
|
||||
lastActive: new Date().toISOString() // Update last seen
|
||||
};
|
||||
console.log(`✅ ID ${accountInfo.ID} found. Updated account info.`);
|
||||
} else {
|
||||
infoAccounts.push(accountInfo);
|
||||
console.log(`✅ New account added: ${accountInfo.name} (${accountInfo.ID}).`);
|
||||
}
|
||||
|
||||
await chrome.storage.local.set({ infoAccounts });
|
||||
await exportInfoAccount();
|
||||
|
||||
} else {
|
||||
console.warn("❌ Current token is invalid, not saving.");
|
||||
}
|
||||
} else {
|
||||
console.warn("[bg] No 'j' cookie found. Skipping account verification.");
|
||||
}
|
||||
|
||||
setTimeout(async () => {
|
||||
await new Promise((resolve) => {
|
||||
chrome.browsingData.remove(
|
||||
{ origins: ["https://wplace.live"] },
|
||||
{ cookies: true },
|
||||
() => {
|
||||
console.log("[bg] Nuke done (delayed)");
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (savedValue) {
|
||||
setCookie(savedValue);
|
||||
} else {
|
||||
console.log("[bg] No j to restore, leaving nuked");
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
} catch (err) {
|
||||
console.error("[bg] Error in preserveAndResetJ:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function filterInvalid() {
|
||||
const store = await chrome.storage.local.get("infoAccounts");
|
||||
let infoAccounts = store.infoAccounts || [];
|
||||
let validAccounts = [];
|
||||
|
||||
for (const account of infoAccounts) {
|
||||
// This is a simplified check for validity
|
||||
const isValid = await checkTokenAndGetInfo(account.token);
|
||||
if (isValid) {
|
||||
console.log("Token valid:", account.token);
|
||||
validAccounts.push(account);
|
||||
|
||||
} else {
|
||||
console.log("Token invalid:", account.token);
|
||||
}
|
||||
}
|
||||
|
||||
await chrome.storage.local.set({ infoAccounts: validAccounts });
|
||||
console.log(`✅ Filtered and saved ${validAccounts.length} valid accounts.`);
|
||||
await exportInfoAccount();
|
||||
}
|
||||
|
||||
async function exportInfoAccount() {
|
||||
const store = await chrome.storage.local.get("infoAccounts");
|
||||
const infoAccounts = store.infoAccounts || [];
|
||||
const accounts = infoAccounts.map(info => info.token);
|
||||
await chrome.storage.local.set({ accounts });
|
||||
}
|
||||
|
||||
async function checkTokenAndGetInfo(token) {
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
console.log("Checking with token:", token);
|
||||
try {
|
||||
await setCookie(token);
|
||||
const response = await fetch("https://backend.wplace.live/me", {
|
||||
method: "GET",
|
||||
headers: { "Authorization": `Bearer ${token}` }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const userData = await response.json();
|
||||
console.log("Basic user data:", userData);
|
||||
|
||||
// Enhanced user info object with WPlace API fields
|
||||
const userInfo = {
|
||||
ID: userData.id,
|
||||
name: userData.name,
|
||||
token: token,
|
||||
Charges: Math.floor(userData.charges.count),
|
||||
Max: userData.charges.max,
|
||||
Droplets: userData.droplets,
|
||||
totalPixelsPainted: userData.pixelsPainted,
|
||||
level: Math.floor(userData.level),
|
||||
showLastPixel: userData.showLastPixel,
|
||||
discord: userData.discord,
|
||||
};
|
||||
// console.log("User info after /me:", userInfo);
|
||||
|
||||
try {
|
||||
const allianceResponse = await fetch(`https://backend.wplace.live/alliance/`, {
|
||||
method: "GET",
|
||||
headers: { "Authorization": `Bearer ${token}` }
|
||||
});
|
||||
|
||||
if (allianceResponse.ok) {
|
||||
const allianceData = await allianceResponse.json();
|
||||
userInfo.allianceName = allianceData.name;
|
||||
userInfo.allianceRole = allianceData.role;
|
||||
}
|
||||
} catch (allianceError) {
|
||||
console.log("Alliance info not available:", allianceError.message);
|
||||
}
|
||||
|
||||
console.log("Enhanced user info:", userInfo);
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
if (response.status === 401) {
|
||||
console.log("Token invalid: Unauthorized (401)");
|
||||
} else {
|
||||
console.error(`⚠️ Token check failed with status: ${response.status}`);
|
||||
}
|
||||
return null;
|
||||
|
||||
} catch (e) {
|
||||
console.error("❌ Network or fetch error:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
chrome.webNavigation.onCompleted.addListener(
|
||||
async (details) => {
|
||||
if (details.url.includes("wplace.live")) {
|
||||
console.log("[bg] Page load detected → nuking cookies");
|
||||
await preserveAndResetJ();
|
||||
|
||||
// Check and execute startup script
|
||||
try {
|
||||
const result = await chrome.storage.local.get('startupScript');
|
||||
if (result.startupScript) {
|
||||
console.log(`[bg] Executing startup script: ${result.startupScript}`);
|
||||
await executeLocalScript(result.startupScript, details.tabId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[bg] Error executing startup script:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ url: [{ hostContains: "wplace.live" }] }
|
||||
);
|
||||
|
||||
async function setCookie(value) {
|
||||
const cleaned = value.trim();
|
||||
console.log("[bg] setCookie CALLED with:", cleaned);
|
||||
|
||||
chrome.cookies.set(
|
||||
{
|
||||
url: "https://backend.wplace.live/",
|
||||
name: "j",
|
||||
value: cleaned,
|
||||
domain: cookieDomain,
|
||||
path: "/",
|
||||
},
|
||||
(cookie) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error(
|
||||
"[bg] cookie set error:",
|
||||
chrome.runtime.lastError.message
|
||||
);
|
||||
} else {
|
||||
console.log("[bg] cookie set result:", cookie);
|
||||
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
if (tabs.length > 0) {
|
||||
chrome.tabs.sendMessage(tabs[0].id, {
|
||||
type: "cookieSet",
|
||||
value: cleaned,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function deleteAccountAtIndex(index) {
|
||||
try {
|
||||
const data = await chrome.storage.local.get("infoAccounts");
|
||||
let infoAccounts = data.infoAccounts || [];
|
||||
|
||||
if (index < 0 || index >= infoAccounts.length) {
|
||||
console.warn("[bg] Invalid index:", index);
|
||||
return false;
|
||||
}
|
||||
|
||||
const removed = infoAccounts.splice(index, 1);
|
||||
await chrome.storage.local.set({ infoAccounts });
|
||||
|
||||
console.log(`[bg] Deleted account at index ${index}:`, removed[0]);
|
||||
console.log(`[bg] Remaining accounts:`, infoAccounts);
|
||||
await exportInfoAccount();
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("[bg] Error in deleteAccountAtIndex:", err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
console.log("📩 Background received message:", msg);
|
||||
|
||||
if (msg.type === "setCookie" && msg.value) {
|
||||
(async () => {
|
||||
try {
|
||||
console.log("🍪 Setting cookie...");
|
||||
await setCookie(msg.value);
|
||||
console.log("✅ Cookie set successfully");
|
||||
sendResponse({ status: "ok" });
|
||||
} catch (e) {
|
||||
console.error("❌ setCookie failed", e);
|
||||
sendResponse({ status: "error", error: e.message });
|
||||
}
|
||||
})();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (msg.type === "getAccounts") {
|
||||
(async () => {
|
||||
try {
|
||||
console.log("📂 Fetching accounts...");
|
||||
await filterInvalid();
|
||||
const result = await chrome.storage.local.get("accounts");
|
||||
console.log("📤 Returning accounts:", result.accounts);
|
||||
sendResponse({ accounts: result.accounts || [] });
|
||||
} catch (e) {
|
||||
console.error("❌ getAccounts failed", e);
|
||||
sendResponse({ accounts: [] });
|
||||
}
|
||||
})();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (msg.type === "deleteAccount" && typeof msg.index === "number") {
|
||||
(async () => {
|
||||
try {
|
||||
const ok = await deleteAccountAtIndex(msg.index);
|
||||
sendResponse({ status: ok ? "ok" : "error" });
|
||||
} catch (e) {
|
||||
console.error("❌ deleteAccount failed", e);
|
||||
sendResponse({ status: "error", error: e.message });
|
||||
}
|
||||
})();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (msg.type === "refreshCurrentAccount") {
|
||||
(async () => {
|
||||
try {
|
||||
console.log("🔄 Refreshing current account...");
|
||||
await preserveAndResetJ();
|
||||
sendResponse({ status: "ok" });
|
||||
} catch (e) {
|
||||
console.error("❌ refreshCurrentAccount failed", e);
|
||||
sendResponse({ status: "error", error: e.message });
|
||||
}
|
||||
})();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (msg.action === 'executeScript') {
|
||||
// Get tabId from sender or request
|
||||
const tabId = msg.tabId || sender.tab?.id;
|
||||
|
||||
if (!tabId) {
|
||||
sendResponse({ success: false, error: 'Could not determine target tab' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Use IIFE for async handling
|
||||
(async () => {
|
||||
try {
|
||||
await executeLocalScript(msg.scriptName, tabId);
|
||||
sendResponse({ success: true });
|
||||
} catch (error) {
|
||||
sendResponse({ success: false, error: error.message });
|
||||
}
|
||||
})();
|
||||
|
||||
return true; // Important: indicates async response
|
||||
}
|
||||
});
|
||||
|
||||
@@ -125,7 +125,7 @@ if (window.location.hostname === 'wplace.live') {
|
||||
window.addEventListener('autobot-execute-script', async (event) => {
|
||||
const { scriptName } = event.detail;
|
||||
console.log(`%c📡 Content script received execution request for: ${scriptName}`, 'color: #00ff41; font-weight: bold;');
|
||||
|
||||
|
||||
// Execute the script using the content script's Chrome API access
|
||||
try {
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
@@ -274,6 +274,76 @@ if (window.location.hostname === 'wplace.live') {
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.source !== window) return;
|
||||
if (event.data.source !== "my-userscript") return;
|
||||
const message = event.data;
|
||||
if (message.type === "setCookie" && message.value) {
|
||||
chrome.runtime.sendMessage(
|
||||
{ type: "setCookie", value: message.value },
|
||||
(response) => {
|
||||
if (response.status === "ok") {
|
||||
console.log("✅ Forwarded token to background.");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.source !== window) return;
|
||||
const msg = event.data;
|
||||
if (msg && msg.source === "my-userscript") {
|
||||
if (msg.type === "getAccounts") {
|
||||
chrome.runtime.sendMessage({ type: "getAccounts" }, (response) => {
|
||||
if (response && response.accounts) {
|
||||
window.postMessage(
|
||||
{
|
||||
source: "extension",
|
||||
type: "accountsData",
|
||||
accounts: response.accounts,
|
||||
},
|
||||
"*"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg) => {
|
||||
if (msg.type === "cookieSet") {
|
||||
window.postMessage(
|
||||
{
|
||||
source: "my-extension",
|
||||
type: "cookieSet",
|
||||
value: msg.value,
|
||||
},
|
||||
"*"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.source !== window) return;
|
||||
const msg = event.data;
|
||||
if (msg?.type === "deleteAccount" && typeof msg.index === "number") {
|
||||
chrome.runtime.sendMessage(
|
||||
{ type: "deleteAccount", index: msg.index },
|
||||
(response) => {
|
||||
window.postMessage(
|
||||
{
|
||||
type: "deleteAccountResult",
|
||||
index: msg.index,
|
||||
status: response?.status || "error",
|
||||
},
|
||||
"*"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialization
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 110 KiB |
@@ -10,11 +10,18 @@
|
||||
"128": "icons/icon128.png"
|
||||
},
|
||||
"permissions": [
|
||||
"activeTab",
|
||||
"scripting"
|
||||
"cookies",
|
||||
"webRequest",
|
||||
"scripting",
|
||||
"tabs",
|
||||
"storage",
|
||||
"browsingData",
|
||||
"webNavigation"
|
||||
],
|
||||
"host_permissions": [
|
||||
"https://wplace.live/*"
|
||||
"https://backend.wplace.live/*",
|
||||
"https://wplace.live/*",
|
||||
"https://*.wplace.live/*"
|
||||
],
|
||||
"action": {
|
||||
"default_popup": "popup/popup.html",
|
||||
|
||||
@@ -105,6 +105,44 @@ header h1 {
|
||||
box-shadow: 0 0 10px #ff073a;
|
||||
}
|
||||
|
||||
/* Settings Dialog Styles */
|
||||
.settings-section {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #00ff41;
|
||||
}
|
||||
|
||||
.settings-section h3 {
|
||||
font-size: 10px;
|
||||
color: #00ff41;
|
||||
margin: 0 0 10px 0;
|
||||
text-shadow: 0 0 8px #00ff41;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.settings-select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background: #16213e;
|
||||
border: 2px solid #00ff41;
|
||||
color: #00ff41;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 8px;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 0 0 5px #00ff41;
|
||||
}
|
||||
|
||||
.settings-select:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 15px rgb(0 255 65 / 50%);
|
||||
}
|
||||
|
||||
.settings-select option {
|
||||
background: #16213e;
|
||||
color: #00ff41;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@keyframes pixel-blink {
|
||||
0%, 50% { opacity: 1; }
|
||||
51%, 100% { opacity: 0.7; }
|
||||
@@ -288,6 +326,498 @@ main {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Account Management Styles */
|
||||
.account-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.section-header h4 {
|
||||
margin: 0;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
color: #00ff41;
|
||||
text-shadow: 0 0 10px #00ff41;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 4px 8px;
|
||||
font-size: 6px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
border-color: #888;
|
||||
color: #888;
|
||||
text-shadow: 0 0 8px #888;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: rgba(136, 136, 136, 0.1);
|
||||
box-shadow: 0 0 15px rgb(136 136 136 / 60%);
|
||||
}
|
||||
|
||||
.accounts-container {
|
||||
background: #16213e;
|
||||
border: 2px solid #00ff41;
|
||||
border-radius: 0;
|
||||
padding: 8px;
|
||||
max-height: 180px;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 0 10px rgb(0 255 65 / 30%);
|
||||
}
|
||||
|
||||
.accounts-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.accounts-container::-webkit-scrollbar-track {
|
||||
background: #16213e;
|
||||
border: 1px solid #00ff41;
|
||||
}
|
||||
|
||||
.accounts-container::-webkit-scrollbar-thumb {
|
||||
background: #00ff41;
|
||||
border-radius: 0;
|
||||
box-shadow: 0 0 5px #00ff41;
|
||||
}
|
||||
|
||||
.account-placeholder {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #00ff41bb;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
font-size: 24px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
text-shadow: 0 0 5px #00ff41;
|
||||
}
|
||||
|
||||
.placeholder-subtext {
|
||||
font-size: 6px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.account-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
margin-bottom: 6px;
|
||||
background: rgba(0, 255, 65, 0.05);
|
||||
border: 1px solid rgba(0, 255, 65, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.account-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.account-item:hover {
|
||||
background: rgba(0, 255, 65, 0.1);
|
||||
border-color: #00ff41;
|
||||
box-shadow: 0 0 10px rgb(0 255 65 / 40%);
|
||||
}
|
||||
|
||||
.account-item.active {
|
||||
background: rgba(57, 255, 20, 0.15);
|
||||
border-color: #39ff14;
|
||||
box-shadow: 0 0 15px rgb(57 255 20 / 50%);
|
||||
}
|
||||
|
||||
.account-item.active::before {
|
||||
content: '🟢';
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 8px;
|
||||
animation: pixel-blink 2s infinite;
|
||||
}
|
||||
|
||||
/* Account item info styling - simplified like old version */
|
||||
.account-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.account-name {
|
||||
font-size: 8px;
|
||||
color: #00ff41;
|
||||
text-shadow: 0 0 8px #00ff41;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 400;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.account-stats {
|
||||
font-size: 11px;
|
||||
color: #00ff41bb;
|
||||
text-shadow: 0 0 5px #00ff41;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.account-stats span {
|
||||
color: #39ff14;
|
||||
text-shadow: 0 0 5px #39ff14;
|
||||
font-size: 11px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* Legacy styles for backward compatibility */
|
||||
.account-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.pixel-status {
|
||||
font-size: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
color: #00ff41bb;
|
||||
}
|
||||
|
||||
.pixel-current {
|
||||
color: #39ff14;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 5px #39ff14;
|
||||
}
|
||||
|
||||
.pixel-max {
|
||||
color: #00ff41;
|
||||
text-shadow: 0 0 5px #00ff41;
|
||||
}
|
||||
|
||||
.pixel-percentage {
|
||||
color: #ff6b35;
|
||||
font-size: 5px;
|
||||
text-shadow: 0 0 5px #ff6b35;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.alliance-status {
|
||||
font-size: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.alliance-status.has-alliance {
|
||||
color: #39ff14;
|
||||
text-shadow: 0 0 5px #39ff14;
|
||||
}
|
||||
|
||||
.alliance-status.no-alliance {
|
||||
color: #888;
|
||||
text-shadow: 0 0 5px #888;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.total-pixels {
|
||||
font-size: 6px;
|
||||
color: #06b6d4;
|
||||
text-shadow: 0 0 5px #06b6d4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.account-id {
|
||||
font-size: 6px;
|
||||
color: #00ff41bb;
|
||||
text-shadow: 0 0 5px #00ff41;
|
||||
}
|
||||
|
||||
.account-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
padding: 4px;
|
||||
min-width: 20px;
|
||||
font-size: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: #1a1a2e;
|
||||
border: 2px solid #00ff41;
|
||||
padding: 0;
|
||||
min-width: 280px;
|
||||
max-width: 320px;
|
||||
box-shadow: 0 0 30px rgb(0 255 65 / 60%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: #16213e;
|
||||
border-bottom: 2px solid #00ff41;
|
||||
padding: 12px 15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header h4 {
|
||||
margin: 0;
|
||||
font-size: 9px;
|
||||
color: #00ff41;
|
||||
text-shadow: 0 0 10px #00ff41;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #ff073a;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-shadow: 0 0 8px #ff073a;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
transform: scale(1.2);
|
||||
text-shadow: 0 0 15px #ff073a;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 7px;
|
||||
color: #00ff41;
|
||||
text-shadow: 0 0 8px #00ff41;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
background: #16213e;
|
||||
border: 2px solid rgba(0, 255, 65, 0.5);
|
||||
color: #00ff41;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 6px;
|
||||
resize: vertical;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus {
|
||||
outline: none;
|
||||
border-color: #00ff41;
|
||||
box-shadow: 0 0 10px rgb(0 255 65 / 50%);
|
||||
}
|
||||
|
||||
.form-group input::placeholder,
|
||||
.form-group textarea::placeholder {
|
||||
color: #00ff41bb;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
background: #16213e;
|
||||
border-top: 2px solid #00ff41;
|
||||
padding: 12px 15px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* Loading States */
|
||||
.loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading .btn {
|
||||
animation: pixel-blink 1s infinite;
|
||||
}
|
||||
|
||||
/* Notification system */
|
||||
.notification {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
padding: 8px 12px;
|
||||
background: #16213e;
|
||||
border: 2px solid #00ff41;
|
||||
color: #00ff41;
|
||||
font-size: 7px;
|
||||
z-index: 2000;
|
||||
max-width: 200px;
|
||||
animation: slideInRight 0.3s ease, fadeOut 0.3s ease 2.7s;
|
||||
box-shadow: 0 0 15px rgb(0 255 65 / 50%);
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
border-color: #ff073a;
|
||||
color: #ff073a;
|
||||
box-shadow: 0 0 15px rgb(255 7 58 / 50%);
|
||||
}
|
||||
|
||||
.notification.success {
|
||||
border-color: #39ff14;
|
||||
color: #39ff14;
|
||||
box-shadow: 0 0 15px rgb(57 255 20 / 50%);
|
||||
}
|
||||
|
||||
.notification.warning {
|
||||
border-color: #ff6b35;
|
||||
color: #ff6b35;
|
||||
box-shadow: 0 0 15px rgb(255 107 53 / 50%);
|
||||
}
|
||||
|
||||
@keyframes slideInRight {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
/* Enhanced account item hover states */
|
||||
.account-item:hover .account-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.account-actions {
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* Enhanced loading state */
|
||||
.loading .account-item {
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.loading .btn {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Better modal animations */
|
||||
.modal-overlay {
|
||||
animation: modalFadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
animation: modalSlideIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes modalFadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes modalSlideIn {
|
||||
from { transform: translateY(-20px) scale(0.95); opacity: 0; }
|
||||
to { transform: translateY(0) scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
/* Better form validation states */
|
||||
.form-group input:invalid,
|
||||
.form-group textarea:invalid {
|
||||
border-color: #ff073a;
|
||||
box-shadow: 0 0 5px rgb(255 7 58 / 30%);
|
||||
}
|
||||
|
||||
.form-group input:valid,
|
||||
.form-group textarea:valid {
|
||||
border-color: #39ff14;
|
||||
box-shadow: 0 0 5px rgb(57 255 20 / 30%);
|
||||
}
|
||||
|
||||
/* Improved active account indicator */
|
||||
.account-item.active .account-name {
|
||||
color: #39ff14;
|
||||
text-shadow: 0 0 10px #39ff14;
|
||||
}
|
||||
|
||||
.account-item.active .account-id {
|
||||
color: #39ff14bb;
|
||||
text-shadow: 0 0 8px #39ff14;
|
||||
}
|
||||
|
||||
/* Better disabled state for buttons */
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.logs-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@@ -27,10 +27,78 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logs-section">
|
||||
<h4>Activity Log</h4>
|
||||
<div class="logs" id="logs">
|
||||
<div class="log-entry">Extension loaded and ready</div>
|
||||
<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="accounts-container" id="accountsContainer">
|
||||
<div class="account-placeholder" id="accountPlaceholder">
|
||||
<span class="placeholder-icon">👤</span>
|
||||
<span class="placeholder-text">No accounts saved</span>
|
||||
<span class="placeholder-subtext">Click "Refresh" to add current account</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Account Modal -->
|
||||
<div class="modal-overlay" id="addAccountModal" style="display: none;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>Add Account Manually</h4>
|
||||
<button class="modal-close" id="closeModalBtn">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="accountName">Account Name</label>
|
||||
<input type="text" id="accountName" placeholder="Enter account name" maxlength="50">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="accountToken">Token (Cookie Value)</label>
|
||||
<textarea id="accountToken" placeholder="Paste authentication token here" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" id="saveAccountBtn">
|
||||
<span class="icon">💾</span>
|
||||
Save Account
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="cancelAccountBtn">
|
||||
<span class="icon">✕</span>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
<div class="modal-overlay" id="settingsModal" style="display: none;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4>Settings</h4>
|
||||
<button class="modal-close" id="closeSettingsBtn">✕</button>
|
||||
</div>
|
||||
<div class="settings-section">
|
||||
<h3>Startup Script</h3>
|
||||
<p style="font-size: 8px; color: #888; margin-bottom: 10px;">Select a script to run automatically when visiting wplace.live</p>
|
||||
<select id="startupScriptSelect" class="settings-select">
|
||||
<option value="">None</option>
|
||||
<!-- Scripts will be populated here -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" id="closeSettingsModalBtn">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// WPlace AutoBOT Popup Script
|
||||
// WPlace AutoBOT Popup Script with Account Management
|
||||
|
||||
// Available scripts configuration - Script Manager as primary launcher
|
||||
const AVAILABLE_SCRIPTS = [
|
||||
@@ -26,30 +26,60 @@ const AVAILABLE_SCRIPTS = [
|
||||
displayName: '🔧 Auto Repair',
|
||||
description: 'Automated repair and maintenance tasks',
|
||||
filename: 'Auto-Repair.js'
|
||||
},
|
||||
{
|
||||
name: 'Acc-Switch.js',
|
||||
displayName: '🔄 Account Switcher',
|
||||
description: 'Temporary account switching script',
|
||||
filename: 'Acc-Switch.js'
|
||||
}
|
||||
];
|
||||
|
||||
// DOM elements
|
||||
let statusDot, statusText, scriptsList, logs;
|
||||
let statusDot, statusText, scriptsList, accountsContainer, accountPlaceholder;
|
||||
let addAccountModal, accountNameInput, accountTokenInput;
|
||||
let refreshAccountBtn, addAccountBtn, saveAccountBtn, cancelAccountBtn, closeModalBtn;
|
||||
|
||||
// Global state
|
||||
let accounts = [];
|
||||
let currentAccountIndex = -1;
|
||||
let isLoading = false;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
initializeElements();
|
||||
await loadScripts();
|
||||
await loadAccounts();
|
||||
setupEventListeners();
|
||||
addLog('Extension popup opened');
|
||||
checkCurrentTab();
|
||||
console.log('🚀 Account Manager initialized');
|
||||
});
|
||||
|
||||
function initializeElements() {
|
||||
// Existing elements
|
||||
statusDot = document.getElementById('statusDot');
|
||||
statusText = document.getElementById('statusText');
|
||||
scriptsList = document.getElementById('scriptsList');
|
||||
logs = document.getElementById('logs');
|
||||
|
||||
// Account management elements
|
||||
accountsContainer = document.getElementById('accountsContainer');
|
||||
accountPlaceholder = document.getElementById('accountPlaceholder');
|
||||
|
||||
// Modal elements
|
||||
addAccountModal = document.getElementById('addAccountModal');
|
||||
accountNameInput = document.getElementById('accountName');
|
||||
accountTokenInput = document.getElementById('accountToken');
|
||||
|
||||
// Button elements
|
||||
refreshAccountBtn = document.getElementById('refreshAccountBtn');
|
||||
addAccountBtn = document.getElementById('addAccountBtn');
|
||||
saveAccountBtn = document.getElementById('saveAccountBtn');
|
||||
cancelAccountBtn = document.getElementById('cancelAccountBtn');
|
||||
closeModalBtn = document.getElementById('closeModalBtn');
|
||||
}
|
||||
|
||||
async function loadScripts() {
|
||||
scriptsList.innerHTML = '';
|
||||
|
||||
|
||||
AVAILABLE_SCRIPTS.forEach(script => {
|
||||
const scriptElement = createScriptElement(script);
|
||||
scriptsList.appendChild(scriptElement);
|
||||
@@ -59,13 +89,13 @@ async function loadScripts() {
|
||||
function createScriptElement(script) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'script-item';
|
||||
|
||||
|
||||
// Add special styling for primary script
|
||||
if (script.isPrimary) {
|
||||
div.style.background = 'rgba(0,255,65,0.1)';
|
||||
div.style.boxShadow = '0 0 15px rgb(0 255 65 / 30%)';
|
||||
}
|
||||
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="script-info-left">
|
||||
<div class="script-name">${script.displayName}</div>
|
||||
@@ -76,7 +106,7 @@ function createScriptElement(script) {
|
||||
${script.isPrimary ? 'Open' : 'Launch'}
|
||||
</button>
|
||||
`;
|
||||
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
@@ -90,25 +120,408 @@ function setupEventListeners() {
|
||||
}
|
||||
});
|
||||
|
||||
// Account management buttons
|
||||
refreshAccountBtn.addEventListener('click', handleRefreshAccount);
|
||||
addAccountBtn.addEventListener('click', openAddAccountModal);
|
||||
saveAccountBtn.addEventListener('click', handleSaveAccount);
|
||||
cancelAccountBtn.addEventListener('click', closeAddAccountModal);
|
||||
closeModalBtn.addEventListener('click', closeAddAccountModal);
|
||||
|
||||
// Modal close on overlay click
|
||||
addAccountModal.addEventListener('click', (e) => {
|
||||
if (e.target === addAccountModal) {
|
||||
closeAddAccountModal();
|
||||
}
|
||||
});
|
||||
|
||||
// Account container click handling
|
||||
accountsContainer.addEventListener('click', handleAccountClick);
|
||||
|
||||
// Settings button
|
||||
document.getElementById('settingsBtn').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
addLog('Settings feature coming soon');
|
||||
document.getElementById('settingsModal').style.display = 'flex';
|
||||
loadStartupScript(); // Load current startup script selection
|
||||
populateStartupScriptSelect(); // Populate script options
|
||||
});
|
||||
|
||||
// Settings modal close buttons
|
||||
document.getElementById('closeSettingsBtn').addEventListener('click', () => {
|
||||
document.getElementById('settingsModal').style.display = 'none';
|
||||
});
|
||||
|
||||
document.getElementById('closeSettingsModalBtn').addEventListener('click', () => {
|
||||
document.getElementById('settingsModal').style.display = 'none';
|
||||
});
|
||||
|
||||
// Settings modal close on overlay click
|
||||
document.getElementById('settingsModal').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('settingsModal')) {
|
||||
document.getElementById('settingsModal').style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Startup script selection
|
||||
document.getElementById('startupScriptSelect').addEventListener('change', async (e) => {
|
||||
const scriptName = e.target.value;
|
||||
await chrome.storage.local.set({ startupScript: scriptName });
|
||||
showNotification(scriptName ?
|
||||
`Startup script set to ${AVAILABLE_SCRIPTS.find(s => s.filename === scriptName).displayName}` :
|
||||
'Startup script disabled',
|
||||
'success'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadAccounts() {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const response = await chrome.runtime.sendMessage({ type: 'getAccounts' });
|
||||
const storedAccounts = response?.accounts || [];
|
||||
const result = await chrome.storage.local.get('infoAccounts');
|
||||
const infoAccounts = result.infoAccounts || [];
|
||||
|
||||
accounts = storedAccounts.map((token, index) => {
|
||||
const info = infoAccounts.find(acc => acc.token === token);
|
||||
console.log(`Account ${index + 1} info:`, info);
|
||||
return {
|
||||
index,
|
||||
token,
|
||||
name: info?.name,
|
||||
id: info?.ID,
|
||||
Charges: info?.Charges,
|
||||
Max: info?.Max,
|
||||
Droplets: info?.Droplets,
|
||||
maxPixels: info?.Max,
|
||||
droplets: info?.Droplets,
|
||||
allianceName: info?.allianceName,
|
||||
allianceRole: info?.allianceRole,
|
||||
totalPixelsPainted: info?.totalPixelsPainted,
|
||||
level: info?.level,
|
||||
};
|
||||
});
|
||||
|
||||
renderAccounts();
|
||||
console.log('📂 Loaded accounts with enhanced status:', accounts);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error loading accounts:', error);
|
||||
showNotification('Failed to load accounts', 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function renderAccounts() {
|
||||
if (accounts.length === 0) {
|
||||
accountPlaceholder.style.display = 'flex';
|
||||
accountsContainer.innerHTML = '';
|
||||
accountsContainer.appendChild(accountPlaceholder);
|
||||
return;
|
||||
}
|
||||
|
||||
accountPlaceholder.style.display = 'none';
|
||||
accountsContainer.innerHTML = '';
|
||||
|
||||
accounts.forEach((account, index) => {
|
||||
const accountElement = createAccountElement(account, index);
|
||||
accountsContainer.appendChild(accountElement);
|
||||
});
|
||||
}
|
||||
|
||||
function createAccountElement(account, index) {
|
||||
const div = document.createElement('div');
|
||||
div.className = `account-item ${account.isActive ? 'active' : ''}`;
|
||||
div.dataset.index = index;
|
||||
|
||||
// Format like the old version: Name, Charges/Max, and Droplets
|
||||
const charges = Math.floor(account.Charges);
|
||||
const max = Math.floor(account.Max);
|
||||
const droplets = Math.floor(account.Droplets);
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="account-info">
|
||||
<div class="account-name">${escapeHtml(account.name)}</div>
|
||||
<div class="account-stats">
|
||||
<span>⚡ ${charges}/${max}</span>
|
||||
<span>💧 ${droplets}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-actions">
|
||||
<button class="btn btn-primary btn-icon switch-btn" title="Switch to this account" data-action="switch" data-index="${index}">
|
||||
<span class="icon">🔄</span>
|
||||
</button>
|
||||
<button class="btn btn-danger btn-icon delete-btn" title="Delete account" data-action="delete" data-index="${index}">
|
||||
<span class="icon">🗑️</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
async function handleAccountClick(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const action = e.target.dataset.action || e.target.closest('[data-action]')?.dataset.action;
|
||||
const index = parseInt(e.target.dataset.index || e.target.closest('[data-index]')?.dataset.index);
|
||||
|
||||
if (isNaN(index) || index < 0 || index >= accounts.length) return;
|
||||
|
||||
switch (action) {
|
||||
case 'switch':
|
||||
await switchToAccount(index);
|
||||
break;
|
||||
case 'delete':
|
||||
await deleteAccount(index);
|
||||
break;
|
||||
default:
|
||||
// Click on account item itself - switch account
|
||||
await switchToAccount(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function switchToAccount(index) {
|
||||
try {
|
||||
if (isLoading) return;
|
||||
|
||||
setLoading(true);
|
||||
showNotification('Switching account...', 'info');
|
||||
|
||||
const account = accounts[index];
|
||||
console.log('🔄 Switching to account:', account);
|
||||
|
||||
// Send switch message to background
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
type: 'setCookie',
|
||||
value: account.token
|
||||
});
|
||||
|
||||
if (response?.status === 'ok') {
|
||||
// Update active state
|
||||
accounts.forEach((acc, i) => acc.isActive = i === index);
|
||||
currentAccountIndex = index;
|
||||
|
||||
renderAccounts();
|
||||
showNotification(`Switched to ${account.name}`, 'success');
|
||||
|
||||
console.log('✅ Account switched successfully');
|
||||
|
||||
|
||||
} else {
|
||||
throw new Error(response?.error || 'Failed to switch account');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error switching account:', error);
|
||||
showNotification('Failed to switch account', 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAccount(index) {
|
||||
try {
|
||||
if (isLoading) return;
|
||||
|
||||
const account = accounts[index];
|
||||
|
||||
// Confirm deletion
|
||||
if (!confirm(`Delete account "${account.name}"?\n\nThis action cannot be undone.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
showNotification('Deleting account...', 'info');
|
||||
|
||||
console.log('🗑️ Deleting account:', account);
|
||||
|
||||
// Send delete message to background
|
||||
const response = await chrome.runtime.sendMessage({
|
||||
type: 'deleteAccount',
|
||||
index: account.index
|
||||
});
|
||||
|
||||
if (response?.status === 'ok') {
|
||||
// Remove from local array
|
||||
accounts.splice(index, 1);
|
||||
|
||||
// Update indices
|
||||
accounts.forEach((acc, i) => acc.index = i);
|
||||
|
||||
// Clear active state if deleted account was active
|
||||
if (currentAccountIndex === index) {
|
||||
currentAccountIndex = -1;
|
||||
} else if (currentAccountIndex > index) {
|
||||
currentAccountIndex--;
|
||||
}
|
||||
|
||||
renderAccounts();
|
||||
showNotification(`Deleted ${account.name}`, 'success');
|
||||
|
||||
console.log('✅ Account deleted successfully');
|
||||
|
||||
} else {
|
||||
throw new Error('Failed to delete account');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error deleting account:', error);
|
||||
showNotification('Failed to delete account', 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRefreshAccount() {
|
||||
try {
|
||||
if (isLoading) return;
|
||||
|
||||
setLoading(true);
|
||||
showNotification('Fetching current account...', 'info');
|
||||
|
||||
console.log('🔄 Refreshing account from current session');
|
||||
|
||||
// Get current tab
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
|
||||
if (!tab.url.includes('wplace.live')) {
|
||||
throw new Error('Please navigate to wplace.live first');
|
||||
}
|
||||
|
||||
// Trigger account preservation which will add current account
|
||||
await chrome.runtime.sendMessage({ type: 'refreshCurrentAccount' });
|
||||
|
||||
// Wait a bit for the account to be processed
|
||||
setTimeout(async () => {
|
||||
await loadAccounts();
|
||||
showNotification('Account refreshed successfully', 'success');
|
||||
}, 2000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error refreshing account:', error);
|
||||
showNotification(error.message || 'Failed to refresh account', 'error');
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function openAddAccountModal() {
|
||||
addAccountModal.style.display = 'flex';
|
||||
accountNameInput.value = '';
|
||||
accountTokenInput.value = '';
|
||||
accountNameInput.focus();
|
||||
}
|
||||
|
||||
function closeAddAccountModal() {
|
||||
addAccountModal.style.display = 'none';
|
||||
accountNameInput.value = '';
|
||||
accountTokenInput.value = '';
|
||||
}
|
||||
|
||||
async function handleSaveAccount() {
|
||||
try {
|
||||
const name = accountNameInput.value.trim();
|
||||
const token = accountTokenInput.value.trim();
|
||||
|
||||
if (!name) {
|
||||
showNotification('Please enter an account name', 'error');
|
||||
accountNameInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
showNotification('Please enter a token', 'error');
|
||||
accountTokenInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic token validation
|
||||
if (token.length < 10) {
|
||||
showNotification('Token appears to be too short', 'error');
|
||||
accountTokenInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
showNotification('Validating and saving account...', 'info');
|
||||
|
||||
console.log('💾 Saving manual account:', { name, token: token.substring(0, 20) + '...' });
|
||||
|
||||
// Validate token format (basic check)
|
||||
const validation = await validateAccountToken(token);
|
||||
if (!validation.valid) {
|
||||
showNotification(validation.error || 'Invalid token format', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await chrome.storage.local.get('infoAccounts');
|
||||
let infoAccounts = result.infoAccounts || [];
|
||||
|
||||
// Check if account already exists by token
|
||||
const existingIndex = infoAccounts.findIndex(acc => acc.token === token);
|
||||
|
||||
const accountInfo = {
|
||||
ID: 'manual_' + Date.now(), // Placeholder ID for manual accounts
|
||||
name: name,
|
||||
token: token,
|
||||
Charges: 0,
|
||||
Max: 100,
|
||||
Droplets: 0,
|
||||
currentPixels: 0,
|
||||
maxPixels: 100,
|
||||
droplets: 0,
|
||||
alliance: 'Unknown',
|
||||
allianceName: 'Unknown',
|
||||
allianceRole: 'Member',
|
||||
totalPixelsPainted: 0,
|
||||
level: 1,
|
||||
};
|
||||
|
||||
if (existingIndex > -1) {
|
||||
// Update existing account
|
||||
infoAccounts[existingIndex] = accountInfo;
|
||||
showNotification('Account updated successfully', 'success');
|
||||
} else {
|
||||
// Add new account
|
||||
infoAccounts.push(accountInfo);
|
||||
showNotification('Account added successfully', 'success');
|
||||
}
|
||||
|
||||
// Save to storage
|
||||
await chrome.storage.local.set({ infoAccounts });
|
||||
|
||||
// Update accounts list in background
|
||||
const accounts = infoAccounts.map(info => info.token);
|
||||
await chrome.storage.local.set({ accounts });
|
||||
|
||||
closeAddAccountModal();
|
||||
await loadAccounts();
|
||||
|
||||
console.log('✅ Manual account saved successfully');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error saving account:', error);
|
||||
showNotification('Failed to save account', 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function launchScript(scriptName, button) {
|
||||
try {
|
||||
addLog('Launching ' + scriptName + '...', 'warning');
|
||||
|
||||
showNotification('Launching ' + scriptName + '...', 'info');
|
||||
|
||||
// Update button state
|
||||
button.disabled = true;
|
||||
const originalContent = button.innerHTML;
|
||||
button.innerHTML = '<span class="icon">⏳</span> Launching...';
|
||||
|
||||
|
||||
// Get active tab
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
|
||||
|
||||
if (!tab.url.includes('wplace.live')) {
|
||||
throw new Error('Please navigate to wplace.live first');
|
||||
}
|
||||
@@ -123,8 +536,8 @@ async function launchScript(scriptName, button) {
|
||||
if (response && response.success) {
|
||||
button.innerHTML = '<span class="icon">✅</span> Launched';
|
||||
button.style.background = '#4CAF50';
|
||||
addLog(scriptName + ' executed successfully', 'success');
|
||||
|
||||
showNotification(scriptName + ' executed successfully', 'success');
|
||||
|
||||
// Close popup after success
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
@@ -135,11 +548,11 @@ async function launchScript(scriptName, button) {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error launching script:', error);
|
||||
addLog('Failed to launch ' + scriptName + ': ' + error.message, 'error');
|
||||
|
||||
showNotification('Failed to launch ' + scriptName + ': ' + error.message, 'error');
|
||||
|
||||
// Reset button
|
||||
button.disabled = false;
|
||||
button.innerHTML = '<span class="icon">▶</span> Launch';
|
||||
button.innerHTML = originalContent;
|
||||
button.style.background = '';
|
||||
}
|
||||
}
|
||||
@@ -147,16 +560,16 @@ async function launchScript(scriptName, button) {
|
||||
async function checkCurrentTab() {
|
||||
try {
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
|
||||
|
||||
if (tab.url.includes('wplace.live')) {
|
||||
statusDot.className = 'status-dot';
|
||||
statusText.textContent = 'Ready';
|
||||
addLog('Ready to launch scripts on wplace.live');
|
||||
showNotification('Ready to launch scripts on wplace.live', 'success');
|
||||
} else {
|
||||
statusDot.className = 'status-dot error';
|
||||
statusText.textContent = 'Not on WPlace';
|
||||
addLog('Please navigate to wplace.live to use scripts', 'warning');
|
||||
|
||||
showNotification('Please navigate to wplace.live to use scripts', 'warning');
|
||||
|
||||
// Disable all launch buttons
|
||||
document.querySelectorAll('.launch-btn').forEach(btn => {
|
||||
btn.disabled = true;
|
||||
@@ -165,20 +578,136 @@ async function checkCurrentTab() {
|
||||
} catch (error) {
|
||||
statusDot.className = 'status-dot error';
|
||||
statusText.textContent = 'Error';
|
||||
addLog('Error checking current tab: ' + error.message, 'error');
|
||||
showNotification('Error checking current tab: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function addLog(message, type = 'info') {
|
||||
const logEntry = document.createElement('div');
|
||||
logEntry.className = 'log-entry ' + type;
|
||||
logEntry.textContent = new Date().toLocaleTimeString() + ': ' + message;
|
||||
function setLoading(loading) {
|
||||
isLoading = loading;
|
||||
document.body.classList.toggle('loading', loading);
|
||||
|
||||
// Disable buttons during loading
|
||||
refreshAccountBtn.disabled = loading;
|
||||
addAccountBtn.disabled = loading;
|
||||
saveAccountBtn.disabled = loading;
|
||||
}
|
||||
|
||||
function showNotification(message, type = 'info') {
|
||||
// Remove existing notifications
|
||||
const existingNotifications = document.querySelectorAll('.notification');
|
||||
existingNotifications.forEach(n => n.remove());
|
||||
|
||||
// Create notification element
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification ${type}`;
|
||||
notification.textContent = message;
|
||||
|
||||
// Add to body
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Auto remove after 3 seconds
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
// Log to console as well
|
||||
// console.log(`📢 ${type.toUpperCase()}: ${message}`);
|
||||
|
||||
// Update status indicator
|
||||
if (type === 'error') {
|
||||
statusDot.className = 'status-dot error';
|
||||
statusText.textContent = 'Error';
|
||||
} else if (type === 'success') {
|
||||
statusDot.className = 'status-dot';
|
||||
statusText.textContent = 'Success';
|
||||
} else if (type === 'warning') {
|
||||
statusDot.className = 'status-dot running';
|
||||
statusText.textContent = 'Warning';
|
||||
} else {
|
||||
statusDot.className = 'status-dot';
|
||||
statusText.textContent = 'Info';
|
||||
}
|
||||
|
||||
// Reset status after 3 seconds
|
||||
setTimeout(() => {
|
||||
checkCurrentTab();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Populate startup script select options
|
||||
function populateStartupScriptSelect() {
|
||||
const select = document.getElementById('startupScriptSelect');
|
||||
select.innerHTML = '<option value="">None</option>';
|
||||
|
||||
logs.appendChild(logEntry);
|
||||
logs.scrollTop = logs.scrollHeight;
|
||||
|
||||
// Keep only last 20 log entries
|
||||
while (logs.children.length > 20) {
|
||||
logs.removeChild(logs.firstChild);
|
||||
AVAILABLE_SCRIPTS.forEach(script => {
|
||||
const option = document.createElement('option');
|
||||
option.value = script.filename;
|
||||
option.textContent = script.displayName;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// Load startup script selection from storage
|
||||
async function loadStartupScript() {
|
||||
const result = await chrome.storage.local.get('startupScript');
|
||||
const select = document.getElementById('startupScriptSelect');
|
||||
if (select) {
|
||||
select.value = result.startupScript || '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Load startup script selection
|
||||
async function loadStartupScript() {
|
||||
const result = await chrome.storage.local.get('startupScript');
|
||||
const select = document.getElementById('startupScriptSelect');
|
||||
if (select) {
|
||||
select.value = result.startupScript || '';
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced account validation
|
||||
async function validateAccountToken(token) {
|
||||
try {
|
||||
// This would typically make a request to validate the token
|
||||
// For now, we'll do basic validation
|
||||
if (!token || typeof token !== 'string' || token.length < 10) {
|
||||
return { valid: false, error: 'Token appears to be invalid' };
|
||||
}
|
||||
|
||||
// You could add actual API validation here
|
||||
return { valid: true };
|
||||
|
||||
} catch (error) {
|
||||
return { valid: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard shortcuts
|
||||
document.addEventListener('keydown', (e) => {
|
||||
// Escape key closes modal
|
||||
if (e.key === 'Escape' && addAccountModal.style.display === 'flex') {
|
||||
closeAddAccountModal();
|
||||
}
|
||||
|
||||
// Ctrl+R or F5 refreshes accounts
|
||||
if ((e.ctrlKey && e.key === 'r') || e.key === 'F5') {
|
||||
e.preventDefault();
|
||||
handleRefreshAccount();
|
||||
}
|
||||
|
||||
// Ctrl+N adds new account
|
||||
if (e.ctrlKey && e.key === 'n') {
|
||||
e.preventDefault();
|
||||
openAddAccountModal();
|
||||
}
|
||||
});
|
||||
@@ -38,7 +38,7 @@ localStorage.removeItem("lp");
|
||||
REPEAT_MINUTES: 5, // repeat reminder while threshold condition holds
|
||||
},
|
||||
OVERLAY: {
|
||||
OPACITY_DEFAULT: 0.2,
|
||||
OPACITY_DEFAULT: 0.6,
|
||||
BLUE_MARBLE_DEFAULT: false,
|
||||
ditheringEnabled: false,
|
||||
}, // --- START: Color data from colour-converter.js ---
|
||||
@@ -229,6 +229,7 @@ localStorage.removeItem("lp");
|
||||
COORDINATE_SNAKE: true,
|
||||
COORDINATE_BLOCK_WIDTH: 6,
|
||||
COORDINATE_BLOCK_HEIGHT: 2,
|
||||
autoSwap: true,
|
||||
};
|
||||
|
||||
const getCurrentTheme = () => CONFIG.THEMES[CONFIG.currentTheme];
|
||||
@@ -251,7 +252,7 @@ localStorage.removeItem("lp");
|
||||
const theme = getCurrentTheme();
|
||||
console.group('%c🎨 Applying Theme in Auto-Image Script', 'color: #8b5cf6; font-weight: bold;');
|
||||
console.log(`%c🎯 Target theme: ${CONFIG.currentTheme}`, 'color: #8b5cf6;');
|
||||
|
||||
|
||||
// Toggle theme class on documentElement so CSS vars cascade to our UI
|
||||
document.documentElement.classList.remove(
|
||||
'wplace-theme-classic',
|
||||
@@ -262,7 +263,7 @@ localStorage.removeItem("lp");
|
||||
|
||||
let themeClass = 'wplace-theme-classic'; // default
|
||||
let themeFileName = 'classic'; // corresponding file name
|
||||
|
||||
|
||||
if (CONFIG.currentTheme === 'Neon Retro') {
|
||||
themeClass = 'wplace-theme-neon';
|
||||
themeFileName = 'neon';
|
||||
@@ -320,7 +321,7 @@ localStorage.removeItem("lp");
|
||||
setVar('--wplace-border-width', '' + (theme.borderWidth || '1px'));
|
||||
setVar('--wplace-backdrop', '' + (theme.backdropFilter || 'blur(10px)'));
|
||||
setVar('--wplace-border-color', 'rgba(255,255,255,0.1)');
|
||||
|
||||
|
||||
console.log(`%c🎨 Theme application complete`, 'color: #8b5cf6; font-weight: bold;');
|
||||
console.groupEnd();
|
||||
}
|
||||
@@ -377,11 +378,11 @@ localStorage.removeItem("lp");
|
||||
// First try: Check if extension has loaded local resources
|
||||
if (window.AUTOBOT_LANGUAGES && Object.keys(window.AUTOBOT_LANGUAGES).length > 0) {
|
||||
console.log(`%c🔍 Checking extension local resources...`, 'color: #06b6d4;');
|
||||
|
||||
|
||||
const langFile = language + '.json';
|
||||
if (window.AUTOBOT_LANGUAGES[langFile]) {
|
||||
const translations = window.AUTOBOT_LANGUAGES[langFile];
|
||||
|
||||
|
||||
// Validate that translations is an object with keys
|
||||
if (
|
||||
typeof translations === 'object' &&
|
||||
@@ -721,6 +722,7 @@ localStorage.removeItem("lp");
|
||||
_lastSaveTime: 0,
|
||||
_saveInProgress: false,
|
||||
paintedMap: null,
|
||||
accountIndex: -1,
|
||||
};
|
||||
|
||||
let _updateResizePreview = () => { };
|
||||
@@ -2976,34 +2978,49 @@ localStorage.removeItem("lp");
|
||||
},
|
||||
|
||||
async getCharges() {
|
||||
const defaultResult = {
|
||||
charges: 0,
|
||||
max: 1,
|
||||
cooldown: CONFIG.COOLDOWN_DEFAULT,
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch('https://backend.wplace.live/me', {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(`Failed to get charges: HTTP ${res.status}`);
|
||||
return defaultResult;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
const res = await fetch("https://backend.wplace.live/me", {
|
||||
credentials: "include",
|
||||
})
|
||||
const data = await res.json()
|
||||
return {
|
||||
charges: data.charges?.count ?? 0,
|
||||
max: data.charges?.max ?? 1,
|
||||
cooldown: data.charges?.cooldownMs ?? CONFIG.COOLDOWN_DEFAULT,
|
||||
};
|
||||
id: data.id,
|
||||
charges: data.charges?.count || 0,
|
||||
max: data.charges?.max || 1,
|
||||
cooldown: data.charges?.next || CONFIG.COOLDOWN_DEFAULT,
|
||||
droplets: data.droplets || 0,
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to get charges:', e);
|
||||
return defaultResult;
|
||||
console.error("Failed to get charges:", e)
|
||||
return {
|
||||
id: null,
|
||||
charges: 0,
|
||||
max: 1,
|
||||
cooldown: CONFIG.COOLDOWN_DEFAULT,
|
||||
droplets: 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async fetchCheck() {
|
||||
try {
|
||||
const res = await fetch("https://backend.wplace.live/me", {
|
||||
credentials: "include",
|
||||
})
|
||||
const data = await res.json()
|
||||
return {
|
||||
ID: data.id,
|
||||
Charges: data.charges.count,
|
||||
Max: data.charges.max,
|
||||
Droplets: data.droplets
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to get ID:", e)
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Desktop Notification Manager
|
||||
@@ -3515,13 +3532,13 @@ localStorage.removeItem("lp");
|
||||
|
||||
// Load auto-image-styles.css - prioritize extension local resources
|
||||
console.group('%c🎨 Loading Auto-Image Styles', 'color: #8b5cf6; font-weight: bold;');
|
||||
|
||||
|
||||
let stylesLoaded = false;
|
||||
|
||||
|
||||
// First try: Check if extension has loaded local CSS resources
|
||||
if (window.AUTOBOT_THEMES && window.AUTOBOT_THEMES['auto-image-styles.css']) {
|
||||
console.log('%c🔍 Found auto-image-styles.css in extension local resources!', 'color: #10b981; font-weight: bold;');
|
||||
|
||||
|
||||
// Check if it's already injected by the extension
|
||||
const existingStyle = document.getElementById('autobot-auto-image-styles');
|
||||
if (existingStyle) {
|
||||
@@ -3536,7 +3553,7 @@ localStorage.removeItem("lp");
|
||||
styleElement.setAttribute('data-wplace-theme', 'true');
|
||||
styleElement.textContent = window.AUTOBOT_THEMES['auto-image-styles.css'];
|
||||
document.head.appendChild(styleElement);
|
||||
|
||||
|
||||
console.log('%c✅ Injected auto-image-styles.css from extension local resources', 'color: #10b981; font-weight: bold;');
|
||||
console.log(' 📍 Source: Extension local file');
|
||||
console.log(' 📏 Size: ' + window.AUTOBOT_THEMES['auto-image-styles.css'].length + ' characters');
|
||||
@@ -3550,7 +3567,7 @@ localStorage.removeItem("lp");
|
||||
console.log(' 📋 Available themes:', Object.keys(window.AUTOBOT_THEMES));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fallback: Load from CDN if local resources not available
|
||||
if (!stylesLoaded) {
|
||||
console.log('%c🌐 Falling back to CDN loading...', 'color: #8b5cf6;');
|
||||
@@ -3563,13 +3580,13 @@ localStorage.removeItem("lp");
|
||||
console.log(' 🌐 URL: https://wplace-autobot.github.io/WPlace-AutoBOT/main/auto-image-styles.css');
|
||||
console.log(' ⚠️ Performance: Network request required');
|
||||
}
|
||||
|
||||
|
||||
console.groupEnd();
|
||||
|
||||
// Ensure default theme is loaded from extension resources
|
||||
if (window.applyTheme && typeof window.applyTheme === 'function') {
|
||||
console.group('%c🎨 Loading Default Theme from Extension', 'color: #8b5cf6; font-weight: bold;');
|
||||
|
||||
|
||||
// Determine the current theme file name
|
||||
let defaultTheme = 'classic'; // fallback
|
||||
if (CONFIG.currentTheme === 'Neon Retro') {
|
||||
@@ -3579,10 +3596,10 @@ localStorage.removeItem("lp");
|
||||
} else if (CONFIG.currentTheme === 'Acrylic') {
|
||||
defaultTheme = 'acrylic';
|
||||
}
|
||||
|
||||
|
||||
console.log(`%c🎯 Loading theme: ${defaultTheme} (${CONFIG.currentTheme})`, 'color: #8b5cf6;');
|
||||
const success = window.applyTheme(defaultTheme);
|
||||
|
||||
|
||||
if (success) {
|
||||
console.log(`%c✅ Default theme loaded from extension local resources`, 'color: #10b981; font-weight: bold;');
|
||||
console.log(` 📍 Source: Extension local file (themes/${defaultTheme}.css)`);
|
||||
@@ -3592,7 +3609,7 @@ localStorage.removeItem("lp");
|
||||
console.warn(`%c⚠️ Failed to load theme ${defaultTheme} from extension`, 'color: #f59e0b;');
|
||||
console.log(` 📝 Note: Theme CSS classes will still work`);
|
||||
}
|
||||
|
||||
|
||||
console.groupEnd();
|
||||
} else {
|
||||
console.warn(`%c⚠️ Extension applyTheme() function not available`, 'color: #f59e0b; font-weight: bold;');
|
||||
@@ -6834,6 +6851,7 @@ localStorage.removeItem("lp");
|
||||
updateUI('startPaintingMsg', 'success');
|
||||
|
||||
try {
|
||||
await getAccounts();
|
||||
await processImage();
|
||||
} catch (e) {
|
||||
console.error('Unexpected error:', e);
|
||||
@@ -7422,20 +7440,79 @@ localStorage.removeItem("lp");
|
||||
|
||||
pixelBatch.pixels = [];
|
||||
}
|
||||
if (!CONFIG.autoSwap) {
|
||||
if (state.displayCharges < state.cooldownChargeThreshold && !state.stopFlag) {
|
||||
await Utils.dynamicSleep(() => {
|
||||
if (state.displayCharges >= state.cooldownChargeThreshold) {
|
||||
NotificationManager.maybeNotifyChargesReached(true);
|
||||
return 0;
|
||||
}
|
||||
if (state.stopFlag) return 0;
|
||||
return getMsToTargetCharges(
|
||||
state.preciseCurrentCharges,
|
||||
state.cooldownChargeThreshold,
|
||||
state.cooldown
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (state.displayCharges < state.cooldownChargeThreshold && !state.stopFlag) {
|
||||
console.log("⚠️ Charges too low, swapping to next account...");
|
||||
|
||||
if (state.displayCharges < state.cooldownChargeThreshold && !state.stopFlag) {
|
||||
await Utils.dynamicSleep(() => {
|
||||
if (state.displayCharges >= state.cooldownChargeThreshold) {
|
||||
NotificationManager.maybeNotifyChargesReached(true);
|
||||
return 0;
|
||||
const accounts = JSON.parse(localStorage.getItem("accounts")) || [];
|
||||
if (accounts.length === 0) {
|
||||
console.warn("❌ No accounts available, stopping painting.");
|
||||
state.stopFlag = true;
|
||||
return;
|
||||
}
|
||||
if (state.stopFlag) return 0;
|
||||
return getMsToTargetCharges(
|
||||
state.preciseCurrentCharges,
|
||||
state.cooldownChargeThreshold,
|
||||
state.cooldown
|
||||
);
|
||||
});
|
||||
|
||||
state.accountIndex = (state.accountIndex + 1) % accounts.length;
|
||||
console.log("🔄 Switching to account index:", state.accountIndex);
|
||||
|
||||
const nextToken = accounts[state.accountIndex];
|
||||
console.log("🔑 Next token:", nextToken);
|
||||
|
||||
if (!nextToken) {
|
||||
console.warn("⚠️ Invalid token, skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
swapAccountTrigger(nextToken);
|
||||
|
||||
let maxRetries = 20;
|
||||
let retryCount = 0;
|
||||
let swapSuccess = false;
|
||||
|
||||
while (!swapSuccess && retryCount < maxRetries) {
|
||||
console.log(`⏳ Waiting for account swap... (Attempt ${retryCount + 1}/${maxRetries})`);
|
||||
|
||||
// Wait for a short period before checking.
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
try {
|
||||
await fetchAccount();
|
||||
|
||||
console.log("✅ Account swap confirmed.");
|
||||
swapSuccess = true;
|
||||
} catch (error) {
|
||||
console.warn("❌ Account swap not yet successful. Retrying...", error);
|
||||
retryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (swapSuccess) {
|
||||
|
||||
const { charges, cooldown } = await WPlaceService.getCharges();
|
||||
state.displayCharges = Math.floor(charges);
|
||||
state.cooldown = cooldown;
|
||||
Utils.performSmartSave();
|
||||
updateStats();
|
||||
} else {
|
||||
console.error("❌ Failed to swap account after multiple retries. Stopping loop.");
|
||||
state.stopFlag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.stopFlag) {
|
||||
@@ -7998,7 +8075,7 @@ localStorage.removeItem("lp");
|
||||
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);
|
||||
const mod = await import(new URL('/_app/immutable/chunks/' + pawtect_chunk, location.origin).href);
|
||||
let wasm;
|
||||
try {
|
||||
wasm = await mod._();
|
||||
@@ -8130,6 +8207,97 @@ localStorage.removeItem("lp");
|
||||
console.error(`❌ Could not find Pawtect chunk: `, error);
|
||||
}
|
||||
|
||||
async function purchase(type) {
|
||||
// loadThemePreference()
|
||||
let id;
|
||||
let chargeMultiplier;
|
||||
if (type === "max_charges") {
|
||||
id = 70;
|
||||
chargeMultiplier = 5;
|
||||
} else if (type === "paint_charges") {
|
||||
id = 80;
|
||||
chargeMultiplier = 30;
|
||||
} else {
|
||||
console.error("Error: Invalid purchase type provided.");
|
||||
return;
|
||||
}
|
||||
const { droplets } = await WPlaceService.getCharges();
|
||||
console.log("There are currently : ", droplets, "droplets.");
|
||||
try {
|
||||
const amounts = Math.floor(droplets / 500);
|
||||
if (amounts < 1) {
|
||||
console.log("Not enough droplets to purchase.");
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
"product": {
|
||||
"id": id,
|
||||
"amount": amounts
|
||||
}
|
||||
};
|
||||
const res = fetch("https://backend.wplace.live/purchase", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "text/plain;charset=UTF-8"
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
credentials: "include"
|
||||
});
|
||||
console.log("Fetch POST return :", res);
|
||||
const { droplets: newDroplets } = await WPlaceService.getCharges();
|
||||
if (droplets != newDroplets) {
|
||||
console.log("Successfully bought", amounts * chargeMultiplier, type.replace('_', ' '), ".");
|
||||
}
|
||||
else {
|
||||
console.log("Failed to buy charges");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("An error occurred during the purchase:", e);
|
||||
}
|
||||
}
|
||||
function swapAccountTrigger(token) {
|
||||
localStorage.removeItem("lp");
|
||||
if (!token) return;
|
||||
console.log("Sending token to extension...");
|
||||
window.postMessage({
|
||||
source: 'my-userscript',
|
||||
type: 'setCookie',
|
||||
value: token
|
||||
}, '*');
|
||||
}
|
||||
async function getAccounts() {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log("Requesting accounts from extension...");
|
||||
// Ask extension for accounts
|
||||
window.postMessage({
|
||||
source: "my-userscript",
|
||||
type: "getAccounts"
|
||||
}, "*");
|
||||
function handler(event) {
|
||||
if (event.source !== window) return;
|
||||
if (event.data.source !== "extension") return;
|
||||
if (event.data.type === "accountsData") {
|
||||
window.removeEventListener("message", handler);
|
||||
// Save to localStorage
|
||||
try {
|
||||
localStorage.setItem("accounts", JSON.stringify(event.data.accounts));
|
||||
console.log("✅ Accounts saved to localStorage:", event.data.accounts);
|
||||
} catch (e) {
|
||||
console.error("❌ Failed to save accounts:", e);
|
||||
}
|
||||
resolve(event.data.accounts);
|
||||
}
|
||||
}
|
||||
window.addEventListener("message", handler);
|
||||
});
|
||||
}
|
||||
async function fetchAccount() {
|
||||
const { ID, Charges, Max, Droplets } = await WPlaceService.fetchCheck();
|
||||
console.log("User's ID :", ID);
|
||||
console.log("User's Charges :", Charges, "/", Max);
|
||||
console.log("User's Droplets :", Droplets);
|
||||
}
|
||||
|
||||
createUI().then(() => {
|
||||
// Generate token automatically after UI is ready
|
||||
setTimeout(initializeTokenGenerator, 1000);
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
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 } },
|
||||
@@ -100,7 +105,7 @@
|
||||
startPosition: null,
|
||||
region: null,
|
||||
paintWhitePixels: true,
|
||||
paintTransparentPixels: false,
|
||||
paintTransparentPixels: true, // Changed to true to fix transparent pixel detection
|
||||
autoRepairEnabled: false,
|
||||
autoRepairInterval: 30,
|
||||
autoRepairTimer: null,
|
||||
@@ -117,6 +122,10 @@
|
||||
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
|
||||
@@ -125,7 +134,7 @@
|
||||
|
||||
const fpStr32 = randStr(32);
|
||||
|
||||
// Enhanced Turnstile token handling - Actualizado con la lógica de new.txt
|
||||
// Enhanced Turnstile token handling - Actualizado con la lógica de new.txt lol
|
||||
let turnstileToken = null;
|
||||
let tokenExpiryTime = 0;
|
||||
let tokenGenerationInProgress = false;
|
||||
@@ -1352,9 +1361,7 @@
|
||||
const d = cached.data;
|
||||
const a = d[idx + 3];
|
||||
|
||||
if (!state.paintTransparentPixels && a < alphaThresh) {
|
||||
return null;
|
||||
}
|
||||
// Always return pixel data, let caller decide about transparency
|
||||
return [d[idx], d[idx + 1], d[idx + 2], a];
|
||||
}
|
||||
|
||||
@@ -1385,10 +1392,7 @@
|
||||
const data = ctx.getImageData(x, y, 1, 1).data;
|
||||
const a = data[3];
|
||||
|
||||
if (!state.paintTransparentPixels && a < alphaThresh) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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');
|
||||
@@ -1457,7 +1461,7 @@
|
||||
|
||||
const overlayManager = new OverlayManager();
|
||||
|
||||
// Enhanced WPlace API Service with autonomous capabilities - ACTUALIZADO CON NUEVA LÓGICA
|
||||
// Enhanced WPlace API Service with auto-batch functionality
|
||||
const WPlaceService = {
|
||||
async paintPixelInRegion(regionX, regionY, pixelX, pixelY, color, retryCount = 0) {
|
||||
try {
|
||||
@@ -1524,6 +1528,83 @@
|
||||
}
|
||||
},
|
||||
|
||||
// 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', {
|
||||
@@ -1543,14 +1624,14 @@
|
||||
},
|
||||
};
|
||||
|
||||
// Anti-grief repair system with autonomous capabilities
|
||||
// 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 autonomous damage scan...', 'info');
|
||||
Utils.addDebugLog('Starting enhanced damage scan with transparent pixel detection...', 'info');
|
||||
updateStatus(Utils.t('scanningForDamage'));
|
||||
|
||||
const damagedPixels = [];
|
||||
@@ -1579,9 +1660,11 @@
|
||||
return [];
|
||||
}
|
||||
|
||||
Utils.addDebugLog(`Scanning ${width}x${height} image for damage (autonomous: ${state.autonomousMode})...`, '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++) {
|
||||
@@ -1594,10 +1677,10 @@
|
||||
const originalB = pixels[idx + 2];
|
||||
const originalA = pixels[idx + 3];
|
||||
|
||||
if (!state.paintTransparentPixels && originalA < CONFIG.TRANSPARENCY_THRESHOLD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Enhanced transparent pixel detection
|
||||
const isOriginalTransparent = originalA < state.customTransparencyThreshold;
|
||||
|
||||
// Skip white pixels if configured
|
||||
if (!state.paintWhitePixels && Utils.isWhitePixel(originalR, originalG, originalB)) {
|
||||
continue;
|
||||
}
|
||||
@@ -1614,30 +1697,93 @@
|
||||
try {
|
||||
const currentPixel = await overlayManager.getTilePixelColor(tileX, tileY, pixelX, pixelY);
|
||||
|
||||
// Handle missing current pixel data (could indicate transparent area)
|
||||
if (!currentPixel) {
|
||||
if (!state.autonomousMode) {
|
||||
Utils.addDebugLog(`No current pixel data for (${x},${y}) - tile ${tileX},${tileY}`, 'warning');
|
||||
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 targetColor = Utils.resolveColor([originalR, originalG, originalB], state.availableColors);
|
||||
const currentColor = Utils.resolveColor(currentPixel.slice(0, 3), state.availableColors);
|
||||
const currentIsTransparent = currentPixel[3] < state.customTransparencyThreshold;
|
||||
|
||||
if (targetColor.id !== currentColor.id) {
|
||||
// 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: currentColor,
|
||||
currentColor: { id: 63, name: 'Transparent', rgb: [0, 0, 0] },
|
||||
originalRgb: [originalR, originalG, originalB],
|
||||
currentRgb: currentPixel.slice(0, 3)
|
||||
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;
|
||||
}
|
||||
|
||||
if (!state.autonomousMode || damagedPixels.length <= 10) {
|
||||
Utils.addDebugLog(`Damage at (${x},${y}): expected color ${targetColor.id}, found color ${currentColor.id}`, 'warning');
|
||||
// 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) {
|
||||
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');
|
||||
@@ -1646,7 +1792,7 @@
|
||||
}
|
||||
|
||||
if (state.autonomousMode && Date.now() - lastProgressUpdate > 5000) {
|
||||
Utils.addDebugLog(`Scan progress: ${y}/${height} rows (${Math.round((y / height) * 100)}%), found ${damagedPixels.length} damaged`, 'info');
|
||||
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');
|
||||
@@ -1654,7 +1800,17 @@
|
||||
}
|
||||
|
||||
const logLevel = damagedPixels.length > 0 ? 'warning' : 'success';
|
||||
Utils.addDebugLog(`Scan complete. Checked ${scannedPixels} pixels, found ${damagedPixels.length} damaged`, logLevel);
|
||||
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;
|
||||
}
|
||||
@@ -1665,98 +1821,174 @@
|
||||
return 0;
|
||||
}
|
||||
|
||||
Utils.addDebugLog(`Starting autonomous repair of ${damagedPixels.length} pixels`, 'info');
|
||||
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;
|
||||
|
||||
for (let i = 0; i < damagedPixels.length; i++) {
|
||||
const pixel = damagedPixels[i];
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Enhanced charge management for autonomous mode
|
||||
await updateCharges();
|
||||
let chargeWaitAttempts = 0;
|
||||
const maxChargeWaitAttempts = state.autonomousMode ? 20 : 10;
|
||||
|
||||
while (state.displayCharges < 1 && !state.stopFlag && chargeWaitAttempts < maxChargeWaitAttempts) {
|
||||
chargeWaitAttempts++;
|
||||
const waitTime = state.autonomousMode ? Math.min(state.cooldown, 10000) : state.cooldown;
|
||||
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);
|
||||
|
||||
if (chargeWaitAttempts === 1) {
|
||||
Utils.addDebugLog(`Waiting for charges... (${state.displayCharges}/${state.maxCharges})`, 'info');
|
||||
}
|
||||
|
||||
await Utils.dynamicSleep(() => {
|
||||
if (state.displayCharges >= 1) return 0;
|
||||
if (state.stopFlag) return 0;
|
||||
return waitTime;
|
||||
});
|
||||
// Enhanced charge management for batch processing
|
||||
await updateCharges();
|
||||
}
|
||||
|
||||
if (state.stopFlag) break;
|
||||
|
||||
if (state.displayCharges < 1) {
|
||||
Utils.addDebugLog(`No charges available after waiting, skipping pixel (${pixel.x},${pixel.y})`, 'warning');
|
||||
continue;
|
||||
}
|
||||
|
||||
const success = await repairSinglePixel(pixel);
|
||||
if (success) {
|
||||
repairedCount++;
|
||||
consecutiveFailures = 0;
|
||||
if (!state.autonomousMode || repairedCount <= 10 || repairedCount % 10 === 0) {
|
||||
Utils.addDebugLog(`Repaired pixel (${pixel.x},${pixel.y}) with color ${pixel.originalColor.id} [${repairedCount}/${damagedPixels.length}]`, 'success');
|
||||
}
|
||||
} else {
|
||||
consecutiveFailures++;
|
||||
Utils.addDebugLog(`Failed to repair pixel (${pixel.x},${pixel.y}) [${consecutiveFailures}/${maxConsecutiveFailures} consecutive failures]`, 'error');
|
||||
let chargeWaitAttempts = 0;
|
||||
const maxChargeWaitAttempts = state.autonomousMode ? 20 : 10;
|
||||
|
||||
if (consecutiveFailures >= maxConsecutiveFailures) {
|
||||
Utils.addDebugLog(`Too many consecutive 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);
|
||||
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');
|
||||
}
|
||||
break;
|
||||
|
||||
await Utils.dynamicSleep(() => {
|
||||
if (state.displayCharges >= actualBatchSize) return 0;
|
||||
if (state.stopFlag) return 0;
|
||||
return waitTime;
|
||||
});
|
||||
await updateCharges();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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)`, 'success');
|
||||
Utils.addDebugLog(`${message} (${damagedPixels.length - repairedCount} remaining) - Used auto-batch with size ${state.currentBatchSize}`, 'success');
|
||||
|
||||
return repairedCount;
|
||||
}
|
||||
|
||||
async function repairSinglePixel(pixel) {
|
||||
const { x, y, originalColor } = pixel;
|
||||
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);
|
||||
|
||||
const absX = state.startPosition.x + x;
|
||||
const absY = state.startPosition.y + y;
|
||||
const regionX = state.region.x + Math.floor(absX / 1000);
|
||||
const regionY = state.region.y + Math.floor(absY / 1000);
|
||||
const pixelX = absX % 1000;
|
||||
const pixelY = absY % 1000;
|
||||
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(
|
||||
@@ -1764,7 +1996,7 @@
|
||||
regionY,
|
||||
pixelX,
|
||||
pixelY,
|
||||
originalColor.id
|
||||
color || originalColor.id
|
||||
);
|
||||
|
||||
if (result === 'token_error') {
|
||||
@@ -1773,10 +2005,16 @@
|
||||
await Utils.sleep(state.autonomousMode ? 500 : 1000);
|
||||
|
||||
// Retry once with new token
|
||||
const retryResult = await WPlaceService.paintPixelInRegion(regionX, regionY, pixelX, pixelY, originalColor.id);
|
||||
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');
|
||||
@@ -1993,6 +2231,42 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -2182,6 +2456,149 @@
|
||||
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;
|
||||
@@ -2312,6 +2729,9 @@
|
||||
<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;">
|
||||
@@ -2354,11 +2774,54 @@
|
||||
<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>
|
||||
|
||||
@@ -2414,6 +2877,9 @@
|
||||
updateSystemStatus();
|
||||
setInterval(updateSystemStatus, 15000);
|
||||
|
||||
// Update batch information
|
||||
updateBatchInfo();
|
||||
|
||||
// Initialize token generator
|
||||
initializeTokenGenerator();
|
||||
}
|
||||
@@ -2648,6 +3114,69 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 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 = [];
|
||||
|
||||
@@ -8,6 +8,32 @@
|
||||
src: url('https://raw.githubusercontent.com/GORAlexComp/AppleColorEmojiFont/refs/heads/main/fonts/AppleColorEmoji.ttf');
|
||||
}
|
||||
|
||||
/* Multi-account panel styling */
|
||||
.wplace-theme-acrylic #wplace-accounts-container {
|
||||
background: var(--wplace-primary);
|
||||
color: var(--wplace-text);
|
||||
border-radius: var(--wplace-radius);
|
||||
box-shadow: none;
|
||||
backdrop-filter: var(--wplace-backdrop);
|
||||
border: var(--wplace-border-width) var(--wplace-border-style)
|
||||
var(--wplace-border-color);
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.wplace-theme-acrylic #wplace-accounts-container * {
|
||||
font-size: 15px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/* Active account styles */
|
||||
.wplace-theme-acrylic .wplace-accounts-container .active-account {
|
||||
--active-bg: color-mix(in srgb, white 15%, transparent);
|
||||
--active-color: var(--wplace-success);
|
||||
--inactive-color: var(--wplace-text);
|
||||
--progress-bg: rgb(255 255 255 / 10%);
|
||||
--progress-fill: white;
|
||||
}
|
||||
|
||||
/* Main Container */
|
||||
.wplace-theme-acrylic #wplace-image-bot-container {
|
||||
background: var(--wplace-primary);
|
||||
|
||||
@@ -73,6 +73,26 @@
|
||||
|
||||
/* === COMPONENT STYLING === */
|
||||
|
||||
/* Multi-account panel styling */
|
||||
.wplace-theme-classic-light #wplace-accounts-container {
|
||||
background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%) !important;
|
||||
color: var(--wplace-text) !important;
|
||||
border: 1px solid rgb(0 0 0 / 15%) !important;
|
||||
border-radius: 12px !important;
|
||||
box-shadow: 0 8px 32px rgb(0 0 0 / 15%), 0 0 0 1px rgb(0 0 0 / 8%) !important;
|
||||
font-family: 'Segoe UI', Roboto, sans-serif !important;
|
||||
backdrop-filter: blur(10px) !important;
|
||||
}
|
||||
|
||||
/* Active account styles */
|
||||
.wplace-theme-classic-light .wplace-accounts-container .active-account {
|
||||
--active-bg: var(--wplace-secondary) !important;
|
||||
--active-color: var(--wplace-success) !important;
|
||||
--inactive-color: var(--wplace-text) !important;
|
||||
--progress-bg: rgb(0 0 0 / 10%) !important;
|
||||
--progress-fill: var(--wplace-highlight) !important;
|
||||
}
|
||||
|
||||
/* Main container with clean light styling */
|
||||
.wplace-theme-classic-light #wplace-image-bot-container {
|
||||
background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%) !important;
|
||||
|
||||
@@ -34,6 +34,29 @@
|
||||
}
|
||||
|
||||
/* Classic theme container styling */
|
||||
/* Multi-account panel styling */
|
||||
:root #wplace-accounts-container,
|
||||
.wplace-theme-classic #wplace-accounts-container {
|
||||
background: var(--wplace-primary);
|
||||
color: var(--wplace-text);
|
||||
border-radius: var(--wplace-radius);
|
||||
box-shadow: var(--wplace-shadow);
|
||||
font-family: var(--wplace-font);
|
||||
backdrop-filter: var(--wplace-backdrop);
|
||||
border: var(--wplace-border-width) var(--wplace-border-style) var(--wplace-border-color);
|
||||
}
|
||||
|
||||
/* Active account styles */
|
||||
:root .wplace-accounts-container .active-account,
|
||||
.wplace-theme-classic .wplace-accounts-container .active-account {
|
||||
--active-bg: var(--wplace-secondary);
|
||||
--active-color: var(--wplace-success);
|
||||
--inactive-color: var(--wplace-text);
|
||||
--progress-bg: rgb(255 255 255 / 10%);
|
||||
--progress-fill: var(--wplace-highlight);
|
||||
}
|
||||
|
||||
/* Base container styling */
|
||||
:root #wplace-image-bot-container,
|
||||
.wplace-theme-classic #wplace-image-bot-container {
|
||||
background: var(--wplace-primary);
|
||||
|
||||
@@ -84,6 +84,122 @@
|
||||
font-family: var(--wplace-font) !important;
|
||||
}
|
||||
|
||||
/* Multi-Account Panel Styles */
|
||||
.wplace-theme-neon #wplace-multi-account-container {
|
||||
position: fixed !important;
|
||||
top: 180px !important;
|
||||
right: 20px !important;
|
||||
width: 320px !important;
|
||||
background: rgb(22 33 62 / 50%) !important;
|
||||
border: 2px solid var(--wplace-text) !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 0 20px var(--wplace-text), inset 0 0 20px rgb(0 255 65 / 10%) !important;
|
||||
font-family: var(--wplace-font) !important;
|
||||
z-index: 9997 !important;
|
||||
color: var(--wplace-text) !important;
|
||||
text-shadow: 0 0 5px var(--wplace-text) !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon #wplace-multi-account-container .wplace-panel-header {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
padding: 10px !important;
|
||||
background: var(--wplace-secondary) !important;
|
||||
border-bottom: 1px solid var(--wplace-text) !important;
|
||||
text-transform: uppercase !important;
|
||||
font-size: 9px !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon #wplace-multi-account-container .wplace-header-btn {
|
||||
background: transparent !important;
|
||||
border: 1px solid var(--wplace-text) !important;
|
||||
color: var(--wplace-text) !important;
|
||||
font-size: 8px !important;
|
||||
padding: 4px 8px !important;
|
||||
cursor: pointer !important;
|
||||
text-shadow: 0 0 5px var(--wplace-text) !important;
|
||||
font-family: var(--wplace-font) !important;
|
||||
text-transform: uppercase !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon #wplace-multi-account-container .wplace-header-btn:hover {
|
||||
box-shadow: 0 0 15px var(--wplace-text) !important;
|
||||
animation: pixel-blink 0.5s infinite !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-content {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-info {
|
||||
font-size: 9px !important;
|
||||
text-transform: uppercase !important;
|
||||
margin-bottom: 10px !important;
|
||||
color: var(--wplace-text) !important;
|
||||
text-shadow: 0 0 5px var(--wplace-text) !important;
|
||||
font-family: var(--wplace-font) !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-current,
|
||||
.wplace-theme-neon .wplace-multi-account-stats {
|
||||
margin-bottom: 8px !important;
|
||||
font-size: 8px !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-toggle {
|
||||
margin-bottom: 12px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-switch {
|
||||
margin-right: 8px !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-list {
|
||||
max-height: 200px !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-item {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
padding: 8px !important;
|
||||
margin-bottom: 4px !important;
|
||||
background: rgb(22 33 62 / 50%) !important;
|
||||
border: 1px solid var(--wplace-text) !important;
|
||||
font-size: 8px !important;
|
||||
text-transform: uppercase !important;
|
||||
font-family: var(--wplace-font) !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-item.active {
|
||||
background: rgb(0 255 65 / 10%) !important;
|
||||
border-color: var(--wplace-success) !important;
|
||||
box-shadow: 0 0 15px var(--wplace-success) !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-list::-webkit-scrollbar {
|
||||
width: 6px !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-list::-webkit-scrollbar-track {
|
||||
background: rgb(22 33 62 / 50%) !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-list::-webkit-scrollbar-thumb {
|
||||
background: var(--wplace-text) !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 0 10px var(--wplace-text) !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon .wplace-multi-account-list::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--wplace-success) !important;
|
||||
box-shadow: 0 0 15px var(--wplace-success) !important;
|
||||
}
|
||||
|
||||
.wplace-theme-neon #wplace-stats-container {
|
||||
background: var(--wplace-primary) !important;
|
||||
border: 2px solid var(--wplace-text) !important;
|
||||
|
||||