bot: Spotify 자동재생 폴백 (sprec EMPTY → YouTube RD 믹스)
- sprec이 EMPTY/ERROR/빈 PLAYLIST면 곡 메타데이터(artist, title)로 ytsearch 후 첫 결과의 videoId로 RD<id> 믹스를 가져와 자동재생 큐 채움 - 리뷰어 확인: ytmsearch는 현재 Lavalink에서 EMPTY를 반환해서 ytsearch 사용 - sprec / 검색 / 믹스 resolve 각 단계에 try/catch 추가 (한 단계 실패 시 다음 단계로 진행하지 않고 폴백 또는 end로 안전하게 빠짐) - 진단 로깅: sprec loadType, 폴백 사유, 검색 매칭 videoId, 최종 후보 곡수
This commit is contained in:
@@ -275,12 +275,45 @@ export class GuildPlayer {
|
||||
if (!this.lastPlayedTrack) return;
|
||||
const source = this.lastPlayedTrack.info.sourceName;
|
||||
const trackId = this.lastPlayedTrack.info.identifier;
|
||||
const title = this.lastPlayedTrack.info.title;
|
||||
const author = this.lastPlayedTrack.info.author;
|
||||
let result;
|
||||
let usedYtFallback = false;
|
||||
|
||||
if (source === "spotify") {
|
||||
result = await this.player.node.rest.resolve(`sprec:seed_tracks=${trackId}&limit=11`);
|
||||
const sprecUri = `sprec:seed_tracks=${trackId}&limit=11`;
|
||||
try {
|
||||
result = await this.player.node.rest.resolve(sprecUri);
|
||||
Logger.info(`[autoPlay] sprec loadType=${result?.loadType ?? "(null)"} for "${author} - ${title}"`);
|
||||
} catch (err) {
|
||||
Logger.warn(`[autoPlay] sprec resolve throw: ${String(err)}`);
|
||||
result = undefined;
|
||||
}
|
||||
// 2024-11-27 이후 신규/development 모드 앱은 Spotify Recommendations API 접근이 막혀서
|
||||
// sprec이 EMPTY로 떨어지는 경우가 있음. extended 모드 토큰이면 동작함.
|
||||
// 결과 없으면 YouTube로 폴백.
|
||||
if (!result
|
||||
|| result.loadType === LoadType.EMPTY
|
||||
|| result.loadType === LoadType.ERROR
|
||||
|| (result.loadType === LoadType.PLAYLIST && result.data.tracks.length === 0)
|
||||
) {
|
||||
if (result?.loadType === LoadType.ERROR) {
|
||||
Logger.warn(`[autoPlay] sprec ERROR: ${result.data?.message ?? "unknown"}. YouTube으로 폴백합니다.`);
|
||||
} else {
|
||||
Logger.warn(`[autoPlay] sprec 결과 없음. YouTube으로 폴백합니다.`);
|
||||
}
|
||||
result = await this.youtubeMixFromSpotifyTrack(author, title);
|
||||
usedYtFallback = true;
|
||||
}
|
||||
} else {
|
||||
result = await this.player.node.rest.resolve(`https://music.youtube.com/watch?v=${trackId}&list=RD${trackId}`);
|
||||
try {
|
||||
result = await this.player.node.rest.resolve(`https://music.youtube.com/watch?v=${trackId}&list=RD${trackId}`);
|
||||
} catch (err) {
|
||||
Logger.warn(`[autoPlay] YouTube mix resolve throw: ${String(err)}`);
|
||||
result = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let tracks: Track[] = [];
|
||||
if (result?.loadType === LoadType.PLAYLIST) {
|
||||
tracks = result.data.tracks;
|
||||
@@ -289,6 +322,7 @@ export class GuildPlayer {
|
||||
} else if (result?.loadType === LoadType.TRACK) {
|
||||
tracks = [ result.data ];
|
||||
}
|
||||
Logger.info(`[autoPlay] 자동재생 후보 ${tracks.length}곡${usedYtFallback ? " (YouTube 폴백)" : ""}`);
|
||||
if (tracks.length === 0) {
|
||||
this.end();
|
||||
return;
|
||||
@@ -301,6 +335,39 @@ export class GuildPlayer {
|
||||
this.addTracks(tracks, "자동재생");
|
||||
}
|
||||
|
||||
/**
|
||||
* Spotify 트랙 메타데이터로 YouTube 검색 후 매칭된 곡의 RD 믹스를 가져옴.
|
||||
* (현재 Lavalink에서 `ytmsearch:`는 EMPTY를 자주 반환하므로 `ytsearch:` 사용)
|
||||
* 각 단계가 실패하면 다음 단계로 진행하지 않고 undefined 반환.
|
||||
*/
|
||||
private async youtubeMixFromSpotifyTrack(author: string, title: string) {
|
||||
const cleanAuthor = author.replace(" - Topic", "");
|
||||
const query = `ytsearch:${cleanAuthor} ${title}`;
|
||||
let searchResult;
|
||||
try {
|
||||
searchResult = await this.player.node.rest.resolve(query);
|
||||
} catch (err) {
|
||||
Logger.warn(`[autoPlay] YouTube 폴백 검색 throw: ${String(err)}`);
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
searchResult?.loadType !== LoadType.SEARCH
|
||||
|| !Array.isArray(searchResult.data)
|
||||
|| searchResult.data.length === 0
|
||||
) {
|
||||
Logger.warn(`[autoPlay] YouTube 폴백 검색 결과 없음 (query="${cleanAuthor} ${title}", loadType=${searchResult?.loadType ?? "(null)"})`);
|
||||
return undefined;
|
||||
}
|
||||
const ytId = searchResult.data[0].info.identifier;
|
||||
Logger.info(`[autoPlay] YouTube 폴백 매칭: videoId=${ytId}`);
|
||||
try {
|
||||
return await this.player.node.rest.resolve(`https://music.youtube.com/watch?v=${ytId}&list=RD${ytId}`);
|
||||
} catch (err) {
|
||||
Logger.warn(`[autoPlay] YouTube 폴백 RD 믹스 resolve throw: ${String(err)}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private clearAllTimers() {
|
||||
if (this.errorTimer !== undefined) {
|
||||
clearTimeout(this.errorTimer);
|
||||
|
||||
Reference in New Issue
Block a user