Files
RedditBot/docs/development.md
T

12 KiB

Development Guide

Guide for contributing to RedditBot development.


Table of Contents

  1. Project Structure
  2. Development Setup
  3. Coding Standards
  4. Adding a New Service
  5. Adding a New Strategy Variant
  6. Debugging
  7. Common Tasks
  8. 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        # Playwright browser manager
    │   ├── message-page.ts  # DM compose automation
    │   └── 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 REDDIT_USERNAME="your-username"
export REDDIT_PASSWORD="your-password"
export OPENROUTER_API_KEY="sk-or-v1-your-key"

4. Initial Login

# Open browser for manual login
npm run login

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 any types (use unknown with 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.yaml
  • src/config/index.ts Zod 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:

  1. Include the new variant in random exploration
  2. Track its response rate once messages are sent
  3. 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

Browser Debugging

Run with visible browser:

# config.yaml
browser:
  headless: false

Or set environment variable:

BROWSER_HEADLESS=false npm run dev

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.