From 09684b2070b55f7cf3ff331467bc8c929f56cf21 Mon Sep 17 00:00:00 2001 From: tkrmagid Date: Wed, 27 May 2026 20:37:22 +0900 Subject: [PATCH] =?UTF-8?q?bot:=20Spotify=E2=86=92YouTube=20=ED=8F=B4?= =?UTF-8?q?=EB=B0=B1=20dedup=20ID=EB=A5=BC=20YouTube=20seed=EB=A1=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 폴백 시 lastPlayed.identifier(Spotify ID)와 RD 믹스 첫 곡(YouTube ID) 비교가 안 맞아서, 방금 끝난 곡의 YouTube 버전이 자동재생 큐에 그대로 들어가던 버그 수정. - youtubeMixFromSpotifyTrack: { result, seedYtId } 반환 - autoPlay: 폴백일 때 dedup 기준 ID를 seedYtId로 교체 --- bot/src/classes/GuildPlayer.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/bot/src/classes/GuildPlayer.ts b/bot/src/classes/GuildPlayer.ts index 37cf2ce..13ac646 100644 --- a/bot/src/classes/GuildPlayer.ts +++ b/bot/src/classes/GuildPlayer.ts @@ -279,6 +279,9 @@ export class GuildPlayer { const author = this.lastPlayedTrack.info.author; let result; let usedYtFallback = false; + // 중복 제거 기준 ID. 기본은 lastPlayed의 identifier, 다만 Spotify→YouTube 폴백 시에는 + // RD 믹스의 시드(YouTube videoId)와 비교해야 첫 곡(=방금 끝난 곡의 YouTube 버전)을 제거할 수 있음. + let dedupId = trackId; if (source === "spotify") { const sprecUri = `sprec:seed_tracks=${trackId}&limit=11`; @@ -302,7 +305,9 @@ export class GuildPlayer { } else { Logger.warn(`[autoPlay] sprec 결과 없음. YouTube으로 폴백합니다.`); } - result = await this.youtubeMixFromSpotifyTrack(author, title); + const fallback = await this.youtubeMixFromSpotifyTrack(author, title); + result = fallback?.result; + if (fallback?.seedYtId) dedupId = fallback.seedYtId; usedYtFallback = true; } } else { @@ -327,7 +332,7 @@ export class GuildPlayer { this.end(); return; } - if (tracks.length > 0 && tracks[0].info.identifier === trackId) tracks = tracks.slice(1); + if (tracks.length > 0 && tracks[0].info.identifier === dedupId) tracks = tracks.slice(1); if (tracks.length === 0) { this.end(); return; @@ -339,6 +344,7 @@ export class GuildPlayer { * Spotify 트랙 메타데이터로 YouTube 검색 후 매칭된 곡의 RD 믹스를 가져옴. * (현재 Lavalink에서 `ytmsearch:`는 EMPTY를 자주 반환하므로 `ytsearch:` 사용) * 각 단계가 실패하면 다음 단계로 진행하지 않고 undefined 반환. + * 호출 측에서 RD 믹스 첫 곡(=시드 자체)을 중복 제거할 수 있도록 seed videoId도 같이 반환. */ private async youtubeMixFromSpotifyTrack(author: string, title: string) { const cleanAuthor = author.replace(" - Topic", ""); @@ -358,10 +364,11 @@ export class GuildPlayer { 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}`); + const seedYtId = searchResult.data[0].info.identifier; + Logger.info(`[autoPlay] YouTube 폴백 매칭: videoId=${seedYtId}`); try { - return await this.player.node.rest.resolve(`https://music.youtube.com/watch?v=${ytId}&list=RD${ytId}`); + const result = await this.player.node.rest.resolve(`https://music.youtube.com/watch?v=${seedYtId}&list=RD${seedYtId}`); + return { result, seedYtId }; } catch (err) { Logger.warn(`[autoPlay] YouTube 폴백 RD 믹스 resolve throw: ${String(err)}`); return undefined;