refactor: make hardcoded values configurable via env vars

This commit is contained in:
MythEclipse
2026-05-13 16:05:19 +07:00
parent 48cc83f624
commit 7a5ac2e34a
6 changed files with 64 additions and 10 deletions

View File

@@ -5,6 +5,15 @@ export interface AppConfig {
recordingSegmentMs: number; recordingSegmentMs: number;
decoderRotateMs: number; decoderRotateMs: number;
decoderCooldownMs: number; decoderCooldownMs: number;
webserverPort: number;
voiceConnectionTimeoutMs: number;
reconnectTimeoutMs: number;
audioStreamSilenceDurationMs: number;
packetFilterMinSize: number;
opusFrameSize: number;
audioSampleRate: number;
audioChannels: number;
avatarSize: number;
} }
export function parseBoolean( export function parseBoolean(
@@ -30,7 +39,22 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
recordingsDir: env.RECORDINGS_DIR ?? "./recordings", recordingsDir: env.RECORDINGS_DIR ?? "./recordings",
recordingSegmentMs: parsePositiveNumber(env.RECORDING_SEGMENT_MS, 5_000), recordingSegmentMs: parsePositiveNumber(env.RECORDING_SEGMENT_MS, 5_000),
decoderRotateMs: parsePositiveNumber(env.DECODER_ROTATE_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),
}; };
} }

View File

@@ -59,7 +59,7 @@ client.on("ready", async () => {
} }
// Start Webserver // Start Webserver
startWebserver(3000); startWebserver(config.webserverPort);
}); });
client.on("error", (err) => { client.on("error", (err) => {

View File

@@ -59,7 +59,11 @@ export async function startRecording(
// Tunggu sampai benar-benar terhubung // Tunggu sampai benar-benar terhubung
try { try {
await entersState(connection, VoiceConnectionStatus.Ready, 15_000); await entersState(
connection,
VoiceConnectionStatus.Ready,
config.voiceConnectionTimeoutMs,
);
if (config.verbose) { if (config.verbose) {
console.log("[recorder] Connected to voice channel. Recording started."); console.log("[recorder] Connected to voice channel. Recording started.");
} }
@@ -97,7 +101,7 @@ export async function startRecording(
try { try {
// --- OGG file recording with segment rotation --- // --- OGG file recording with segment rotation ---
const packetFilterForOgg = new PacketFilter(8); const packetFilterForOgg = new PacketFilter(config.packetFilterMinSize);
const audioStream = receiver.subscribe(userId, { const audioStream = receiver.subscribe(userId, {
end: { end: {
behavior: EndBehaviorType.AfterSilence, behavior: EndBehaviorType.AfterSilence,
@@ -201,8 +205,16 @@ export async function startRecording(
} }
try { try {
await Promise.race([ await Promise.race([
entersState(connection, VoiceConnectionStatus.Signalling, 5_000), entersState(
entersState(connection, VoiceConnectionStatus.Connecting, 5_000), connection,
VoiceConnectionStatus.Signalling,
config.reconnectTimeoutMs,
),
entersState(
connection,
VoiceConnectionStatus.Connecting,
config.reconnectTimeoutMs,
),
]); ]);
// Berhasil reconnect // Berhasil reconnect
} catch { } catch {

View File

@@ -1,4 +1,5 @@
import { EndBehaviorType, type VoiceReceiver } from "@discordjs/voice"; import { EndBehaviorType, type VoiceReceiver } from "@discordjs/voice";
import { config } from "../config";
export interface AudioStreamHandlers { export interface AudioStreamHandlers {
onPacket: (chunk: Buffer) => void; onPacket: (chunk: Buffer) => void;
@@ -14,7 +15,7 @@ export function subscribeToAudioStream(
const audioStream = receiver.subscribe(userId, { const audioStream = receiver.subscribe(userId, {
end: { end: {
behavior: EndBehaviorType.AfterSilence, behavior: EndBehaviorType.AfterSilence,
duration: 3000, duration: config.audioStreamSilenceDurationMs,
}, },
}); });

View File

@@ -1,4 +1,5 @@
import prism from "prism-media"; import prism from "prism-media";
import { config } from "../config";
export interface OpusDecoderOptions { export interface OpusDecoderOptions {
cooldownMs: number; cooldownMs: number;
@@ -23,7 +24,11 @@ export class OpusDecoder {
this.createDecoderFn = this.createDecoderFn =
options.createDecoder ?? 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 { rotateIfNeeded(): void {

View File

@@ -1,5 +1,6 @@
import path from "node:path"; import path from "node:path";
import type { Client, VoiceChannel } from "discord.js-selfbot-v13"; import type { Client, VoiceChannel } from "discord.js-selfbot-v13";
import { config } from "../config";
import type { SegmentMetadata, SegmentState, UserMetadata } from "../types"; import type { SegmentMetadata, SegmentState, UserMetadata } from "../types";
export async function collectUserMetadata( export async function collectUserMetadata(
@@ -30,8 +31,19 @@ export async function collectUserMetadata(
tag: user?.tag ?? "Unknown#0000", tag: user?.tag ?? "Unknown#0000",
displayName: member?.displayName ?? username, displayName: member?.displayName ?? username,
avatarUrl: avatarUrl:
user?.displayAvatarURL({ format: "png", size: 64 }) ?? user?.displayAvatarURL({
"https://cdn.discordapp.com/embed/avatars/0.png", 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, bot: user?.bot ?? false,
roles, roles,
highestRole: roles[0] ?? null, highestRole: roles[0] ?? null,