mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-11 01:36:16 +00:00
306 lines
9.2 KiB
TypeScript
306 lines
9.2 KiB
TypeScript
import { Router, Request, Response } from 'express'
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import { dashboardState } from './state'
|
|
import { loadAccounts, loadConfig, getConfigPath } from '../util/Load'
|
|
import { spawn } from 'child_process'
|
|
|
|
export const apiRouter = Router()
|
|
|
|
// GET /api/status - Bot status
|
|
apiRouter.get('/status', (_req: Request, res: Response) => {
|
|
try {
|
|
const accounts = dashboardState.getAccounts()
|
|
|
|
// If no accounts loaded yet, try to load them
|
|
if (accounts.length === 0) {
|
|
try {
|
|
const loadedAccounts = loadAccounts()
|
|
dashboardState.initializeAccounts(loadedAccounts.map(a => a.email))
|
|
} catch (error) {
|
|
console.error('[Dashboard] Failed to load accounts for status:', error)
|
|
}
|
|
}
|
|
|
|
res.json(dashboardState.getStatus())
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// GET /api/accounts - List all accounts with masked emails
|
|
apiRouter.get('/accounts', (_req: Request, res: Response) => {
|
|
try {
|
|
let accounts = dashboardState.getAccounts()
|
|
|
|
// If no accounts in state, try to load from config
|
|
if (accounts.length === 0) {
|
|
try {
|
|
const loadedAccounts = loadAccounts()
|
|
dashboardState.initializeAccounts(loadedAccounts.map(a => a.email))
|
|
accounts = dashboardState.getAccounts()
|
|
} catch (error) {
|
|
console.error('[Dashboard] Failed to load accounts:', error)
|
|
}
|
|
}
|
|
|
|
res.json(accounts)
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// GET /api/logs - Recent logs
|
|
apiRouter.get('/logs', (req: Request, res: Response) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit as string) || 100
|
|
const logs = dashboardState.getLogs(Math.min(limit, 500))
|
|
res.json(logs)
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// DELETE /api/logs - Clear logs
|
|
apiRouter.delete('/logs', (_req: Request, res: Response) => {
|
|
try {
|
|
dashboardState.clearLogs()
|
|
res.json({ success: true })
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// GET /api/history - Recent run summaries
|
|
apiRouter.get('/history', (_req: Request, res: Response): void => {
|
|
try {
|
|
const reportsDir = path.join(process.cwd(), 'reports')
|
|
if (!fs.existsSync(reportsDir)) {
|
|
res.json([])
|
|
return
|
|
}
|
|
|
|
const days = fs.readdirSync(reportsDir).filter(d => /^\d{4}-\d{2}-\d{2}$/.test(d)).sort().reverse().slice(0, 7)
|
|
const summaries: unknown[] = []
|
|
|
|
for (const day of days) {
|
|
const dayDir = path.join(reportsDir, day)
|
|
const files = fs.readdirSync(dayDir).filter(f => f.startsWith('summary_') && f.endsWith('.json'))
|
|
for (const file of files) {
|
|
try {
|
|
const content = fs.readFileSync(path.join(dayDir, file), 'utf-8')
|
|
summaries.push(JSON.parse(content))
|
|
} catch {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
res.json(summaries.slice(0, 50))
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// GET /api/config - Current config (tokens masked)
|
|
apiRouter.get('/config', (_req: Request, res: Response) => {
|
|
try {
|
|
const config = loadConfig()
|
|
const safe = JSON.parse(JSON.stringify(config))
|
|
|
|
// Mask sensitive data
|
|
if (safe.webhook?.url) safe.webhook.url = maskUrl(safe.webhook.url)
|
|
if (safe.conclusionWebhook?.url) safe.conclusionWebhook.url = maskUrl(safe.conclusionWebhook.url)
|
|
if (safe.ntfy?.authToken) safe.ntfy.authToken = '***'
|
|
|
|
res.json(safe)
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// POST /api/config - Update config (with backup)
|
|
apiRouter.post('/config', (req: Request, res: Response): void => {
|
|
try {
|
|
const newConfig = req.body
|
|
const configPath = getConfigPath()
|
|
|
|
if (!configPath || !fs.existsSync(configPath)) {
|
|
res.status(404).json({ error: 'Config file not found' })
|
|
return
|
|
}
|
|
|
|
// Backup current config
|
|
const backupPath = `${configPath}.backup.${Date.now()}`
|
|
fs.copyFileSync(configPath, backupPath)
|
|
|
|
// Write new config
|
|
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2), 'utf-8')
|
|
|
|
res.json({ success: true, backup: backupPath })
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// POST /api/start - Start bot in background
|
|
apiRouter.post('/start', (_req: Request, res: Response): void => {
|
|
try {
|
|
const status = dashboardState.getStatus()
|
|
if (status.running) {
|
|
res.status(400).json({ error: 'Bot already running' })
|
|
return
|
|
}
|
|
|
|
// Set running state
|
|
dashboardState.setRunning(true)
|
|
|
|
// Log the start
|
|
dashboardState.addLog({
|
|
timestamp: new Date().toISOString(),
|
|
level: 'log',
|
|
platform: 'MAIN',
|
|
title: 'DASHBOARD',
|
|
message: 'Bot start requested from dashboard'
|
|
})
|
|
|
|
res.json({ success: true, message: 'Bot start requested. Check logs for progress.' })
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// POST /api/stop - Stop bot
|
|
apiRouter.post('/stop', (_req: Request, res: Response) => {
|
|
try {
|
|
dashboardState.setRunning(false)
|
|
|
|
dashboardState.addLog({
|
|
timestamp: new Date().toISOString(),
|
|
level: 'warn',
|
|
platform: 'MAIN',
|
|
title: 'DASHBOARD',
|
|
message: 'Bot stop requested from dashboard'
|
|
})
|
|
|
|
res.json({ success: true, message: 'Bot will stop after current task completes' })
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// POST /api/sync/:email - Force sync single account
|
|
apiRouter.post('/sync/:email', async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { email } = req.params
|
|
if (!email) {
|
|
res.status(400).json({ error: 'Email parameter required' })
|
|
return
|
|
}
|
|
|
|
const accounts = loadAccounts()
|
|
const account = accounts.find(a => a.email === email)
|
|
|
|
if (!account) {
|
|
res.status(404).json({ error: 'Account not found' })
|
|
return
|
|
}
|
|
|
|
dashboardState.updateAccount(email, { status: 'running', lastSync: new Date().toISOString() })
|
|
|
|
// Spawn single account run
|
|
const child = spawn(process.execPath, [
|
|
path.join(process.cwd(), 'dist', 'index.js'),
|
|
'-account',
|
|
email
|
|
], { detached: true, stdio: 'ignore' })
|
|
|
|
if (child.unref) child.unref()
|
|
|
|
res.json({ success: true, pid: child.pid || undefined })
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// GET /api/metrics - Basic metrics
|
|
apiRouter.get('/metrics', (_req: Request, res: Response) => {
|
|
try {
|
|
const accounts = dashboardState.getAccounts()
|
|
const totalPoints = accounts.reduce((sum, a) => sum + (a.points || 0), 0)
|
|
const accountsWithErrors = accounts.filter(a => a.errors && a.errors.length > 0).length
|
|
const avgPoints = accounts.length > 0 ? Math.round(totalPoints / accounts.length) : 0
|
|
|
|
res.json({
|
|
totalAccounts: accounts.length,
|
|
totalPoints,
|
|
avgPoints,
|
|
accountsWithErrors,
|
|
accountsRunning: accounts.filter(a => a.status === 'running').length,
|
|
accountsCompleted: accounts.filter(a => a.status === 'completed').length,
|
|
accountsIdle: accounts.filter(a => a.status === 'idle').length,
|
|
accountsError: accounts.filter(a => a.status === 'error').length
|
|
})
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// GET /api/account/:email - Get specific account details
|
|
apiRouter.get('/account/:email', (req: Request, res: Response): void => {
|
|
try {
|
|
const { email } = req.params
|
|
if (!email) {
|
|
res.status(400).json({ error: 'Email parameter required' })
|
|
return
|
|
}
|
|
|
|
const account = dashboardState.getAccount(email)
|
|
|
|
if (!account) {
|
|
res.status(404).json({ error: 'Account not found' })
|
|
return
|
|
}
|
|
|
|
res.json(account)
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
// POST /api/account/:email/reset - Reset account status
|
|
apiRouter.post('/account/:email/reset', (req: Request, res: Response): void => {
|
|
try {
|
|
const { email } = req.params
|
|
if (!email) {
|
|
res.status(400).json({ error: 'Email parameter required' })
|
|
return
|
|
}
|
|
|
|
const account = dashboardState.getAccount(email)
|
|
|
|
if (!account) {
|
|
res.status(404).json({ error: 'Account not found' })
|
|
return
|
|
}
|
|
|
|
dashboardState.updateAccount(email, {
|
|
status: 'idle',
|
|
errors: []
|
|
})
|
|
|
|
res.json({ success: true })
|
|
} catch (error) {
|
|
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' })
|
|
}
|
|
})
|
|
|
|
function maskUrl(url: string): string {
|
|
try {
|
|
const parsed = new URL(url)
|
|
return `${parsed.protocol}//${parsed.hostname.slice(0, 3)}***${parsed.pathname.slice(0, 5)}***`
|
|
} catch {
|
|
return '***'
|
|
}
|
|
}
|