Commit Graph
831 Commits
Author SHA1 Message Date
asepharyana 7e50c000dc fix(backend): remove dead user_reputations schema (table + type aliases)
Follow-up to d0453881: the pgUserReputationsTable definition and the
UserReputation/UserReputationInsert type aliases were the only remaining
references to the removed user reputation feature. Nothing imports them
(verified via grep across src/ + tests/), so they're pure dead code that
could mislead future devs into re-joining a dropped table. Railed out to
close the 42P01 failure class completely.
2026-09-02 00:17:54 +07:00
asepharyana d0453881b8 fix(users): drop dead user_reputations JOIN that broke Members page + audit
Root cause: gateway migration 0016 removed the per-user reputation feature
(DROP TABLE user_reputations), but the backend still LEFT JOINed
pgUserReputationsTable in dashboard listUsers/getUserDetail and the chatbot
get_user_reputation tool. Against the live DB these queries raised 42P01
(undefined_table) -> the new /users page could never load (oRPC WS error).

Fix:
- dashboard.repository.ts: remove reputation columns/join from listUsers and
  getUserDetail (data sourced only from messages + user_profiles).
- chatbot.tools.ts: get_user_reputation now returns honest 'unavailable'
  (feature removed) instead of querying the dropped table.
- chatbot.toolDefs.ts: update tool description so the LLM doesn't advertise
  a dead feature.
- frontend types/users view: drop trust_score/clean_message_streak/
  total_infractions/last_infraction_at; derive risk label from real flagged%
  and add warn_count to the inspector (real available data).

Verified: backend+frontend tsc clean, biome clean, 37 unit tests pass,
query tested against live DB (returns real members), build green.
2026-09-02 00:12:52 +07:00
asepharyana 7122258d7c fix(voice): read SSRCMap internal map via 'map' (not '_map')
videoReceiver.getSsrcInternalMap read asAny._map, but @discordjs/voice
0.19.x exposes the SSRC map as the public field 'map'. So inferVideoOwner
(e.g. matching a video RTP SSRC to a user via audio-SSRC proximity) always
returned null -> every video packet was silently dropped after decryption.
Accept both 'map' and '_map'. Unblocks camera/screen-share attribute when
op12 does not carry a videoSSRC stream entry.
2026-09-02 00:10:02 +07:00
asepharyana 30d640ca5b feat(fe): add Member Intelligence (/users) page wired to full reputation data
- New /users route: member roster (trust score, clean/flagged counts, message volume)
  + inspector detail (trust breakdown, clean streak, infractions, AI profile,
  recent messages) with live search
- Backend dashboard.listUsers/getUserDetail now JOIN user_reputations to expose
  trust_score, clean_message_streak, total_infractions, last_infraction_at +
  warn_count/clean_count breakdowns
- Frontend types updated to match; useUsers refactored to PaginatedUsers shape,
  added useUserSearch for the old search behavior
- Nav rail + mobile nav: add Users entry
2026-09-02 00:01:12 +07:00
asepharyana 7c21629404 fix(voice): DAVE video decrypt fallback chain (VIDEO→AUDIO→passthrough)
streamWatchReceiver: DAVE decrypt for screen-share video packets was
returning null every time (VIDEO-PKT diag showed packets arriving but
no 'Video segment opened'). Three possible causes:
1. No VIDEO decryptor in MLS group (audio-only handshake)
2. GoLive stream tags video packets as AUDIO
3. Screen-share payloads are unencrypted above the AES layer

Fix: try MediaType.VIDEO first, then MediaType.AUDIO, then passthrough
(legacy-decrypted payload as-is). This covers all three modes without
breaking audio recording.
2026-09-01 23:54:07 +07:00
asepharyana 5fdd6ed80c feat(fe): add reactor message/emoji stats to analysis leaderboard 2026-09-01 23:52:28 +07:00
asepharyana 38b74cc1db feat(fe): enrich dashboard, moderation, messages inspector, channels roster with full backend data
- Dashboard: 6-tile metric deck (flagged today, active users 24h, mod queue),
  pipeline status bar, top channels ranking, enriched reactions with emojis,
  warned counter in gauge, 4 quick-nav links
- Moderation: coverage tile + header rate, top flagged domains, top flagged
  channels, category/severity/action breakdown trends, coverage progress bar
- Messages inspector: severity meter, confidence %, risk score, recommended
  action badge, reply-context chain, mention + role chips
