From f7c8257ef1fa5424f12b18f52ec0805164a6aaee Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Tue, 19 May 2026 14:58:09 +0700 Subject: [PATCH] refactor: move webserver orchestration --- src/http/server.ts | 113 +++++++++++++++++++++++++++++++++++++++++++ src/webserver.ts | 116 +-------------------------------------------- 2 files changed, 114 insertions(+), 115 deletions(-) create mode 100644 src/http/server.ts diff --git a/src/http/server.ts b/src/http/server.ts new file mode 100644 index 0000000..ee5455a --- /dev/null +++ b/src/http/server.ts @@ -0,0 +1,113 @@ +import http from "node:http"; +import type { Client } from "discord.js-selfbot-v13"; +import { config } from "../config"; +import { createChildLogger } from "../logger"; +import { MediaController } from "../media/mediaController"; +import { createScreenShareController } from "../media/screenShareController"; +import { createBroadcaster } from "../moderation/broadcaster"; +import { + initializeMediaSettings, + persistMediaSettings, +} from "../state/mediaSettings"; +import { createSharedUIStateStore } from "../state/uiState"; +import { Streamer } from "../streaming"; +import type { VoiceController } from "../voiceController"; +import { + exposeActiveUserGlobal, + exposeModerationGlobals, + exposePcmBroadcastGlobal, + exposeVideoBroadcastGlobal, +} from "../ws/broadcastGlobals"; +import { startWebSocketServer } from "../ws/server"; +import { createHttpApp } from "./app"; + +const serverLogger = createChildLogger("webserver"); + +const activeUsers = new Map< + string, + { username: string; avatar: string; speaking: boolean } +>(); + +export async function startWebserver( + port: number = 3000, + client: Client, + voiceController: VoiceController, +) { + const { getSharedUIState, patchSharedUIState } = await createSharedUIStateStore(); + let mediaSettings = await initializeMediaSettings(); + + const wsPath = "/ws"; + + const broadcaster = createBroadcaster(); + exposeModerationGlobals(broadcaster, config.ADMIN_PASSWORD); + + const streamer = new Streamer(client); + const screenController = createScreenShareController({ + getVoiceStatus: () => voiceController.getStatus(), + streamer, + useTranscoder: true, + onBeforeStreamStart: async () => { + await voiceController.disconnect(); + await new Promise((resolve) => setTimeout(resolve, 1500)); + }, + onAfterStreamEnd: async (guildId: string, channelId: string) => { + const current = voiceController.getStatus(); + if (current.connected && current.activeGuildId === guildId) return; + await voiceController.connect(guildId, channelId); + }, + }); + + const mediaController = new MediaController({ + isVoiceConnected: () => voiceController.getStatus().connected, + isBrowserStreaming: () => getSharedUIState().isStreaming, + screenController, + onStateChange: (state) => broadcaster.mediaState(state), + initialMusicVolume: mediaSettings.musicVolume, + onMusicVolumeChange: async (volume) => { + mediaSettings = { ...mediaSettings, musicVolume: volume }; + await persistMediaSettings(mediaSettings); + }, + }); + + const app = createHttpApp({ + client, + voiceController, + mediaController, + broadcaster, + adminPassword: config.ADMIN_PASSWORD, + getSharedUIState, + patchSharedUIState, + activeUserCount: () => activeUsers.size, + wsClientCount: () => broadcaster.clientCount(), + logger: serverLogger, + }); + + const server = http.createServer(app); + + function broadcastUserState() { + const users = Array.from(activeUsers.entries()).map(([id, data]) => ({ + id, + ...data, + })); + broadcaster.userState(users); + } + + exposePcmBroadcastGlobal(broadcaster); + exposeVideoBroadcastGlobal(() => broadcaster.getClients(), serverLogger); + exposeActiveUserGlobal(activeUsers, broadcastUserState); + + startWebSocketServer({ + server, + port, + wsPath, + broadcaster, + activeUsers, + getSharedUIState, + mediaController, + logger: serverLogger, + }); + + server.listen(port, "0.0.0.0", () => { + serverLogger.info({ port }, "Web interface listening"); + }); +} diff --git a/src/webserver.ts b/src/webserver.ts index 2c93850..0dc0b9f 100644 --- a/src/webserver.ts +++ b/src/webserver.ts @@ -1,115 +1 @@ -import http from "node:http"; -import type { Client } from "discord.js-selfbot-v13"; -import { config } from "./config"; -import { createChildLogger } from "./logger"; -import { MediaController } from "./media/mediaController"; -import { createScreenShareController } from "./media/screenShareController"; -import { createBroadcaster } from "./moderation/broadcaster"; -import { createSharedUIStateStore } from "./state/uiState"; -import { Streamer } from "./streaming"; -import type { VoiceController } from "./voiceController"; -import { - initializeMediaSettings, - persistMediaSettings, -} from "./state/mediaSettings"; -import { - exposeActiveUserGlobal, - exposeModerationGlobals, - exposePcmBroadcastGlobal, - exposeVideoBroadcastGlobal, -} from "./ws/broadcastGlobals"; -import { startWebSocketServer } from "./ws/server"; -import { createHttpApp } from "./http/app"; - -const wsLogger = createChildLogger("webserver"); - -const activeUsers = new Map< - string, - { username: string; avatar: string; speaking: boolean } ->(); - -export async function startWebserver( - port: number = 3000, - _client: Client, - voiceController: VoiceController, -) { - const { getSharedUIState, patchSharedUIState } = await createSharedUIStateStore(); - let mediaSettings = await initializeMediaSettings(); - - const wsPath = "/ws"; - - // Create broadcaster instance - const broadcaster = createBroadcaster(); - exposeModerationGlobals(broadcaster, config.ADMIN_PASSWORD); - - const streamer = new Streamer(_client); - const screenController = createScreenShareController({ - getVoiceStatus: () => voiceController.getStatus(), - streamer, - useTranscoder: true, - onBeforeStreamStart: async (guildId: string, channelId: string) => { - await voiceController.disconnect(); - // Wait for Discord gateway to fully process the disconnect - await new Promise((resolve) => setTimeout(resolve, 1500)); - }, - onAfterStreamEnd: async (guildId: string, channelId: string) => { - const current = voiceController.getStatus(); - if (current.connected && current.activeGuildId === guildId) return; - await voiceController.connect(guildId, channelId); - }, - }); - - const mediaController = new MediaController({ - isVoiceConnected: () => voiceController.getStatus().connected, - isBrowserStreaming: () => getSharedUIState().isStreaming, - screenController, - onStateChange: (state) => broadcaster.mediaState(state), - initialMusicVolume: mediaSettings.musicVolume, - onMusicVolumeChange: async (volume) => { - mediaSettings = { ...mediaSettings, musicVolume: volume }; - await persistMediaSettings(mediaSettings); - }, - }); - - const app = createHttpApp({ - client: _client, - voiceController, - mediaController, - broadcaster, - adminPassword: config.ADMIN_PASSWORD, - getSharedUIState, - patchSharedUIState, - activeUserCount: () => activeUsers.size, - wsClientCount: () => broadcaster.clientCount(), - logger: wsLogger, - }); - - const server = http.createServer(app); - - function broadcastUserState() { - const users = Array.from(activeUsers.entries()).map(([id, data]) => ({ - id, - ...data, - })); - broadcaster.userState(users); - } - - exposePcmBroadcastGlobal(broadcaster); - exposeVideoBroadcastGlobal(() => broadcaster.getClients(), wsLogger); - exposeActiveUserGlobal(activeUsers, broadcastUserState); - - startWebSocketServer({ - server, - port, - wsPath, - broadcaster, - activeUsers, - getSharedUIState, - mediaController, - logger: wsLogger, - }); - - server.listen(port, "0.0.0.0", () => { - wsLogger.info({ port }, "Web interface listening"); - }); -} +export { startWebserver } from "./http/server";