diff --git a/lib/services/ytmusic/index.js b/lib/services/ytmusic/index.js index b2fe78b..887bb16 100644 --- a/lib/services/ytmusic/index.js +++ b/lib/services/ytmusic/index.js @@ -190,14 +190,54 @@ async function lookupAlbum(id) { artist: { name: artists.join(", "), }, + playlistId: album_data.audioPlaylistId }); } +async function lookupPlaylist(id) { + let request_body = {enablePersistentPlaylistPanel: true, isAudioOnly: true, playlistId: id, ...standard_body} + const { body } = await request.post("https://music.youtube.com/youtubei/v1/next") + .set(standard_headers) + .query(standard_params) + .send(request_body) + + // The playlist object is rather complex, but here's what I'm doing: I'll parse the very minimum to get to the first track. + // At that point, I'm going to check the id of the album in that track. And make a lookup on it. If the album looked up + // has the same playlist id as the one I'm looking up, it means it's an album and not a playlist and we're good. + const watchNextRenderer = body.contents.singleColumnMusicWatchNextResultsRenderer.tabbedRenderer.watchNextTabbedResultsRenderer + const firstTrack = watchNextRenderer.tabs[0].tabRenderer.content.musicQueueRenderer.content.playlistPanelRenderer.contents[0] + const runs = firstTrack.playlistPanelVideoRenderer.longBylineText.runs + const reverse_last_artist_idx = runs.reverse().findIndex((entry) => { + if (entry.navigationEndpoint === undefined) { + return false + } + return entry.navigationEndpoint.browseEndpoint.browseId.startsWith("UC") || + entry.navigationEndpoint.browseEndpoint.browseId.startsWith("FEmusic_library_privately_owned_artist") + }); + if (reverse_last_artist_idx == -1) { + debug("Could not find an artist. Implement extra logic from ytmusicapi!"); + throw new Error(); + } + const last_artist_idx = runs.length - reverse_last_artist_idx - 1; + if (runs.length - last_artist_idx != 5) { + debug("No album found, can't find this."); + throw new Error(); + } + const albumId = runs[last_artist_idx + 2].navigationEndpoint.browseEndpoint.browseId + const possibleAlbum = await lookupAlbum(albumId) + if (possibleAlbum.playlistId = id) { + return possibleAlbum; + } + throw new Error(); +} + export async function lookupId(id, type) { if (type == 'track') { return lookupTrack(id); } else if (type == 'album') { return lookupAlbum(id); + } else if (type == 'playlist') { + return lookupPlaylist(id); } return { service: 'ytmusic', id }; } @@ -214,9 +254,7 @@ export function parseUrl(url) { } else if (match = parsed.path.match(/^\/browse\/([A-Za-z0-9_]+)/)) { return lookupId(match[1], 'album'); } else if (match = parsed.path.match(/^\/playlist/) && list_id !== undefined) { - if (list_id == 'OLAK5uy_lx9K5RpiBEwd3E4C1GKqY7e06qTlwydvs') { // TODO: Parse playlist correctly - return lookupId('MPREb_9C36yscfgmJ', 'album'); - } + return lookupId(list_id, 'playlist'); } throw new Error(); }