feat(discord): migrate voice join from selfbot to official bot (bot.mjs)

Replace the user-token selfbot voice path with an official Discord bot using
discord.js 14 + @discordjs/voice 0.19. The bot logs in with the stored testbot
token, joins the target voice channel, passes the DAVE/MLS E2EE handshake, and
receives per-user Opus audio via VoiceReceiver (the STT input path). ToS-safe.

Live-verified: bot joined guild "사지방" / channel "일반" and reached Ready.
Selfbot (gate.mjs/join.mjs) kept only for the deferred screenshare-video track,
which official bots cannot receive. Docs updated (README/PLAN); M1 done on bot path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-08-16 18:32:44 +09:00
parent 5327f8ec7c
commit 27e449f9f1
5 changed files with 1069 additions and 15 deletions

View File

@@ -8,8 +8,9 @@ STT → 두뇌 → TTS부터 완성**하는 단계다.
- 현재 상태: 음성 루프 뼈대 동작 — 파이프라인이 **눈 없이(화면공유 없이)** 돌아간다
(`python -m wsai --voice`). STT/TTS/두뇌는 아직 mock이며, 실제 엔진 연결이 다음 목표.
- 보류 중: 화면공유 **비디오** 수신(눈). 단, STT 입력은 **디스코드 보이스로 유저 음성을
수신**하므로 셀프봇의 보이스 접속 자체는 지금도 쓴다(비디오만 미룸). E2EE 합류 관문
이미 통과해 두었다(아래 8장).
수신**하므로 보이스 접속 자체는 지금도 쓴다(비디오만 미룸). 이 보이스 접속
**공식 Discord 봇**(`dave/bot.mjs`, discord.js + @discordjs/voice)으로 하며 DAVE/MLS
E2EE 합류·수신까지 라이브로 검증됐다(아래 8장). 셀프봇 경로는 폐기(비디오 트랙용만 보존).
---
@@ -53,9 +54,10 @@ STT → 두뇌 → TTS부터 완성**하는 단계다.
## 3. 귀 / 두뇌 / 입 — 지금 만드는 음성 루프
### 귀 (STT) — 디스코드 보이스 수신
- 로컬 마이크가 아니라 **디스코드 보이스에서 유저가 말하는 음성을 수신**한다. 셀프봇
보이스 채널에 접속해(이미 통과한 DAVE/MLS E2EE 경로) 유저의 Opus 오디오 RTP를 받고,
복호 → Opus 디코드 → PCM으로 만들어 STT에 흘려보낸다.
- 로컬 마이크가 아니라 **디스코드 보이스에서 유저가 말하는 음성을 수신**한다. **공식 봇**
보이스 채널에 접속해(이미 통과한 DAVE/MLS E2EE 경로) 유저의 Opus 오디오를 받는다.
@discordjs/voice의 `VoiceReceiver`가 DAVE 복호까지 처리해 유저별 Opus 스트림을 주고,
이를 Opus 디코드 → PCM으로 만들어 STT에 흘려보낸다.
- `faster-whisper`로 실시간 부분 전사를 계속 돌리고, VAD로 발화 종료(endpointing)를 잡는다.
종료 판정 시점엔 전사가 사실상 끝나 있어, 판정 후 남는 건 VAD hangover + 짧은 마지막
디코드뿐이다. 목표 종료→텍스트 확정 ~150ms.
@@ -149,7 +151,8 @@ ffmpeg 디코드 → 프레임. DAVE는 `@snazzah/davey`(Rust NAPI), 수신은 `
| `wsai/backends/mock.py` | 무의존성 mock 전 계열 |
| `wsai/backends/capture_mss.py` | 로컬 화면 캡처(트랙 B, 보류) |
| `wsai/backends/claude.py` | Claude 비전 + 두뇌 |
| `dave/gate.mjs` | DAVE/MLS E2EE 합류 관문 검증(통과 증거, 눈 재개 시 재사용) |
| `dave/bot.mjs` | **공식 봇** 보이스 접속 + 유저별 Opus 수신(DAVE E2EE, 현재 경로) |
| `dave/gate.mjs`, `dave/join.mjs` | 레거시 셀프봇 경로(폐기, 비디오 트랙 재개 시 참고용) |
| `poc/` | Xvfb+Chromium 캡처 실험(트랙 B 참고) |
| `PLAN.md` | 단계별 착수 계획 |
| `tests/test_pipeline.py` | 파이프라인 스모크 테스트 |
@@ -183,17 +186,25 @@ ffmpeg 디코드 → 프레임. DAVE는 `@snazzah/davey`(Rust NAPI), 수신은 `
첫 소리 지연을 GPU에서 실측해, 1초 예산에 맞는 가장 자연스러운 엔진을 채택한다(측정 기반 선택).
- **두뇌** — Claude **OAuth**, 이 호스트에 연결된 것과 **동일 크리덴셜 공유**
(`/home/claude/EJClaw/data/claude/.credentials.json`). API키 아님.
- **STT 입력 경로** — 디스코드 보이스 수신(셀프봇). 유저 음성 Opus RTP를 받아 DAVE 복호·디코드해 STT로.
- **STT 입력 경로** — 디스코드 보이스 수신(**공식 봇**). 유저 음성 Opus DAVE 복호·디코드해 STT로.
### 공식 봇 전환 (2026-08-16)
- 보이스 접속을 셀프봇(유저 토큰)에서 **공식 Discord 봇**으로 마이그레이션. `dave/bot.mjs`
(discord.js 14 + @discordjs/voice 0.19)가 봇 토큰으로 로그인 → 대상 채널 join →
DAVE/MLS E2EE Ready → `VoiceReceiver`로 유저별 Opus 수신 → PCM 디코드까지 라이브 검증됨.
- 봇은 이미 대상 서버(사지방)에 초대돼 있어 추가 초대 조치 불필요. 미초대 시
`node dave/bot.mjs --invite`로 초대 URL을 출력한다.
- 셀프봇이 필요한 건 공식 봇이 막힌 **화면공유 비디오 수신**뿐이며, 그건 보류 상태다.
### 배포/테스트 목표
- 대상: 디스코드 서버 `1352269198297923648`의 보이스 채널 `1352269198914621465`.
- 유저봇(셀프봇) 토큰: `.env``DISCORD_SELFBOT_TOKEN`(버너 계정, 밴 리스크 수용).
- 토큰: `.env``DISCORD_BOT_TOKEN`(테스트봇, app id `1538122882528321536`). ToS-safe.
- .9 로컬 GPU 도커 이미지로 올려, 그 채널에 접속한 뒤 사람이 말하면 대화하도록 한다.
- 첫 로딩 워밍업: 시작 시 모델 프리로드 + 더미 추론(CUDA 워밍) + 보이스 미리 접속 → 첫 대화도 지연 최소.
### 구현 마일스톤
- **M1** 셀프봇이 대상 보이스 채널에 상주 접속(DAVE 통과) + 발화자(SSRC) 감지 ← 지금
- **M2** 유저 음성 Opus RTP 수신 → DAVE 복호 → PCM (라이브 발화자 필요, 최고 난이도)
- **M1** ✅ **공식 봇**이 대상 보이스 채널에 상주 접속(DAVE 통과) + 발화자 감지 — `dave/bot.mjs`로 라이브 검증
- **M2** 유저 음성 Opus 수신 → DAVE 복호 → PCM @discordjs/voice `VoiceReceiver`가 대부분 처리(라이브 발화자로 최종 검증만 남음)
- **M3** faster-whisper STT(부분전사+VAD) → **M4** Claude OAuth(Haiku) 두뇌
- **M5** 한국어 TTS 첫 구절 청크를 보이스로 송신(DAVE 암호화) + barge-in
- **M6** 통합 + 워밍업 + .9 GPU 도커 이미지화, 채널 라이브 테스트