159 lines
4.5 KiB
TypeScript
159 lines
4.5 KiB
TypeScript
import express from 'express';
|
|
import path from 'path';
|
|
import type { Server } from 'http';
|
|
import type { Logger } from '../utils/logger.js';
|
|
import type { PostsRepository, MessagesRepository, SuccessfulMessagesRepository } from '../database/repositories.js';
|
|
import type { Config } from '../config/index.js';
|
|
|
|
export interface BotStats {
|
|
isRunning: boolean;
|
|
dryRunMode: boolean;
|
|
totalProcessed: number;
|
|
totalMessagesSent: number;
|
|
totalResponses: number;
|
|
responseRate: number;
|
|
messagesThisHour: number;
|
|
maxMessagesPerHour: number;
|
|
subreddits: string[];
|
|
targetGenders: string[];
|
|
recentActivity: ActivityItem[];
|
|
}
|
|
|
|
export interface ActivityItem {
|
|
timestamp: number;
|
|
type: 'sent' | 'skipped' | 'response' | 'error';
|
|
message: string;
|
|
recipient?: string;
|
|
}
|
|
|
|
export class WebServer {
|
|
private app: express.Application;
|
|
private server: Server | null = null;
|
|
private activityLog: ActivityItem[] = [];
|
|
private botRunning = false; // Start stopped
|
|
private messagesThisHour = 0;
|
|
|
|
constructor(
|
|
private port: number,
|
|
private config: Config,
|
|
private postsRepo: PostsRepository,
|
|
private messagesRepo: MessagesRepository,
|
|
private successfulMessagesRepo: SuccessfulMessagesRepository,
|
|
private logger: Logger
|
|
) {
|
|
this.app = express();
|
|
this.setupRoutes();
|
|
}
|
|
|
|
private setupRoutes(): void {
|
|
// Serve static files
|
|
this.app.use(express.static(path.join(process.cwd(), 'public')));
|
|
this.app.use(express.json());
|
|
|
|
// API: Get stats
|
|
this.app.get('/api/stats', (req, res) => {
|
|
const stats = this.getStats();
|
|
res.json(stats);
|
|
});
|
|
|
|
// API: Get recent activity
|
|
this.app.get('/api/activity', (req, res) => {
|
|
res.json(this.activityLog.slice(-50).reverse());
|
|
});
|
|
|
|
// API: Get recent messages
|
|
this.app.get('/api/messages', (req, res) => {
|
|
const messages = this.messagesRepo.getRecent(20);
|
|
res.json(messages);
|
|
});
|
|
|
|
// API: Get successful messages (ones that got responses)
|
|
this.app.get('/api/successful', (req, res) => {
|
|
const successful = this.successfulMessagesRepo.getRecent(10);
|
|
res.json(successful);
|
|
});
|
|
|
|
// API: Start bot
|
|
this.app.post('/api/start', (req, res) => {
|
|
this.botRunning = true;
|
|
this.logActivity('sent', 'Bot started by user');
|
|
this.logger.info('Bot started via web UI');
|
|
res.json({ success: true, running: true });
|
|
});
|
|
|
|
// API: Stop bot
|
|
this.app.post('/api/stop', (req, res) => {
|
|
this.botRunning = false;
|
|
this.logActivity('skipped', 'Bot stopped by user');
|
|
this.logger.info('Bot stopped via web UI');
|
|
res.json({ success: true, running: false });
|
|
});
|
|
|
|
// Serve dashboard for all other routes
|
|
this.app.get(/.*/, (req, res) => {
|
|
res.sendFile(path.join(process.cwd(), 'public', 'index.html'));
|
|
});
|
|
}
|
|
|
|
isRunning(): boolean {
|
|
return this.botRunning;
|
|
}
|
|
|
|
private getStats(): BotStats {
|
|
const messages = this.messagesRepo.getRecent(1000);
|
|
const totalSent = messages.length;
|
|
const totalResponses = messages.filter((m) => m.gotResponse).length;
|
|
|
|
return {
|
|
isRunning: this.botRunning,
|
|
dryRunMode: this.config.browser.dryRun,
|
|
totalProcessed: this.getTotalProcessed(),
|
|
totalMessagesSent: totalSent,
|
|
totalResponses,
|
|
responseRate: totalSent > 0 ? Math.round((totalResponses / totalSent) * 100) : 0,
|
|
messagesThisHour: this.messagesThisHour,
|
|
maxMessagesPerHour: this.config.rateLimit.maxMessagesPerHour,
|
|
subreddits: this.config.reddit.subreddits,
|
|
targetGenders: this.config.reddit.targetGenders,
|
|
recentActivity: this.activityLog.slice(-10).reverse(),
|
|
};
|
|
}
|
|
|
|
private getTotalProcessed(): number {
|
|
// This is a simple count - in production you'd query the DB
|
|
return this.activityLog.filter((a) => a.type === 'sent' || a.type === 'skipped').length;
|
|
}
|
|
|
|
logActivity(type: ActivityItem['type'], message: string, recipient?: string): void {
|
|
const item: ActivityItem = {
|
|
timestamp: Date.now(),
|
|
type,
|
|
message,
|
|
recipient,
|
|
};
|
|
this.activityLog.push(item);
|
|
|
|
// Keep only last 200 items
|
|
if (this.activityLog.length > 200) {
|
|
this.activityLog = this.activityLog.slice(-200);
|
|
}
|
|
}
|
|
|
|
updateStats(messagesThisHour: number): void {
|
|
this.messagesThisHour = messagesThisHour;
|
|
}
|
|
|
|
start(): void {
|
|
this.server = this.app.listen(this.port, () => {
|
|
this.logger.info(`Web dashboard running at http://localhost:${this.port}`);
|
|
});
|
|
}
|
|
|
|
stop(): void {
|
|
if (this.server) {
|
|
this.server.close();
|
|
this.server = null;
|
|
}
|
|
}
|
|
}
|