refactor: make hardcoded values configurable via env vars
This commit is contained in:
@@ -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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user