From e5304fde294e9ae5720e121ae0ba96e0d4a7452f Mon Sep 17 00:00:00 2001 From: asepharyana Date: Wed, 2 Sep 2026 10:12:03 +0700 Subject: [PATCH] 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. --- ...6-09-02_selfbot-manual-video-watch-spec.md | 81 ++++++++++ .../modules/command-handler/commandHandler.ts | 4 + .../command-handler/handler-registry.ts | 12 ++ .../modules/command-handler/video.handler.ts | 145 +++++++++++++++++ .../src/shared/redis-channels.ts | 2 + .../tests/llmChunkExtraction.test.ts | 2 +- .../tests/videoHandler.test.ts | 147 ++++++++++++++++++ .../tests/videoReceiver.test.ts | 2 +- 8 files changed, 393 insertions(+), 2 deletions(-) create mode 100644 .hermes/plans/2026-09-02_selfbot-manual-video-watch-spec.md create mode 100644 services/discord-gateway/src/modules/command-handler/video.handler.ts create mode 100644 services/discord-gateway/tests/videoHandler.test.ts diff --git a/.hermes/plans/2026-09-02_selfbot-manual-video-watch-spec.md b/.hermes/plans/2026-09-02_selfbot-manual-video-watch-spec.md new file mode 100644 index 00000000..f7efc84f --- /dev/null +++ b/.hermes/plans/2026-09-02_selfbot-manual-video-watch-spec.md @@ -0,0 +1,81 @@ +# Spec: Selfbot-Viable Video Capture — manual screen-share watch command (Phase D) + +Status: PLANNED (not yet built) +Date: 2026-09-02 +Author: Hermes +Related: `.hermes/plans/2026-08-31_video-receive-phaseC-spec.md` (auto-receive, superseded +for selfbot), `gmw-ops/references/selfbot-presence-detection-limits.md`, +`gmw-ops/references/discord-voice-fork-video-receive.md` + +## TL;DR — the decisive finding (verified live 2026-09-02) + +User insists on keeping the **selfbot** (no bot-token migration). Live diagnostics prove +a selfbot CANNOT auto-detect other members' camera/share because: +- It never receives `VOICE_STATE_UPDATE` for other members (only its own). +- `guild.members.fetch()` → 403, `GET /channels/{id}/voice-states` → 404. +- No `GUILD_CREATE`, no `READY.broadcaster_user_ids` presence. +- `scanExistingStreamers` + `handleVoiceStateUpdate` (the only two `startStreamWatch` + triggers) are therefore both **dead on a selfbot**. +- No manual watch command exists today, so even on-demand capture is impossible. + +→ The ONE selfbot-viable path is a **manual, operator-initiated STREAM_WATCH** on a + member known to be screen-sharing. Gateway op 20 (STREAM_WATCH) is **NOT gated on + bot-vs-user**; the DAVE handshake to Ready+MLS was already verified live in earlier + sessions. The receive/mux/segment/upload pipeline (`streamWatchReceiver.ts`) is already + built and only lacks a real streamer to produce its first `.mp4`. + +Camera-of-others is NOT viable on a selfbot even with `unknown-ssrc` fallback: +`@discordjs/voice` `parsePacket` calls `daveSession.decrypt(packet, userId)` keyed per +REAL userId (vendor fork dist/index.js:2143), so a fake id selects no MLS decryptor → +garbage, not H264. (The uncommitted `unknown-ssrc` change was reverted this session.) + +Selfbot CAN capture the OWNER's own video (its own VOICE_STATE_UPDATE + fork op12 +videoSSRC are attributable), but `videoRecorder.ts` hard-skips its own id — parameterized +self-capture is a follow-up, not the default. + +## Goal + +Add a **manual watch command** so an operator can say "record 's screen share" +and the gateway `startStreamWatch`s that member → DAVE watch → per-burst `.mp4` segments +(mirroring voice silence split) → upload → DB `voice_recordings` → dashboard `