diff --git a/src/@types/types.ts b/src/@types/types.ts index 056f87a..13eef4b 100644 --- a/src/@types/types.ts +++ b/src/@types/types.ts @@ -9,7 +9,7 @@ export const ThumbnailFull = type({ export type ArtistBasic = typeof ArtistBasic.infer export const ArtistBasic = type({ - artistId: "string|null", + artistId: "string|null", // Only null for YouTube Music name: "string", }) @@ -24,7 +24,7 @@ export const SongDetailed = type({ type: '"SONG"', videoId: "string", name: "string", - artists: [ArtistBasic, "[]"], + artist: ArtistBasic, album: AlbumBasic, duration: "number|null", thumbnails: [ThumbnailFull, "[]"], @@ -35,7 +35,7 @@ export const VideoDetailed = type({ type: '"VIDEO"', videoId: "string", name: "string", - artists: [ArtistBasic, "[]"], + artist: ArtistBasic, duration: "number|null", thumbnails: [ThumbnailFull, "[]"], }) @@ -54,7 +54,7 @@ export const AlbumDetailed = type({ albumId: "string", playlistId: "string", name: "string", - artists: [ArtistBasic, "[]"], + artist: ArtistBasic, year: "number|null", thumbnails: [ThumbnailFull, "[]"], }) @@ -73,10 +73,9 @@ export const SongFull = type({ type: '"SONG"', videoId: "string", name: "string", - artists: [ArtistBasic, "[]"], + artist: ArtistBasic, duration: "number", thumbnails: [ThumbnailFull, "[]"], - description: "string", formats: "any[]", adaptiveFormats: "any[]", }) @@ -86,10 +85,9 @@ export const VideoFull = type({ type: '"VIDEO"', videoId: "string", name: "string", - artists: [ArtistBasic, "[]"], + artist: ArtistBasic, duration: "number", thumbnails: [ThumbnailFull, "[]"], - description: "string", unlisted: "boolean", familySafe: "boolean", paid: "boolean", @@ -102,7 +100,6 @@ export const ArtistFull = type({ name: "string", type: '"ARTIST"', thumbnails: [ThumbnailFull, "[]"], - description: "string", topSongs: [SongDetailed, "[]"], topAlbums: [AlbumDetailed, "[]"], topSingles: [AlbumDetailed, "[]"], @@ -117,7 +114,7 @@ export const AlbumFull = type({ albumId: "string", playlistId: "string", name: "string", - artists: [ArtistBasic, "[]"], + artist: ArtistBasic, year: "number|null", thumbnails: [ThumbnailFull, "[]"], songs: [SongDetailed, "[]"], diff --git a/src/YTMusic.ts b/src/YTMusic.ts index 56e27f8..d2d0dd4 100644 --- a/src/YTMusic.ts +++ b/src/YTMusic.ts @@ -389,16 +389,12 @@ export default class YTMusic { * @returns Artist's Songs */ public async getArtistSongs(artistId: string): Promise<(typeof SongDetailed.infer)[]> { - const artistData = await this.constructRequest("browse", { - browseId: artistId, - }) + const artistData = await this.constructRequest("browse", { browseId: artistId }) const browseToken = traverse(artistData, "musicShelfRenderer", "title", "browseId") if (browseToken instanceof Array) return [] - const songsData = await this.constructRequest("browse", { - browseId: browseToken, - }) + const songsData = await this.constructRequest("browse", { browseId: browseToken }) const continueToken = traverse(songsData, "continuation") const moreSongsData = await this.constructRequest( "browse", @@ -409,7 +405,12 @@ export default class YTMusic { return [ ...traverseList(songsData, "musicResponsiveListItemRenderer"), ...traverseList(moreSongsData, "musicResponsiveListItemRenderer"), - ].map(SongParser.parseArtistSong) + ].map(s => + SongParser.parseArtistSong(s, { + artistId, + name: traverseString(artistData, "header", "title", "text")(), + }), + ) } /** diff --git a/src/parsers/AlbumParser.ts b/src/parsers/AlbumParser.ts index 095ab92..2b5be79 100644 --- a/src/parsers/AlbumParser.ts +++ b/src/parsers/AlbumParser.ts @@ -1,5 +1,7 @@ import { AlbumBasic, AlbumDetailed, AlbumFull, ArtistBasic } from "../@types/types" import checkType from "../utils/checkType" +import { isArtist } from "../utils/filters" +import traverse from "../utils/traverse" import traverseList from "../utils/traverseList" import traverseString from "../utils/traverseString" import SongParser from "./SongParser" @@ -11,12 +13,11 @@ export default class AlbumParser { name: traverseString(data, "header", "title", "text")(), } - const artists: ArtistBasic[] = traverseList(data, "header", "subtitle", "runs") - .filter(run => "navigationEndpoint" in run) - .map(run => ({ - artistId: traverseString(run, "browseId")(), - name: traverseString(run, "text")(), - })) + const artistData = traverse(data, "header", "subtitle", "runs") + const artistBasic: ArtistBasic = { + artistId: traverseString(artistData, "browseId")(), + name: traverseString(artistData, "text")(), + } const thumbnails = traverseList(data, "header", "thumbnails") @@ -25,13 +26,13 @@ export default class AlbumParser { type: "ALBUM", ...albumBasic, playlistId: traverseString(data, "buttonRenderer", "playlistId")(), - artists, + artist: artistBasic, year: AlbumParser.processYear( traverseString(data, "header", "subtitle", "text")(-1), ), thumbnails, songs: traverseList(data, "musicResponsiveListItemRenderer").map(item => - SongParser.parseAlbumSong(item, artists, albumBasic, thumbnails), + SongParser.parseAlbumSong(item, artistBasic, albumBasic, thumbnails), ), }, AlbumFull, @@ -39,21 +40,23 @@ export default class AlbumParser { } public static parseSearchResult(item: any): AlbumDetailed { - const flexColumns = traverseList(item, "flexColumns") + const columns = traverseList(item, "flexColumns", "runs").flat() + + // No specific way to identify the title + const title = columns[0] + const artist = columns.find(isArtist) || columns[3] return checkType( { type: "ALBUM", albumId: traverseString(item, "browseId")(-1), playlistId: traverseString(item, "overlay", "playlistId")(), - artists: traverseList(flexColumns[1], "runs") - .filter(run => "navigationEndpoint" in run) - .map(run => ({ - artistId: traverseString(run, "browseId")(), - name: traverseString(run, "text")(), - })), - year: AlbumParser.processYear(traverseString(flexColumns[1], "runs", "text")(-1)), - name: traverseString(flexColumns[0], "runs", "text")(), + artist: { + name: traverseString(artist, "text")(), + artistId: traverseString(artist, "browseId")() || null, + }, + year: AlbumParser.processYear(traverseString(columns[1], "runs", "text")(-1)), + name: traverseString(title, "text")(), thumbnails: traverseList(item, "thumbnails"), }, AlbumDetailed, @@ -67,7 +70,7 @@ export default class AlbumParser { albumId: traverseString(item, "browseId")(-1), playlistId: traverseString(item, "thumbnailOverlay", "playlistId")(), name: traverseString(item, "title", "text")(), - artists: [artistBasic], + artist: artistBasic, year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)), thumbnails: traverseList(item, "thumbnails"), }, @@ -82,7 +85,7 @@ export default class AlbumParser { albumId: traverseString(item, "browseId")(-1), playlistId: traverseString(item, "musicPlayButtonRenderer", "playlistId")(), name: traverseString(item, "title", "text")(), - artists: [artistBasic], + artist: artistBasic, year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)), thumbnails: traverseList(item, "thumbnails"), }, diff --git a/src/parsers/ArtistParser.ts b/src/parsers/ArtistParser.ts index ed1c5b0..4848574 100644 --- a/src/parsers/ArtistParser.ts +++ b/src/parsers/ArtistParser.ts @@ -14,14 +14,11 @@ export default class ArtistParser { name: traverseString(data, "header", "title", "text")(), } - const description = traverseString(data, "header", "description", "text")() - return checkType( { type: "ARTIST", ...artistBasic, thumbnails: traverseList(data, "header", "thumbnails"), - description, topSongs: traverseList(data, "musicShelfRenderer", "contents").map(item => SongParser.parseArtistTopSong(item, artistBasic), ), @@ -46,8 +43,9 @@ export default class ArtistParser { featuredOn: traverseList(data, "musicCarouselShelfRenderer") ?.at(3) - ?.contents.map((item: any) => PlaylistParser.parseArtistFeaturedOn(item)) ?? - [], + ?.contents.map((item: any) => + PlaylistParser.parseArtistFeaturedOn(item, artistBasic), + ) ?? [], similarArtists: traverseList(data, "musicCarouselShelfRenderer") ?.at(4) @@ -58,7 +56,7 @@ export default class ArtistParser { } public static parseSearchResult(item: any): ArtistDetailed { - const columns = traverseList(item, "flexColumns") + const columns = traverseList(item, "flexColumns", "runs").flat() // No specific way to identify the title const title = columns[0] @@ -67,7 +65,7 @@ export default class ArtistParser { { type: "ARTIST", artistId: traverseString(item, "browseId")(), - name: traverseString(title, "runs", "text")(), + name: traverseString(title, "text")(), thumbnails: traverseList(item, "thumbnails"), }, ArtistDetailed, diff --git a/src/parsers/PlaylistParser.ts b/src/parsers/PlaylistParser.ts index 7d0203a..4005e27 100644 --- a/src/parsers/PlaylistParser.ts +++ b/src/parsers/PlaylistParser.ts @@ -1,4 +1,4 @@ -import { PlaylistDetailed, PlaylistFull } from "../@types/types" +import { ArtistBasic, PlaylistDetailed, PlaylistFull } from "../@types/types" import checkType from "../utils/checkType" import { isArtist } from "../utils/filters" import traverse from "../utils/traverse" @@ -53,16 +53,13 @@ export default class PlaylistParser { ) } - public static parseArtistFeaturedOn(item: any): PlaylistDetailed { + public static parseArtistFeaturedOn(item: any, artistBasic: ArtistBasic): PlaylistDetailed { return checkType( { type: "PLAYLIST", playlistId: traverseString(item, "navigationEndpoint", "browseId")(), name: traverseString(item, "runs", "text")(), - artist: { - artistId: traverseString(item, "browseId")(), - name: traverseString(item, "runs", "text")(-3), - }, + artist: artistBasic, thumbnails: traverseList(item, "thumbnails"), }, PlaylistDetailed, diff --git a/src/parsers/SongParser.ts b/src/parsers/SongParser.ts index 92425e6..664b013 100644 --- a/src/parsers/SongParser.ts +++ b/src/parsers/SongParser.ts @@ -12,15 +12,12 @@ export default class SongParser { type: "SONG", videoId: traverseString(data, "videoDetails", "videoId")(), name: traverseString(data, "videoDetails", "title")(), - artists: [ - { - artistId: traverseString(data, "videoDetails", "channelId")(), - name: traverseString(data, "author")(), - }, - ], + artist: { + name: traverseString(data, "author")(), + artistId: traverseString(data, "videoDetails", "channelId")(), + }, duration: +traverseString(data, "videoDetails", "lengthSeconds")(), thumbnails: traverseList(data, "videoDetails", "thumbnails"), - description: traverseString(data, "description")(), formats: traverseList(data, "streamingData", "formats"), adaptiveFormats: traverseList(data, "streamingData", "adaptiveFormats"), }, @@ -41,12 +38,10 @@ export default class SongParser { type: "SONG", videoId: traverseString(item, "playlistItemData", "videoId")(), name: traverseString(title, "text")(), - artists: [ - { - name: traverseString(artist, "text")(), - artistId: traverseString(artist, "browseId")(), - }, - ], + artist: { + name: traverseString(artist, "text")(), + artistId: traverseString(artist, "browseId")(), + }, album: { name: traverseString(album, "text")(), albumId: traverseString(album, "browseId")(), @@ -58,11 +53,10 @@ export default class SongParser { ) } - public static parseArtistSong(item: any): SongDetailed { + public static parseArtistSong(item: any, artistBasic: ArtistBasic): SongDetailed { const columns = traverseList(item, "flexColumns", "runs").flat() const title = columns.find(isTitle) - const artist = columns.find(isArtist) const album = columns.find(isAlbum) const duration = columns.find(isDuration) @@ -71,12 +65,7 @@ export default class SongParser { type: "SONG", videoId: traverseString(item, "playlistItemData", "videoId")(), name: traverseString(title, "text")(), - artists: [ - { - name: traverseString(artist, "text")(), - artistId: traverseString(artist, "browseId")(), - }, - ], + artist: artistBasic, album: { name: traverseString(album, "text")(), albumId: traverseString(album, "browseId")(), @@ -99,7 +88,7 @@ export default class SongParser { type: "SONG", videoId: traverseString(item, "playlistItemData", "videoId")(), name: traverseString(title, "text")(), - artists: [artistBasic], + artist: artistBasic, album: { name: traverseString(album, "text")(), albumId: traverseString(album, "browseId")(), @@ -113,7 +102,7 @@ export default class SongParser { public static parseAlbumSong( item: any, - artists: ArtistBasic[], + artistBasic: ArtistBasic, albumBasic: AlbumBasic, thumbnails: ThumbnailFull[], ): SongDetailed { @@ -127,7 +116,7 @@ export default class SongParser { type: "SONG", videoId: traverseString(item, "playlistItemData", "videoId")(), name: traverseString(title, "text")(), - artists, + artist: artistBasic, album: albumBasic, duration: duration ? Parser.parseDuration(duration.text) : null, thumbnails, diff --git a/src/parsers/VideoParser.ts b/src/parsers/VideoParser.ts index 4391e68..f5019d5 100644 --- a/src/parsers/VideoParser.ts +++ b/src/parsers/VideoParser.ts @@ -12,15 +12,12 @@ export default class VideoParser { type: "VIDEO", videoId: traverseString(data, "videoDetails", "videoId")(), name: traverseString(data, "videoDetails", "title")(), - artists: [ - { - artistId: traverseString(data, "videoDetails", "channelId")(), - name: traverseString(data, "author")(), - }, - ], + artist: { + artistId: traverseString(data, "videoDetails", "channelId")(), + name: traverseString(data, "author")(), + }, duration: +traverseString(data, "videoDetails", "lengthSeconds")(), thumbnails: traverseList(data, "videoDetails", "thumbnails"), - description: traverseString(data, "description")(), unlisted: traverse(data, "unlisted"), familySafe: traverse(data, "familySafe"), paid: traverse(data, "paid"), @@ -39,12 +36,10 @@ export default class VideoParser { type: "VIDEO", videoId: traverseString(item, "playNavigationEndpoint", "videoId")(), name: traverseString(title, "text")(), - artists: [ - { - name: traverseString(artist, "text")(), - artistId: traverseString(artist, "browseId")(), - }, - ], + artist: { + name: traverseString(artist, "text")(), + artistId: traverseString(artist, "browseId")(), + }, duration: Parser.parseDuration(duration.text), thumbnails: traverseList(item, "thumbnails"), } @@ -55,7 +50,7 @@ export default class VideoParser { type: "VIDEO", videoId: traverseString(item, "videoId")(), name: traverseString(item, "runs", "text")(), - artists: [artistBasic], + artist: artistBasic, duration: null, thumbnails: traverseList(item, "thumbnails"), } @@ -77,12 +72,10 @@ export default class VideoParser { /https:\/\/i\.ytimg\.com\/vi\/(.+)\//, )[1], name: traverseString(title, "text")(), - artists: [ - { - name: traverseString(artist, "text")(), - artistId: traverseString(artist, "browseId")() || null, - }, - ], + artist: { + name: traverseString(artist, "text")(), + artistId: traverseString(artist, "browseId")() || null, + }, duration: duration ? Parser.parseDuration(duration.text) : null, thumbnails: traverseList(item, "thumbnails"), },