11 KiB
Development Guide
Guide for contributing to RedditBot development.
Table of Contents
- Project Structure
- Development Setup
- Coding Standards
- Adding a New Service
- Adding a New Strategy Variant
- Debugging
- Common Tasks
- Known Issues
Project Structure
RedditBot/
├── config.yaml # Main configuration
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript config
├── Dockerfile # Container build
├── docker-compose.yml # Docker Compose
├── apps/
│ └── dashboard/ # React + Vite operator console
├── packages/
│ └── api-contracts/ # Shared Zod transport contracts
├── scripts/
│ ├── login.ts # Manual login helper
│ └── inspect-chat-dom.ts # DOM inspection debug script
└── src/
├── index.ts # Entry point / orchestrator
├── config/
│ └── index.ts # Config loader + Zod schema
├── database/
│ ├── client.ts # SQLite setup + migrations
│ └── repositories.ts # Repository classes
├── reddit/
│ └── api.ts # Reddit API client
├── ai/
│ └── openrouter.ts # OpenRouter AI client
├── browser/
│ ├── client.ts # Managed Playwright context
│ └── media-downloader.ts # Media download
├── services/
│ ├── monitor.ts # Post discovery
│ ├── processor.ts # Processing pipeline
│ ├── response-checker.ts # Response monitoring
│ ├── strategy-selector.ts # A/B testing
│ ├── chat.ts # Chat scraping + messaging
│ ├── media.ts # Media management
│ ├── media-sync.ts # Media sync scheduler
│ └── auto-reply.ts # Auto-reply service
├── utils/
│ ├── logger.ts # Winston logger
│ ├── rate-limiter.ts # Rate limiting
│ ├── retry.ts # Retry wrapper
│ └── image.ts # Image utilities
└── web/ # Express API, events, and dashboard hosting
Development Setup
1. Install Dependencies
npm install
2. Configure
Copy and edit config.yaml:
cp config.yaml config.yaml.backup # Backup existing
# Edit config.yaml with your settings
3. Set Environment Variables
export OPENROUTER_API_KEY="sk-or-v1-your-key"
export WEB_DASHBOARD_TOKEN="long-random-secret"
4. Account authentication
Start the app, then import a combined Reddit and Matrix session dump from the local Chrome extension under Settings > Access.
5. Start Development Server
# Terminal 1: backend reload with tsx
npm run dev
# Terminal 2: Vite frontend with /api proxied to port 3000
npm run dashboard:dev
# Production build: server plus apps/dashboard/dist
npm run build && npm start
6. Access Dashboard
During frontend development, open the URL printed by Vite (normally http://localhost:5173). The Express dashboard remains on http://localhost:3000 for production/fallback verification.
Useful frontend checks:
npm run dashboard:typecheck
npm run dashboard:build
npx vitest run tests/dashboard
Coding Standards
TypeScript
- Strict mode: No
anytypes (useunknownwith type guards) - Interfaces: Define interfaces for all data structures
- Null checks: Use optional chaining (
?.) and nullish coalescing (??) - Async/Await: Use async/await, not
.then()chains
// Good
interface PostData {
id: string;
subreddit: string;
author: string;
}
async function fetchPost(id: string): Promise<PostData> {
const response = await api.get(`/posts/${id}`);
return response.data;
}
// Bad
async function fetchPost(id: any): Promise<any> {
return api.get(`/posts/${id}`).then(r => r.data);
}
Logging
Use winston logger, never console.log:
// Good
this.logger.info("Processed post {id}", { id: post.id });
this.logger.warn("AI refusal detected", { postId: post.id });
this.logger.error("Failed to send DM", { error: err.message });
// Bad
console.log("Processed post", post.id);
Error Handling
Use try/catch with structured error handling:
try {
await this.processPost(post);
} catch (err) {
this.logger.error("Post processing failed", {
postId: post.id,
error: err instanceof Error ? err.message : String(err)
});
// Don't throw - allow other posts to be processed
}
Service Structure
Follow single responsibility:
class NewService {
constructor(
private config: Config,
private dependency: ExistingService,
private logger: Logger
) {}
async doSomething(input: string): Promise<Result> {
// Validate input
if (!input) {
throw new Error("Input is required");
}
// Do work
const result = await this.dependency.process(input);
// Return result
return result;
}
}
File Organization
- One class per file (except repositories which share one file)
- Services in
src/services/ - Utilities in
src/utils/ - External integrations in their own folders (
src/ai/,src/reddit/,src/browser/)
Adding a New Service
1. Create the Service File
// src/services/new-service.ts
import { Config } from '../config';
import { Logger } from '../utils/logger';
interface NewServiceDeps {
config: Config;
logger: Logger;
// other dependencies
}
interface Result {
success: boolean;
data?: any;
error?: string;
}
export class NewService {
constructor(private deps: NewServiceDeps) {}
async execute(input: string): Promise<Result> {
const { config, logger } = this.deps;
logger.info("NewService executing", { input });
// Implementation here
return { success: true };
}
}
2. Add to Dependencies Interface
If the service needs other services, add them to the interface:
interface NewServiceDeps {
config: Config;
logger: Logger;
redditApi: RedditApi;
openrouter: OpenRouterService;
}
3. Wire Up in main()
In src/index.ts, add initialization:
const newService = new NewService({
config,
logger,
redditApi,
openrouter
});
// If it needs to run periodically:
setInterval(() => {
newService.execute(someInput).catch(err => {
logger.error("NewService error", { error: err.message });
});
}, intervalMs);
4. Add to the Web API and Dashboard (Optional)
If the service needs an API, add a focused router under src/web/routes/ and
mount it from src/web/server.ts. Define its transport schema in
packages/api-contracts; do not expose repository rows directly.
router.get('/new-service', async (req, res) => {
try {
const result = await newService.execute(req.query.input as string);
res.json(result);
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
Add frontend behavior under apps/dashboard/src/features/<feature>/ and drive
it with Testing Library and MSW tests under tests/dashboard/.
5. Update Config (If Needed)
Add new configuration options to:
config.yamlsrc/config/index.tsZod schema- Configuration documentation
Adding a New Strategy Variant
1. Add to config.yaml
strategyVariants:
- name: "existing_variant"
prompt: "Existing prompt..."
enabled: true
- name: "new_variant"
prompt: |
Write a friendly message that references the image content.
Be specific and genuine. Keep it under 2 sentences.
enabled: true
2. No Code Changes Needed
The StrategySelector automatically picks up new variants from the config. The algorithm will:
- Include the new variant in random exploration
- Track its response rate once messages are sent
- Promote it to exploitation if it has good stats
3. Monitor Performance
Check performance via the dashboard or API:
curl http://localhost:3000/api/analytics/by-strategy
Debugging
Account debugging
Use dashboard session status and structured logs. The backend does not expose or launch an interactive login browser.
DOM Inspection
Use the debug endpoint to inspect Reddit's shadow DOM:
curl -X POST http://localhost:3000/api/debug/inspect-dom
Or run the standalone script:
npx tsx scripts/inspect-chat-dom.ts
Logging
Adjust log level by modifying src/utils/logger.ts:
const logger = winston.createLogger({
level: 'debug', // Change from 'info' to 'debug'
// ...
});
Database Inspection
Open the database with SQLite viewer:
# Install sqlite3 if needed
brew install sqlite3
# Open database
sqlite3 data/reddit-bot.db
# Query
SELECT * FROM sent_messages LIMIT 5;
Network Debugging
Enable axios debug:
// In src/reddit/api.ts
import axios from 'axios';
axios.defaults.debug = true;
Common Tasks
Add a New Subreddit
# config.yaml
reddit:
subreddits:
- gonewild
- ratemyboobs
- new_subreddit_name # Add here
Change AI Model
# config.yaml
openrouter:
model: "new-model-name"
fallbackModels:
- "fallback-model-1"
- "fallback-model-2"
Adjust Rate Limits
# config.yaml
rateLimit:
minDelayMs: 45000 # Increase delay
maxMessagesPerHour: 15 # Reduce cap
jitterMs: 20000 # More jitter
Enable Dry Run
# config.yaml
dryRun: true # Logs all actions without sending
Or via dashboard: PUT /api/config with {"dryRun": true}
Add Database Column
// In src/database/client.ts
function runMigrations(db: Database) {
// ... existing migrations ...
// Check if column exists
const tableInfo = db.prepare(`PRAGMA table_info(sent_messages)`).all();
const hasNewColumn = tableInfo.some((col: any) => col.name === 'new_column');
if (!hasNewColumn) {
db.exec("ALTER TABLE sent_messages ADD COLUMN new_column TEXT");
}
}
Add New API Endpoint
// In src/web/server.ts
import { Request, Response } from 'express';
server.app.get('/api/new-endpoint', async (req: Request, res: Response) => {
try {
const result = await someService.doSomething();
res.json({ success: true, data: result });
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
Known Issues
Hardcoded Username in ChatService
ChatService has myUsername = 'Upset_Marsupial1006' hardcoded in JavaScript evaluation scripts. This should be parameterized from config.
Fix needed: Pass username to page.evaluate() calls.
Shadow DOM Fragility
Reddit frequently updates their faceplate-* components. Shadow DOM traversal code may break without warning.
Mitigation: Use scripts/inspect-chat-dom.ts to debug when selectors change.
Dashboard Migration Boundary
The React application lives in apps/dashboard. A production build is required
before the Express server starts; there is no legacy HTML fallback.
The repository has automated service, contract, component, and transport tests. Manual browser verification is still required for Reddit DOM changes and the final dashboard cutover.