feat(gateway): add manual video-watch command for selfbot screen-share capture

A selfbot (user token) cannot auto-detect other members' camera/share
(no VOICE_STATE_UPDATE for others, 403 on member fetch). The only
selfbot-viable path to capture another member's SCREEN SHARE is an
operator-initiated STREAM_WATCH (gateway op 20, not gated on bot-vs-user).

Add video:watch / video:unwatch Redis commands routed via the existing
command handler to startStreamWatch/stopStreamWatch, which then does the
DAVE handshake + per-burst MP4 segmentation + DB insert + Tele upload
(already implemented in streamWatchReceiver).

- new VideoHandler (command-handler/video.handler.ts)
- register video:watch / video:unwatch in handler-registry + CommandHandler
- command constants COMMAND_VIDEO_WATCH / COMMAND_VIDEO_UNWATCH
- resolve active voice channel from voice controller + client cache
- 8 unit tests (videoHandler.test.ts)
- biome fixes for pre-existing test import ordering

All green: typecheck, build, lint (174 files), 200 tests.
This commit is contained in:
asepharyana
2026-09-02 10:12:03 +07:00
parent 43594af3c8
commit e5304fde29
8 changed files with 393 additions and 2 deletions
@@ -18,6 +18,7 @@ import {
import { MediaHandler } from "./media.handler.js";
import { wireMediaStatusWriter } from "./mediaStatusSink.js";
import { ModerationHandler } from "./moderation.handler.js";
import { VideoHandler } from "./video.handler.js";
import { VoiceHandler } from "./voice.handler.js";
const logger = createChildLogger("command-handler");
@@ -52,6 +53,7 @@ export class CommandHandler {
private mediaHandler!: MediaHandler;
private guildHandler!: GuildHandler;
private moderationHandler!: ModerationHandler;
private videoHandler!: VideoHandler;
constructor() {
// Dedicated Redis connection needed because: Redis requires a dedicated
@@ -86,6 +88,7 @@ export class CommandHandler {
this.mediaHandler = new MediaHandler();
this.guildHandler = new GuildHandler(client);
this.moderationHandler = new ModerationHandler(client);
this.videoHandler = new VideoHandler(client, voiceController);
// Wire the media status sink so MediaHandler can persist status on
// queue advances that happen outside a command (natural track end).
@@ -97,6 +100,7 @@ export class CommandHandler {
this.mediaHandler,
this.guildHandler,
this.moderationHandler,
this.videoHandler,
);
this.redisSub.on("message", (_channel, message) => {