mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-09 00:56:16 +00:00
1. Completely Rebuilt Dashboard
index.html - New Modern Interface: Header with animated status badge (Running/Stopped) Grid of 6 statistics (Accounts, Points, Completed, Errors, Uptime, Memory) Control Panel with 4 buttons (Start/Stop/Restart/Reset State) Account List with avatars and statuses Quick Actions (Run Single, Export Logs, Config, History) Points History Graph (Chart.js) with 7D/30D selection Activity Breakdown Graph (donut chart) Real-time log viewer with level filtering Toast system for notifications Modal system for confirmations WebSocket connection indicator Light/dark theme with toggle Responsive mobile design style.css - Modern CSS with: CSS variables for theming (dark + light) Smooth animations (pulse, fade-in, slideIn) GitHub-inspired design Custom scrollbar Responsive breakpoints app.js - Full JavaScript: WebSocket handling with automatic reconnection Chart.js initialized with 2 charts Centralized state management Real-time uptime timer Log export to text file Email hiding Persistent theme in localStorage 2. Improved backend routes.ts:233-302 - New API routes: POST /api/reset-state - Resets the state of today's jobs GET /api/memory - Returns current memory usage 3. Enhanced anti-detection (previous session) Browser.ts - 8 new layers of protection: WebRTC Leak Prevention Battery API Spoofing Hardware Concurrency Normalization Device Memory Consistency Audio Fingerprint Protection Timezone Consistency Connection Info Spoofing
This commit is contained in:
BIN
assets/logo.png
BIN
assets/logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 973 KiB After Width: | Height: | Size: 661 KiB |
1134
public/app.js
1134
public/app.js
File diff suppressed because it is too large
Load Diff
@@ -1,47 +1,229 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Microsoft Rewards Bot Dashboard</title>
|
<title>Microsoft Rewards Bot Dashboard</title>
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="/style.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<!-- Header -->
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div><h1>🎯 Microsoft Rewards Bot</h1></div>
|
<div class="header-left">
|
||||||
<div class="status">
|
<h1>🎯 Microsoft Rewards Bot</h1>
|
||||||
<button class="theme-toggle" onclick="toggleTheme()"><i class="fas fa-moon"></i></button>
|
<span class="version" id="version">v3.5.0</span>
|
||||||
<div id="statusBadge" class="status-badge status-stopped">STOPPED</div>
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<button class="theme-toggle" onclick="toggleTheme()" title="Toggle theme">
|
||||||
|
<i class="fas fa-moon"></i>
|
||||||
|
</button>
|
||||||
|
<div id="statusBadge" class="status-badge status-stopped">
|
||||||
|
<i class="fas fa-circle"></i>
|
||||||
|
<span>STOPPED</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats Grid -->
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-card"><div class="label">Total Accounts</div><div class="value" id="totalAccounts">0</div></div>
|
<div class="stat-card">
|
||||||
<div class="stat-card"><div class="label">Total Points</div><div class="value" id="totalPoints">0</div></div>
|
<div class="stat-icon"><i class="fas fa-users"></i></div>
|
||||||
<div class="stat-card"><div class="label">Completed</div><div class="value" id="completed">0</div></div>
|
<div class="stat-content">
|
||||||
<div class="stat-card"><div class="label">Errors</div><div class="value" id="errors">0</div></div>
|
<div class="stat-label">Total Accounts</div>
|
||||||
</div>
|
<div class="stat-value" id="totalAccounts">0</div>
|
||||||
<div class="card">
|
</div>
|
||||||
<div class="card-header"><h2 class="card-title">🎮 Control Panel</h2></div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="stat-card stat-points">
|
||||||
<button class="btn btn-success" onclick="startBot()" id="btnStart"><i class="fas fa-play"></i> Start</button>
|
<div class="stat-icon"><i class="fas fa-coins"></i></div>
|
||||||
<button class="btn btn-danger" onclick="stopBot()" id="btnStop" disabled><i class="fas fa-stop"></i> Stop</button>
|
<div class="stat-content">
|
||||||
<button class="btn btn-primary" onclick="restartBot()"><i class="fas fa-redo"></i> Restart</button>
|
<div class="stat-label">Total Points</div>
|
||||||
<button class="btn btn-primary" onclick="refreshData()"><i class="fas fa-sync-alt"></i> Refresh</button>
|
<div class="stat-value" id="totalPoints">0</div>
|
||||||
<button class="btn btn-primary" onclick="clearLogs()"><i class="fas fa-trash"></i> Clear Logs</button>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card stat-success">
|
||||||
|
<div class="stat-icon"><i class="fas fa-check-circle"></i></div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">Completed Today</div>
|
||||||
|
<div class="stat-value" id="completed">0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card stat-error">
|
||||||
|
<div class="stat-icon"><i class="fas fa-exclamation-triangle"></i></div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">Errors</div>
|
||||||
|
<div class="stat-value" id="errors">0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon"><i class="fas fa-clock"></i></div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">Uptime</div>
|
||||||
|
<div class="stat-value" id="uptime">00:00:00</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon"><i class="fas fa-memory"></i></div>
|
||||||
|
<div class="stat-content">
|
||||||
|
<div class="stat-label">Memory</div>
|
||||||
|
<div class="stat-value" id="memory">0 MB</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
|
||||||
<div class="card-header"><h2 class="card-title">👥 Accounts</h2></div>
|
<!-- Main Grid -->
|
||||||
<div id="accountsList"></div>
|
<div class="main-grid">
|
||||||
|
<!-- Left Column -->
|
||||||
|
<div class="left-column">
|
||||||
|
<!-- Control Panel -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title"><i class="fas fa-gamepad"></i> Control Panel</h2>
|
||||||
|
<div class="card-actions">
|
||||||
|
<button class="btn btn-sm btn-secondary" onclick="refreshData()" title="Refresh">
|
||||||
|
<i class="fas fa-sync-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-grid">
|
||||||
|
<button class="control-btn control-start" onclick="startBot()" id="btnStart">
|
||||||
|
<i class="fas fa-play"></i>
|
||||||
|
<span>Start Bot</span>
|
||||||
|
</button>
|
||||||
|
<button class="control-btn control-stop" onclick="stopBot()" id="btnStop" disabled>
|
||||||
|
<i class="fas fa-stop"></i>
|
||||||
|
<span>Stop Bot</span>
|
||||||
|
</button>
|
||||||
|
<button class="control-btn control-restart" onclick="restartBot()">
|
||||||
|
<i class="fas fa-redo"></i>
|
||||||
|
<span>Restart</span>
|
||||||
|
</button>
|
||||||
|
<button class="control-btn control-reset" onclick="resetJobState()">
|
||||||
|
<i class="fas fa-eraser"></i>
|
||||||
|
<span>Reset State</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Accounts Card -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title"><i class="fas fa-users"></i> Accounts</h2>
|
||||||
|
<span class="badge" id="accountsBadge">0</span>
|
||||||
|
</div>
|
||||||
|
<div id="accountsList" class="accounts-list"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title"><i class="fas fa-bolt"></i> Quick Actions</h2>
|
||||||
|
</div>
|
||||||
|
<div class="quick-actions">
|
||||||
|
<button class="action-btn" onclick="runSingleAccount()">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
<span>Run Single</span>
|
||||||
|
</button>
|
||||||
|
<button class="action-btn" onclick="exportLogs()">
|
||||||
|
<i class="fas fa-download"></i>
|
||||||
|
<span>Export Logs</span>
|
||||||
|
</button>
|
||||||
|
<button class="action-btn" onclick="openConfig()">
|
||||||
|
<i class="fas fa-cog"></i>
|
||||||
|
<span>Config</span>
|
||||||
|
</button>
|
||||||
|
<button class="action-btn" onclick="viewHistory()">
|
||||||
|
<i class="fas fa-history"></i>
|
||||||
|
<span>History</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column -->
|
||||||
|
<div class="right-column">
|
||||||
|
<!-- Charts Card -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title"><i class="fas fa-chart-line"></i> Points History</h2>
|
||||||
|
<div class="chart-period">
|
||||||
|
<button class="period-btn active" data-period="7d" onclick="setChartPeriod('7d', this)">7D</button>
|
||||||
|
<button class="period-btn" data-period="30d" onclick="setChartPeriod('30d', this)">30D</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas id="pointsChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Activity Chart -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title"><i class="fas fa-tasks"></i> Activity Breakdown</h2>
|
||||||
|
</div>
|
||||||
|
<div class="chart-container chart-small">
|
||||||
|
<canvas id="activityChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Logs Card (Full Width) -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header"><h2 class="card-title">📝 Live Logs</h2></div>
|
<div class="card-header">
|
||||||
|
<h2 class="card-title"><i class="fas fa-terminal"></i> Live Logs</h2>
|
||||||
|
<div class="card-actions">
|
||||||
|
<select id="logFilter" class="log-filter" onchange="filterLogs()">
|
||||||
|
<option value="all">All Levels</option>
|
||||||
|
<option value="error">Errors Only</option>
|
||||||
|
<option value="warn">Warnings</option>
|
||||||
|
<option value="log">Info</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-sm btn-secondary" onclick="toggleAutoScroll()" id="btnAutoScroll" title="Auto-scroll">
|
||||||
|
<i class="fas fa-arrow-down"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-danger" onclick="clearLogs()" title="Clear logs">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="logs-container" id="logsContainer"></div>
|
<div class="logs-container" id="logsContainer"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="footer">
|
||||||
|
<span>Microsoft Rewards Bot Dashboard</span>
|
||||||
|
<span class="separator">•</span>
|
||||||
|
<a href="https://github.com/LightZirconite/Microsoft-Rewards-Bot" target="_blank">
|
||||||
|
<i class="fab fa-github"></i> GitHub
|
||||||
|
</a>
|
||||||
|
<span class="separator">•</span>
|
||||||
|
<span id="connectionStatus" class="connection-status disconnected">
|
||||||
|
<i class="fas fa-circle"></i> Connecting...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast Container -->
|
||||||
<div id="toastContainer"></div>
|
<div id="toastContainer"></div>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div id="modal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 id="modalTitle">Modal Title</h3>
|
||||||
|
<button class="modal-close" onclick="closeModal()">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="modalBody"></div>
|
||||||
|
<div class="modal-footer" id="modalFooter"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/app.js"></script>
|
<script src="/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
</html>
|
||||||
968
public/style.css
968
public/style.css
File diff suppressed because it is too large
Load Diff
@@ -257,11 +257,162 @@ class Browser {
|
|||||||
description: 'Portable Document Format',
|
description: 'Portable Document Format',
|
||||||
filename: 'internal-pdf-viewer',
|
filename: 'internal-pdf-viewer',
|
||||||
length: 2
|
length: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Chromium PDF Viewer',
|
||||||
|
description: 'Portable Document Format',
|
||||||
|
filename: 'internal-pdf-viewer',
|
||||||
|
length: 2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
} catch { /* Plugins may be frozen */ }
|
} catch { /* Plugins may be frozen */ }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ANTI-DETECTION LAYER 5: WebRTC Leak Prevention
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// CRITICAL: Prevent WebRTC from leaking real IP address
|
||||||
|
try {
|
||||||
|
// Override RTCPeerConnection to prevent IP leaks
|
||||||
|
const originalRTCPeerConnection = window.RTCPeerConnection
|
||||||
|
// @ts-ignore
|
||||||
|
window.RTCPeerConnection = function (config?: RTCConfiguration) {
|
||||||
|
// Force STUN servers through proxy or disable
|
||||||
|
const modifiedConfig: RTCConfiguration = {
|
||||||
|
...config,
|
||||||
|
iceServers: [] // Disable ICE to prevent IP leak
|
||||||
|
}
|
||||||
|
return new originalRTCPeerConnection(modifiedConfig)
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
window.RTCPeerConnection.prototype = originalRTCPeerConnection.prototype
|
||||||
|
} catch { /* WebRTC override may fail */ }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ANTI-DETECTION LAYER 6: Battery API Spoofing
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// Headless browsers may have unusual battery states
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
if (navigator.getBattery) {
|
||||||
|
// @ts-ignore
|
||||||
|
navigator.getBattery = () => Promise.resolve({
|
||||||
|
charging: true,
|
||||||
|
chargingTime: 0,
|
||||||
|
dischargingTime: Infinity,
|
||||||
|
level: 1,
|
||||||
|
addEventListener: () => { },
|
||||||
|
removeEventListener: () => { },
|
||||||
|
dispatchEvent: () => true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch { /* Battery API override may fail */ }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ANTI-DETECTION LAYER 7: Hardware Concurrency Consistency
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// Ensure hardware concurrency looks realistic
|
||||||
|
try {
|
||||||
|
const realCores = navigator.hardwareConcurrency || 4
|
||||||
|
// Round to common values: 2, 4, 6, 8, 12, 16
|
||||||
|
const commonCores = [2, 4, 6, 8, 12, 16]
|
||||||
|
const normalizedCores = commonCores.reduce((prev, curr) =>
|
||||||
|
Math.abs(curr - realCores) < Math.abs(prev - realCores) ? curr : prev
|
||||||
|
)
|
||||||
|
Object.defineProperty(navigator, 'hardwareConcurrency', {
|
||||||
|
get: () => normalizedCores,
|
||||||
|
configurable: true
|
||||||
|
})
|
||||||
|
} catch { /* Hardware concurrency override may fail */ }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ANTI-DETECTION LAYER 8: Device Memory Consistency
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
const realMemory = navigator.deviceMemory || 8
|
||||||
|
// Round to common values: 2, 4, 8, 16
|
||||||
|
const commonMemory = [2, 4, 8, 16]
|
||||||
|
const normalizedMemory = commonMemory.reduce((prev, curr) =>
|
||||||
|
Math.abs(curr - realMemory) < Math.abs(prev - realMemory) ? curr : prev
|
||||||
|
)
|
||||||
|
Object.defineProperty(navigator, 'deviceMemory', {
|
||||||
|
get: () => normalizedMemory,
|
||||||
|
configurable: true
|
||||||
|
})
|
||||||
|
} catch { /* Device memory override may fail */ }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ANTI-DETECTION LAYER 9: Audio Fingerprint Protection
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
try {
|
||||||
|
const originalCreateOscillator = AudioContext.prototype.createOscillator
|
||||||
|
const originalCreateDynamicsCompressor = AudioContext.prototype.createDynamicsCompressor
|
||||||
|
|
||||||
|
// Add slight randomization to audio context to prevent fingerprinting
|
||||||
|
AudioContext.prototype.createOscillator = function () {
|
||||||
|
const oscillator = originalCreateOscillator.apply(this)
|
||||||
|
const originalGetFloatFrequencyData = AnalyserNode.prototype.getFloatFrequencyData
|
||||||
|
AnalyserNode.prototype.getFloatFrequencyData = function (array) {
|
||||||
|
originalGetFloatFrequencyData.apply(this, [array])
|
||||||
|
// Add imperceptible noise
|
||||||
|
for (let i = 0; i < array.length; i++) {
|
||||||
|
array[i] = array[i]! + (Math.random() * 0.0001)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oscillator
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioContext.prototype.createDynamicsCompressor = function () {
|
||||||
|
const compressor = originalCreateDynamicsCompressor.apply(this)
|
||||||
|
// Slightly randomize default values
|
||||||
|
try {
|
||||||
|
compressor.threshold.value = -24 + (Math.random() * 0.001)
|
||||||
|
compressor.knee.value = 30 + (Math.random() * 0.001)
|
||||||
|
} catch { /* May be read-only */ }
|
||||||
|
return compressor
|
||||||
|
}
|
||||||
|
} catch { /* Audio API override may fail */ }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ANTI-DETECTION LAYER 10: Timezone & Locale Consistency
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure Date.prototype.getTimezoneOffset is consistent
|
||||||
|
const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset
|
||||||
|
const consistentOffset = originalGetTimezoneOffset.call(new Date())
|
||||||
|
Date.prototype.getTimezoneOffset = function () {
|
||||||
|
return consistentOffset
|
||||||
|
}
|
||||||
|
} catch { /* Timezone override may fail */ }
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
// ANTI-DETECTION LAYER 11: Connection Info Spoofing
|
||||||
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
if (navigator.connection) {
|
||||||
|
Object.defineProperty(navigator, 'connection', {
|
||||||
|
get: () => ({
|
||||||
|
effectiveType: '4g',
|
||||||
|
rtt: 50,
|
||||||
|
downlink: 10,
|
||||||
|
saveData: false,
|
||||||
|
addEventListener: () => { },
|
||||||
|
removeEventListener: () => { }
|
||||||
|
}),
|
||||||
|
configurable: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch { /* Connection API override may fail */ }
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
// Standard styling (non-detection related)
|
// Standard styling (non-detection related)
|
||||||
// ═══════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -1,305 +0,0 @@
|
|||||||
# Dashboard - Modern Real-Time Interface
|
|
||||||
|
|
||||||
## 🎨 New Features (2025 Update - November)
|
|
||||||
|
|
||||||
### ✨ Modern UI Enhancements v2.0
|
|
||||||
- **Professional Dark Theme**: Default dark mode with improved color palette and contrast
|
|
||||||
- **Refined Design System**: Consistent spacing, typography, and component styling
|
|
||||||
- **Improved Animations**: Smoother transitions with optimized performance
|
|
||||||
- **Enhanced Glassmorphism**: Better backdrop blur and shadow layering
|
|
||||||
- **Staggered Card Entrance**: Beautiful loading animations for stats cards
|
|
||||||
- **Better Visual Hierarchy**: Improved text sizing and weight differentiation
|
|
||||||
- **Refined Components**: Polished buttons, badges, and interactive elements
|
|
||||||
- **Optimized Icons**: Gradient overlays with better sizing
|
|
||||||
|
|
||||||
### Previous v1.0 Features
|
|
||||||
- **Dark Mode Support**: Toggle between light and dark themes with persistent preference
|
|
||||||
- **Real-Time Updates**: WebSocket-powered live log streaming and status updates
|
|
||||||
- **Glassmorphism Design**: Modern blur effects and smooth animations
|
|
||||||
- **Responsive Layout**: Optimized for desktop, tablet, and mobile devices
|
|
||||||
- **Enhanced Stats Cards**: Animated counters with gradient icons
|
|
||||||
- **Log Statistics**: Real-time error and warning counters
|
|
||||||
|
|
||||||
### 🚀 Performance Improvements
|
|
||||||
- **Optimized Log Management**: Maximum 200 logs in memory with automatic cleanup
|
|
||||||
- **Smart WebSocket Reconnection**: Automatic reconnection on network failures
|
|
||||||
- **Reduced Bundle Size**: Removed unused console.log calls (-15% size)
|
|
||||||
- **Better Error Handling**: Comprehensive validation and user-friendly error messages
|
|
||||||
|
|
||||||
### 🔧 Technical Enhancements
|
|
||||||
- **Proper Log Interception**: Fixed "No logs yet..." issue by intercepting at module level
|
|
||||||
- **Type-Safe API**: Full TypeScript support with proper error handling
|
|
||||||
- **Consistent Logging**: All console.log calls replaced with structured logging
|
|
||||||
- **Memory Management**: Automatic cleanup of old WebSocket buffers
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Dashboard UI
|
|
||||||
|
|
||||||
### Control Panel
|
|
||||||
- **Start/Stop/Restart Bot**: Full bot lifecycle management
|
|
||||||
- **Refresh Data**: Manual data synchronization
|
|
||||||
- **Clear Logs**: Reset log history
|
|
||||||
|
|
||||||
### Real-Time Monitoring
|
|
||||||
- **Live Logs**: Color-coded logs with timestamps, platform tags, and titles
|
|
||||||
- **Account Status**: Per-account progress with points tracking
|
|
||||||
- **Statistics Dashboard**: Total accounts, points, completed runs, errors
|
|
||||||
|
|
||||||
### Theme Support
|
|
||||||
- **Light Mode**: Clean white interface with subtle shadows
|
|
||||||
- **Dark Mode**: Eye-friendly dark interface for night work
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Endpoints
|
|
||||||
|
|
||||||
### Status & Control
|
|
||||||
|
|
||||||
#### `GET /api/status`
|
|
||||||
Get current bot status.
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"running": false,
|
|
||||||
"lastRun": "2025-11-03T10:30:00.000Z",
|
|
||||||
"currentAccount": "user@example.com",
|
|
||||||
"totalAccounts": 5
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `POST /api/start`
|
|
||||||
Start bot execution in background.
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"pid": 12345
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `POST /api/stop`
|
|
||||||
Stop bot execution.
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Accounts
|
|
||||||
|
|
||||||
#### `GET /api/accounts`
|
|
||||||
List all accounts with masked emails and status.
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"email": "user@example.com",
|
|
||||||
"maskedEmail": "u***@e***.com",
|
|
||||||
"points": 5420,
|
|
||||||
"lastSync": "2025-11-03T10:30:00.000Z",
|
|
||||||
"status": "completed",
|
|
||||||
"errors": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `POST /api/sync/:email`
|
|
||||||
Force synchronization for a single account.
|
|
||||||
|
|
||||||
**Parameters:**
|
|
||||||
- `email` (path): Account email
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"pid": 12346
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Logs & History
|
|
||||||
|
|
||||||
#### `GET /api/logs?limit=100`
|
|
||||||
Get recent logs.
|
|
||||||
|
|
||||||
**Query Parameters:**
|
|
||||||
- `limit` (optional): Max number of logs (default: 100, max: 500)
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"timestamp": "2025-11-03T10:30:00.000Z",
|
|
||||||
"level": "log",
|
|
||||||
"platform": "DESKTOP",
|
|
||||||
"title": "SEARCH",
|
|
||||||
"message": "Completed 30 searches"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `DELETE /api/logs`
|
|
||||||
Clear all logs from memory.
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `GET /api/history`
|
|
||||||
Get recent run summaries (last 7 days).
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"runId": "abc123",
|
|
||||||
"timestamp": "2025-11-03T10:00:00.000Z",
|
|
||||||
"totals": {
|
|
||||||
"totalCollected": 450,
|
|
||||||
"totalAccounts": 5,
|
|
||||||
"accountsWithErrors": 0
|
|
||||||
},
|
|
||||||
"perAccount": [...]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
#### `GET /api/config`
|
|
||||||
Get current configuration (sensitive data masked).
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"baseURL": "https://rewards.bing.com",
|
|
||||||
"headless": true,
|
|
||||||
"clusters": 2,
|
|
||||||
"webhook": {
|
|
||||||
"enabled": true,
|
|
||||||
"url": "htt***://dis***"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `POST /api/config`
|
|
||||||
Update configuration (creates automatic backup).
|
|
||||||
|
|
||||||
**Request Body:** Full config object
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"success": true,
|
|
||||||
"backup": "/path/to/config.jsonc.backup.1730634000000"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Metrics
|
|
||||||
|
|
||||||
#### `GET /api/metrics`
|
|
||||||
Get aggregated metrics.
|
|
||||||
|
|
||||||
**Response:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"totalAccounts": 5,
|
|
||||||
"totalPoints": 27100,
|
|
||||||
"accountsWithErrors": 0,
|
|
||||||
"accountsRunning": 0,
|
|
||||||
"accountsCompleted": 5
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## WebSocket
|
|
||||||
|
|
||||||
Connect to `ws://localhost:3000/ws` for real-time log streaming.
|
|
||||||
|
|
||||||
**Message Format:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "log",
|
|
||||||
"log": {
|
|
||||||
"timestamp": "2025-11-03T10:30:00.000Z",
|
|
||||||
"level": "log",
|
|
||||||
"platform": "DESKTOP",
|
|
||||||
"title": "SEARCH",
|
|
||||||
"message": "Completed search"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**On Connect:**
|
|
||||||
Receives history of last 50 logs:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "history",
|
|
||||||
"logs": [...]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Start Dashboard
|
|
||||||
```bash
|
|
||||||
npm run dashboard
|
|
||||||
# or in dev mode
|
|
||||||
npm run dashboard-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Default: `http://127.0.0.1:3000`
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
- `DASHBOARD_PORT`: Port number (default: 3000)
|
|
||||||
- `DASHBOARD_HOST`: Bind address (default: 127.0.0.1)
|
|
||||||
|
|
||||||
### Security
|
|
||||||
- **Localhost only**: Dashboard binds to `127.0.0.1` by default
|
|
||||||
- **Email masking**: Emails are partially masked in API responses
|
|
||||||
- **Token masking**: Webhook URLs and auth tokens are masked
|
|
||||||
- **Config backup**: Automatic backup before any config modification
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Example Usage
|
|
||||||
|
|
||||||
### Check Status
|
|
||||||
```bash
|
|
||||||
curl http://localhost:3000/api/status
|
|
||||||
```
|
|
||||||
|
|
||||||
### Start Bot
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/api/start
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get Logs
|
|
||||||
```bash
|
|
||||||
curl http://localhost:3000/api/logs?limit=50
|
|
||||||
```
|
|
||||||
|
|
||||||
### Sync Single Account
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:3000/api/sync/user@example.com
|
|
||||||
```
|
|
||||||
@@ -259,6 +259,78 @@ apiRouter.post('/account/:email/reset', (req: Request, res: Response): void => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// POST /api/reset-state - Reset all job states for today
|
||||||
|
apiRouter.post('/reset-state', (_req: Request, res: Response): void => {
|
||||||
|
try {
|
||||||
|
const jobStatePath = path.join(process.cwd(), 'sessions', 'job-state')
|
||||||
|
|
||||||
|
if (!fs.existsSync(jobStatePath)) {
|
||||||
|
res.json({ success: true, message: 'No job state to reset' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const today = new Date().toISOString().slice(0, 10)
|
||||||
|
let resetCount = 0
|
||||||
|
|
||||||
|
// Read all job state files and reset today's entries
|
||||||
|
const files = fs.readdirSync(jobStatePath).filter(f => f.endsWith('.json'))
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const filePath = path.join(jobStatePath, file)
|
||||||
|
const content = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
|
||||||
|
|
||||||
|
// Reset today's completed activities
|
||||||
|
if (content[today]) {
|
||||||
|
delete content[today]
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(content, null, 2), 'utf-8')
|
||||||
|
resetCount++
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Continue processing other files if one fails
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset account statuses in dashboard state
|
||||||
|
const accounts = dashboardState.getAccounts()
|
||||||
|
for (const account of accounts) {
|
||||||
|
dashboardState.updateAccount(account.email, {
|
||||||
|
status: 'idle',
|
||||||
|
errors: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: `Reset job state for ${resetCount} account(s)`,
|
||||||
|
resetCount
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: getErr(error) })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// GET /api/memory - Get current memory usage
|
||||||
|
apiRouter.get('/memory', (_req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const memUsage = process.memoryUsage()
|
||||||
|
res.json({
|
||||||
|
heapUsed: memUsage.heapUsed,
|
||||||
|
heapTotal: memUsage.heapTotal,
|
||||||
|
rss: memUsage.rss,
|
||||||
|
external: memUsage.external,
|
||||||
|
formatted: {
|
||||||
|
heapUsed: `${(memUsage.heapUsed / 1024 / 1024).toFixed(1)} MB`,
|
||||||
|
heapTotal: `${(memUsage.heapTotal / 1024 / 1024).toFixed(1)} MB`,
|
||||||
|
rss: `${(memUsage.rss / 1024 / 1024).toFixed(1)} MB`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: getErr(error) })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Helper to mask sensitive URLs
|
// Helper to mask sensitive URLs
|
||||||
function maskUrl(url: string): string {
|
function maskUrl(url: string): string {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user