Restructure chat service, web routes, and add test suite
Split monolithic chat service into modular structure with Matrix client support, extract web server into per-resource route modules, introduce bootstrap/main-loop separation, and add comprehensive vitest coverage across services, repositories, and utilities. Adds project docs and CLAUDE.md agent instructions.
This commit is contained in:
1 parent
dfa0580859
commit
633bb0ec04
84 files changed
+19278
-3888
No files matched your search
@@ -0,0 +1,488 @@
|
||||
# Configuration Guide
|
||||
|
||||
Complete reference for configuring RedditBot.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Configuration Overview](#configuration-overview)
|
||||
2. [Configuration Files](#configuration-files)
|
||||
3. [Environment Variables](#environment-variables)
|
||||
4. [config.yaml Reference](#configyaml-reference)
|
||||
5. [Runtime Configuration](#runtime-configuration)
|
||||
6. [Configuration Validation](#configuration-validation)
|
||||
7. [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Configuration Overview
|
||||
|
||||
RedditBot uses a layered configuration approach:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Priority Order (High → Low) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ 1. Environment Variables │
|
||||
│ (OPENROUTER_API_KEY, REDDIT_USERNAME, etc.) │
|
||||
│ │
|
||||
│ 2. config.yaml │
|
||||
│ (Primary configuration file) │
|
||||
│ │
|
||||
│ 3. Defaults │
|
||||
│ (Hardcoded defaults in code) │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Configuration is loaded at startup by `loadConfig()` in `src/config/index.ts`, which:
|
||||
|
||||
1. Reads `config.yaml` from the project root
|
||||
2. Merges environment variable overrides
|
||||
3. Validates the entire configuration tree using Zod
|
||||
4. Returns a strongly-typed `Config` object
|
||||
|
||||
---
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### config.yaml
|
||||
|
||||
The primary configuration file, located at the project root:
|
||||
|
||||
```
|
||||
RedditBot/
|
||||
├── config.yaml # ← Main configuration
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── src/
|
||||
```
|
||||
|
||||
### Data Directory
|
||||
|
||||
Runtime data is stored in `data/` (created automatically):
|
||||
|
||||
```
|
||||
data/
|
||||
├── reddit-bot.db # SQLite database
|
||||
├── browser/ # Browser session data (cookies, storage)
|
||||
├── media/ # Downloaded media files
|
||||
│ ├── index.json # Media index (SHA-1 → filename)
|
||||
│ ├── abc123.jpg # Media file 1
|
||||
│ └── def456.png # Media file 2
|
||||
└── chats/ # Cached conversation JSON files
|
||||
├── chat_abc123.json
|
||||
└── chat_def456.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Environment variables override corresponding values in `config.yaml`.
|
||||
|
||||
| Variable | Required | Type | Description | Example |
|
||||
|---|---|---|---|---|
|
||||
| `OPENROUTER_API_KEY` | Yes | string | OpenRouter API key for AI services | `sk-or-v1-abc123...` |
|
||||
| `REDDIT_USERNAME` | Yes | string | Reddit username for authentication | `MyBotUser` |
|
||||
| `REDDIT_PASSWORD` | Yes | string | Reddit password for authentication | `secret123` |
|
||||
| `BROWSER_HEADLESS` | No | boolean | Run browser in headless mode | `true` or `false` |
|
||||
|
||||
### Setting Environment Variables
|
||||
|
||||
**Option 1: `.env` file** (recommended for development)
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
|
||||
```env
|
||||
OPENROUTER_API_KEY=sk-or-v1-abc123...
|
||||
REDDIT_USERNAME=MyBotUser
|
||||
REDDIT_PASSWORD=secret123
|
||||
BROWSER_HEADLESS=true
|
||||
```
|
||||
|
||||
> **Note**: Add `.env` to `.gitignore` - never commit credentials.
|
||||
|
||||
**Option 2: Shell export**
|
||||
|
||||
```bash
|
||||
export OPENROUTER_API_KEY=sk-or-v1-abc123...
|
||||
export REDDIT_USERNAME=MyBotUser
|
||||
export REDDIT_PASSWORD=secret123
|
||||
```
|
||||
|
||||
**Option 3: Docker**
|
||||
|
||||
Pass via `docker-compose.yml` or Docker CLI:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
- OPENROUTER_API_KEY=sk-or-v1-abc123...
|
||||
- REDDIT_USERNAME=MyBotUser
|
||||
- REDDIT_PASSWORD=secret123
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## config.yaml Reference
|
||||
|
||||
Complete reference for every configuration option.
|
||||
|
||||
### Reddit Settings
|
||||
|
||||
```yaml
|
||||
reddit:
|
||||
# Subreddits to monitor for new posts
|
||||
subreddits:
|
||||
- gonewild
|
||||
- ratemyboobs
|
||||
- HomemadeNsfw
|
||||
- realgirls
|
||||
- plussize_gonewild
|
||||
# ... add or remove subreddits
|
||||
|
||||
# Minutes between polling cycles
|
||||
pollInterval: 15
|
||||
|
||||
# Target gender for image analysis filtering
|
||||
# Options: "F" (female), "M" (male)
|
||||
targetGender: "F"
|
||||
|
||||
# Keywords used to detect sellers/spam profiles
|
||||
# Posts from users matching these keywords are skipped
|
||||
sellerKeywords:
|
||||
- seller
|
||||
- onlyfans
|
||||
- link in bio
|
||||
- check my profile
|
||||
- promo
|
||||
- discount
|
||||
- affiliate
|
||||
```
|
||||
|
||||
### OpenRouter AI Settings
|
||||
|
||||
```yaml
|
||||
openrouter:
|
||||
# OpenRouter API key (can also be set via OPENROUTER_API_KEY env var)
|
||||
apiKey: "sk-or-v1-abc123..."
|
||||
|
||||
# Primary AI model for image analysis
|
||||
# Models must be available on OpenRouter
|
||||
model: "meta-llama/llama-3.2-90b-vision-preview"
|
||||
|
||||
# Fallback models tried if the primary model refuses or fails
|
||||
fallbackModels:
|
||||
- "google/gemini-2.0-flash-001"
|
||||
- "meta-llama/llama-3.2-11b-vision-instruct"
|
||||
|
||||
# Quick detection model (cheaper/faster, used for gender pre-filter)
|
||||
quickModel: "google/gemini-2.0-flash-lite-001"
|
||||
|
||||
# System prompt for image analysis
|
||||
# The AI will describe the image and detect gender
|
||||
systemPrompt: |
|
||||
Analyze this image and provide:
|
||||
1. Gender detected (F or M)
|
||||
2. Detailed description of the image content
|
||||
3. Notable features (hair color, clothing, setting, etc.)
|
||||
|
||||
Return as JSON: {"gender": "F", "description": "...", "features": [...]}
|
||||
|
||||
# System prompt for message generation
|
||||
# Used when crafting DMs to post authors
|
||||
messagePrompt: |
|
||||
Generate a natural, friendly DM for this Reddit post.
|
||||
|
||||
Image analysis: {description}
|
||||
Post title: {title}
|
||||
Strategy: {strategy}
|
||||
|
||||
Guidelines:
|
||||
- Be complimentary but not creepy
|
||||
- Reference something specific from the image
|
||||
- Keep it under 2 sentences
|
||||
- Sound like a genuine person, not a bot
|
||||
- Do NOT ask for anything in return
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
```yaml
|
||||
rateLimit:
|
||||
# Minimum delay between messages (milliseconds)
|
||||
# Higher values reduce detection risk
|
||||
minDelayMs: 30000
|
||||
|
||||
# Maximum messages per hour
|
||||
# Bot will pause when this limit is reached
|
||||
maxMessagesPerHour: 20
|
||||
|
||||
# Random jitter added to delay (milliseconds)
|
||||
# Adds unpredictability to timing
|
||||
jitterMs: 15000
|
||||
```
|
||||
|
||||
**Timing example with defaults:**
|
||||
```
|
||||
Minimum delay: 30,000ms (30 seconds)
|
||||
Jitter: 0 to 15,000ms (0-15 seconds)
|
||||
Effective delay: 30-45 seconds between messages
|
||||
Max messages: 20 per hour
|
||||
```
|
||||
|
||||
### Browser Settings
|
||||
|
||||
```yaml
|
||||
browser:
|
||||
# Run browser in headless mode (no UI)
|
||||
# Set to false for debugging
|
||||
headless: true
|
||||
|
||||
# Directory for persistent browser data
|
||||
# Cookies and session storage are saved here
|
||||
userDataDir: "./data/browser"
|
||||
```
|
||||
|
||||
**Browser session persistence:**
|
||||
|
||||
```
|
||||
data/browser/
|
||||
├── Default/
|
||||
│ ├── Cookies # Reddit session cookies
|
||||
│ ├── Local Storage # Local storage data
|
||||
│ └── Preferences # Browser preferences
|
||||
├── Login Data # Saved login info
|
||||
└── Preferences # Browser state
|
||||
```
|
||||
|
||||
> **Important**: Keep this directory intact. Deleting it requires re-login.
|
||||
|
||||
### Database Settings
|
||||
|
||||
```yaml
|
||||
database:
|
||||
# Path to SQLite database file
|
||||
path: "./data/reddit-bot.db"
|
||||
```
|
||||
|
||||
The database is auto-created on first run. Migrations run automatically on startup.
|
||||
|
||||
### Media Sync Settings
|
||||
|
||||
```yaml
|
||||
mediaSync:
|
||||
# Enable periodic media synchronization
|
||||
enabled: true
|
||||
|
||||
# Interval between sync cycles (minutes)
|
||||
intervalMinutes: 30
|
||||
```
|
||||
|
||||
**Sync behavior:**
|
||||
- **Incremental sync** (automatic): Downloads media from top 10 conversations
|
||||
- **Full sync** (manual via dashboard): Downloads media from all conversations
|
||||
|
||||
### Auto-Reply Settings
|
||||
|
||||
```yaml
|
||||
autoReply:
|
||||
# Enable automatic chat replies
|
||||
enabled: true
|
||||
|
||||
# Auto-send replies (true) or mark as pending for approval (false)
|
||||
autoSend: false
|
||||
|
||||
# Minutes between checking for new messages
|
||||
checkInterval: 10
|
||||
```
|
||||
|
||||
**Reply modes:**
|
||||
|
||||
| autoSend | Behavior |
|
||||
|---|---|
|
||||
| `true` | Bot sends AI-generated replies automatically |
|
||||
| `false` | Bot suggests replies, user approves via dashboard |
|
||||
|
||||
### Strategy Variants (A/B Testing)
|
||||
|
||||
```yaml
|
||||
strategyVariants:
|
||||
# Variant 1: Compliment-based
|
||||
- name: "compliment"
|
||||
prompt: |
|
||||
Write a friendly compliment about the person's appearance.
|
||||
Be genuine and specific. Keep it short and natural.
|
||||
enabled: true
|
||||
|
||||
# Variant 2: Question-based
|
||||
- name: "question"
|
||||
prompt: |
|
||||
Write a friendly message that includes a question.
|
||||
Engage them in conversation naturally.
|
||||
enabled: true
|
||||
|
||||
# Variant 3: Casual observation
|
||||
- name: "observation"
|
||||
prompt: |
|
||||
Write a casual observation about the photo or setting.
|
||||
Sound like you're commenting to a friend.
|
||||
enabled: true
|
||||
```
|
||||
|
||||
### Web Dashboard Settings
|
||||
|
||||
```yaml
|
||||
web:
|
||||
# Port for the web dashboard
|
||||
port: 3000
|
||||
```
|
||||
|
||||
### Global Settings
|
||||
|
||||
```yaml
|
||||
# Global dry run mode
|
||||
# When true, all actions are logged but not actually performed
|
||||
dryRun: false
|
||||
|
||||
# Number of successful message examples to include in prompts
|
||||
fewShotExamples: 3
|
||||
|
||||
# Minimum responses needed before trusting strategy stats
|
||||
minStrategySamples: 10
|
||||
|
||||
# Exploration rate for A/B testing (0.0 to 1.0)
|
||||
# 0.15 = 15% of messages use random variants (exploration)
|
||||
explorationRate: 0.15
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Runtime Configuration
|
||||
|
||||
The following settings can be modified via the web dashboard without restarting the bot:
|
||||
|
||||
| Setting | Endpoint | Type | Description |
|
||||
|---|---|---|---|
|
||||
| `rateLimit.minDelayMs` | `PUT /api/config` | number | Message delay |
|
||||
| `rateLimit.maxMessagesPerHour` | `PUT /api/config` | number | Hourly cap |
|
||||
| `rateLimit.jitterMs` | `PUT /api/config` | number | Jitter range |
|
||||
| `reddit.pollInterval` | `PUT /api/config` | number | Poll frequency |
|
||||
| `dryRun` | `PUT /api/config` | boolean | Dry run mode |
|
||||
|
||||
### Updating Runtime Config
|
||||
|
||||
Via dashboard:
|
||||
1. Open the settings modal
|
||||
2. Modify desired values
|
||||
3. Click "Save"
|
||||
|
||||
Via API:
|
||||
```bash
|
||||
curl -X PUT http://localhost:3000/api/config \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"rateLimit": {
|
||||
"minDelayMs": 45000,
|
||||
"maxMessagesPerHour": 15,
|
||||
"jitterMs": 20000
|
||||
},
|
||||
"dryRun": true
|
||||
}'
|
||||
```
|
||||
|
||||
> **Note**: Runtime changes are in-memory only. They are lost when the bot restarts. To make changes persistent, update `config.yaml`.
|
||||
|
||||
---
|
||||
|
||||
## Configuration Validation
|
||||
|
||||
All configuration is validated using Zod at startup. Invalid configuration will cause the bot to exit with a descriptive error.
|
||||
|
||||
### Validation Rules
|
||||
|
||||
| Field | Validation |
|
||||
|---|---|
|
||||
| `reddit.subreddits` | Must be non-empty array of strings |
|
||||
| `reddit.pollInterval` | Must be positive integer |
|
||||
| `reddit.targetGender` | Must be "F" or "M" |
|
||||
| `openrouter.apiKey` | Must be non-empty string |
|
||||
| `openrouter.model` | Must be non-empty string |
|
||||
| `openrouter.fallbackModels` | Must be non-empty array |
|
||||
| `rateLimit.minDelayMs` | Must be positive integer |
|
||||
| `rateLimit.maxMessagesPerHour` | Must be positive integer |
|
||||
| `rateLimit.jitterMs` | Must be non-negative integer |
|
||||
| `browser.headless` | Must be boolean |
|
||||
| `browser.userDataDir` | Must be non-empty string |
|
||||
| `database.path` | Must be non-empty string |
|
||||
| `mediaSync.enabled` | Must be boolean |
|
||||
| `mediaSync.intervalMinutes` | Must be positive integer (if enabled) |
|
||||
| `autoReply.enabled` | Must be boolean |
|
||||
| `autoReply.autoSend` | Must be boolean |
|
||||
| `autoReply.checkInterval` | Must be positive integer (if enabled) |
|
||||
| `strategyVariants` | Must be array with name + prompt |
|
||||
| `web.port` | Must be integer 1-65535 |
|
||||
| `dryRun` | Must be boolean |
|
||||
|
||||
### Common Validation Errors
|
||||
|
||||
**Error**: `Invalid config: openrouter.apiKey must be a string`
|
||||
**Fix**: Set `OPENROUTER_API_KEY` environment variable or add `apiKey` to `config.yaml`
|
||||
|
||||
**Error**: `Invalid config: reddit.subreddits must be a non-empty array`
|
||||
**Fix**: Add at least one subreddit to `reddit.subreddits` in `config.yaml`
|
||||
|
||||
**Error**: `Invalid config: browser.userDataDir must be a string`
|
||||
**Fix**: Ensure `browser.userDataDir` is set in `config.yaml`
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Configuration Not Loading
|
||||
|
||||
**Symptom**: Bot fails to start with validation errors.
|
||||
|
||||
**Steps**:
|
||||
1. Verify `config.yaml` is in the project root
|
||||
2. Check YAML syntax (use a YAML linter)
|
||||
3. Verify environment variables are set correctly
|
||||
4. Check bot logs for specific validation errors
|
||||
|
||||
### Credentials Not Working
|
||||
|
||||
**Symptom**: Browser login fails.
|
||||
|
||||
**Steps**:
|
||||
1. Verify `REDDIT_USERNAME` and `REDDIT_PASSWORD` are correct
|
||||
2. Check that the account is not locked or requires 2FA
|
||||
3. Try manual login via `npm run login`
|
||||
4. Ensure `BROWSER_HEADLESS=false` for debugging login issues
|
||||
|
||||
### AI Not Working
|
||||
|
||||
**Symptom**: Image analysis fails or returns errors.
|
||||
|
||||
**Steps**:
|
||||
1. Verify `OPENROUTER_API_KEY` is valid
|
||||
2. Check API key has sufficient balance
|
||||
3. Verify `openrouter.model` is available on OpenRouter
|
||||
4. Check fallback models are set correctly
|
||||
5. Review logs for specific AI errors
|
||||
|
||||
### Browser Session Expired
|
||||
|
||||
**Symptom**: Bot stops with "Browser session expired" error.
|
||||
|
||||
**Steps**:
|
||||
1. Delete `data/browser/` directory
|
||||
2. Run `npm run login` to re-authenticate
|
||||
3. Restart the bot
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
**Symptom**: Dashboard fails to start with "Port already in use".
|
||||
|
||||
**Steps**:
|
||||
1. Change `web.port` in `config.yaml`
|
||||
2. Or kill the process using the port: `lsof -i :3000`
|
||||
Reference in new issue
Block a user