- Channels: roster/culture tabs, rich table (messages, clean, flagged, risk
  bar, last activity) wired to dashboard.channels endpoint
2026-09-01 23:51:39 +07:00
asepharyana 7bfdf9c85c feat(voice): fork @discordjs/voice with video receive support
Fork @discordjs/voice 0.19.2 into vendor/discord-voice-fork and patch the
voice gateway handshake so Discord sends camera/screen-share RTP video:

- Identify payload now declares video:true + streams:[] (derived from
  Discord-RE/Discord-video-stream) - this is what makes Discord deliver
  H264 (PT 96-127) to the voice socket. Previously the client never
  declared video capability, so Discord omitted all video SSRCs/packets.
- op-12 Speaking handler maps streams[].ssrc -> videoSSRC alongside the
  audio SSRC, so ssrcMap carries camera/screen-share stream IDs.
- SSRCMap.get() now also resolves video SSRCs (video RTP arrives on a
  different SSRC than audio), enabling attribution for videoReceiver.ts.
- Both dist/index.js (CJS) and dist/index.mjs (ESM) patched; 3 isolated
  hunks vs upstream, verified by diff.
- package.json points @discordjs/voice -> file:vendor/discord-voice-fork
  (pnpm lockfile updated, CI --frozen-lockfile compatible).
- .gitignore: replace global dist/ with per-service explicit patterns so
  the vendored fork's dist/ is committed while build outputs stay ignored.
- tests: +3 SSRCMap videoSSRC cases (190 total, all pass).

The receive-side pipeline (H264Depacketizer -> .h264 -> muxToMp4 -> MP4)
already exists in videoReceiver.ts; this unblocks it by making Discord
actually deliver video packets.
2026-09-01 23:35:13 +07:00
asepharyana 1367a2257f fix(gateway): detect camera-only video (selfVideo) for stream watch
Previously only member.voice.streaming (screen share, self_stream) triggered
video capture. Discord reports camera via self_video:true, so camera-only
users were never watched. Now scanExistingStreamers and handleVoiceStateUpdate
start a watch when streaming OR selfVideo is set, and stop it when both clear.
2026-09-01 22:26:38 +07:00
mytheclipsebotreview a70ecb7bfd Merge remote-tracking branch 'origin/main' into dependabot/npm_and_yarn/services/frontend/production-23ca0026d1 2026-09-01 21:52:50 +07:00
mytheclipsebotreview[bot] ca0a015ebf Auto-merge PR #32
build(deps): bump zod from 4.4.3 to 4.5.2 in /services/backend in the production group
2026-09-01 14:52:10 +00:00
asepharyana 4ec9685194 feat(gateway): split video recording into silence-based segments like voice
Video (camera + screen share) DAVE stream-watch now produces per-burst
MP4 segments instead of one long .h264 per watch:
- Detects VIDEO_SILENCE_MS (4000ms) of no H264 packets → closes the
  current segment, muxes to MP4, registers in voice_recordings + uploads
  to TeleUploader, then reopens for the next burst (mirrors voice AfterSilence).
- Per-watch segment counter + per-segment depacketizer reset + closing
  guard + write-error swallow so races (silence close vs in-flight UDP
  packet) never corrupt files or crash the gateway.
- Frontend: recordings deck renders a native <video> player for MP4 rows
  (detected by filename), keeps single-playback registry across audio+video.
2026-09-01 21:51:44 +07:00
mytheclipsebotreview 446a4f28ea Merge remote-tracking branch 'origin/main' into dependabot/npm_and_yarn/services/frontend/production-23ca0026d1 2026-09-01 21:42:36 +07:00
mytheclipsebotreview f70148308b Merge branch 'main' of https://github.com/asepharyana/GMW into dependabot/npm_and_yarn/services/backend/production-908e2177c6 2026-09-01 21:40:30 +07:00
dependabot[bot] 1f196d2267 build(deps): bump zod
Bumps the production group in /services/discord-gateway with 1 update: [zod](https://github.com/colinhacks/zod).


Updates `zod` from 4.4.3 to 4.5.2
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.4.3...v4.5.2)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.5.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-01 14:33:45 +00:00
dependabot[bot] 297320a75d build(deps): bump lucide-react
Bumps the production group in /services/frontend with 1 update: [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react).


