Files
music_bot_v2/page/README.md
tkrmagid 63456d8101 feat: 스포티파이 재생목록 최대 50,000곡까지 불러오기
- 봇: partner API(fetchPlaylist/getAlbum, 토크너 익명 토큰)로 첫 100곡을 받아 바로 재생,
  나머지는 1000곡씩(동시 3페이지) 백그라운드로 이어 붙임. 곡마다 앨범 커버 포함.
  실패 시 임베드 페이지(100곡) → Lavalink 순으로 폴백
- 정지·끝·모두 지우기 시 queueEpoch 증가 → 이어 받기 중단. '-s'는 페이지마다 재생목록 곡끼리 재섞기
- 토크너 토큰 캐시를 정식 API 토큰 캐시와 분리
- queue_list: 앞 200곡 + total/totalMs만 전송, queue_set: windowSize 부분만 교체·clearAll 지원
- 대시보드: 곡 수·총 길이는 전체 기준, '외 N곡' 표시, 모두 지우기는 clearAll

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 18:11:31 +09:00

9.9 KiB

music_bot_v2 — 웹 대시보드(page/)

디스코드 음악봇(../bot)을 브라우저에서 제어하는 웹 대시보드. 디스코드 로그인 후 내가 속한 서버를 골라 검색·재생·대기열 관리·볼륨/탐색 등을 조작하고, 현재 재생 상태를 실시간으로 본다.

  • 스택: Next.js 16 (App Router) · React 19 · TypeScript · Tailwind CSS 4 · NextAuth 4(Discord OAuth) · ioredis
  • 봇과의 통신: 직접 호출이 아니라 Redis Pub/Sub + Key로 봇과 주고받는다(아래 3장).
  • 리포 전체 개요는 ../README.md, 봇 본체는 ../bot/README.md 참고.

1. 빠른 시작 (개발)

cd page
npm install
# .env.local 작성 (2장 참고)
npm run dev        # http://localhost:3000

빌드/구동: npm run build → npm run start. 린트: npm run lint.

Redis(봇과 공유)와 디스코드 OAuth 앱이 있어야 실제 동작한다. Redis에 연결되지 않으면 부팅 시 에러를 던진다(src/lib/Redis.ts).


2. 환경변수 (.env.local)

