Files
GMW/services/discord-gateway/src/modules/command-handler/voice.handler.ts
T
MythEclipseandClaude Opus 4.8 b68789fffc refactor: atomic, DRY, and logging improvements
- Shared Redis channel constants as single source of truth (redis-channels.ts)
- commandHandler.ts split into VoiceHandler, MediaHandler, GuildHandler,
  ModerationHandler with handler-registry.ts dispatch
- messageStore.ts (1322 lines) split into domain-specific DB files:
  messages.db.ts, attachments.db.ts, reviews.db.ts,
  moderation-actions.db.ts, retention.db.ts
- recorder.ts startSpeaking callback extracted into speakingHandler.ts,
  streamSetup.ts, segmentFinalizer.ts
- autoDeleteManager.ts split into autoDeleteEligibility.ts,
  autoDeleteNotify.ts, autoDeleteLogger.ts
- Added createChildLogger() logging across 8 service files
- Backend messages.repository.ts migrated from raw SQL to Drizzle ORM
- Fixed biome.json to exclude packages/**/dist/* from lint
- Fixed config.ts GUILD_ID pre-existing type error

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 17:34:18 +07:00

175 lines
4.6 KiB
TypeScript

import { type CommandMessage, type CommandReply } from "@bete/shared";
import { createChildLogger } from "@bete/shared/logger";
import type { Client } from "discord.js-selfbot-v13";
import Redis from "ioredis";
import { config } from "../../shared/config/config.js";
import { discordPlayer } from "../voice-recording/player.js";
import { voiceTransmitter } from "../voice-recording/transmitter.js";
import type { VoiceController } from "../voice-recording/voiceController.js";
// ---------------------------------------------------------------------------
// VoiceHandler
// ---------------------------------------------------------------------------
export class VoiceHandler {
private logger = createChildLogger("voice-handler");
constructor(
private client: Client | null,
private voiceController: VoiceController | null,
) {}
setClient(client: Client): void {
this.client = client;
}
setVoiceController(voiceController: VoiceController): void {
this.voiceController = voiceController;
}
async handleVoiceConnect(
cmd: CommandMessage,
): Promise<CommandReply<unknown>> {
if (!this.client || !this.voiceController) {
return {
id: cmd.id,
success: false,
data: null,
error: "Gateway not initialized",
};
}
const guildId = String(cmd.payload.guildId ?? "");
const channelId = String(cmd.payload.channelId ?? "");
if (!guildId || !channelId) {
return {
id: cmd.id,
success: false,
data: null,
error: "guildId and channelId are required",
};
}
const status = await this.voiceController.connect(guildId, channelId);
return { id: cmd.id, success: true, data: status };
}
async handleVoiceDisconnect(
cmd: CommandMessage,
): Promise<CommandReply<unknown>> {
if (!this.voiceController) {
return {
id: cmd.id,
success: false,
data: null,
error: "Gateway not initialized",
};
}
const status = await this.voiceController.disconnect();
return { id: cmd.id, success: true, data: status };
}
async handleVoiceChannels(
cmd: CommandMessage,
): Promise<CommandReply<unknown>> {
if (!this.client) {
return {
id: cmd.id,
success: false,
data: null,
error: "Gateway not initialized",
};
}
const guildId = String(cmd.payload.guildId ?? "");
if (!guildId) {
return {
id: cmd.id,
success: false,
data: null,
error: "guildId is required",
};
}
try {
const guild = await this.client.guilds.fetch(guildId);
const channels = await guild.channels.fetch();
const voiceChannels = channels
.filter((c) => c?.type === "GUILD_VOICE")
.map((c) => ({
id: c.id,
name: c.name,
type: "voice" as const,
}));
return { id: cmd.id, success: true, data: voiceChannels };
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return { id: cmd.id, success: false, data: null, error: msg };
}
}
async handleVoiceTransmitStart(
cmd: CommandMessage,
): Promise<CommandReply<unknown>> {
if (!discordPlayer.isConnected()) {
return {
id: cmd.id,
success: false,
data: null,
error: "Not connected to voice channel",
};
}
try {
// Create a new Redis connection for the transmitter
const transmitRedis = new Redis(config.REDIS_URL);
await voiceTransmitter.start(transmitRedis);
const status = voiceTransmitter.getStatus();
this.logger.info({ status }, "Voice transmit started");
return {
id: cmd.id,
success: true,
data: status,
};
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
this.logger.error({ error: message }, "Failed to start voice transmit");
return {
id: cmd.id,
success: false,
data: null,
error: message,
};
}
}
async handleVoiceTransmitStop(
cmd: CommandMessage,
): Promise<CommandReply<unknown>> {
try {
await voiceTransmitter.stop();
this.logger.info("Voice transmit stopped");
return {
id: cmd.id,
success: true,
data: { status: "stopped" },
};
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
this.logger.error({ error: message }, "Failed to stop voice transmit");
return {
id: cmd.id,
success: false,
data: null,
error: message,
};
}
}
}