Updates `lucide-react` from 1.35.0 to 1.37.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.37.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-01 14:33:30 +00:00
dependabot[bot] 49d27a8a1c build(deps): bump zod in /services/backend in the production group
Bumps the production group in /services/backend with 1 update: [zod](https://github.com/colinhacks/zod).


Updates `zod` from 4.4.3 to 4.5.2
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.4.3...v4.5.2)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.5.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-01 14:33:26 +00:00
asepharyana fa72fe03cd feat(archive): show real channel/thread names in semantic search UI
Gateway archive embedder now parses metadata.channel.{channelName,threadName}
from each message and stores channel_name/thread_name in the Qdrant payload.
Backend exposes them; the semantic results card renders the thread name (or
channel name) instead of a raw #snowflake, with the ID as a last-resort
fallback for legacy points. Matches the message feed's channel-label logic
(getMessageChannelLabel).
2026-09-01 19:27:56 +07:00
asepharyana 26a690943d feat(archive): rich metadata in semantic search — username/channel/guild context + guild filter
Archive payload now stores username, channel_id, guild_id, thread_id and the
real message created_at (not embed time). Backend searchArray accepts an
optional guildId and applies a Qdrant payload filter so results can be scoped
to the guild being viewed. API/frontend expose the new fields and the
semantic results card shows who said it, in which channel, and when —
turning bare text blobs into contextual results. Old points fall back to
analyzed_at and omit the new fields gracefully.
2026-09-01 18:56:14 +07:00
asepharyana c704fbf7a5 fix(gateway): make voice transcription model configurable + router-compatible
- AI_VOICE_TRANSCRIPTION_MODEL config (default whisper-1) so the model can be a provider-qualified id (openrouter/openai/whisper-1) that actually has credentials through 9router/omniroute — bare whisper-1 maps to the openai provider which has none
- response_format json (not text): 9router proxies only json/verbose_json transcription responses; text returns 400
- parse text from the json response object
- prod env updated: model=openrouter/openai/whisper-1 (still needs OpenRouter STT balance — 402 until funded)
2026-09-01 18:25:14 +07:00
asepharyana 7ef86c81ca feat(ai): audit + harden embedding pipeline
- Normalize text before embedding (strip mentions/URLs/emoji/markdown/control chars, lowercase, truncate) on both write and query sides so vectors aren't diluted and tokens aren't wasted
- embeddingClient: retry embeddings (maxRetries 2), validate batch dimension consistency, preserve index alignment for empty-normalized texts
- archiveEmbedder: store normalized text in archive payload, skip empty-normalized content
- backend: normalize search queries, make archive search similarity threshold configurable (AI_LLM_EMBEDDING_ARCHIVE_MIN_SIMILARITY, default 0.6)
2026-09-01 18:01:47 +07:00
asepharyana 0e31aa06b8 feat(gateway): refactor term extraction and scoring logic into textSignals.ts for reuse 2026-08-31 22:59:27 +07:00
asepharyana 12cc956329 feat(gateway): implement separate Piscina pools for text and media analysis to optimize processing 2026-08-31 22:59:27 +07:00
mytheclipsebotreview[bot] 3ce594d9b5 Auto-merge PR #29
build(deps): bump the production group across 1 directory with 4 updates
2026-08-31 14:46:39 +00:00
mytheclipsebotreview 91cf0ad33f Merge branch 'dependabot/npm_and_yarn/services/frontend/production-f6f770ead8' of https://github.com/asepharyana/GMW into dependabot/npm_and_yarn/services/frontend/production-f6f770ead8
# Conflicts:
#	services/frontend/pnpm-lock.yaml
2026-08-31 21:45:54 +07:00
dependabot[bot] a0794a67d1 build(deps): bump the production group across 1 directory with 4 updates
Bumps the production group with 4 updates in the /services/discord-gateway directory: [axios](https://github.com/axios/axios), [openai](https://github.com/openai/openai-node), [piscina](https://github.com/piscinajs/piscina) and [sharp](https://github.com/lovell/sharp).


Updates `axios` from 1.19.0 to 1.20.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.19.0...v1.20.0)