변수 설명
REDIS_HOST / REDIS_PORT 봇과 공유하는 Redis. 봇 .env의 값과 동일해야 한다(미설정 시 부팅 에러).
DISCORD_CLIENT_ID / DISCORD_CLIENT_SECRET 디스코드 OAuth 앱 자격증명(로그인·서버목록 scope).
NEXTAUTH_SECRET NextAuth 세션 서명 키.
NEXTAUTH_URL 배포 URL(예: https://music.example.com). 개발은 http://localhost:3000.
  • 로그인 scope는 identify email guilds(유저 정보 + 서버 목록). src/app/api/auth/[...nextauth]/route.ts.
  • 디스코드 개발자 포털의 OAuth2 Redirect에 <NEXTAUTH_URL>/api/auth/callback/discord를 등록해야 한다.

3. 봇과의 통신 구조 (Redis)

대시보드는 봇을 직접 호출하지 않고 Redis를 메시지 버스로 쓴다. 봇 쪽 구현은 ../bot/src/classes/RedisClient.ts.

3-1. 명령 (site → bot, 요청/응답 RPC) — src/lib/api.ts의 botRpc

site: PUBLISH "site-bot"  {action, serverId, userId, requestId, …}
bot : 처리 후  SETEX "<action-key>:<requestId>"  (예: player:play:<reqId>)  에 결과 저장
site: 그 키를 짧게 폴링(100ms→최대400ms 백오프, 기본 3초 타임아웃)해 결과 수신
  • requestId는 CSPRNG(randomUUID). 결과 키는 읽은 즉시 삭제 + TTL 보험.
  • action: search, player_play, player_playlist, player_now, player_pause, player_skip, player_seek, player_volume, queue_list, queue_set, queue_remove, queue_shuffle, player_recommend.

3-2. 실시간 상태 (bot → site, SSE) — src/lib/sse.ts의 botEventStream

bot : PUBLISH "bot-site"  {event:"player_update"|"queue_update", guildId}
site: "bot-site" 구독 → serverId 일치하는 것만 EventSource(SSE)로 브라우저에 전달(30초 keep-alive)
  • 브라우저는 /api/player/events?serverId=…, /api/queue/events?serverId=…(SSE)를 구독해 상태 변경 알림을 받고 now/queue/list를 다시 당겨온다.

4. API 라우트 (src/app/api)

모든 라우트는 세션 가드(requireSession)를 거치고 { success, … } 형태로 응답한다.

  • player: play, pause, skip, seek, volume, playlist, now, autoplay(자동재생 토글), events(SSE)
  • queue: list, set, remove, shuffle(다음 트랙 섞기), events(SSE)
  • play/playlist는 음성채널 접속 + 곡·재생목록 해석까지 기다려야 해서 응답 대기 시간을 15초로 둔다.
  • servers: 로그인 유저의 디스코드 서버 목록(봇이 있는 서버로 필터링)
  • search: 검색(봇의 search action → Spotify/YT Music/YT Video 결과)
  • auth: [...nextauth](Discord OAuth)

공용 헬퍼(src/lib/api.ts): requireSession, botRpc, readJsonBody, requireString/Number/Boolean, errorResponse.


5. UI 구성 (src/)

디자인은 유튜브 뮤직(웹) 다크 테마를 따른다: 배경 #030303, 재생바/메뉴 #212121, 강조 빨강 #ff0000, 보조 글자는 흰색 70%/50% 투명도(색상 토큰은 app/globals.css의 --color-yt-*).

5-1. 페이지(주소)

주소 내용 접근 규칙
/ 바로 이동만 한다 로그인 안 했으면 /login, 했으면 /servers
/login 로그인 전용 페이지(Discord 로그인 버튼 + 사용 3단계 안내, 로그인 오류 표시) 이미 로그인했으면 callbackUrl(없으면 /servers)로 이동
/servers 서버 선택 전용 페이지(봇이 있는 내 서버, 최근 사용 서버를 맨 앞에 "최근" 표시, 새로고침, 8개 넘으면 이름 검색) 로그인 필수
/servers/[serverId] 유튜브 뮤직 형태의 봇 플레이어. 검색하면 ?q=검색어가 붙어 뒤로가기로 홈↔검색을 오간다 로그인 필수. 내 서버 목록에 없는 서버면 "열 수 없어요" 안내 후 서버 선택으로
  • 로그인 검사는 서버 컴포넌트에서 한다(lib/auth.ts의 requirePageSession). 돌아올 주소(callbackUrl)는 같은 사이트 경로만 허용한다.
  • NextAuth 로그인/오류 화면도 /login으로 돌린다(authOptions.pages).
  • 쓰다가 세션이 끊기면 클라이언트에서 /login으로 보낸다.

5-2. 파일 구성

  • app/login, app/servers, app/servers/[serverId] — 위 페이지(서버 컴포넌트, 로그인 검사 후 화면 컴포넌트 렌더).
  • components/pages/ — LoginView, ServerPicker, PlayerApp(플레이어 전체 배치·검색 주소 관리·탭 제목).
  • components/layout/ — TopNav(☰·로고·검색·최근 검색어), LeftSidebar(홈/검색/서버 변경 + 조작 중인 서버·음성채널), AccountMenu(프로필 → 서버 변경·로그아웃만).
  • components/player/ — MainContent(서버 홈·검색 결과·주소 불러오기), PlayerBar(하단 재생바), PlayerPage(펼친 재생 화면), QueueList(다음 트랙 + 섞기/모두 지우기/자동재생).
  • components/ui.tsx — 로고, 썸네일, 서버 아이콘, 로딩 표시, 재생 중 이퀄라이저. ToastProvider.tsx — 하단 스낵바.
  • hooks/ — usePlayer(재생 상태·조작·단축키·자동재생), useQueue(대기열·섞기·지우기), useServers(서버 목록·최근 서버). 재생 상태·대기열은 PlayerApp에서 한 번만 구독해 화면 여러 곳에 내려준다.
  • lib/ — auth.ts(페이지 로그인 검사), url.ts(음악 주소 판별), recent.ts(최근 검색어), api.ts(봇 RPC).

5-3. 플레이어 화면 구성

화면 진입 보이는 것
서버 홈 서버 선택 직후, 로고·"홈" 왼쪽: 서버 아이콘·이름·권한·봇이 있는 음성채널, 노래 검색 버튼, (재생 전) 사용 안내, 최근 검색 칩 / 오른쪽: 재생 목록(자동재생 스위치, 지금 곡, 다음 트랙)
검색 결과 검색창 Enter, 최근 검색어 필터 칩(전체/노래/동영상/Spotify, 개수 표시), 상위 검색결과, 구역별 목록(5곡 + "모두 표시"), 실패 시 "다시 시도"
주소 불러오기 유튜브·유튜브 뮤직·스포티파이 주소를 검색창에 붙여넣고 Enter 곡/재생목록·앨범 구분, "대기열에 추가" → POST /api/player/playlist
재생 화면 재생바 곡 정보·⌃, 서버 홈의 목록 아이콘 넓은 화면: 큰 앨범아트 + 다음 트랙 / 좁은 화면: 전체 화면 플레이어(진행바·조작·볼륨) + 다음 트랙
  • 재생바는 재생 중인 곡이 있을 때만 보인다. 브라우저 탭 제목에 지금 곡(▶/⏸)이 표시된다.
  • 왼쪽 메뉴: 넓은 화면은 ☰ 로 펼침/접힘, 좁은 화면은 ☰ 로 서랍.

5-4. 버튼·조작 동작 정리

위치 요소 동작
상단 검색창 Enter 로 검색(주소면 불러오기 화면). 누르면 최근 검색어 8개(✕로 개별 삭제)
상단 프로필 서버 변경 · 로그아웃
왼쪽 메뉴 홈 / 검색 / 서버 변경 서버 홈 / 검색창 포커스 / /servers
검색 곡 행·상위 검색결과 "재생" POST /api/player/play. 요청 중에는 스피너를 띄우고 다른 곡 추가를 막아 중복 추가를 방지
재생바 ⏮ 지금 곡을 처음부터 다시(유튜브 뮤직 동작. 봇에 이전 곡 기록이 없다)
재생바 ⏯ / ⏭ / 진행바 일시정지·재개 / 다음 곡 / 탐색
재생바 스피커 클릭 = 음소거/해제, hover 시 볼륨 슬라이더
재생바 섞기 다음 트랙 섞기(2곡 이상일 때)
다음 트랙 섞기 · 모두 지우기 섞기 / 지금 곡은 두고 나머지 비우기(되돌릴 수 없어 3초 안에 한 번 더 눌러야 실행)
다음 트랙 자동재생 스위치 대기열이 끝나면 비슷한 곡 이어 재생. 재생 중이 아니어도 바꿀 수 있다(디스코드 버튼과 같은 설정)
다음 트랙 곡 / 손잡이 / ▲▼ / 휴지통 그 곡부터 재생 / 드래그로 순서 변경(마우스) / 한 칸 이동(터치) / 삭제
다음 트랙 (목록 길이) 앞 200곡만 표시하고 곡 수·총 길이는 전체 기준. 나머지는 "외 N곡"으로 표시(스포티파이 재생목록은 최대 5만 곡)
재생 화면 ⌄ / Esc 닫기
전역 키보드 Space 재생/일시정지, M 음소거, ←/→ 5초 탐색, Ctrl/⌘+→ 다음 곡

미지원(봇 기능 없음): 이전 곡으로 돌아가기, 반복 재생, 좋아요/가사. 동작하지 않는 버튼이 되므로 넣지 않았다.


6. 배포 메모

  • Next.js 16 standalone/next start로 구동. 봇과 같은 Redis(.7)에 접근 가능해야 한다.
  • NEXTAUTH_URL/디스코드 Redirect URI를 실제 도메인으로 맞춘다.
  • 이 디렉터리는 Next가 제공하는 에이전트 규칙 파일(AGENTS.md, CLAUDE.md)을 둔다 — 코드 수정 전 참고.