/** * Pluggable VNC screen-broadcast backends. * * Per the chosen design (option 1): the streaming method is swappable via * STREAM_BACKEND in .env. The default is the real live "Go Live" stream via a * selfbot account (only way to get a native Discord video broadcast), with safe * fallbacks (noVNC link / periodic screenshots) available without code changes. */ import type { AppConfig } from "../config.ts"; /** A live voice session to reuse for single-account Go-Live: the conversation's * own selfbot client + its negotiated voice session_id. The Go-Live stream is * created on THIS session (no second login / voice join), so one account both * hears/speaks (via @discordjs/voice) and broadcasts (via @dank074). */ export interface SharedVoiceSession { client: unknown; guildId: string; channelId: string; sessionId: string; botId: string; } export interface StreamContext { guildId: string; voiceChannelId: string; /** Post an image to the invoking text channel (used by the screenshot backend). */ postImage?: (png: Buffer, name: string) => Promise; /** If present and it returns a session, the selfbot backend broadcasts on the * conversation's existing session instead of a second login (single account). */ getSharedSession?: () => SharedVoiceSession | null; } export interface ScreenStreamer { readonly kind: AppConfig["streamBackend"]; /** Start broadcasting. Returns a short user-facing status/link message. */ start(ctx: StreamContext): Promise; stop(): Promise; isActive(): boolean; } export async function createStreamer(config: AppConfig): Promise { switch (config.streamBackend) { case "selfbot": { const { SelfbotStreamer } = await import("./selfbot.ts"); return new SelfbotStreamer(config); } case "novnc": { const { NoVncStreamer } = await import("./novnc.ts"); return new NoVncStreamer(config); } case "screenshot": { const { ScreenshotStreamer } = await import("./screenshot.ts"); return new ScreenshotStreamer(config); } case "none": default: return { kind: "none", async start() { return "화면 송출이 비활성화되어 있습니다 (STREAM_BACKEND=none)."; }, async stop() {}, isActive: () => false, }; } }