Updates `openai` from 7.5.0 to 7.8.0
- [Release notes](https://github.com/openai/openai-node/releases)
- [Changelog](https://github.com/openai/openai-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai/openai-node/compare/v7.5.0...v7.8.0)

Updates `piscina` from 5.3.1 to 5.3.2
- [Release notes](https://github.com/piscinajs/piscina/releases)
- [Changelog](https://github.com/piscinajs/piscina/blob/v5.3.2/CHANGELOG.md)
- [Commits](https://github.com/piscinajs/piscina/compare/v5.3.1...v5.3.2)

Updates `sharp` from 0.35.3 to 0.35.4
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.35.3...v0.35.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: openai
  dependency-version: 7.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: piscina
  dependency-version: 5.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
- dependency-name: sharp
  dependency-version: 0.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 14:44:40 +00:00
mytheclipsebotreview 81c6a95f4c Merge remote-tracking branch 'origin/main' into dependabot/npm_and_yarn/services/frontend/production-f6f770ead8
# Conflicts:
#	services/frontend/pnpm-lock.yaml
2026-08-31 21:44:40 +07:00
dependabot[bot] d8a52eeb4b build(deps): bump the production group across 1 directory with 2 updates
Bumps the production group with 2 updates in the /services/frontend directory: [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) and [next](https://github.com/vercel/next.js).


Updates `lucide-react` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.35.0/packages/lucide-react)

Updates `next` from 16.3.2 to 16.3.3
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.3.2...v16.3.3)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: next
  dependency-version: 16.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 14:41:46 +00:00
mytheclipsebotreview[bot] 400466b5c5 Auto-merge PR #31
build(deps-dev): bump the development group in /services/discord-gateway with 2 updates
2026-08-31 14:41:39 +00:00
mytheclipsebotreview e291a0e2e3 Merge remote-tracking branch 'origin/main' into dependabot/npm_and_yarn/services/frontend/development-f6fa283631 2026-08-31 21:38:10 +07:00
mytheclipsebotreview 62fe25e5c1 Merge remote-tracking branch 'origin/main' into dependabot/npm_and_yarn/services/backend/development-f6fa283631 2026-08-31 21:36:38 +07:00
dependabot[bot] cb36a2fbb0 build(deps-dev): bump the development group
Bumps the development group in /services/discord-gateway with 2 updates: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@biomejs/biome` from 2.5.10 to 2.5.11
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.11/packages/@biomejs/biome)

Updates `@types/node` from 26.3.0 to 26.4.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: "@types/node"
  dependency-version: 26.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 14:30:00 +00:00
dependabot[bot] 1a494260db build(deps-dev): bump the development group
Bumps the development group in /services/frontend with 2 updates: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@biomejs/biome` from 2.5.10 to 2.5.11
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.11/packages/@biomejs/biome)

Updates `@types/node` from 26.3.0 to 26.4.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: "@types/node"
  dependency-version: 26.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 14:29:42 +00:00
dependabot[bot] 04ab117044 build(deps-dev): bump the development group
Bumps the development group in /services/backend with 2 updates: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node).


Updates `@biomejs/biome` from 2.5.10 to 2.5.11
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.11/packages/@biomejs/biome)

Updates `@types/node` from 26.3.0 to 26.4.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.11
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: "@types/node"
  dependency-version: 26.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 14:29:15 +00:00
dependabot[bot] ad21aa07ed build(deps): bump the production group
Bumps the production group in /services/frontend with 2 updates: [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) and [next](https://github.com/vercel/next.js).


Updates `lucide-react` from 1.34.0 to 1.35.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.35.0/packages/lucide-react)

Updates `next` from 16.3.2 to 16.3.3
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/compare/v16.3.2...v16.3.3)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
- dependency-name: next
  dependency-version: 16.3.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 14:29:05 +00:00
