- soloist/: soloist-bridge 중계 서버(docker). 곡마다 soloist --single-track을 띄우고 PulseAudio 빈 싱크를 캡처해 MP3 스트림으로 Lavalink(http 소스)에 넘긴다. 첫 소리가 나와야 성공으로 응답, 15초 안에 소리가 없거나 오류면 실패. 한 계정 한 곡 제한 → 다른 길드가 쓰는 중이면 busy. 빌드 만료(90일) 자동 갱신, 페어링 대기 자동 진입. - bot: SOLOIST_URL 설정 시 스포티파이 곡은 재생 직전에 Soloist를 먼저 시도, 실패하면 기존 유튜브 경로. 곡 도중 끊김·위치 이동·음성 재접속은 같은 곡의 유튜브 음원으로 그 위치부터 이어 재생. 오래 걸려 실패하면 5분 동안 유튜브로만 재생. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
4.5 KiB
Markdown
58 lines
4.5 KiB
Markdown
# music_bot_v2
|
|
|
|
디스코드 음악봇 + 웹 대시보드 모노레포. 디스코드에서 명령으로 음악을 틀 수도 있고, 웹에서 로그인해
|
|
검색·재생·대기열을 제어할 수도 있다. 성인인증(연령제한) 영상 재생까지 지원한다.
|
|
|
|
## 구성
|
|
|
|
| 디렉터리 | 내용 | 문서 |
|
|
|----------|------|------|
|
|
| [`bot/`](bot) | 디스코드 봇 본체(discord.js + shoukaku/Lavalink). 검색·재생·대기열·자동재생·연령제한 우회. | [bot/README.md](bot/README.md) |
|
|
| [`page/`](page) | 웹 대시보드(Next.js 16 + NextAuth + Redis). 브라우저에서 봇 제어·상태 표시. | [page/README.md](page/README.md) |
|
|
| [`resolver/`](resolver) | `ytresolver.py` — 오디오 URL 리졸버(+systemd 유닛). 유튜브 내부 API 우선, 실패 시 yt-dlp로 폴백. 봇이 SABR/연령제한 우회에 사용. | 아래 + bot/README §1·2 |
|
|
| [`soloist/`](soloist) | `soloist-bridge` — 스포티파이 곡을 스포티파이 공식 헤드리스 클라이언트(Soloist)로 재생해 MP3 스트림으로 Lavalink에 넘기는 중계 서버(docker). 안 되면 봇이 유튜브로 재생. | [soloist/README.md](soloist/README.md) |
|
|
| [`scripts/`](scripts) | `refresh-cookies.mjs` — 유튜브 쿠키(`ytcookie.txt`) 재발급 스크립트(Chromium). | bot/README §4 |
|
|
|
|
## 전체 아키텍처
|
|
|
|
```
|
|
┌─────────────── 디스코드 제어 평면 ───────────────┐
|
|
[Discord 유저] ── 명령 ──▶ 봇(music1/2/3, .5 docker) ── shoukaku ──▶ Lavalink(.6:2333)
|
|
│ ▲ └ youtube-plugin(일반)
|
|
│ │ 재생 직전 리졸브
|
|
▼ │
|
|
ytresolver(.5:8779) ─┬─ ① 유튜브 내부 API(VISIONOS) ~0.3초
|
|
│ ├─ ② yt-dlp 상주 호출 ~1.7초
|
|
│ └─ ③ yt-dlp + 쿠키(연령제한) ~6~8초
|
|
│ ──▶ 직접 오디오 URL
|
|
│ (Lavalink http 소스로 재생)
|
|
│
|
|
┌───────── Redis(.7) Pub/Sub + Key ─────────┐ ← 제어/상태 버스
|
|
│ │
|
|
[브라우저] ──▶ 웹 대시보드 page/(Next.js, NextAuth) ──┘
|
|
· site→bot: "site-bot" 명령 + 결과키 폴링(botRpc)
|
|
· bot→site: "bot-site" 이벤트 → SSE 실시간 표시
|
|
```
|
|
|
|
- **봇**과 **대시보드**는 직접 연결되지 않고 **Redis**로 느슨하게 연동된다(대시보드는 Redis만 있으면 봇과 통신).
|
|
- 유튜브가 스트리밍을 SABR 방식으로 바꾼 뒤 Lavalink의 youtube-plugin(1.18.2)만으로는 실재생이 막힌다.
|
|
그래서 `resolver/`가 재생 직전에 **직접 오디오 URL**을 뽑아 주고, 봇은 그 URL을 Lavalink http 소스로 재생한다.
|
|
- 리졸버는 3단계로 폴백한다. ①이 대부분을 처리하고, 연령제한처럼 로그인이 필요한 영상만 ③으로 내려간다.
|
|
1. **유튜브 내부 API(VISIONOS 클라이언트)** — player API 한 번으로 오디오 직링크 획득(~0.3초).
|
|
`visitorData`는 리졸버가 스스로 발급·캐시(6시간)하고 만료 시 자동 재발급한다.
|
|
2. **yt-dlp 상주 호출** — zipapp을 import해 `YoutubeDL` 인스턴스를 재사용(프로세스 기동 비용 제거, ~1.7초).
|
|
3. **yt-dlp + 쿠키** — 연령제한(로그인 필요) 영상 전용 경로(~6~8초).
|
|
- 봇 서버(.5)와 Lavalink(.6)가 같은 공인 IP라, .5에서 뽑은 스트림 URL을 .6이 그대로 재생한다.
|
|
- 첫 곡 재생 지연은 음성채널 접속과 곡 해석을 **병렬**로 돌리고, 곡이 정해지는 즉시 리졸브를 **예열**해 줄였다
|
|
(`bot/src/classes/LavalinkManager.ts`의 `resolveQuery`/`prewarmTrack`).
|
|
|
|
## 빠르게 보기
|
|
|
|
- 봇 배포/쿠키/검색·재생 동작·성능·트러블슈팅 → [bot/README.md](bot/README.md)
|
|
- 대시보드 개발/환경변수/Redis 통신 구조/API·UI → [page/README.md](page/README.md)
|
|
|
|
## 리포 규칙
|
|
|
|
- `bot/.gitignore`가 `*.env`, `*.db`를 제외 → 실제 토큰/DB는 커밋되지 않는다. 예시는 [`bot/.env.example`](bot/.env.example).
|
|
- 원격: Gitea `git.tkrmagid.kr/tkrmagid/music_bot_v2` (`master`).
|