Files
RedditBot/packages/api-contracts/src/config.ts
T

93 lines
3.8 KiB
TypeScript

import { z } from 'zod';
export const MAX_CONFIGURED_AUTOMATED_CONVERSATION_TURNS = 20;
export const ToneKeySchema = z.union([
z.literal('default'),
z.literal('dominant'),
z.literal('submissive'),
z.literal('playful'),
z.literal('direct'),
z.literal('gentle'),
z.literal('kink-friendly'),
z.literal('teasing'),
]);
export const RateLimitConfigSchema = z.strictObject({
minDelayBetweenMessagesMs: z.number(),
maxMessagesPerHour: z.number(),
jitterMs: z.number(),
});
export const RuntimeRedditConfigSchema = z.strictObject({
prioritizeRecentPosts: z.boolean(),
recentWindowMinutes: z.number(),
maxReadyCandidates: z.number().int().min(1).max(500),
physiqueTargeting: z.strictObject({
enabled: z.boolean(),
minScore: z.number().int().min(1).max(10),
maxScore: z.number().int().min(1).max(10),
}).refine((targeting) => targeting.minScore <= targeting.maxScore, {
message: 'Minimum physique score cannot exceed maximum physique score.',
path: ['minScore'],
}).default({ enabled: false, minScore: 1, maxScore: 10 }),
});
export const RuntimeConfigSchema = z.strictObject({
rateLimit: RateLimitConfigSchema,
browser: z.strictObject({ dryRun: z.boolean() }),
reddit: RuntimeRedditConfigSchema,
});
export const ConversationBehaviourSchema = z.strictObject({
mode: z.union([z.literal('legacy'), z.literal('r4r'), z.literal('r4r-media'), z.literal('manual')]).default('legacy'),
maxAutomatedTurns: z.number().int().min(1).max(MAX_CONFIGURED_AUTOMATED_CONVERSATION_TURNS).default(3),
rapportTurnsBeforeMediaAsk: z.number().int().min(1).max(MAX_CONFIGURED_AUTOMATED_CONVERSATION_TURNS - 1).default(3),
}).superRefine((behaviour, context) => {
if (behaviour.mode === 'r4r-media' && behaviour.rapportTurnsBeforeMediaAsk >= behaviour.maxAutomatedTurns) {
context.addIssue({
code: 'custom',
path: ['rapportTurnsBeforeMediaAsk'],
message: 'Rapport turns must be lower than the maximum automated turns.',
});
}
}).default({ mode: 'legacy', maxAutomatedTurns: 3, rapportTurnsBeforeMediaAsk: 3 });
export const SubredditConfigSchema = z.strictObject({
name: z.string(),
enabled: z.boolean().default(true),
type: z.union([z.literal('image'), z.literal('chat')]),
targetingMode: z.union([z.literal('post-author'), z.literal('comment-author')]).default('post-author'),
weight: z.number().optional(),
tone: ToneKeySchema,
scripted: z.string().optional(),
conversation: ConversationBehaviourSchema,
}).superRefine((subreddit, context) => {
if (subreddit.type === 'chat' && subreddit.targetingMode === 'comment-author') {
context.addIssue({
code: 'custom',
path: ['targetingMode'],
message: 'Comment-author targeting is available only for image posts.',
});
}
});
export const ConfigResponseSchema = RuntimeConfigSchema.extend({
subreddits: z.array(SubredditConfigSchema),
tones: z.array(ToneKeySchema),
advanced: z.record(z.string(), z.unknown()).optional(),
});
export const ConfigUpdateRequestSchema = z.strictObject({
rateLimit: RateLimitConfigSchema.partial().optional(),
browser: z.strictObject({ dryRun: z.boolean().optional() }).optional(),
reddit: RuntimeRedditConfigSchema.partial().optional(),
subreddits: z.array(SubredditConfigSchema).min(1).optional(),
advanced: z.record(z.string(), z.unknown()).optional(),
});
export const ConfigUpdateResponseSchema = z.strictObject({
success: z.literal(true),
config: RuntimeConfigSchema,
subreddits: z.array(SubredditConfigSchema),
advanced: z.record(z.string(), z.unknown()).optional(),
});
export type ToneKey = z.infer<typeof ToneKeySchema>;
export type RuntimeConfig = z.infer<typeof RuntimeConfigSchema>;
export type ConfigResponse = z.infer<typeof ConfigResponseSchema>;
export type ConfigUpdateRequest = z.infer<typeof ConfigUpdateRequestSchema>;