dependabot[bot] 2ae3c06c4a build(deps): bump axios in /services/backend in the production group
Bumps the production group in /services/backend with 1 update: [axios](https://github.com/axios/axios).


Updates `axios` from 1.19.0 to 1.20.0
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.19.0...v1.20.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 14:28:43 +00:00
asepharyana 2b6a1eca19 fix(gateway): re-assert server-undeafen+unmute before every video watch
The bot's own VOICE_STATE_UPDATE showed server-level deaf:true — a
server-deafened member is NOT sent the streamer's audiovisual RTP by Discord,
which is the likely reason no H264 arrives despite the DAVE watch reaching
Ready. The previous fire-and-forget forceSelfServerUnmuteUndeafen() ran once
after the first Ready join and silently reverted on reconnect/restart.

- Export forceSelfServerUnmuteUndeafen from recorder.ts; re-assert it (with
  read-back verification logging stillDeaf) at the START of every
  startStreamWatch() before STREAM_WATCH is sent (dynamic import avoids the
  recorder <-> videoRecorder <-> streamWatchReceiver module cycle).
- Re-assert it again after a successful voice reconnect.
- startStreamWatch() is now async; callers use void.
2026-08-31 19:16:56 +07:00
asepharyana 9606187861 feat(gateway): hexdump+ssrc of watch UDP packets
maxLen stayed 72 across 243 packets (no real H264, which is hundreds+ bytes) —
only 44-72-byte RTP packets on PT 76/72/73 arrive. Add ssrc + first-32-bytes
hex so we can identify exactly what Discord sends to the watch socket (control
packets vs stale video), which determines whether the gap is upstream routing
or whether large H264 packets are missing entirely.
2026-08-31 16:47:27 +07:00
asepharyana b423d21b23 feat(gateway): aggregate VIDEO-PKT diag — distinct PTs + maxLen
Enhance watch-socket diagnostic to report distinct RTP payload types seen and
the max packet length, so we can distinguish 'only small control packets arrive
(no real H264)' from 'H264 arrives but decrypt fails'. Live already confirmed
dave=true ready=true with packets flowing but no burst — need to know if they're
tiny 52-byte control packets (PT 73) or large H264.
2026-08-31 16:40:36 +07:00
asepharyana 266e149233 feat(gateway): add VIDEO-PKT diagnostic logging to stream-watch UDP handler
Instrument handleUdpMessage to log (rate-limited, first 3 then /20s) whether
video RTP packets actually reach the watch socket, and whether the DAVE session
is attached+ready and encryption key material present. Needed to diagnose why
no .h264 is written despite DAVE Ready + MLS: is the packet not arriving, or is
decrypt returning null?
2026-08-31 16:29:17 +07:00
asepharyana f1a7b0c2a1 fix(gateway): resolve DAVE session from net.state.dave, not connectionData
CRITICAL: djs/voice stores the DAVESession wrapper at net.state.dave
(createDaveSession assigns to state.dave on op4 SessionDescription), NOT
inside connectionData. decryptVideoPacket looked up connectionData.dave which
is ALWAYS undefined -> every video packet hit '!dave?.session' guard and was
silently dropped, so no .h264/.mp4 ever got written despite the handshake
reaching Ready.

Fix: pass net.state.dave as a separate arg (the wrapper has .session ->
Davey.DAVESession) so the DAVE-layer decrypt (MediaType.VIDEO) actually runs.
Typecheck + build pass, lint clean (src/), 179/179 tests.
2026-08-31 16:08:55 +07:00
asepharyana 87f1f8be8d feat(gateway): detect pre-existing streamers on bot voice join
If someone is ALREADY sharing screen / camera on when the bot joins the
channel, no voiceStateUpdate with streaming:true fires for them, so the
bot never sent STREAM_WATCH and missed their video entirely. trackChannel
now scans channel.members and starts a watch for anyone already streaming
(ignoring the bot itself and non-streamers). Idempotent: startStreamWatch
no-ops if a watch already exists. +2 tests (9/9 in videoRecorder).
2026-08-31 15:48:20 +07:00
asepharyana 4405647b33 fix(gateway): swap stateChange arg order so Ready actually attaches UDP
djs/voice Networking emits stateChange(oldState, newState), but the watch
handler declared (newState, oldState) -- reversed. So the code-4 Ready
branch (which attaches udp.on('message') + logs 'DAVE watch READY') never
fired when entering Ready; it only fired spuriously when LEAVING Ready.
Result: full DAVE handshake completed on the watch RTC (Ready + DAVE MLA +
video stream 21029 active 1920x1080@60) but no UDP listener -> no video
captured. Swap to (oldState, newState) so enter-Ready wires the socket.
2026-08-31 15:42:12 +07:00
asepharyana 4534b7a17d feat(gateway): log full watch Networking state transitions
Add watch-state N->M log on every djs/voice Networking stateChange (with
hasUdp flag) so the stream-watch connection's exact progression is visible:
OpeningWs(0)->Identifying(1)->UdpHandshaking(2)->SelectingProtocol(3)->
Ready(4). Pins down where the DAVE flow stalls instead of guessing from the
absence of logs. Pairs with debug:true + watch-djs-debug.
2026-08-31 15:36:05 +07:00
asepharyana b3e350d40f feat(gateway): enable djs/voice debug logging on watch Networking
Add debug:true to watch Networking options and wire net.on('debug') to
logger.info so djs/voice internal WS/DAVE state transitions appear in
journal. Without this, the stream-watch connection went silent after
'Streaming DAVE Networking' — no ready/error/close visible. Needed to
diagnose why the WS to stream endpoint 'c-sin14-xxx:2083' produced no
events.
2026-08-31 15:23:57 +07:00
asepharyana 75050bc088 fix(gateway): use rtc_server_id-1 as watch DAVE MLS channelId (WrongGroupId)
Live log (14:52) showed the stream-watch flow reaching STREAM_CREATE +
STREAM_SERVER_UPDATE but then DAVE processProposals threw
'ValidationError(WrongGroupId)' -- the Davey MLS session derived the wrong
group because connectionOptions.channelId was the guild voice channel id.
Per Discord-RE StreamConnection.daveChannelId = BigInt(serverId)-1n, the
stream-watch DAVE MLS group is keyed to rtc_server_id-1, not the vc channel.
Fix: pass BigInt(serverId)-1n as channelId to the watch Networking.

This error also surface as an uncaughtException that crashed the gateway
(systemd restarted it). Correct channel id prevents it at the root.
2026-08-31 14:54:32 +07:00
asepharyana 374fd5a9c2 fix(gateway): use guild voice sessionId for watch RTC identify
The previous code read the sessionId from the selfbot client's voice manager
(client.voice.connection), which is no longer established since ensureSelfbotVoice
was removed — it would have sent sessionId:'none' in the watch Networking
identify and been rejected. Read the active session from the guild
@discordjs/voice connection (getVoiceConnection(guildId).state.networking...
connectionOptions.sessionId) instead.
2026-08-31 14:03:00 +07:00
asepharyana 77c8454bb2 fix(gateway): replicate dual-layer DAVE+LTS decrypt for watch video RTP
The first streamWatchReceiver only did dave.session.decrypt(msg.subarray(12))
which skipped the outer legacy-AES layer Discord wraps around the DAVE payload
on every RTC packet (encrypt = dave.encrypt then aead_aes256_gcm with RTP
header as AAD). Port @discordjs/voice VoiceReceiver.decrypt/parsePacket
faithfully (header strip incl CSRC+extension+padding, AES-GCM auth tag, then
DAVE MediaType.VIDEO). Without this the .h264 would be garbage.
2026-08-31 13:55:58 +07:00
asepharyana 0ea76a8373 feat(gateway): DAVE-capable stream-watch video receive (Phase D)
Replace the dead selfbot-v13 video path (WS 4017 DAVE). videoRecorder
now delegates to a new streamWatchReceiver that:
- sends STREAM_WATCH (op 20) on voiceState.streaming
- opens a @discordjs/voice Networking to the watch RTC (STREAM_CREATE +
  STREAM_SERVER_UPDATE) with DAVE enabled
- decrypts H264 via Davey MediaType.VIDEO, depacketizes + muxes to mp4
- tears down on streaming-stop / leave / untrack

Remove ensureSelfbotVoice/createVideoStream/joinStreamConnection (dead).
recorder.ts no longer fires the futile eager selfbot join.
2026-08-31 13:46:24 +07:00
asepharyana 6aeebe7826 fix(gateway): establish selfbot voice connection eagerly so video (camera/screen-share) capture works
Video capture (camera + screen share) recorded ZERO frames because the selfbot
ClientVoiceManager.connection was created LAZILY — only when a user started
streaming. At that point the bot is already connected via @discordjs/voice, so
the selfbot re-join never gets a fresh VOICE_SERVER_UPDATE and times out with
VOICE_CONNECTION_TIMEOUT after 15s. joinStreamConnection (STREAM_WATCH) +
receiver.createVideoStream both need that selfbot VoiceConnection CONNECTED.

Fix: establish the selfbot VoiceConnection eagerly in recorder.startRecording,
BEFORE joinVoiceChannel, so it rides the bot's fresh join (Discord emits
VOICE_SERVER_UPDATE → selfbot authenticates). videoRecorder reuses the cached
connection per guild, tears it down on voice stop/destroy. Best-effort — never
blocks audio recording.

Verified: typecheck + build + biome (src/) green; 9/9 videoRecorder tests.
2026-08-31 12:44:36 +07:00