diff --git a/src/config.ts b/src/config.ts index 281d02a..09e51ea 100644 --- a/src/config.ts +++ b/src/config.ts @@ -5,6 +5,15 @@ export interface AppConfig { recordingSegmentMs: number; decoderRotateMs: number; decoderCooldownMs: number; + webserverPort: number; + voiceConnectionTimeoutMs: number; + reconnectTimeoutMs: number; + audioStreamSilenceDurationMs: number; + packetFilterMinSize: number; + opusFrameSize: number; + audioSampleRate: number; + audioChannels: number; + avatarSize: number; } export function parseBoolean( @@ -30,7 +39,22 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig { recordingsDir: env.RECORDINGS_DIR ?? "./recordings", recordingSegmentMs: parsePositiveNumber(env.RECORDING_SEGMENT_MS, 5_000), decoderRotateMs: parsePositiveNumber(env.DECODER_ROTATE_MS, 5_000), - decoderCooldownMs: 30_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), }; } diff --git a/src/index.ts b/src/index.ts index 8585207..de79fa7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -59,7 +59,7 @@ client.on("ready", async () => { } // Start Webserver - startWebserver(3000); + startWebserver(config.webserverPort); }); client.on("error", (err) => { diff --git a/src/recorder.ts b/src/recorder.ts index b2fee12..8d064e7 100644 --- a/src/recorder.ts +++ b/src/recorder.ts @@ -59,7 +59,11 @@ export async function startRecording( // Tunggu sampai benar-benar terhubung try { - await entersState(connection, VoiceConnectionStatus.Ready, 15_000); + await entersState( + connection, + VoiceConnectionStatus.Ready, + config.voiceConnectionTimeoutMs, + ); if (config.verbose) { console.log("[recorder] Connected to voice channel. Recording started."); } @@ -97,7 +101,7 @@ export async function startRecording( try { // --- OGG file recording with segment rotation --- - const packetFilterForOgg = new PacketFilter(8); + const packetFilterForOgg = new PacketFilter(config.packetFilterMinSize); const audioStream = receiver.subscribe(userId, { end: { behavior: EndBehaviorType.AfterSilence, @@ -201,8 +205,16 @@ export async function startRecording( } try { await Promise.race([ - entersState(connection, VoiceConnectionStatus.Signalling, 5_000), - entersState(connection, VoiceConnectionStatus.Connecting, 5_000), + entersState( + connection, + VoiceConnectionStatus.Signalling, + config.reconnectTimeoutMs, + ), + entersState( + connection, + VoiceConnectionStatus.Connecting, + config.reconnectTimeoutMs, + ), ]); // Berhasil reconnect } catch { diff --git a/src/recorder/audioStream.ts b/src/recorder/audioStream.ts index 036174a..2d3ef96 100644 --- a/src/recorder/audioStream.ts +++ b/src/recorder/audioStream.ts @@ -1,4 +1,5 @@ import { EndBehaviorType, type VoiceReceiver } from "@discordjs/voice"; +import { config } from "../config"; export interface AudioStreamHandlers { onPacket: (chunk: Buffer) => void; @@ -14,7 +15,7 @@ export function subscribeToAudioStream( const audioStream = receiver.subscribe(userId, { end: { behavior: EndBehaviorType.AfterSilence, - duration: 3000, + duration: config.audioStreamSilenceDurationMs, }, }); diff --git a/src/recorder/decoder.ts b/src/recorder/decoder.ts index e0e8f1a..dfdf58f 100644 --- a/src/recorder/decoder.ts +++ b/src/recorder/decoder.ts @@ -1,4 +1,5 @@ import prism from "prism-media"; +import { config } from "../config"; export interface OpusDecoderOptions { cooldownMs: number; @@ -23,7 +24,11 @@ export class OpusDecoder { this.createDecoderFn = options.createDecoder ?? (() => - new prism.opus.Decoder({ frameSize: 960, channels: 2, rate: 48000 })); + new prism.opus.Decoder({ + frameSize: config.opusFrameSize, + channels: config.audioChannels as 1 | 2, + rate: config.audioSampleRate as 8000 | 12000 | 16000 | 24000 | 48000, + })); } rotateIfNeeded(): void { diff --git a/src/recorder/metadata.ts b/src/recorder/metadata.ts index bfd8982..56c6d96 100644 --- a/src/recorder/metadata.ts +++ b/src/recorder/metadata.ts @@ -1,5 +1,6 @@ import path from "node:path"; import type { Client, VoiceChannel } from "discord.js-selfbot-v13"; +import { config } from "../config"; import type { SegmentMetadata, SegmentState, UserMetadata } from "../types"; export async function collectUserMetadata( @@ -30,8 +31,19 @@ export async function collectUserMetadata( tag: user?.tag ?? "Unknown#0000", displayName: member?.displayName ?? username, avatarUrl: - user?.displayAvatarURL({ format: "png", size: 64 }) ?? - "https://cdn.discordapp.com/embed/avatars/0.png", + user?.displayAvatarURL({ + format: "png", + size: config.avatarSize as + | 16 + | 32 + | 64 + | 128 + | 256 + | 512 + | 1024 + | 2048 + | 4096, + }) ?? "https://cdn.discordapp.com/embed/avatars/0.png", bot: user?.bot ?? false, roles, highestRole: roles[0] ?? null,