feat: enhance configuration management and add error handling
This commit is contained in:
+39
-54
@@ -1,61 +1,46 @@
|
||||
// Configuration for the bot
|
||||
export interface AppConfig {
|
||||
verbose: boolean;
|
||||
recordingsDir: string;
|
||||
recordingSegmentMs: number;
|
||||
decoderRotateMs: number;
|
||||
decoderCooldownMs: number;
|
||||
webserverPort: number;
|
||||
voiceConnectionTimeoutMs: number;
|
||||
reconnectTimeoutMs: number;
|
||||
audioStreamSilenceDurationMs: number;
|
||||
packetFilterMinSize: number;
|
||||
opusFrameSize: number;
|
||||
audioSampleRate: number;
|
||||
audioChannels: number;
|
||||
avatarSize: number;
|
||||
}
|
||||
import { z } from "zod";
|
||||
import { ConfigError } from "./errors";
|
||||
|
||||
export function parseBoolean(
|
||||
value: string | undefined,
|
||||
fallback: boolean,
|
||||
): boolean {
|
||||
if (value === "true") return true;
|
||||
if (value === "false") return false;
|
||||
return fallback;
|
||||
}
|
||||
const configSchema = z.object({
|
||||
DISCORD_TOKEN: z.string().min(1, "DISCORD_TOKEN is required"),
|
||||
VOICE_CHANNEL_ID: z.string().min(1, "VOICE_CHANNEL_ID is required"),
|
||||
GUILD_ID: z.string().min(1, "GUILD_ID is required"),
|
||||
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"),
|
||||
NODE_ENV: z.enum(["development", "production"]).default("development"),
|
||||
});
|
||||
|
||||
export function parsePositiveNumber(
|
||||
value: string | undefined,
|
||||
fallback: number,
|
||||
): number {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
export type AppConfig = z.infer<typeof configSchema>;
|
||||
|
||||
export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
|
||||
return {
|
||||
verbose: parseBoolean(env.VERBOSE, false),
|
||||
recordingsDir: env.RECORDINGS_DIR ?? "./recordings",
|
||||
recordingSegmentMs: parsePositiveNumber(env.RECORDING_SEGMENT_MS, 5_000),
|
||||
decoderRotateMs: parsePositiveNumber(env.DECODER_ROTATE_MS, 5_000),
|
||||
decoderCooldownMs: parsePositiveNumber(env.DECODER_COOLDOWN_MS, 30_000),
|
||||
webserverPort: parsePositiveNumber(env.WEBSERVER_PORT, 3000),
|
||||
voiceConnectionTimeoutMs: parsePositiveNumber(
|
||||
env.VOICE_CONNECTION_TIMEOUT_MS,
|
||||
15_000,
|
||||
),
|
||||
reconnectTimeoutMs: parsePositiveNumber(env.RECONNECT_TIMEOUT_MS, 5_000),
|
||||
audioStreamSilenceDurationMs: parsePositiveNumber(
|
||||
env.AUDIO_STREAM_SILENCE_DURATION_MS,
|
||||
3000,
|
||||
),
|
||||
packetFilterMinSize: parsePositiveNumber(env.PACKET_FILTER_MIN_SIZE, 8),
|
||||
opusFrameSize: parsePositiveNumber(env.OPUS_FRAME_SIZE, 960),
|
||||
audioSampleRate: parsePositiveNumber(env.AUDIO_SAMPLE_RATE, 48000),
|
||||
audioChannels: parsePositiveNumber(env.AUDIO_CHANNELS, 2),
|
||||
avatarSize: parsePositiveNumber(env.AVATAR_SIZE, 64),
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export const config = loadConfig();
|
||||
|
||||
Reference in New Issue
Block a user