5f8dc184b4b276d534c630df8e2c328d7467ade3
실제 화면을 캡처해 확인한 문제: - 모바일(390px)에서 3열 고정 레이아웃(사이드바 240 + 대기열 320)이 본문을 ~50px 로 눌러 "로그인이 필요합니다"가 한 글자씩 세로로 표시되는 등 사실상 사용 불가였다. - 대기열의 삭제/드래그가 hover 에서만 노출돼, hover 가 없는 터치 기기에서는 접근 자체가 불가능. 순서 변경도 HTML5 드래그앤드롭뿐이라 모바일에서 동작하지 않았다. - 조작 요소가 터치 기준(44px)에 한참 못 미쳤다(재생 32px, 삭제 ~24px, 진행바 높이 1px). - 로그아웃 상태에서도 사이드바·대기열·플레이어바가 자리를 차지했고, 본문은 "로그인이 필요합니다" 라고만 적어둔 채 정작 버튼은 우상단에만 있어 동선이 끊겼다. - 아이콘 버튼에 접근성 이름이 없고(title 만), 검색창 안내가 로그인 전에도 "서버를 먼저 선택"이었다. 변경: - 레이아웃: 반응형으로 재구성. 좁은 화면은 단일 컬럼 + 대기열을 슬라이드 패널(FAB/플레이어바에서 토글), 넓은 화면은 기존 3열 유지. 로그인 전·서버 미선택 시에는 재생/대기열 영역을 아예 렌더링하지 않는다. - 대기열: 조작 버튼 상시 노출, 좁은 화면에는 위/아래 이동 버튼 제공(드래그 대체), 넓은 화면은 드래그 유지. 번호·곡수 표시 추가, 실패 시 이전 순서로 롤백. - 플레이어바: 버튼을 44~48px 로 키우고 모바일은 진행바를 상단 전체 폭으로 분리. 볼륨은 좁은 화면에서 숨김. - 검색 결과/서버 카드: 카드 전체를 버튼으로 만들어 hover 없이도 누를 수 있게 하고 재생 아이콘 상시 노출. - 로그인 화면에 실제 로그인 버튼 배치, 서버 미선택/결과 없음 등 빈 상태 문구 정리. - 좌측 사이드바를 안내문 전용 죽은 공간에서 "조작 중인 서버 + 서버 전환 + 사용법"으로 교체. - 아이콘 버튼 aria-label, range input aria-label, 장식 이미지 alt="" 정리. 프로필 이미지 없을 때 이니셜 대체. 검증: tsc 통과, lint 에러 0. 실제 렌더링을 데스크톱(1440x900)·모바일(390x844)에서 캡처해 로그인 전/서버 목록/서버 상세/검색 결과/대기열 패널 화면을 모두 확인했다.
music_bot_v2
디스코드 음악봇 + 웹 대시보드 모노레포. 디스코드에서 명령으로 음악을 틀 수도 있고, 웹에서 로그인해 검색·재생·대기열을 제어할 수도 있다. 성인인증(연령제한) 영상 재생까지 지원한다.
구성
| 디렉터리 | 내용 | 문서 |
|---|---|---|
bot/ |
디스코드 봇 본체(discord.js + shoukaku/Lavalink). 검색·재생·대기열·자동재생·연령제한 우회. | bot/README.md |
page/ |
웹 대시보드(Next.js 16 + NextAuth + Redis). 브라우저에서 봇 제어·상태 표시. | page/README.md |
resolver/ |
ytresolver.py — 오디오 URL 리졸버(+systemd 유닛). 유튜브 내부 API 우선, 실패 시 yt-dlp로 폴백. 봇이 SABR/연령제한 우회에 사용. |
아래 + bot/README §1·2 |
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단계로 폴백한다. ①이 대부분을 처리하고, 연령제한처럼 로그인이 필요한 영상만 ③으로 내려간다.
- 유튜브 내부 API(VISIONOS 클라이언트) — player API 한 번으로 오디오 직링크 획득(~0.3초).
visitorData는 리졸버가 스스로 발급·캐시(6시간)하고 만료 시 자동 재발급한다. - yt-dlp 상주 호출 — zipapp을 import해
YoutubeDL인스턴스를 재사용(프로세스 기동 비용 제거, ~1.7초). - yt-dlp + 쿠키 — 연령제한(로그인 필요) 영상 전용 경로(
68초).
- 유튜브 내부 API(VISIONOS 클라이언트) — player API 한 번으로 오디오 직링크 획득(~0.3초).
- 봇 서버(.5)와 Lavalink(.6)가 같은 공인 IP라, .5에서 뽑은 스트림 URL을 .6이 그대로 재생한다.
- 첫 곡 재생 지연은 음성채널 접속과 곡 해석을 병렬로 돌리고, 곡이 정해지는 즉시 리졸브를 예열해 줄였다
(
bot/src/classes/LavalinkManager.ts의resolveQuery/prewarmTrack).
빠르게 보기
- 봇 배포/쿠키/검색·재생 동작·성능·트러블슈팅 → bot/README.md
- 대시보드 개발/환경변수/Redis 통신 구조/API·UI → page/README.md
리포 규칙
bot/.gitignore가*.env,*.db를 제외 → 실제 토큰/DB는 커밋되지 않는다. 예시는bot/.env.example.- 원격: Gitea
git.tkrmagid.kr/tkrmagid/music_bot_v2(master).
Description
Languages
TypeScript
88.2%
Python
8.8%
JavaScript
1.9%
CSS
0.5%
Dockerfile
0.4%
Other
0.2%