Files
RedditBot/src/config/index.ts
T
jjbruntonandClaude Opus 4.6 dfa0580859 Update source code and remove runtime data from tracking
Add gitignore rules for browser session data, SQLite databases,
media downloads, and build artifacts. Remove previously tracked
runtime files from the repository.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-20 18:54:07 +01:00

158 lines
4.9 KiB
TypeScript

import { z } from 'zod';
import fs from 'fs';
import path from 'path';
import YAML from 'yaml';
import dotenv from 'dotenv';
dotenv.config();
export const ConfigSchema = z.object({
reddit: z.object({
subreddits: z.array(z.string()).min(1),
pollIntervalMs: z.number().default(120000),
userAgent: z.string().default('RedditBot/1.0'),
targetGenders: z.array(z.enum(['male', 'female', 'other', 'unknown'])).default(['female']),
filterSellers: z.boolean().default(true),
sellerKeywords: z
.array(z.string())
.default([
'onlyfans',
'only fans',
'fansly',
'manyvids',
'linktree',
'linktr.ee',
'allmylinks',
'beacons.ai',
'cash.app',
'cashapp',
'venmo',
'paypal',
'subscribe',
'premium snap',
'premiumsnap',
'fan.ly',
'fanvue',
'loyalfans',
'pornhub.com',
'xvideos.com',
'chaturbate',
'myfreecams',
'cam girl',
'camgirl',
'buy my',
'custom content',
'custom vid',
'selling content',
'paid content',
'tip me',
'spoil me',
'findom',
'send tribute',
'wishlist',
'throne.com',
]),
username: z.string().optional(),
password: z.string().optional(),
}),
openrouter: z.object({
apiKey: z.string(),
imageAnalysisModel: z.string().default('google/gemini-3-flash-preview'),
imageAnalysisFallbackModels: z.array(z.string()).default([]),
chatModel: z.string().default('google/gemini-3-flash-preview'),
imageAnalysisPrompt: z.string(),
messageGenerationPrompt: z.string(),
}),
rateLimit: z.object({
minDelayBetweenMessagesMs: z.number().default(300000),
maxMessagesPerHour: z.number().default(8),
jitterMs: z.number().default(60000),
}),
browser: z.object({
sessionDir: z.string().default('./data/browser-session'),
headless: z.boolean().default(false),
dryRun: z.boolean().default(false),
}),
database: z.object({
path: z.string().default('./data/reddit-bot.db'),
}),
media: z.object({
syncIntervalMs: z.number().default(1800000), // 30 minutes
syncEnabled: z.boolean().default(true),
}).default({ syncIntervalMs: 1800000, syncEnabled: true }),
autoReply: z.object({
enabled: z.boolean().default(false),
autoSend: z.boolean().default(false), // If true, send replies automatically. If false, require manual approval
checkIntervalMs: z.number().default(300000), // 5 minutes
upsellKeywords: z.array(z.string()).default([
'onlyfans', 'only fans', 'fansly', 'manyvids', 'linktree', 'linktr.ee',
'subscribe', 'premium', 'paid content', 'custom content', 'custom vid',
'cashapp', 'cash app', 'venmo', 'paypal', 'tip me', 'spoil me',
'wishlist', 'throne.com', 'link in bio', 'check my profile',
'fanvue', 'loyalfans', 'cam show', 'buy my', 'selling',
'dm me for price', 'menu', 'rates', 'ppv',
]),
}).default({ enabled: false, autoSend: false, checkIntervalMs: 300000, upsellKeywords: [] }),
strategies: z.object({
explorationRate: z.number().min(0).max(1).default(0.2),
minSamples: z.number().default(5),
variants: z.array(z.object({
id: z.string(),
weight: z.number().default(1),
promptOverride: z.string(),
})).default([]),
}).default({ explorationRate: 0.2, minSamples: 5, variants: [] }),
web: z.object({
enabled: z.boolean().default(true),
port: z.number().default(3000),
}),
});
export type Config = z.infer<typeof ConfigSchema>;
export function loadConfig(): Config {
const configPath = path.resolve(process.cwd(), 'config.yaml');
if (!fs.existsSync(configPath)) {
throw new Error(`Config file not found at ${configPath}`);
}
const configFile = fs.readFileSync(configPath, 'utf-8');
const rawConfig = YAML.parse(configFile);
// Inject API key from environment
if (!rawConfig.openrouter) {
rawConfig.openrouter = {};
}
rawConfig.openrouter.apiKey = process.env.OPENROUTER_API_KEY || rawConfig.openrouter.apiKey;
// Inject Reddit credentials from environment
if (process.env.REDDIT_USERNAME) {
rawConfig.reddit.username = process.env.REDDIT_USERNAME;
}
if (process.env.REDDIT_PASSWORD) {
rawConfig.reddit.password = process.env.REDDIT_PASSWORD;
}
// Override browser settings from environment
if (process.env.BROWSER_HEADLESS !== undefined) {
if (!rawConfig.browser) rawConfig.browser = {};
rawConfig.browser.headless = process.env.BROWSER_HEADLESS === 'true';
}
if (!rawConfig.openrouter.apiKey) {
throw new Error('OPENROUTER_API_KEY environment variable is required');
}
const result = ConfigSchema.safeParse(rawConfig);
if (!result.success) {
const errors = result.error.issues
.map((e) => ` - ${String(e.path.join('.'))}: ${e.message}`)
.join('\n');
throw new Error(`Invalid configuration:\n${errors}`);
}
return result.data;
}