New structure

This commit is contained in:
2025-11-11 12:59:42 +01:00
parent 088a3a024f
commit 89bc226d6b
46 changed files with 990 additions and 944 deletions

View File

@@ -1,6 +1,6 @@
import type { MicrosoftRewardsBot } from '../index'
import { log as botLog } from '../util/Logger'
import { getErrorMessage } from '../util/Utils'
import { getErrorMessage } from '../util/core/Utils'
import { log as botLog } from '../util/notifications/Logger'
import { dashboardState } from './state'
export class BotController {
@@ -14,7 +14,7 @@ export class BotController {
private log(message: string, level: 'log' | 'warn' | 'error' = 'log'): void {
botLog('main', 'BOT-CONTROLLER', message, level)
dashboardState.addLog({
timestamp: new Date().toISOString(),
level,
@@ -29,7 +29,7 @@ export class BotController {
if (this.botInstance) {
return { success: false, error: 'Bot is already running' }
}
if (this.isStarting) {
return { success: false, error: 'Bot is currently starting, please wait' }
}
@@ -39,7 +39,7 @@ export class BotController {
this.log('🚀 Starting bot...', 'log')
const { MicrosoftRewardsBot } = await import('../index')
this.botInstance = new MicrosoftRewardsBot(false)
this.startTime = new Date()
dashboardState.setRunning(true)
@@ -49,10 +49,10 @@ export class BotController {
void (async () => {
try {
this.log('✓ Bot initialized, starting execution...', 'log')
await this.botInstance!.initialize()
await this.botInstance!.run()
this.log('✓ Bot completed successfully', 'log')
} catch (error) {
this.log(`Bot error: ${getErrorMessage(error)}`, 'error')
@@ -81,7 +81,7 @@ export class BotController {
try {
this.log('🛑 Stopping bot...', 'warn')
this.log('⚠ Note: Bot will complete current task before stopping', 'warn')
this.cleanup()
return { success: true }
@@ -95,14 +95,14 @@ export class BotController {
public async restart(): Promise<{ success: boolean; error?: string; pid?: number }> {
this.log('🔄 Restarting bot...', 'log')
const stopResult = this.stop()
if (!stopResult.success && stopResult.error !== 'Bot is not running') {
return { success: false, error: `Failed to stop: ${stopResult.error}` }
}
await this.wait(2000)
return await this.start()
}

View File

@@ -1,7 +1,7 @@
import { Request, Response, Router } from 'express'
import fs from 'fs'
import path from 'path'
import { getConfigPath, loadAccounts, loadConfig } from '../util/Load'
import { getConfigPath, loadAccounts, loadConfig } from '../util/state/Load'
import { botController } from './BotController'
import { dashboardState } from './state'
@@ -100,7 +100,7 @@ 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)
@@ -117,7 +117,7 @@ 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
@@ -146,7 +146,7 @@ apiRouter.post('/start', async (_req: Request, res: Response): Promise<void> =>
}
const result = await botController.start()
if (result.success) {
sendSuccess(res, { message: 'Bot started successfully', pid: result.pid })
} else {
@@ -161,7 +161,7 @@ apiRouter.post('/start', async (_req: Request, res: Response): Promise<void> =>
apiRouter.post('/stop', (_req: Request, res: Response): void => {
try {
const result = botController.stop()
if (result.success) {
sendSuccess(res, { message: 'Bot stopped successfully' })
} else {
@@ -176,7 +176,7 @@ apiRouter.post('/stop', (_req: Request, res: Response): void => {
apiRouter.post('/restart', async (_req: Request, res: Response): Promise<void> => {
try {
const result = await botController.restart()
if (result.success) {
sendSuccess(res, { message: 'Bot restarted successfully', pid: result.pid })
} else {
@@ -194,7 +194,7 @@ apiRouter.get('/metrics', (_req: Request, res: Response) => {
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,
@@ -218,14 +218,14 @@ apiRouter.get('/account/:email', (req: Request, res: Response): void => {
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: getErr(error) })
@@ -240,19 +240,19 @@ apiRouter.post('/account/:email/reset', (req: Request, res: Response): void => {
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: getErr(error) })
@@ -263,10 +263,10 @@ apiRouter.post('/account/:email/reset', (req: Request, res: Response): void => {
function maskUrl(url: string): string {
try {
const parsed = new URL(url)
const maskedHost = parsed.hostname.length > 6
const maskedHost = parsed.hostname.length > 6
? `${parsed.hostname.slice(0, 3)}***${parsed.hostname.slice(-3)}`
: '***'
const maskedPath = parsed.pathname.length > 5
const maskedPath = parsed.pathname.length > 5
? `${parsed.pathname.slice(0, 3)}***`
: '***'
return `${parsed.protocol}//${maskedHost}${maskedPath}`

View File

@@ -3,7 +3,7 @@ import fs from 'fs'
import { createServer } from 'http'
import path from 'path'
import { WebSocket, WebSocketServer } from 'ws'
import { log as botLog } from '../util/Logger'
import { log as botLog } from '../util/notifications/Logger'
import { apiRouter } from './routes'
import { DashboardLog, dashboardState } from './state'
@@ -41,7 +41,7 @@ export class DashboardServer {
private setupMiddleware(): void {
this.app.use(express.json())
// Disable caching for all static files
this.app.use((req, res, next) => {
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private')
@@ -49,7 +49,7 @@ export class DashboardServer {
res.set('Expires', '0')
next()
})
this.app.use('/assets', express.static(path.join(__dirname, '../../assets'), {
etag: false,
maxAge: 0
@@ -62,7 +62,7 @@ export class DashboardServer {
private setupRoutes(): void {
this.app.use('/api', apiRouter)
// Health check
this.app.get('/health', (_req, res) => {
res.json({ status: 'ok', uptime: process.uptime() })
@@ -71,12 +71,12 @@ export class DashboardServer {
// Serve dashboard UI
this.app.get('/', (_req, res) => {
const indexPath = path.join(__dirname, '../../public/index.html')
// Force no cache on HTML files
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private')
res.set('Pragma', 'no-cache')
res.set('Expires', '0')
if (fs.existsSync(indexPath)) {
res.sendFile(indexPath)
} else {
@@ -117,9 +117,9 @@ export class DashboardServer {
const recentLogs = dashboardState.getLogs(100)
const status = dashboardState.getStatus()
const accounts = dashboardState.getAccounts()
ws.send(JSON.stringify({
type: 'init',
ws.send(JSON.stringify({
type: 'init',
data: {
logs: recentLogs,
status,
@@ -135,7 +135,7 @@ export class DashboardServer {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const loggerModule = require('../util/Logger') as { log: typeof botLog }
const originalLog = loggerModule.log
loggerModule.log = (
isMobile: boolean | 'main',
title: string,
@@ -145,7 +145,7 @@ export class DashboardServer {
) => {
// Call original log function
const result = originalLog(isMobile, title, message, type, color as keyof typeof import('chalk'))
// Create log entry for dashboard
const logEntry: DashboardLog = {
timestamp: new Date().toISOString(),
@@ -154,14 +154,14 @@ export class DashboardServer {
title,
message
}
// Add to dashboard state and broadcast
dashboardState.addLog(logEntry)
this.broadcastUpdate('log', { log: logEntry })
return result
}
dashLog('Bot log interception active')
}