2026-05-13 16:57:07 +07:00
|
|
|
import { z } from "zod";
|
|
|
|
|
import { ConfigError } from "./errors";
|
2026-05-13 15:56:00 +07:00
|
|
|
|
2026-05-13 16:57:07 +07:00
|
|
|
const configSchema = z.object({
|
|
|
|
|
DISCORD_TOKEN: z.string().min(1, "DISCORD_TOKEN is required"),
|
2026-05-13 18:23:20 +07:00
|
|
|
VOICE_CHANNEL_ID: z.string().min(1).optional(),
|
|
|
|
|
GUILD_ID: z.string().min(1).optional(),
|
2026-05-13 16:57:07 +07:00
|
|
|
VERBOSE: z
|
|
|
|
|
.string()
|
|
|
|
|
.optional()
|
|
|
|
|
.transform((v) => v === "true")
|
|
|
|
|
.default(false),
|
|
|
|
|
RECORDINGS_DIR: z.string().default("./recordings"),
|
|
|
|
|
RECORDING_SEGMENT_MS: z.coerce.number().positive().default(5000),
|
|
|
|
|
DECODER_ROTATE_MS: z.coerce.number().positive().default(5000),
|
|
|
|
|
DECODER_COOLDOWN_MS: z.coerce.number().positive().default(30000),
|
|
|
|
|
WEBSERVER_PORT: z.coerce.number().positive().default(3000),
|
|
|
|
|
VOICE_CONNECTION_TIMEOUT_MS: z.coerce.number().positive().default(15000),
|
|
|
|
|
RECONNECT_TIMEOUT_MS: z.coerce.number().positive().default(5000),
|
|
|
|
|
AUDIO_STREAM_SILENCE_DURATION_MS: z.coerce.number().positive().default(3000),
|
|
|
|
|
PACKET_FILTER_MIN_SIZE: z.coerce.number().positive().default(8),
|
|
|
|
|
OPUS_FRAME_SIZE: z.coerce.number().positive().default(960),
|
|
|
|
|
AUDIO_SAMPLE_RATE: z.coerce.number().positive().default(48000),
|
|
|
|
|
AUDIO_CHANNELS: z.coerce.number().positive().default(2),
|
|
|
|
|
AVATAR_SIZE: z.coerce.number().positive().default(64),
|
|
|
|
|
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
2026-05-13 17:49:33 +07:00
|
|
|
NODE_ENV: z
|
|
|
|
|
.enum(["development", "production", "test"])
|
|
|
|
|
.default("development"),
|
2026-05-13 19:34:14 +07:00
|
|
|
MONITOR_GUILD_ID: z.string().min(1).optional(),
|
|
|
|
|
PICSER_UPLOAD_URL: z.string().url().default("https://picser.asepharyana.tech/api/upload"),
|
|
|
|
|
ATTACHMENT_UPLOAD_TIMEOUT_MS: z.coerce.number().positive().default(30000),
|
|
|
|
|
ATTACHMENT_MAX_SIZE_MB: z.coerce.number().positive().default(100),
|
|
|
|
|
ATTACHMENT_RETRY_ATTEMPTS: z.coerce.number().positive().default(3),
|
2026-05-13 16:57:07 +07:00
|
|
|
});
|
2026-05-13 15:56:00 +07:00
|
|
|
|
2026-05-13 16:57:07 +07:00
|
|
|
export type AppConfig = z.infer<typeof configSchema>;
|
2026-05-13 15:56:00 +07:00
|
|
|
|
|
|
|
|
export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
|
2026-05-13 16:57:07 +07:00
|
|
|
try {
|
|
|
|
|
return configSchema.parse(env);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error instanceof z.ZodError) {
|
|
|
|
|
const messages = error.issues
|
|
|
|
|
.map((e) => `${e.path.join(".")}: ${e.message}`)
|
|
|
|
|
.join("\n");
|
|
|
|
|
throw new ConfigError(`Configuration validation failed:\n${messages}`);
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
2026-05-13 15:56:00 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const config = loadConfig();
|