diff --git a/src/@types/types.ts b/src/@types/types.ts index 13eef4b..8a48566 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", // Only null for YouTube Music + artistId: "string|null", name: "string", }) diff --git a/src/YTMusic.ts b/src/YTMusic.ts index d2d0dd4..31f7b90 100644 --- a/src/YTMusic.ts +++ b/src/YTMusic.ts @@ -20,9 +20,7 @@ import PlaylistParser from "./parsers/PlaylistParser" import SearchParser from "./parsers/SearchParser" import SongParser from "./parsers/SongParser" import VideoParser from "./parsers/VideoParser" -import traverse from "./utils/traverse" -import traverseList from "./utils/traverseList" -import traverseString from "./utils/traverseString" +import { traverse, traverseList, traverseString } from "./utils/traverse" export default class YTMusic { private cookiejar: CookieJar @@ -358,7 +356,7 @@ export default class YTMusic { const browseId = traverse(traverseList(data, "tabs", "tabRenderer")[1], "browseId") const lyricsData = await this.constructRequest("browse", { browseId }) - const lyrics = traverseString(lyricsData, "description", "runs", "text")() + const lyrics = traverseString(lyricsData, "description", "runs", "text") return lyrics ? lyrics @@ -408,7 +406,7 @@ export default class YTMusic { ].map(s => SongParser.parseArtistSong(s, { artistId, - name: traverseString(artistData, "header", "title", "text")(), + name: traverseString(artistData, "header", "title", "text"), }), ) } @@ -431,7 +429,7 @@ export default class YTMusic { return traverseList(albumsData, "musicTwoRowItemRenderer").map(item => AlbumParser.parseArtistAlbum(item, { artistId, - name: traverseString(albumsData, "header", "runs", "text")(), + name: traverseString(albumsData, "header", "runs", "text"), }), ) } diff --git a/src/parsers/AlbumParser.ts b/src/parsers/AlbumParser.ts index 0d45ea8..e4cd99d 100644 --- a/src/parsers/AlbumParser.ts +++ b/src/parsers/AlbumParser.ts @@ -1,22 +1,20 @@ 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 { traverse, traverseList, traverseString } from "../utils/traverse" import SongParser from "./SongParser" export default class AlbumParser { public static parse(data: any, albumId: string): AlbumFull { const albumBasic: AlbumBasic = { albumId, - name: traverseString(data, "header", "title", "text")(), + name: traverseString(data, "header", "title", "text"), } const artistData = traverse(data, "header", "subtitle", "runs") const artistBasic: ArtistBasic = { - artistId: traverseString(artistData, "browseId")() || null, - name: traverseString(artistData, "text")(), + artistId: traverseString(artistData, "browseId") || null, + name: traverseString(artistData, "text"), } const thumbnails = traverseList(data, "header", "thumbnails") @@ -25,10 +23,10 @@ export default class AlbumParser { { type: "ALBUM", ...albumBasic, - playlistId: traverseString(data, "buttonRenderer", "playlistId")(), + playlistId: traverseString(data, "buttonRenderer", "playlistId"), artist: artistBasic, year: AlbumParser.processYear( - traverseString(data, "header", "subtitle", "text")(-1), + traverseList(data, "header", "subtitle", "text").at(-1), ), thumbnails, songs: traverseList(data, "musicResponsiveListItemRenderer").map(item => @@ -49,14 +47,14 @@ export default class AlbumParser { return checkType( { type: "ALBUM", - albumId: traverseString(item, "browseId")(-1), - playlistId: traverseString(item, "overlay", "playlistId")(), + albumId: traverseList(item, "browseId").at(-1), + playlistId: traverseString(item, "overlay", "playlistId"), artist: { - name: traverseString(artist, "text")(), - artistId: traverseString(artist, "browseId")() || null, + name: traverseString(artist, "text"), + artistId: traverseString(artist, "browseId") || null, }, - year: AlbumParser.processYear(traverseString(columns[1], "runs", "text")(-1)), - name: traverseString(title, "text")(), + year: AlbumParser.processYear(columns.at(-1).text), + name: traverseString(title, "text"), thumbnails: traverseList(item, "thumbnails"), }, AlbumDetailed, @@ -67,11 +65,11 @@ export default class AlbumParser { return checkType( { type: "ALBUM", - albumId: traverseString(item, "browseId")(-1), - playlistId: traverseString(item, "thumbnailOverlay", "playlistId")(), - name: traverseString(item, "title", "text")(), + albumId: traverseList(item, "browseId").at(-1), + playlistId: traverseString(item, "thumbnailOverlay", "playlistId"), + name: traverseString(item, "title", "text"), artist: artistBasic, - year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)), + year: AlbumParser.processYear(traverseList(item, "subtitle", "text").at(-1)), thumbnails: traverseList(item, "thumbnails"), }, AlbumDetailed, @@ -82,11 +80,11 @@ export default class AlbumParser { return checkType( { type: "ALBUM", - albumId: traverseString(item, "browseId")(-1), - playlistId: traverseString(item, "musicPlayButtonRenderer", "playlistId")(), - name: traverseString(item, "title", "text")(), + albumId: traverseList(item, "browseId").at(-1), + playlistId: traverseString(item, "musicPlayButtonRenderer", "playlistId"), + name: traverseString(item, "title", "text"), artist: artistBasic, - year: AlbumParser.processYear(traverseString(item, "subtitle", "text")(-1)), + year: AlbumParser.processYear(traverseList(item, "subtitle", "text").at(-1)), thumbnails: traverseList(item, "thumbnails"), }, AlbumDetailed, diff --git a/src/parsers/ArtistParser.ts b/src/parsers/ArtistParser.ts index 4848574..68c459a 100644 --- a/src/parsers/ArtistParser.ts +++ b/src/parsers/ArtistParser.ts @@ -1,7 +1,6 @@ import { ArtistDetailed, ArtistFull } from "../@types/types" import checkType from "../utils/checkType" -import traverseList from "../utils/traverseList" -import traverseString from "../utils/traverseString" +import { traverseList, traverseString } from "../utils/traverse" import AlbumParser from "./AlbumParser" import PlaylistParser from "./PlaylistParser" import SongParser from "./SongParser" @@ -11,7 +10,7 @@ export default class ArtistParser { public static parse(data: any, artistId: string): ArtistFull { const artistBasic = { artistId, - name: traverseString(data, "header", "title", "text")(), + name: traverseString(data, "header", "title", "text"), } return checkType( @@ -64,8 +63,8 @@ export default class ArtistParser { return checkType( { type: "ARTIST", - artistId: traverseString(item, "browseId")(), - name: traverseString(title, "text")(), + artistId: traverseString(item, "browseId"), + name: traverseString(title, "text"), thumbnails: traverseList(item, "thumbnails"), }, ArtistDetailed, @@ -76,8 +75,8 @@ export default class ArtistParser { return checkType( { type: "ARTIST", - artistId: traverseString(item, "browseId")(), - name: traverseString(item, "runs", "text")(), + artistId: traverseString(item, "browseId"), + name: traverseString(item, "runs", "text"), thumbnails: traverseList(item, "thumbnails"), }, ArtistDetailed, diff --git a/src/parsers/PlaylistParser.ts b/src/parsers/PlaylistParser.ts index 1fdaad4..252c4ec 100644 --- a/src/parsers/PlaylistParser.ts +++ b/src/parsers/PlaylistParser.ts @@ -1,9 +1,7 @@ import { ArtistBasic, PlaylistDetailed, PlaylistFull } 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 { traverse, traverseList, traverseString } from "../utils/traverse" export default class PlaylistParser { public static parse(data: any, playlistId: string): PlaylistFull { @@ -13,10 +11,10 @@ export default class PlaylistParser { { type: "PLAYLIST", playlistId, - name: traverseString(data, "header", "title", "text")(), + name: traverseString(data, "header", "title", "text"), artist: { - name: traverseString(artist, "text")(), - artistId: traverseString(artist, "browseId")() || null, + name: traverseString(artist, "text"), + artistId: traverseString(artist, "browseId") || null, }, videoCount: +traverseList(data, "header", "secondSubtitle", "text") @@ -40,11 +38,11 @@ export default class PlaylistParser { return checkType( { type: "PLAYLIST", - playlistId: traverseString(item, "overlay", "playlistId")(), - name: traverseString(title, "text")(), + playlistId: traverseString(item, "overlay", "playlistId"), + name: traverseString(title, "text"), artist: { - name: traverseString(artist, "text")(), - artistId: traverseString(artist, "browseId")() || null, + name: traverseString(artist, "text"), + artistId: traverseString(artist, "browseId") || null, }, thumbnails: traverseList(item, "thumbnails"), }, @@ -56,8 +54,8 @@ export default class PlaylistParser { return checkType( { type: "PLAYLIST", - playlistId: traverseString(item, "navigationEndpoint", "browseId")(), - name: traverseString(item, "runs", "text")(), + playlistId: traverseString(item, "navigationEndpoint", "browseId"), + name: traverseString(item, "runs", "text"), artist: artistBasic, thumbnails: traverseList(item, "thumbnails"), }, diff --git a/src/parsers/SearchParser.ts b/src/parsers/SearchParser.ts index 57dd535..54e87ba 100644 --- a/src/parsers/SearchParser.ts +++ b/src/parsers/SearchParser.ts @@ -1,5 +1,5 @@ import { SearchResult } from "../@types/types" -import traverseList from "../utils/traverseList" +import { traverseList } from "../utils/traverse" import AlbumParser from "./AlbumParser" import ArtistParser from "./ArtistParser" import PlaylistParser from "./PlaylistParser" diff --git a/src/parsers/SongParser.ts b/src/parsers/SongParser.ts index f193c06..bad7993 100644 --- a/src/parsers/SongParser.ts +++ b/src/parsers/SongParser.ts @@ -1,8 +1,7 @@ import { AlbumBasic, ArtistBasic, SongDetailed, SongFull, ThumbnailFull } from "../@types/types" import checkType from "../utils/checkType" import { isAlbum, isArtist, isDuration, isTitle } from "../utils/filters" -import traverseList from "../utils/traverseList" -import traverseString from "../utils/traverseString" +import { traverseList, traverseString } from "../utils/traverse" import Parser from "./Parser" export default class SongParser { @@ -10,13 +9,13 @@ export default class SongParser { return checkType( { type: "SONG", - videoId: traverseString(data, "videoDetails", "videoId")(), - name: traverseString(data, "videoDetails", "title")(), + videoId: traverseString(data, "videoDetails", "videoId"), + name: traverseString(data, "videoDetails", "title"), artist: { - name: traverseString(data, "author")(), - artistId: traverseString(data, "videoDetails", "channelId")(), + name: traverseString(data, "author"), + artistId: traverseString(data, "videoDetails", "channelId"), }, - duration: +traverseString(data, "videoDetails", "lengthSeconds")(), + duration: +traverseString(data, "videoDetails", "lengthSeconds"), thumbnails: traverseList(data, "videoDetails", "thumbnails"), formats: traverseList(data, "streamingData", "formats"), adaptiveFormats: traverseList(data, "streamingData", "adaptiveFormats"), @@ -36,15 +35,15 @@ export default class SongParser { return checkType( { type: "SONG", - videoId: traverseString(item, "playlistItemData", "videoId")(), - name: traverseString(title, "text")(), + videoId: traverseString(item, "playlistItemData", "videoId"), + name: traverseString(title, "text"), artist: { - name: traverseString(artist, "text")(), - artistId: traverseString(artist, "browseId")() || null, + name: traverseString(artist, "text"), + artistId: traverseString(artist, "browseId") || null, }, album: { - name: traverseString(album, "text")(), - albumId: traverseString(album, "browseId")(), + name: traverseString(album, "text"), + albumId: traverseString(album, "browseId"), }, duration: Parser.parseDuration(duration.text), thumbnails: traverseList(item, "thumbnails"), @@ -63,12 +62,12 @@ export default class SongParser { return checkType( { type: "SONG", - videoId: traverseString(item, "playlistItemData", "videoId")(), - name: traverseString(title, "text")(), + videoId: traverseString(item, "playlistItemData", "videoId"), + name: traverseString(title, "text"), artist: artistBasic, album: { - name: traverseString(album, "text")(), - albumId: traverseString(album, "browseId")(), + name: traverseString(album, "text"), + albumId: traverseString(album, "browseId"), }, duration: duration ? Parser.parseDuration(duration.text) : null, thumbnails: traverseList(item, "thumbnails"), @@ -86,12 +85,12 @@ export default class SongParser { return checkType( { type: "SONG", - videoId: traverseString(item, "playlistItemData", "videoId")(), - name: traverseString(title, "text")(), + videoId: traverseString(item, "playlistItemData", "videoId"), + name: traverseString(title, "text"), artist: artistBasic, album: { - name: traverseString(album, "text")(), - albumId: traverseString(album, "browseId")(), + name: traverseString(album, "text"), + albumId: traverseString(album, "browseId"), }, duration: null, thumbnails: traverseList(item, "thumbnails"), @@ -114,8 +113,8 @@ export default class SongParser { return checkType( { type: "SONG", - videoId: traverseString(item, "playlistItemData", "videoId")(), - name: traverseString(title, "text")(), + videoId: traverseString(item, "playlistItemData", "videoId"), + name: traverseString(title, "text"), artist: artistBasic, album: albumBasic, duration: duration ? Parser.parseDuration(duration.text) : null, diff --git a/src/parsers/VideoParser.ts b/src/parsers/VideoParser.ts index 5e30582..41275af 100644 --- a/src/parsers/VideoParser.ts +++ b/src/parsers/VideoParser.ts @@ -1,22 +1,20 @@ import { ArtistBasic, VideoDetailed, VideoFull } from "../@types/types" import checkType from "../utils/checkType" import { isArtist, isDuration, isTitle } from "../utils/filters" -import traverse from "../utils/traverse" -import traverseList from "../utils/traverseList" -import traverseString from "../utils/traverseString" +import { traverse, traverseList, traverseString } from "../utils/traverse" import Parser from "./Parser" export default class VideoParser { public static parse(data: any): VideoFull { return { type: "VIDEO", - videoId: traverseString(data, "videoDetails", "videoId")(), - name: traverseString(data, "videoDetails", "title")(), + videoId: traverseString(data, "videoDetails", "videoId"), + name: traverseString(data, "videoDetails", "title"), artist: { - artistId: traverseString(data, "videoDetails", "channelId")(), - name: traverseString(data, "author")(), + artistId: traverseString(data, "videoDetails", "channelId"), + name: traverseString(data, "author"), }, - duration: +traverseString(data, "videoDetails", "lengthSeconds")(), + duration: +traverseString(data, "videoDetails", "lengthSeconds"), thumbnails: traverseList(data, "videoDetails", "thumbnails"), unlisted: traverse(data, "unlisted"), familySafe: traverse(data, "familySafe"), @@ -34,11 +32,11 @@ export default class VideoParser { return { type: "VIDEO", - videoId: traverseString(item, "playNavigationEndpoint", "videoId")(), - name: traverseString(title, "text")(), + videoId: traverseString(item, "playNavigationEndpoint", "videoId"), + name: traverseString(title, "text"), artist: { artistId: traverseString(artist, "browseId") || null, - name: traverseString(artist, "text")(), + name: traverseString(artist, "text"), }, duration: Parser.parseDuration(duration.text), thumbnails: traverseList(item, "thumbnails"), @@ -48,8 +46,8 @@ export default class VideoParser { public static parseArtistTopVideo(item: any, artistBasic: ArtistBasic): VideoDetailed { return { type: "VIDEO", - videoId: traverseString(item, "videoId")(), - name: traverseString(item, "runs", "text")(), + videoId: traverseString(item, "videoId"), + name: traverseString(item, "runs", "text"), artist: artistBasic, duration: null, thumbnails: traverseList(item, "thumbnails"), @@ -67,14 +65,14 @@ export default class VideoParser { { type: "VIDEO", videoId: - traverseString(item, "playNavigationEndpoint", "videoId")() || + traverseString(item, "playNavigationEndpoint", "videoId") || traverseList(item, "thumbnails")[0].url.match( /https:\/\/i\.ytimg\.com\/vi\/(.+)\//, )[1], - name: traverseString(title, "text")(), + name: traverseString(title, "text"), artist: { - name: traverseString(artist, "text")(), - artistId: traverseString(artist, "browseId")() || null, + name: traverseString(artist, "text"), + artistId: traverseString(artist, "browseId") || null, }, duration: duration ? Parser.parseDuration(duration.text) : null, thumbnails: traverseList(item, "thumbnails"), diff --git a/src/utils/filters.ts b/src/utils/filters.ts index 6495339..719436f 100644 --- a/src/utils/filters.ts +++ b/src/utils/filters.ts @@ -1,19 +1,19 @@ -import traverseString from "./traverseString" +import { traverseString } from "./traverse" export const isTitle = (data: any) => { - return traverseString(data, "musicVideoType")().startsWith("MUSIC_VIDEO_TYPE_") + return traverseString(data, "musicVideoType").startsWith("MUSIC_VIDEO_TYPE_") } export const isArtist = (data: any) => { return ["MUSIC_PAGE_TYPE_USER_CHANNEL", "MUSIC_PAGE_TYPE_ARTIST"].includes( - traverseString(data, "pageType")(), + traverseString(data, "pageType"), ) } export const isAlbum = (data: any) => { - return traverseString(data, "pageType")() === "MUSIC_PAGE_TYPE_ALBUM" + return traverseString(data, "pageType") === "MUSIC_PAGE_TYPE_ALBUM" } export const isDuration = (data: any) => { - return traverseString(data, "text")().match(/(\d{1,2}:)?\d{1,2}:\d{1,2}/) + return traverseString(data, "text").match(/(\d{1,2}:)?\d{1,2}:\d{1,2}/) } diff --git a/src/utils/traverse.ts b/src/utils/traverse.ts index 92be35c..02764d0 100644 --- a/src/utils/traverse.ts +++ b/src/utils/traverse.ts @@ -1,4 +1,4 @@ -const traverse = (data: any, ...keys: string[]) => { +export const traverse = (data: any, ...keys: string[]) => { const again = (data: any, key: string): any => { const res = [] @@ -27,4 +27,10 @@ const traverse = (data: any, ...keys: string[]) => { return value } -export default traverse +export const traverseList = (data: any, ...keys: string[]): any[] => { + return [traverse(data, ...keys)].flat() +} + +export const traverseString = (data: any, ...keys: string[]): string => { + return traverseList(data, ...keys).at(0) || "" +} diff --git a/src/utils/traverseList.ts b/src/utils/traverseList.ts deleted file mode 100644 index 3c12592..0000000 --- a/src/utils/traverseList.ts +++ /dev/null @@ -1,7 +0,0 @@ -import traverse from "./traverse" - -export default (data: any, ...keys: string[]): any[] => { - const value = traverse(data, ...keys) - const flatValue = [value].flat() - return flatValue -} diff --git a/src/utils/traverseString.ts b/src/utils/traverseString.ts deleted file mode 100644 index c679d4e..0000000 --- a/src/utils/traverseString.ts +++ /dev/null @@ -1,8 +0,0 @@ -import traverse from "./traverse" - -export default (data: any, ...keys: string[]) => - (index = 0): string => { - const value = traverse(data, ...keys) - const flatValue = [value].flat().at(index) - return flatValue || "" - }