page - /login(디스코드 로그인 전용), /servers(서버 선택 전용), /servers/[id](유튜브 뮤직 플레이어)로 분리 · 서버 컴포넌트에서 로그인 검사, callbackUrl 은 같은 사이트 경로만 허용 · NextAuth 로그인/오류 화면도 /login 으로 - 서버 선택: 최근 사용 서버 맨 앞 "최근" 표시, 새로고침, 8개 초과 시 이름 검색, 오류 표시 - 플레이어: 검색어를 ?q= 로 남겨 뒤로가기 지원, 탭 제목에 지금 곡, 내 서버가 아니면 안내 - 검색창: 최근 검색어 목록, 유튜브·스포티파이 주소를 붙여넣으면 곡/재생목록 바로 불러오기 - 다음 트랙: 섞기, 모두 지우기(두 번 눌러 확인), 자동재생 스위치, 곡 수·총 길이 - 재생바: 처음부터 다시(⏮), 섞기 버튼 / 서버 홈: 음성채널 표시, 사용 안내, 최근 검색 칩 - 곡 추가 중 스피너 + 중복 추가 방지, 검색 실패 시 다시 시도, play/playlist 응답 대기 15초 - 계정 전환형 프로필 메뉴와 사이드바 서버 목록 제거(프로필은 서버 변경·로그아웃만) bot - Redis 액션 queue_shuffle, player_recommend 추가 - player_now 응답에 recommend(자동재생), voiceChannelName 추가 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.7 KiB
9.7 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: 검색(봇의
searchaction → 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초 안에 한 번 더 눌러야 실행) |
| 다음 트랙 | 자동재생 스위치 | 대기열이 끝나면 비슷한 곡 이어 재생. 재생 중이 아니어도 바꿀 수 있다(디스코드 버튼과 같은 설정) |
| 다음 트랙 | 곡 / 손잡이 / ▲▼ / 휴지통 | 그 곡부터 재생 / 드래그로 순서 변경(마우스) / 한 칸 이동(터치) / 삭제 |
| 재생 화면 | ⌄ / Esc | 닫기 |
| 전역 | 키보드 | Space 재생/일시정지, M 음소거, ←/→ 5초 탐색, Ctrl/⌘+→ 다음 곡 |
미지원(봇 기능 없음): 이전 곡으로 돌아가기, 반복 재생, 좋아요/가사. 동작하지 않는 버튼이 되므로 넣지 않았다.
6. 배포 메모
- Next.js 16 standalone/
next start로 구동. 봇과 같은 Redis(.7)에 접근 가능해야 한다. NEXTAUTH_URL/디스코드 Redirect URI를 실제 도메인으로 맞춘다.- 이 디렉터리는 Next가 제공하는 에이전트 규칙 파일(
AGENTS.md,CLAUDE.md)을 둔다 — 코드 수정 전 참고.