From 692962408f0c686972dc202799b549611f81f373 Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Wed, 13 May 2026 19:34:15 +0700 Subject: [PATCH] feat: add REST API and WebSocket events for moderation - Add /api/messages endpoint for querying text and image data - Add WebSocket broadcast functions for real-time updates - Support message_created, message_updated, message_deleted events - Support attachment_uploaded event for real-time image updates --- src/webserver.ts | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/webserver.ts b/src/webserver.ts index 3dd9472..16a53f2 100644 --- a/src/webserver.ts +++ b/src/webserver.ts @@ -11,6 +11,8 @@ import { createChildLogger, logger } from "./logger"; import { getMetrics, uptimeGauge } from "./metrics"; import { discordPlayer } from "./player"; import type { VoiceController } from "./voiceController"; +import { getDatabase } from "./muxer-queue"; +import { getMessagesByChannel, getAttachmentsByChannel } from "./moderation/messageStore"; const wsLogger = createChildLogger("webserver"); @@ -133,6 +135,44 @@ export function startWebserver( } }); + // Moderation API endpoints + app.get("/api/messages", async (req, res, next) => { + try { + const db = getDatabase(); + const { channel, type, limit = "50", offset = "0" } = req.query as { + channel?: string; + type?: string; + limit?: string; + offset?: string; + }; + + if (!channel) { + throw new AppError("channel query parameter is required", "MISSING_CHANNEL", 400); + } + + const limitNum = Math.min(parseInt(limit) || 50, 100); + const offsetNum = parseInt(offset) || 0; + + if (type === "image") { + const attachments = getAttachmentsByChannel(db, channel, limitNum, offsetNum); + res.json({ + type: "image", + data: attachments, + count: attachments.length, + }); + } else { + const messages = getMessagesByChannel(db, channel, limitNum, offsetNum); + res.json({ + type: "text", + data: messages, + count: messages.length, + }); + } + } catch (error) { + next(error); + } + }); + // Inbound: Discord PCM → tagged chunks → browser (global as any).broadcastPcmToWeb = (chunk: Buffer, userId: string) => { let hash = 0; @@ -169,6 +209,33 @@ export function startWebserver( }); } + function broadcastMessageEvent(type: string, data: any) { + const payload = JSON.stringify({ + type, + data, + timestamp: Date.now(), + }); + wsClients.forEach((client) => { + if (client.readyState === 1) client.send(payload); + }); + } + + (global as any).broadcastMessageCreated = (data: any) => { + broadcastMessageEvent("message_created", data); + }; + + (global as any).broadcastMessageUpdated = (data: any) => { + broadcastMessageEvent("message_updated", data); + }; + + (global as any).broadcastMessageDeleted = (data: any) => { + broadcastMessageEvent("message_deleted", data); + }; + + (global as any).broadcastAttachmentUploaded = (data: any) => { + broadcastMessageEvent("attachment_uploaded", data); + }; + // --- Outbound: browser PCM (24kHz mono) → Opus → Discord --- const RATE = 48000; const CHANNELS = 2;