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>
158 lines
4.9 KiB
TypeScript
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;
|
|
}
|