fix: resolve architecture disconnects and codebase weaknesses

- Add TEXT_CHANNEL_ID and TEXT_GUILD_ID to config schema (fix silent channel monitoring)
- Remove dead files: message-capture/broadcaster.ts, voice-recording/index.ts
- Fix WebSocket voice_command payload to forward from frontend
- Implement moderation:action handler in commandHandler
- Fix useMascotChat to use canonical request() wrapper
- Fix useAudioPlayback userId hash collision (use string not parseInt)
- Add catch blocks to useMediaControl.skip/stop
- Add typed broadcast functions (messageAnalyzed, voicePcmData, voiceActiveUser)
- Apply Biome formatting and lint fixes

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-09 16:36:23 +07:00
co-authored by Claude Opus 4.8
parent 30c9e86edc
commit 3a7b005d95
13 changed files with 171 additions and 109 deletions
@@ -2,6 +2,7 @@ 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 { createModerationAction } from "../message-capture/messageStore.js";
import { discordPlayer } from "../voice-recording/player.js";
import { voiceTransmitter } from "../voice-recording/transmitter.js";
import type { VoiceController } from "../voice-recording/voiceController.js";
@@ -155,6 +156,9 @@ export class CommandHandler {
case "media:volume":
reply = await this.handleMediaVolume(cmd);
break;
case "moderation:action":
reply = await this.handleModerationAction(cmd);
break;
default:
logger.warn({ type: cmd.type }, "Unknown command type");
reply = {
@@ -437,6 +441,128 @@ export class CommandHandler {
}
}
private async handleModerationAction(
cmd: BackendCommand,
): Promise<CommandReply> {
const payload = cmd.payload as {
message_id?: string;
user_id?: string;
guild_id?: string;
channel_id?: string;
action_type?: string;
reason?: string;
executed_by?: string;
};
if (
!payload.message_id ||
!payload.user_id ||
!payload.guild_id ||
!payload.action_type
) {
return {
id: cmd.id,
success: false,
data: null,
error: "message_id, user_id, guild_id, and action_type are required",
};
}
const validActions = [
"delete_message",
"mute_user",
"warn_user",
"kick_user",
"ban_user",
] as const;
if (
!validActions.includes(
payload.action_type as (typeof validActions)[number],
)
) {
return {
id: cmd.id,
success: false,
data: null,
error: `Invalid action_type: ${payload.action_type}. Must be one of: ${validActions.join(", ")}`,
};
}
try {
// For delete_message, also actually delete via Discord if client is available
if (payload.action_type === "delete_message" && this.client) {
try {
const channelId = String(cmd.payload.channel_id ?? "");
if (channelId) {
const channel = await this.client.channels.fetch(channelId);
if (channel?.isText()) {
const msg = await channel.messages
.fetch(payload.message_id)
.catch(() => null);
if (msg) {
await msg.delete().catch((err: unknown) => {
logger.warn(
{ error: err, messageId: payload.message_id },
"Failed to delete message via Discord",
);
});
}
}
}
} catch (err) {
logger.warn(
{ error: err, messageId: payload.message_id },
"Failed to fetch channel/message for deletion",
);
}
}
const action = await createModerationAction({
message_id: payload.message_id,
user_id: payload.user_id,
guild_id: payload.guild_id,
action_type: payload.action_type as
| "delete_message"
| "mute_user"
| "warn_user"
| "kick_user"
| "ban_user",
reason: payload.reason ?? null,
executed_by: payload.executed_by ?? "command-handler",
status: "executed",
error: null,
executed_at: Date.now(),
});
logger.info(
{
actionId: action.id,
actionType: payload.action_type,
userId: payload.user_id,
},
"Moderation action executed",
);
return {
id: cmd.id,
success: true,
data: action,
};
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
logger.error(
{ error: message, commandId: cmd.id },
"Failed to execute moderation action",
);
return {
id: cmd.id,
success: false,
data: null,
error: message,
};
}
}
// ---- Status publishing ----
private publishVoiceStatus(): void {
@@ -1,82 +0,0 @@
import { createChildLogger } from "@bete/shared/logger";
import type { WebSocket } from "ws";
import type {
AnalysisQueueStatus,
AttachmentRecord,
MessageRecord,
ModerationWsEvent,
} from "../message-capture/types.js";
import type { MediaState } from "../voice-recording/mediaTypes.js";
export type BroadcasterClient = Pick<WebSocket, "readyState" | "send">;
const log = createChildLogger("broadcaster");
function sendJson(
clients: Set<BroadcasterClient>,
event: ModerationWsEvent,
): void {
const payload = JSON.stringify({ ...event, timestamp: Date.now() });
for (const client of clients) {
if (client.readyState === 1) {
try {
client.send(payload);
} catch (error) {
log.warn(
{ error, eventType: event.type },
"Failed to send event to client",
);
}
}
}
}
export function createBroadcaster() {
const clients = new Set<BroadcasterClient>();
return {
addClient(client: BroadcasterClient) {
clients.add(client);
log.debug({ clientCount: clients.size }, "Client added");
},
removeClient(client: BroadcasterClient) {
clients.delete(client);
log.debug({ clientCount: clients.size }, "Client removed");
},
clientCount() {
return clients.size;
},
getClients() {
return Array.from(clients);
},
uiState(state: unknown) {
sendJson(clients, { type: "ui_state", state });
},
userState(users: unknown[]) {
sendJson(clients, { type: "user_state", users });
},
messageCreated(data: MessageRecord) {
sendJson(clients, { type: "message_created", data });
},
messageUpdated(data: Partial<MessageRecord> & { id: string }) {
sendJson(clients, { type: "message_updated", data });
},
messageDeleted(data: { id: string; deleted_at: number }) {
sendJson(clients, { type: "message_deleted", data });
},
messageAnalyzed(data: MessageRecord) {
sendJson(clients, { type: "message_analyzed", data });
},
attachmentCreated(data: AttachmentRecord) {
sendJson(clients, { type: "attachment_created", data });
},
analysisQueueStatus(data: AnalysisQueueStatus) {
sendJson(clients, { type: "analysis_queue_status", data });
},
mediaState(state: MediaState) {
sendJson(clients, { type: "media_state", state });
},
};
}
export type ModerationBroadcaster = ReturnType<typeof createBroadcaster>;
@@ -1,5 +0,0 @@
export { OpusDecoder } from "./recorder/decoder.js";
export { SegmentManager } from "./recorder/segment.js";
export { startRecording, stopRecording } from "./recorder.js";
export { voiceTransmitter } from "./transmitter.js";
export { VoiceController } from "./voiceController.js";