fix(play): 스포티파이 트랙은 유튜브뮤직 오디오로 재생(뮤비 폴백 방지)
문제: 대시보드에서 스포티파이 곡을 고르면 track.url(open.spotify.com/track/..)을
Lavalink가 그대로 resolve → 유튜브 '영상(뮤비)'으로 폴백 재생됨.
수정: LavalinkManager.search에서 스포티파이 트랙 URL을 감지하면 메타데이터
(제목+아티스트)를 조회해 YoutubeMusic.getSearchUrl ?? `ytsearch:.. Topic` 오디오
검색으로 대체. 직접 입력/대시보드/`-p` 모든 경로에 적용. youtube 등 타 URL은 무영향.
- Spotify.parseTrackId / Spotify.getTrackById 추가.
- 검증: tsc 통과, Spotify /tracks/{id} 메타데이터 조회 정상, 파싱 정상.
This commit is contained in:
@@ -118,6 +118,17 @@ export class LavalinkManager {
|
|||||||
if (isUrl) searchText = text;
|
if (isUrl) searchText = text;
|
||||||
else if (flags.has('p')) searchText = (await Spotify.getSearchUrl(text)) ?? (await YoutubeMusic.getSearchUrl(text)) ?? `ytsearch:${text}`;
|
else if (flags.has('p')) searchText = (await Spotify.getSearchUrl(text)) ?? (await YoutubeMusic.getSearchUrl(text)) ?? `ytsearch:${text}`;
|
||||||
else searchText = (await YoutubeMusic.getSearchUrl(text)) ?? `ytsearch:${text}`;
|
else searchText = (await YoutubeMusic.getSearchUrl(text)) ?? `ytsearch:${text}`;
|
||||||
|
// 스포티파이 트랙 URL은 Lavalink가 유튜브 '영상(뮤비)'으로 폴백한다(유튜브뮤직 연결돼 있어도).
|
||||||
|
// 대시보드에서 고른 스포티파이 곡이나 `-p` 결과가 트랙 URL이면, 메타데이터(제목+아티스트)로
|
||||||
|
// 유튜브뮤직 '오디오'를 직접 찾아 대체한다(텍스트 검색 경로와 동일하게 원곡 오디오 우선).
|
||||||
|
const spotifyTrackId = Spotify.parseTrackId(searchText);
|
||||||
|
if (spotifyTrackId) {
|
||||||
|
const meta = await Spotify.getTrackById(spotifyTrackId);
|
||||||
|
if (meta) {
|
||||||
|
const q = `${meta.title} ${meta.artist}`.trim();
|
||||||
|
searchText = (await YoutubeMusic.getSearchUrl(q)) ?? `ytsearch:${q}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (searchText.startsWith("ytsearch") && !flags.has('o')) searchText += " Topic";
|
if (searchText.startsWith("ytsearch") && !flags.has('o')) searchText += " Topic";
|
||||||
const result = await node.rest.resolve(searchText);
|
const result = await node.rest.resolve(searchText);
|
||||||
if (!result || result.loadType === LoadType.EMPTY || result.loadType === LoadType.ERROR) {
|
if (!result || result.loadType === LoadType.EMPTY || result.loadType === LoadType.ERROR) {
|
||||||
|
|||||||
@@ -126,5 +126,35 @@ export const Spotify = {
|
|||||||
const track = (await this.getSearchFull(query) ?? [])?.[0];
|
const track = (await this.getSearchFull(query) ?? [])?.[0];
|
||||||
if (track?.url) searchCache.set(lowerQuery, track.url);
|
if (track?.url) searchCache.set(lowerQuery, track.url);
|
||||||
return track?.url ?? null;
|
return track?.url ?? null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** open.spotify.com/track/<id> 또는 spotify:track:<id> 에서 트랙 id를 추출. 아니면 null. */
|
||||||
|
parseTrackId(text: string): string | null {
|
||||||
|
const m = text.trim().match(/(?:open\.spotify\.com\/track\/|spotify:track:)([A-Za-z0-9]+)/);
|
||||||
|
return m ? m[1] : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 트랙 id로 제목/아티스트 메타데이터 조회 (유튜브뮤직 오디오 검색어 생성용). */
|
||||||
|
async getTrackById(id: string): Promise<{ title: string; artist: string } | null> {
|
||||||
|
try {
|
||||||
|
const token = await this.getToken();
|
||||||
|
if (!token) return null;
|
||||||
|
const response = await fetch(`${SPOTIFY_API_URL}/tracks/${encodeURIComponent(id)}?market=KR`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
Logger.error(`스포티파이 트랙 조회 에러 (${response.status})`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const track = await response.json() as SpotifyApi.TrackObjectFull;
|
||||||
|
if (!track?.name) return null;
|
||||||
|
return {
|
||||||
|
title: track.name,
|
||||||
|
artist: (track.artists ?? []).map((a) => a.name).join(", "),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
Logger.error(`스포티파이 트랙 조회 실패: ${err}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user