Files
RedditBot/docs/architecture.md
T

932 lines
33 KiB
Markdown

# RedditBot - Software Architecture Documentation
## Table of Contents
1. [Project Overview](#project-overview)
2. [System Architecture](#system-architecture)
3. [Directory Structure](#directory-structure)
4. [Component Reference](#component-reference)
5. [Data Flow](#data-flow)
6. [Database Schema](#database-schema)
7. [API Reference](#api-reference)
8. [Configuration](#configuration)
9. [Design Patterns](#design-patterns)
10. [External Integrations](#external-integrations)
11. [Deployment](#deployment)
12. [Technical Debt & Recommendations](#technical-debt--recommendations)
---
## Project Overview
**RedditBot** is an automated Reddit monitoring and messaging system that:
- Monitors 30+ NSFW subreddits for image posts
- Uses AI (OpenRouter) to analyze images and detect gender
- Generates personalized direct messages to post authors
- Sends DMs via browser automation (Playwright)
- Monitors for responses and tracks success rates
- Implements A/B testing with explore/exploit strategy selection
- Learns from successful message/response pairs (few-shot learning)
- Provides a real-time web dashboard for monitoring and control
### Key Capabilities
| Capability | Description |
|---|---|
| Post Discovery | Polls subreddits for new image posts, filters by gender and spam profiles |
| AI Analysis | Multi-model image analysis with fallback chain via OpenRouter |
| Message Generation | AI-crafted DMs using successful examples as few-shot prompts |
| A/B Testing | Explore/exploit strategy selector optimizes response rates over time |
| Response Monitoring | Background service checks inbox for user replies |
| Chat Automation | Reddit chat scraping via shadow DOM traversal, auto-reply support |
| Media Management | Download, cache, deduplicate, and sync media from conversations |
| Web Dashboard | React operator console with typed SSE events, analytics, and guarded controls |
---
## System Architecture
### High-Level Architecture
```
┌─────────────────────────────────────────────────────────────────────┐
│ main (index.ts) │
│ Orchestrator / Entry Point │
└────────────────────────┬────────────────────────────────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌──────────────┐ ┌──────────────┐
│ Monitor │ │ Processor │ │ Response │
│ Service │ │ │ │ Checker │
│ │ │ 1. Download │ │ │
│ Poll subreddits│ │ 2. Validate │ │ Check inbox │
│ for new posts │ │ 3. Gender │ │ for replies │
└───────┬───────┘ │ Filter │ └──────────────┘
│ │ 4. AI Analysis│ │
│ │ 5. Strategy │ │
│ │ Selection │ │
│ │ 6. Message │ │
│ │ Generation │ │
└────┬────┘ └─────────────┘ │
│ │
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Strategy Selector │ │ Chat Service │
│ │ │ │
│ A/B Testing │ │ Scrapes chat (shadow │
│ Explore/Exploit │ │ DOM traversal) │
│ Variant Selection │ │ Auto-reply, upsell │
└───────────────────────┘ │ detection │
└───────────────────────┘
│ │
▼ ▼
┌───────────────────────┐ ┌───────────────────────┐
│ Message Sender │ │ Media Service │
│ (Browser) │ │ │
│ │ │ Download, cache, │
│ Playwright DM compose │ │ deduplicate (SHA-1) │
│ Shadow DOM handling │ │ Matrix thumbnail │
└───────────────────────┘ │ resolution │
└───────────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ SQLite Database │
│ │
│ processed_posts │ sent_messages │ successful_messages │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ React Dashboard + Express API/SSE host │
│ │
│ Typed contracts │ SSE events │ Analytics │
│ Runtime config │ Bot controls │ Media gallery │
└─────────────────────────────────────────────────────────┘
```
### Service Communication
```
main.ts (Orchestrator)
│
├──► Config (Zod-validated YAML + env vars)
│
├──► Database (Better-SQLite3)
│ │
│ └──► PostsRepository
│ └──► MessagesRepository
│ └──► SuccessfulMessagesRepository
│
├──► MonitorService ──► RedditApi ──► Playwright (cookies)
│
├──► ProcessorService
│ ├──► Image download (axios)
│ ├──► Gender detection (OpenRouter)
│ ├──► Full analysis (OpenRouter)
│ └──► StrategySelector ──► MessagesRepository
│
├──► MessageSender (Browser - Playwright)
│
├──► ResponseChecker ──► Playwright (inbox navigation)
│
├──► ChatService ──► Playwright (chat.reddit.com shadow DOM)
│
├──► MediaService ──► Playwright (media download)
│
├──► MediaSyncService (background periodic sync)
│
├──► AutoReplyService ──► OpenRouter (reply generation)
│
├──► RateLimiter (p-queue, jitter)
│
└──► WebServer (Express, SSE)
```
---
## Directory Structure
```
RedditBot/
├── .dockerignore # Docker ignore rules
├── .gitignore # Git ignore rules
├── agents.md # Engineering standards & agent guidelines
├── config.yaml # Main YAML configuration file
├── docker-compose.yml # Docker Compose setup
├── DOKPLOY_DEPLOY.md # Dokploy deployment guide
├── Dockerfile # Container build definition
├── package.json # Node.js dependencies & scripts
├── tsconfig.json # TypeScript configuration
├── apps/
│ └── dashboard/ # React, TypeScript, React Router, and Vite
│ ├── src/ # Shell, routes, feature slices, API clients
│ └── dist/ # Generated production assets (not committed)
├── packages/
│ └── api-contracts/ # Shared Zod HTTP and realtime schemas
├── scripts/
│ ├── inspect-chat-dom.ts # Debug: inspect chat DOM structure
│ └── login.ts # Manual browser login helper
└── src/
├── index.ts # Main entry point / orchestrator (391 lines)
├── config/
│ └── index.ts # Config loader + Zod validation schema (157 lines)
├── database/
│ ├── client.ts # SQLite setup + migration runner (121 lines)
│ └── repositories.ts # Repository pattern: Posts, Messages, Success (433 lines)
├── reddit/
│ └── api.ts # Reddit API client (axios + Playwright cookies) (233 lines)
├── ai/
│ └── openrouter.ts # OpenRouter AI client (image analysis, message gen) (307 lines)
├── browser/
│ ├── client.ts # Playwright persistent context + login (302 lines)
│ ├── message-page.ts # DM compose page automation + shadow DOM (560 lines)
│ └── media-downloader.ts # Playwright-based media downloader (29 lines)
├── services/
│ ├── monitor.ts # Post discovery service (72 lines)
│ ├── processor.ts # Image download + AI + message pipeline (132 lines)
│ ├── response-checker.ts # Inbox response checker (179 lines)
│ ├── strategy-selector.ts # A/B testing explore/exploit (101 lines)
│ ├── chat.ts # Chat scraping + messaging + auto-reply (1123+ lines)
│ ├── media.ts # Media download, caching, indexing (254 lines)
│ ├── media-sync.ts # Periodic background media sync (119 lines)
│ └── auto-reply.ts # Auto-reply to chat conversations (199 lines)
├── utils/
│ ├── logger.ts # Winston structured logging (32 lines)
│ ├── rate-limiter.ts # Token-bucket rate limiting (114 lines)
│ ├── retry.ts # Exponential backoff wrapper (28 lines)
│ └── image.ts # Image download + base64 + MIME validation (34 lines)
└── web/
├── server.ts # Express API, SSE, and dashboard host
├── dashboard-assets.ts # Required production React-build resolver
└── events/ # Typed event hub and Express SSE adapter
```
---
## Component Reference
### Core Services
#### MonitorService (`src/services/monitor.ts`)
Discovers new image posts across configured subreddits.
- Shuffles subreddit order each cycle
- Fetches new posts via `RedditApi`
- Filters out already-processed posts and previously-messaged users
- Returns the single newest valid post per cycle
#### ProcessorService (`src/services/processor.ts`)
Core processing pipeline for each discovered post.
1. Downloads image via `ImageService`
2. Validates MIME type and file size
3. Runs quick gender detection (cost-saving pre-filter)
4. Selects A/B strategy variant via `StrategySelector`
5. Runs full AI analysis + message generation via `OpenRouterService`
6. Handles AI refusals gracefully (marks post, tries next)
#### StrategySelector (`src/services/strategy-selector.ts`)
Implements explore/exploit A/B testing for message variants.
- Tracks response rates per strategy variant
- Uses minimum sample thresholds before trusting statistics
- Applies random exploration rate for continued discovery
- Selects best variant based on weighted response rates
#### ResponseChecker (`src/services/response-checker.ts`)
Background service that monitors for user replies.
- Navigates Reddit inbox via Playwright
- Identifies responses to sent messages
- Updates `got_response` flag in database
- Stores successful message/response pairs for few-shot learning
#### ChatService (`src/services/chat.ts`)
Most complex service - manages Reddit chat interactions.
- Scrapes `chat.reddit.com` using JavaScript injection (shadow DOM traversal)
- Manages conversation cache (in-memory + disk JSON)
- Loads full conversation history with pagination
- Sends messages with human-like typing delays
- Verifies message delivery
- Detects upsell/spam keywords in chat
- Parameter: `myUsername` is hardcoded (should be from config)
#### MediaService (`src/services/media.ts`)
Media download, caching, and indexing.
- Downloads images from chat conversations
- Resolves Matrix blurred thumbnails to full resolution
- Stores files in `data/media/` directory
- Maintains JSON index with SHA-1 deduplication
- Tracks download status per conversation
#### MediaSyncService (`src/services/media-sync.ts`)
Periodic background media synchronization.
- Runs incremental syncs (top 10 chats) or full syncs (all conversations)
- Configurable interval via `mediaSyncInterval`
- Tracks sync status in memory
- Exposed via web dashboard endpoints
#### AutoReplyService (`src/services/auto-reply.ts`)
Automated reply to Reddit chat conversations.
- Checks for unread messages in cached conversations
- Detects upsell/spam keywords
- Generates AI reply suggestions via `OpenRouterService`
- Sends replies (auto or pending for manual approval)
- Event-based architecture for dashboard integration
### External Integrations
#### RedditApi (`src/reddit/api.ts`)
Axios-based Reddit API client.
- Fetches posts from subreddits (`/r/{name}/new.json`)
- Validates posts (image-only, skips deleted authors)
- Extracts image URLs with fallback chain: `preview` > `i.redd.it` > `imgur` > `thumbnail`
- Checks user profiles against seller/spam keywords
- Uses Playwright browser cookies for authenticated requests
#### OpenRouterService (`src/ai/openrouter.ts`)
OpenRouter API client using OpenAI-compatible SDK.
- `analyzeImage()`: Multi-model image analysis with fallback chain
- `detectGenderOnly()`: Quick cost-saving gender detection
- `generateMessage()`: DM text with few-shot examples from DB
- `generateReply()`: AI chat reply from conversation history
- `detectUpsell()`: Keyword-based spam/OnlyFans detection
- Refusal detection via heuristic patterns
#### Browser Client (`src/browser/client.ts`)
Playwright persistent context manager.
- Browser session persistence (cookies/storage)
- Login verification (multiple UI checks + API fallback)
- Automated login flow (form fill, 2FA/captcha detection)
- Debug screenshot capture on login failure
#### MessagePage (`src/browser/message-page.ts`)
Reddit DM compose page automation.
- Fills recipient/subject/body fields
- Extensive Shadow DOM support for `faceplate-*` components
- Detects recipient errors, verifies submission success
- Supports dry-run mode for testing
### Data Layer
#### Database Client (`src/database/client.ts`)
Better-SQLite3 database initialization.
- Creates database at configured path
- Enables WAL mode and foreign keys
- Runs migrations on startup
- Safe column-addition for schema evolution
#### Repositories (`src/database/repositories.ts`)
Three repository classes abstracting SQLite operations.
- **PostsRepository**: Processed post CRUD, mark sent, update analysis
- **MessagesRepository**: Sent message CRUD, response tracking, strategy stats, subreddit stats, daily trends
- **SuccessfulMessagesRepository**: Successful pairs for few-shot learning, subreddit-weighted retrieval
### Utilities
#### Logger (`src/utils/logger.ts`)
Winston structured logging.
- JSON format for log files
- Colored console output
- File logging: `error.log` and combined log
#### RateLimiter (`src/utils/rate-limiter.ts`)
Token-bucket rate limiting.
- p-queue with concurrency=1
- Minimum delay between messages with random jitter
- Hourly message count tracking
- Timer status exposed to dashboard
#### Retry (`src/utils/retry.ts`)
Exponential backoff wrapper.
- p-retry with configurable retries (default 3)
- Backoff range: 5s to 60s
- Logging on retries and failures
#### Image (`src/utils/image.ts`)
Image processing utilities.
- Download via axios
- Base64 conversion for AI API
- MIME type validation (image/jpeg, image/png, image/webp)
### Web Dashboard
#### WebServer (`src/web/server.ts`)
Express server with API route modules and SSE transport.
- Stats, activity, messages, chats, conversations, media endpoints
- Bot controls (start/stop)
- Typed resumable events at `/api/events/stream`
- Temporary legacy log stream at `/api/logs/stream`
- Analytics: strategy, subreddit, daily trends
- Runtime config management (GET/PUT)
- Debug endpoints
#### React dashboard (`apps/dashboard`)
- React Router routes Overview, Conversations, Media, Performance, Operations,
and Settings.
- TanStack Query owns authoritative HTTP snapshots and mutation state.
- Feature-local clients validate responses with schemas from
`packages/api-contracts`.
- Operations consumes typed dashboard events and invalidates affected query
keys; cached content remains visible if the stream reconnects.
- Vite writes production assets to `apps/dashboard/dist`.
The asset resolver requires the production React build. Express serves those
assets, API/SSE routes, and private media behind one authentication boundary.
---
## Data Flow
### Post Processing Pipeline
```
1. MonitorService polls subreddits
│
├─► RedditApi.fetchPosts()
│ └─► GET /r/{subreddit}/new.json
│ └─► Filter: image posts only, valid author
│
├─► Filter: skip processed posts (DB check)
├─► Filter: skip previously messaged users
│
└─► Return newest valid post
│
2. ProcessorService.processPost(post)
│
├─► ImageService.downloadImage(url)
│ └─► Validate MIME type, file size
│
├─► OpenRouter.detectGenderOnly()
│ └─► Quick pre-filter (cost-saving)
│ └─► Skip if gender doesn't match target
│
├─► StrategySelector.selectStrategy(subreddit, author)
│ └─► Explore/exploit algorithm
│
├─► OpenRouter.analyzeImage(image)
│ └─► Multi-model fallback chain
│ └─► Detect gender, describe image
│
├─► OpenRouter.generateMessage(analysis, title, examples, strategy)
│ └─► Few-shot learning from successful_messages
│
└─► Mark post as processed in DB
│
3. MessagePage.sendDM(author, subject, message)
│
├─► Navigate to /compose?to={author}
├─► Fill fields (with shadow DOM handling)
├─► Submit message
└─► Verify success
│
└─► Record sent_messages in DB
```
### Response Monitoring Flow
```
1. ResponseChecker.checkResponses()
│
├─► Navigate to /message/inbox
├─► Parse response items
│
├─► For each response:
│ ├─► Match to sent_messages by recipient + timestamp
│ ├─► Update got_response = 1
│ ├─► Store message/response pair
│ └─► Add to successful_messages (for few-shot learning)
│
└─► Update strategy stats in DB
```
### Chat Interaction Flow
```
1. ChatService.fetchChats()
│
├─► Navigate to chat.reddit.com
├─► Traverse shadow DOM via JS injection
├─► Extract active conversations
└─► Cache in memory + disk JSON
│
2. ChatService.loadConversation(chatId)
│
├─► Load from cache
├─► Fetch full history (pagination)
└─► Return conversation object
│
3. ChatService.sendMessage(chatId, text)
│
├─► Navigate to chat view
├─► Fill message input (shadow DOM)
├─► Apply typing delay (human-like)
├─► Submit
└─► Verify delivery
```
---
## Database Schema
### Table: `processed_posts`
Tracks all posts that have been discovered and processed.
| Column | Type | Constraints | Description |
|---|---|---|---|
| `id` | TEXT | PRIMARY KEY | Reddit post ID |
| `subreddit` | TEXT | NOT NULL | Source subreddit |
| `author` | TEXT | NOT NULL | Post author username |
| `title` | TEXT | | Post title |
| `image_url` | TEXT | | Original image URL |
| `image_analysis` | TEXT | | AI-generated image description |
| `generated_message` | TEXT | | AI-generated DM text |
| `processed_at` | INTEGER | NOT NULL | Unix timestamp |
| `message_sent` | INTEGER | DEFAULT 0 | 0 = not sent, 1 = sent |
| `error` | TEXT | | Error message if processing failed |
**Indexes**: `idx_posts_subreddit`, `idx_posts_author`
### Table: `sent_messages`
Tracks all DMs sent by the bot.
| Column | Type | Constraints | Description |
|---|---|---|---|
| `id` | INTEGER | PRIMARY KEY AUTOINCREMENT | Internal ID |
| `post_id` | TEXT | FK → processed_posts.id | Source post |
| `recipient` | TEXT | NOT NULL | Recipient username |
| `subject` | TEXT | DEFAULT 'Stunning' | DM subject line |
| `message` | TEXT | NOT NULL | DM body |
| `sent_at` | INTEGER | NOT NULL | Unix timestamp |
| `strategy_id` | TEXT | | A/B test variant identifier |
| `got_response` | INTEGER | DEFAULT 0 | 0 = no response, 1 = responded |
| `response_checked_at` | INTEGER | | Timestamp of last response check |
**Indexes**: `idx_messages_recipient`, `idx_messages_strategy`, `idx_messages_response`
### Table: `successful_messages`
Stores successful message/response pairs for few-shot learning.
| Column | Type | Constraints | Description |
|---|---|---|---|
| `id` | INTEGER | PRIMARY KEY AUTOINCREMENT | Internal ID |
| `sent_message_id` | INTEGER | FK → sent_messages.id (nullable) | Related message |
| `original_message` | TEXT | NOT NULL | The DM that got a response |
| `image_analysis` | TEXT | | Context from image analysis |
| `post_title` | TEXT | | Original post title |
| `response_snippet` | TEXT | | First reply text |
| `subreddit` | TEXT | | For weighted example selection |
| `created_at` | INTEGER | NOT NULL | Unix timestamp |
**Indexes**: `idx_success_subreddit`, `idx_success_message`
### Migration Strategy
The database uses progressive migrations that:
- Run on every startup
- Use `ALTER TABLE ADD COLUMN IF NOT EXISTS` for safe schema evolution
- Create indexes with `IF NOT EXISTS` for idempotency
- Enable WAL mode and foreign keys for performance and integrity
---
## API Reference
### Web Dashboard Endpoints
All endpoints are served by the Express server at `http://localhost:{port}`.
#### Bot Status & Control
| Method | Endpoint | Description |
|---|---|---|
| GET | `/api/stats` | Bot statistics (running state, post/message/response counts, response rate) |
| POST | `/api/start` | Start the bot |
| POST | `/api/stop` | Stop the bot |
#### Activity & Messages
| Method | Endpoint | Description |
|---|---|---|
| GET | `/api/activity` | Recent activity log entries |
| GET | `/api/posts/by-author/:username` | Look up processed posts by user |
| GET | `/api/messages` | Recent sent messages |
| GET | `/api/successful` | Successful message/response pairs |
#### Chat & Conversations
| Method | Endpoint | Description |
|---|---|---|
| GET | `/api/chats` | Active cached conversations |
| POST | `/api/chats/fetch-all` | Fetch all chats from Reddit |
| GET | `/api/conversations` | All cached conversations |
| GET | `/api/conversations/:id` | Single conversation detail |
| POST | `/api/conversations/:id/suggest-reply` | AI suggest reply for conversation |
| POST | `/api/conversations/:id/send-reply` | Send chat reply |
| POST | `/api/conversations/:id/approve-reply` | Approve pending auto-reply |
| POST | `/api/conversations/:id/reject-reply` | Reject pending auto-reply |
| POST | `/api/conversations/:id/dismiss-upsell` | Dismiss upsell flag |
#### Media
| Method | Endpoint | Description |
|---|---|---|
| GET | `/api/media` | Downloaded media gallery |
| GET | `/api/media/sync` | Media sync status |
| POST | `/api/media/sync` | Trigger incremental sync (top 10 chats) |
| POST | `/api/media/sync/full` | Trigger full sync (all conversations) |
#### Analytics
| Method | Endpoint | Description |
|---|---|---|
| GET | `/api/analytics/by-subreddit` | Response rate by subreddit |
| GET | `/api/analytics/by-strategy` | Response rate by strategy variant |
| GET | `/api/analytics/trend` | Daily response rate trend |
#### Configuration
| Method | Endpoint | Description |
|---|---|---|
| GET | `/api/config` | Current runtime configuration |
| PUT | `/api/config` | Update runtime config (rate limits, dry run, poll interval) |
#### Logs & Debugging
| Method | Endpoint | Description |
|---|---|---|
| GET | `/api/events/stream` | Typed resumable SSE event stream |
| GET | `/api/logs/stream` | Temporary legacy SSE log stream |
| GET | `/api/logs` | Recent logs (HTTP) |
| GET | `/api/timers` | Rate limiter timer status |
| POST | `/api/debug/inspect-dom` | Debug: inspect chat DOM structure |
| POST | `/api/debug/check-responses` | Debug: manual response check |
---
## Configuration
### Configuration Sources (Priority Order)
1. **`config.yaml`** - Primary configuration file (project root)
2. **Environment Variables** - Override specific fields:
- `OPENROUTER_API_KEY` - OpenRouter API key
- `REDDIT_USERNAME` - Reddit username
- `REDDIT_PASSWORD` - Reddit password
- `BROWSER_HEADLESS` - Browser headless mode (`true`/`false`)
### Configuration Schema (Zod-Validated)
```typescript
{
reddit: {
subreddits: string[] // Subreddits to monitor
pollInterval: number // Minutes between polls
targetGender: string // Target gender filter
sellerKeywords: string[] // Keywords for spam/seller detection
}
openrouter: {
apiKey: string // OpenRouter API key
model: string // Primary AI model
fallbackModels: string[] // Fallback models on refusal
systemPrompt: string // System prompt for analysis
messagePrompt: string // System prompt for message generation
}
rateLimit: {
minDelayMs: number // Minimum delay between messages
maxMessagesPerHour: number // Hourly message cap
jitterMs: number // Random jitter range
}
browser: {
headless: boolean // Headless mode
userDataDir: string // Persistent session directory
}
database: {
path: string // SQLite database path
}
mediaSync: {
enabled: boolean // Enable media sync
intervalMinutes: number // Sync interval
}
autoReply: {
enabled: boolean // Enable auto-reply
autoSend: boolean // Auto-send or pending approval
checkInterval: number // Check interval in minutes
}
strategyVariants: {
name: string // Variant name
prompt: string // Message style prompt
enabled: boolean // Enable/disable variant
}[]
web: {
port: number // Dashboard port
}
}
```
### Runtime Configuration
The following settings can be modified via the web dashboard without restarting:
- `rateLimit.minDelayMs` - Message delay
- `rateLimit.maxMessagesPerHour` - Hourly cap
- `rateLimit.jitterMs` - Jitter range
- `reddit.pollInterval` - Poll frequency
- `dryRun` - Dry run mode (log only, no actual sends)
---
## Design Patterns
### Repository Pattern
All database operations are abstracted behind three repository classes:
- `PostsRepository` - Post lifecycle management
- `MessagesRepository` - Message tracking and analytics
- `SuccessfulMessagesRepository` - Few-shot learning data
Benefits:
- Single source of truth for data access
- Easy to swap implementations (e.g., different databases)
- Consistent error handling and query patterns
### Dependency Injection
Services receive dependencies via constructor parameters:
```typescript
class ProcessorService {
constructor(
private config: Config,
private redditApi: RedditApi,
private openrouter: OpenRouterService,
private strategySelector: StrategySelector,
private messageSender: MessagePage,
private postsRepo: PostsRepository,
private messagesRepo: MessagesRepository,
private successfulRepo: SuccessfulMessagesRepository,
private logger: Logger
) {}
}
```
Benefits:
- Loose coupling between components
- Easy to mock dependencies for testing
- Clear dependency graph
### Strategy Pattern
`StrategySelector` implements A/B testing with multiple message variants:
```typescript
class StrategySelector {
selectStrategy(subreddit: string, author: string): string {
// Explore/exploit algorithm
// Returns variant name based on response rates
}
}
```
Benefits:
- Swappable message strategies
- Measurable performance per variant
- Configurable exploration rate
### Factory Pattern
Configuration and database creation use factory functions:
- `loadConfig()` - Loads, merges env vars, validates with Zod
- `createDatabase()` - Initializes SQLite, runs migrations
### Observer Pattern
SSE (Server-Sent Events) carries validated dashboard events:
```typescript
// The transport-agnostic hub assigns IDs and retains a bounded replay window.
eventHub.publish({ type: "message.sent", payload: { /* validated fields */ } });
// Express adapts the hub to resumable SSE.
router.get("/events/stream", broadcaster.events);
```
### Circuit Breaker / Retry
`withRetry()` wrapper for resilient external calls:
```typescript
await withRetry(
() => redditApi.fetchPosts(subreddit),
{ retries: 3, logger }
);
```
Multiple fallback chains:
- AI model fallback (primary → fallback models)
- Image URL fallback (preview → i.redd.it → imgur → thumbnail)
### Rate Limiter Pattern
Token-bucket-like implementation:
```typescript
class RateLimiter {
async waitForSlot(): Promise<void> {
// Enforce minimum delay + jitter
// Track hourly counts
// Block if hourly cap reached
}
}
```
---
## External Integrations
### Reddit API
- **Authentication**: Playwright browser cookies (persistent session)
- **Endpoints Used**:
- `GET /r/{name}/new.json` - Fetch new posts
- `GET /user/{username}/profile` - Check for seller/spam indicators
- `GET /message/inbox` - Check for responses
- `GET chat.reddit.com` - Chat scraping
- **Rate Limits**: Enforced by `RateLimiter` utility
### OpenRouter AI
- **SDK**: OpenAI-compatible (`openai` npm package)
- **Base URL**: `https://openrouter.ai/api/v1`
- **Endpoints Used**:
- `POST /chat/completions` - Image analysis, message generation, replies
- **Model Fallback**: Primary model → fallback models on refusal
- **Cost Optimization**: Quick gender detection before full analysis
### Playwright Browser
- **Context**: Persistent (cookies/session storage saved to disk)
- **Usage**:
- Automated Reddit login
- DM compose page automation
- Inbox navigation for response checking
- Chat page scraping (shadow DOM traversal)
- Media file downloads
- **Shadow DOM**: JavaScript injection for `faceplate-*` component traversal
---
## Deployment
### Docker
The project includes full Docker support:
- **`Dockerfile`** - Playwright image with browser, VNC, and frontend/server build tooling
- **`docker-compose.yml`** - Service orchestration
- **`.dockerignore`** - Excludes unnecessary files
### Dokploy
Deployment documentation is provided in `DOKPLOY_DEPLOY.md`.
### Required Environment Variables
| Variable | Required | Description |
|---|---|---|
| `OPENROUTER_API_KEY` | Yes | OpenRouter API key for AI |
| `REDDIT_USERNAME` | Yes | Reddit username |
| `REDDIT_PASSWORD` | Yes | Reddit password |
| `BROWSER_HEADLESS` | No | Browser headless mode (default: `true`) |
### Pre-Deployment Checklist
1. Set up `config.yaml` with target subreddits and settings
2. Set environment variables (or use `.env` file)
3. Run `npm install` to install dependencies
4. Run `npm run build` to compile the server and Vite dashboard
5. Run `scripts/login.ts` once to establish browser session
6. Start the bot (via `npm start` or Docker)
---
## Technical Debt & Recommendations
### Critical
1. **Dashboard security cutover** - Wire authenticated React asset, API, SSE,
debug, and private-media hosting before exposing the dashboard externally.
2. **No README.md** - No project documentation for new developers.
3. **Hardcoded Username** - `ChatService` has `myUsername = 'Upset_Marsupial1006'` hardcoded in JS evaluation scripts. Should be parameterized from config.
### High Priority
4. **God Class** - `main.ts` (391 lines) creates all dependencies and wires everything. Consider extracting an `AppContainer` or `DependencyContainer`.
5. **ChatService Size** - 1123+ lines. Consider splitting into:
- `ChatScraper` (navigation + DOM traversal)
- `ConversationCache` (cache management)
- `ChatMessenger` (sending + verification)
6. **No .env.example** - No template showing required environment variables.
### Medium Priority
7. **Config Mutability** - Runtime config updates via `PUT /api/config` modify the in-memory config object. Consider a proper config service with validation on updates.
8. **Error Handling** - Some services have basic try/catch. Consider structured error types with retry logic.
### Low Priority
11. **Logging** - Winston is well-configured, but consider adding request IDs for tracing.
12. **Type Safety** - Some `page.evaluate()` calls use `any` types. Consider stricter typing for DOM structures.
13. **Media Storage** - Media files stored in `data/media/` with no cleanup policy. Consider retention policies.