mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-09 09:06:15 +00:00
feat: Implement real data fetching for points chart and enhance UI with config editor and history viewer styles
This commit is contained in:
@@ -1,24 +0,0 @@
|
|||||||
# Error Reporting
|
|
||||||
|
|
||||||
Automatically sends anonymized error reports to help improve the project.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
In `src/config.jsonc`:
|
|
||||||
|
|
||||||
```jsonc
|
|
||||||
{
|
|
||||||
"errorReporting": {
|
|
||||||
"enabled": true // Set to false to disable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Privacy
|
|
||||||
|
|
||||||
- No sensitive data sent (emails, passwords, tokens, paths are redacted)
|
|
||||||
- Only genuine bugs reported (config errors filtered)
|
|
||||||
- Optional - can be disabled anytime
|
|
||||||
|
|
||||||
---
|
|
||||||
**[Back to Documentation](index.md)**
|
|
||||||
153
public/app.js
153
public/app.js
@@ -142,7 +142,7 @@ function initPointsChart() {
|
|||||||
labels: generateDateLabels(7),
|
labels: generateDateLabels(7),
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Points',
|
label: 'Points',
|
||||||
data: generatePlaceholderData(7),
|
data: new Array(7).fill(0), // Real data loaded from API
|
||||||
borderColor: '#58a6ff',
|
borderColor: '#58a6ff',
|
||||||
backgroundColor: gradient,
|
backgroundColor: gradient,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
@@ -203,16 +203,6 @@ function generateDateLabels(days) {
|
|||||||
return labels
|
return labels
|
||||||
}
|
}
|
||||||
|
|
||||||
function generatePlaceholderData(count) {
|
|
||||||
const data = []
|
|
||||||
let base = 150
|
|
||||||
for (let i = 0; i < count; i++) {
|
|
||||||
base += Math.floor(Math.random() * 50) - 20
|
|
||||||
data.push(Math.max(50, base))
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
function setChartPeriod(period, btn) {
|
function setChartPeriod(period, btn) {
|
||||||
document.querySelectorAll('.period-btn').forEach((b) => { b.classList.remove('active') })
|
document.querySelectorAll('.period-btn').forEach((b) => { b.classList.remove('active') })
|
||||||
btn.classList.add('active')
|
btn.classList.add('active')
|
||||||
@@ -220,11 +210,58 @@ function setChartPeriod(period, btn) {
|
|||||||
const days = period === '7d' ? 7 : 30
|
const days = period === '7d' ? 7 : 30
|
||||||
if (pointsChart) {
|
if (pointsChart) {
|
||||||
pointsChart.data.labels = generateDateLabels(days)
|
pointsChart.data.labels = generateDateLabels(days)
|
||||||
pointsChart.data.datasets[0].data = generatePlaceholderData(days)
|
|
||||||
pointsChart.update('none')
|
// Fetch actual data from account history
|
||||||
|
fetch('/api/account-history')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(history => {
|
||||||
|
const pointsData = extractPointsFromHistory(history, days)
|
||||||
|
pointsChart.data.datasets[0].data = pointsData
|
||||||
|
pointsChart.update('none')
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Fallback to zeros if API fails
|
||||||
|
pointsChart.data.datasets[0].data = new Array(days).fill(0)
|
||||||
|
pointsChart.update('none')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractPointsFromHistory(history, days) {
|
||||||
|
const dataByDate = {}
|
||||||
|
const today = new Date()
|
||||||
|
|
||||||
|
// Initialize all days with 0
|
||||||
|
for (let i = 0; i < days; i++) {
|
||||||
|
const d = new Date(today)
|
||||||
|
d.setDate(d.getDate() - i)
|
||||||
|
const key = d.toISOString().slice(0, 10)
|
||||||
|
dataByDate[key] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill with actual data
|
||||||
|
for (const email in history) {
|
||||||
|
const accountHistory = history[email]
|
||||||
|
for (const day in accountHistory) {
|
||||||
|
if (dataByDate.hasOwnProperty(day)) {
|
||||||
|
const dayData = accountHistory[day]
|
||||||
|
dataByDate[day] += dayData.pointsEarnedToday || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to array (reverse chronological)
|
||||||
|
const result = []
|
||||||
|
for (let i = days - 1; i >= 0; i--) {
|
||||||
|
const d = new Date(today)
|
||||||
|
d.setDate(d.getDate() - i)
|
||||||
|
const key = d.toISOString().slice(0, 10)
|
||||||
|
result.push(dataByDate[key] || 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// Data Loading
|
// Data Loading
|
||||||
function loadInitialData() {
|
function loadInitialData() {
|
||||||
fetch('/api/status').then((r) => { return r.json() }).then(updateBotStatus).catch(() => { })
|
fetch('/api/status').then((r) => { return r.json() }).then(updateBotStatus).catch(() => { })
|
||||||
@@ -298,7 +335,7 @@ function updateChartsWithRealData(histories) {
|
|||||||
var data = sortedDates.map((d) => { return last7Days[d] })
|
var data = sortedDates.map((d) => { return last7Days[d] })
|
||||||
|
|
||||||
pointsChart.data.labels = labels.length > 0 ? labels : generateDateLabels(7)
|
pointsChart.data.labels = labels.length > 0 ? labels : generateDateLabels(7)
|
||||||
pointsChart.data.datasets[0].data = data.length > 0 ? data : generatePlaceholderData(7)
|
pointsChart.data.datasets[0].data = data.length > 0 ? data : new Array(7).fill(0)
|
||||||
pointsChart.update()
|
pointsChart.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,11 +712,95 @@ function exportLogs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openConfig() {
|
function openConfig() {
|
||||||
showToast('Config editor coming soon', 'info')
|
showModal('Configuration Editor', `
|
||||||
|
<div class="config-loading">Loading configuration...</div>
|
||||||
|
`, [])
|
||||||
|
|
||||||
|
// Fetch current config
|
||||||
|
fetch('/api/config')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(config => {
|
||||||
|
const body = `
|
||||||
|
<div class="config-editor">
|
||||||
|
<textarea id="configEditor" class="config-textarea">${JSON.stringify(config, null, 2)}</textarea>
|
||||||
|
<p class="config-hint">⚠️ Advanced users only. Invalid JSON will break the bot.</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
const buttons = [
|
||||||
|
{ cls: 'btn btn-sm btn-secondary', action: 'closeModal()', text: 'Cancel' },
|
||||||
|
{ cls: 'btn btn-sm btn-primary', action: 'saveConfig()', text: 'Save Changes' }
|
||||||
|
]
|
||||||
|
showModal('Configuration Editor', body, buttons)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
showToast('Failed to load config: ' + e.message, 'error')
|
||||||
|
closeModal()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConfig() {
|
||||||
|
const editor = document.getElementById('configEditor')
|
||||||
|
if (!editor) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newConfig = JSON.parse(editor.value)
|
||||||
|
|
||||||
|
fetch('/api/config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(newConfig)
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
showToast('Configuration saved! Restart bot for changes to apply.', 'success')
|
||||||
|
closeModal()
|
||||||
|
} else {
|
||||||
|
showToast('Save failed: ' + (data.error || 'Unknown error'), 'error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
showToast('Save failed: ' + e.message, 'error')
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
showToast('Invalid JSON format: ' + e.message, 'error')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewHistory() {
|
function viewHistory() {
|
||||||
showToast('History viewer coming soon', 'info')
|
showModal('Run History', '<div class="config-loading">Loading history...</div>', [])
|
||||||
|
|
||||||
|
fetch('/api/history')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(history => {
|
||||||
|
if (!history || history.length === 0) {
|
||||||
|
showModal('Run History', '<p class="log-empty">No history available yet</p>', [
|
||||||
|
{ cls: 'btn btn-sm btn-secondary', action: 'closeModal()', text: 'Close' }
|
||||||
|
])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = history.slice(0, 10).map(h => `
|
||||||
|
<div class="history-row">
|
||||||
|
<div class="history-date">${new Date(h.timestamp || Date.now()).toLocaleString()}</div>
|
||||||
|
<div class="history-stats">
|
||||||
|
<span>✅ ${h.successCount || 0} success</span>
|
||||||
|
<span>❌ ${h.errorCount || 0} errors</span>
|
||||||
|
<span>🎯 ${h.totalPoints || 0} pts</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('')
|
||||||
|
|
||||||
|
const body = `<div class="history-list">${rows}</div>`
|
||||||
|
const buttons = [
|
||||||
|
{ cls: 'btn btn-sm btn-secondary', action: 'closeModal()', text: 'Close' }
|
||||||
|
]
|
||||||
|
showModal('Run History (Last 10 Runs)', body, buttons)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
showToast('Failed to load history: ' + e.message, 'error')
|
||||||
|
closeModal()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI Utilities
|
// UI Utilities
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<link rel="icon" type="image/svg+xml"
|
<link rel="icon" type="image/svg+xml"
|
||||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E🎯%3C/text%3E%3C/svg%3E">
|
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E🎯%3C/text%3E%3C/svg%3E">
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<link rel="stylesheet" href="/style-extensions.css">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
/* Inline SVG icon styles */
|
/* Inline SVG icon styles */
|
||||||
|
|||||||
82
public/style-extensions.css
Normal file
82
public/style-extensions.css
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/* Dashboard Extensions - Config Editor & History Viewer */
|
||||||
|
|
||||||
|
/* Config Editor Styles */
|
||||||
|
.config-editor {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 400px;
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
resize: vertical;
|
||||||
|
transition: border-color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent-blue);
|
||||||
|
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-hint {
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* History Viewer Styles */
|
||||||
|
.history-list {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-row {
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border-left: 3px solid var(--accent-blue);
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-row:hover {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-left-color: var(--accent-green);
|
||||||
|
transform: translateX(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-date {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-stats span {
|
||||||
|
padding: 2px 8px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
@@ -49,17 +49,17 @@
|
|||||||
|
|
||||||
/* Light Theme */
|
/* Light Theme */
|
||||||
.light-theme {
|
.light-theme {
|
||||||
--bg-primary: #ffffff;
|
--bg-primary: #f5f7fa;
|
||||||
--bg-secondary: #f6f8fa;
|
--bg-secondary: #ffffff;
|
||||||
--bg-tertiary: #eaeef2;
|
--bg-tertiary: #edf1f5;
|
||||||
--bg-card: #ffffff;
|
--bg-card: #ffffff;
|
||||||
--border-color: #d0d7de;
|
--border-color: #d8dee4;
|
||||||
--text-primary: #1f2328;
|
--text-primary: #1a202c;
|
||||||
--text-secondary: #656d76;
|
--text-secondary: #4a5568;
|
||||||
--text-muted: #8c959f;
|
--text-muted: #718096;
|
||||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.1);
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.15);
|
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.12);
|
||||||
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.2);
|
--shadow-lg: 0 12px 30px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset & Base */
|
/* Reset & Base */
|
||||||
|
|||||||
@@ -44,6 +44,16 @@ export async function getUserAgent(isMobile: boolean): Promise<UserAgentResult>
|
|||||||
const system = getSystemComponents(isMobile)
|
const system = getSystemComponents(isMobile)
|
||||||
const app = await getAppComponents(isMobile)
|
const app = await getAppComponents(isMobile)
|
||||||
|
|
||||||
|
// IMPROVED: Add random patch variation to Edge version for uniqueness
|
||||||
|
// e.g., 130.0.2849.68 → 130.0.2849.[68-75] random
|
||||||
|
const edgeVersionParts = app.edge_version.split('.')
|
||||||
|
if (edgeVersionParts.length === 4 && edgeVersionParts[3]) {
|
||||||
|
const basePatch = parseInt(edgeVersionParts[3], 10) || 0
|
||||||
|
const randomizedPatch = basePatch + Math.floor(Math.random() * 8) // +0 to +7 variation
|
||||||
|
edgeVersionParts[3] = String(randomizedPatch)
|
||||||
|
app.edge_version = edgeVersionParts.join('.')
|
||||||
|
}
|
||||||
|
|
||||||
const uaTemplate = isMobile ?
|
const uaTemplate = isMobile ?
|
||||||
`Mozilla/5.0 (${system}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${app.chrome_reduced_version} Mobile Safari/537.36 EdgA/${app.edge_version}` :
|
`Mozilla/5.0 (${system}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${app.chrome_reduced_version} Mobile Safari/537.36 EdgA/${app.edge_version}` :
|
||||||
`Mozilla/5.0 (${system}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${app.chrome_reduced_version} Safari/537.36 Edg/${app.edge_version}`
|
`Mozilla/5.0 (${system}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${app.chrome_reduced_version} Safari/537.36 Edg/${app.edge_version}`
|
||||||
@@ -142,8 +152,11 @@ export async function getEdgeVersions(isMobile: boolean): Promise<EdgeVersionRes
|
|||||||
|
|
||||||
export function getSystemComponents(mobile: boolean): string {
|
export function getSystemComponents(mobile: boolean): string {
|
||||||
if (mobile) {
|
if (mobile) {
|
||||||
const androidVersion = 10 + Math.floor(Math.random() * 5)
|
// IMPROVED: Android 10-14 coverage (was 10-14, now with sub-versions)
|
||||||
return `Linux; Android ${androidVersion}; K`
|
const androidMajor = 10 + Math.floor(Math.random() * 5) // 10, 11, 12, 13, 14
|
||||||
|
const androidMinor = Math.floor(Math.random() * 3) // 0, 1, 2
|
||||||
|
const androidPatch = Math.floor(Math.random() * 10) // 0-9
|
||||||
|
return `Linux; Android ${androidMajor}.${androidMinor}.${androidPatch}; K`
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Windows NT 10.0; Win64; x64'
|
return 'Windows NT 10.0; Win64; x64'
|
||||||
|
|||||||
@@ -406,7 +406,9 @@ export function getAntiDetectionScript(options: {
|
|||||||
// LAYER 10: Hardware Concurrency & Device Memory
|
// LAYER 10: Hardware Concurrency & Device Memory
|
||||||
// ═══════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
const commonCores = [4, 6, 8, 12, 16];
|
// IMPROVED: Limit to common consumer CPUs (4-8 cores)
|
||||||
|
// 12-16 cores are rare and can flag datacenter/server detection
|
||||||
|
const commonCores = [4, 6, 8];
|
||||||
const realCores = navigator.hardwareConcurrency || 4;
|
const realCores = navigator.hardwareConcurrency || 4;
|
||||||
const normalizedCores = commonCores.reduce((prev, curr) =>
|
const normalizedCores = commonCores.reduce((prev, curr) =>
|
||||||
Math.abs(curr - realCores) < Math.abs(prev - realCores) ? curr : prev
|
Math.abs(curr - realCores) < Math.abs(prev - realCores) ? curr : prev
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
export type BanStatus = { status: boolean; reason: string }
|
export type BanStatus = { status: boolean; reason: string }
|
||||||
|
|
||||||
|
// IMPROVED: Expanded ban detection patterns for better early warning
|
||||||
const BAN_PATTERNS: Array<{ re: RegExp; reason: string }> = [
|
const BAN_PATTERNS: Array<{ re: RegExp; reason: string }> = [
|
||||||
{ re: /suspend|suspended|suspension/i, reason: 'account suspended' },
|
{ re: /suspend|suspended|suspension/i, reason: 'account suspended' },
|
||||||
{ re: /locked|lockout|serviceabuse|abuse/i, reason: 'locked or service abuse detected' },
|
{ re: /locked|lockout|serviceabuse|abuse/i, reason: 'locked or service abuse detected' },
|
||||||
{ re: /unusual.*activity|unusual activity/i, reason: 'unusual activity prompts' },
|
{ re: /unusual.*activity|unusual activity/i, reason: 'unusual activity prompts' },
|
||||||
{ re: /verify.*identity|identity.*verification/i, reason: 'identity verification required' }
|
{ re: /verify.*identity|identity.*verification/i, reason: 'identity verification required' },
|
||||||
|
{ re: /captcha|recaptcha|hcaptcha/i, reason: 'CAPTCHA challenge detected (potential bot detection)' },
|
||||||
|
{ re: /blocked|block|restriction|restricted/i, reason: 'access restricted or blocked' },
|
||||||
|
{ re: /security.*code|verification.*code|two.*factor/i, reason: 'unexpected 2FA prompt (suspicious activity)' },
|
||||||
|
{ re: /rate.*limit|too.*many.*requests|slow.*down/i, reason: 'rate limiting detected' },
|
||||||
|
{ re: /temporarily.*unavailable|service.*unavailable/i, reason: 'service temporarily unavailable (may be IP ban)' },
|
||||||
|
{ re: /automated.*request|bot.*detected|automated.*access/i, reason: 'automated access detected' }
|
||||||
]
|
]
|
||||||
|
|
||||||
export function detectBanReason(input: unknown): BanStatus {
|
export function detectBanReason(input: unknown): BanStatus {
|
||||||
|
|||||||
Reference in New Issue
Block a user