From cdbf3da1aa3189adb5742dc5a520541303195cc3 Mon Sep 17 00:00:00 2001 From: Zechariah Date: Fri, 24 Dec 2021 22:30:17 +0800 Subject: [PATCH] Added more consistency --- src/YTMusic.ts | 56 +++++++++---------- src/index.ts | 6 +- src/parsers/AlbumParser.ts | 16 +++--- src/parsers/ArtistParser.ts | 49 ++++++++-------- src/parsers/PlaylistParser.ts | 4 +- src/parsers/SearchParser.ts | 16 +++--- src/parsers/SongParser.ts | 45 +++++++-------- src/parsers/VideoParser.ts | 2 +- .../traverse}/data.json | 0 .../traverse_tests => tests/traverse}/his.ts | 0 .../traverse_tests => tests/traverse}/mine.ts | 3 +- 11 files changed, 90 insertions(+), 107 deletions(-) rename src/{utils/traverse_tests => tests/traverse}/data.json (100%) rename src/{utils/traverse_tests => tests/traverse}/his.ts (100%) rename src/{utils/traverse_tests => tests/traverse}/mine.ts (93%) diff --git a/src/YTMusic.ts b/src/YTMusic.ts index c43ccc5..83036bc 100644 --- a/src/YTMusic.ts +++ b/src/YTMusic.ts @@ -93,19 +93,6 @@ export default class YTMusic { } } - /** - * Asserts that the API has been initialized - * - * @returns Non-null config - */ - private assertInitialized() { - if (!this.config) { - throw new Error("API not initialized. Make sure to call the initialize() method first") - } - - return this.config - } - /** * Constructs a basic YouTube Music API request with all essential headers * and body parameters needed to make the API work @@ -120,17 +107,19 @@ export default class YTMusic { body: Record = {}, query: Record = {} ) { - const config = this.assertInitialized() + if (!this.config) { + throw new Error("API not initialized. Make sure to call the initialize() method first") + } const headers: Record = { ...this.client.defaults.headers, "x-origin": this.client.defaults.baseURL, - "X-Goog-Visitor-Id": config.VISITOR_DATA, - "X-YouTube-Client-Name": config.INNERTUBE_CONTEXT_CLIENT_NAME, - "X-YouTube-Client-Version": config.INNERTUBE_CLIENT_VERSION, - "X-YouTube-Device": config.DEVICE, - "X-YouTube-Page-CL": config.PAGE_CL, - "X-YouTube-Page-Label": config.PAGE_BUILD_LABEL, + "X-Goog-Visitor-Id": this.config.VISITOR_DATA, + "X-YouTube-Client-Name": this.config.INNERTUBE_CONTEXT_CLIENT_NAME, + "X-YouTube-Client-Version": this.config.INNERTUBE_CLIENT_VERSION, + "X-YouTube-Device": this.config.DEVICE, + "X-YouTube-Page-CL": this.config.PAGE_CL, + "X-YouTube-Page-Label": this.config.PAGE_BUILD_LABEL, "X-YouTube-Utc-Offset": String(-new Date().getTimezoneOffset()), "X-YouTube-Time-Zone": new Intl.DateTimeFormat().resolvedOptions().timeZone } @@ -138,22 +127,21 @@ export default class YTMusic { const searchParams = new URLSearchParams({ ...query, alt: "json", - key: config.INNERTUBE_API_KEY + key: this.config.INNERTUBE_API_KEY }) - // prettier-ignore const res = await this.client.post( - `youtubei/${config.INNERTUBE_API_VERSION}/${endpoint}?${searchParams.toString()}`, + `youtubei/${this.config.INNERTUBE_API_VERSION}/${endpoint}?${searchParams.toString()}`, { context: { capabilities: {}, client: { - clientName: config.INNERTUBE_CLIENT_NAME, - clientVersion: config.INNERTUBE_CLIENT_VERSION, + clientName: this.config.INNERTUBE_CLIENT_NAME, + clientVersion: this.config.INNERTUBE_CLIENT_VERSION, experimentIds: [], experimentsToken: "", - gl: config.GL, - hl: config.HL, + gl: this.config.GL, + hl: this.config.HL, locationInfo: { locationPermissionAuthorizationStatus: "LOCATION_PERMISSION_AUTHORIZATION_STATUS_UNSUPPORTED" @@ -270,7 +258,7 @@ export default class YTMusic { public async getArtist(artistId: string): Promise { const data = await this.constructRequest("browse", { browseId: artistId }) - return new ArtistParser(data).parse(artistId) + return ArtistParser.parse(data, artistId) } /** @@ -291,7 +279,10 @@ export default class YTMusic { { continuation: continueToken } ) - return SongParser.parseArtistSongs(songsData, moreSongsData) + return [ + ...traverse(songsData, "musicResponsiveListItemRenderer"), + ...traverse(moreSongsData, "musicResponsiveListItemRenderer") + ].map(SongParser.parseArtistSong) } /** @@ -307,7 +298,12 @@ export default class YTMusic { const albumsData = await this.constructRequest("browse", browseBody) - return AlbumParser.parseArtistAlbums(artistId, albumsData) + return traverse(albumsData, "musicTwoRowItemRenderer").map((item: any) => + AlbumParser.parseArtistAlbum(item, { + artistId, + name: traverse(albumsData, "header", "text").at(0) + }) + ) } public async getAlbum(albumId: string) { diff --git a/src/index.ts b/src/index.ts index 24756d7..1f7b32d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,9 +2,9 @@ import YTMusic from "./YTMusic" const ytmusic = new YTMusic() ytmusic.initialize().then(() => { - ytmusic.search("Yours Raiden", "ARTIST").then(res => { - ytmusic.getArtistAlbums(res[0].artistId).then(res => { + ytmusic.search("Lilac").then(res => { + // ytmusic.getPlaylist(res[0].playlistId).then(res => { - }) + // }) }) }) diff --git a/src/parsers/AlbumParser.ts b/src/parsers/AlbumParser.ts index 817100a..03afa6c 100644 --- a/src/parsers/AlbumParser.ts +++ b/src/parsers/AlbumParser.ts @@ -18,21 +18,19 @@ export default class AlbumParser { } } - public static parseArtistAlbums(artistId: string, albumsData: any): YTMusic.AlbumDetailed[] { - return traverse(albumsData, "musicTwoRowItemRenderer").map((item: any) => ({ + public static parseArtistAlbum( + item: any, + artistBasic: YTMusic.ArtistBasic + ): YTMusic.AlbumDetailed { + return { type: "ALBUM", albumId: [traverse(item, "browseId")].flat().at(-1), playlistId: traverse(item, "thumbnailOverlay", "playlistId"), name: traverse(item, "title", "text").at(0), - artists: [ - { - artistId, - name: traverse(albumsData, "header", "text").at(0) - } - ], + artists: [artistBasic], year: +traverse(item, "subtitle", "text").at(-1), thumbnails: [traverse(item, "thumbnails")].flat() - })) + } } public static parseArtistTopAlbums( diff --git a/src/parsers/ArtistParser.ts b/src/parsers/ArtistParser.ts index 2fa9fb5..61af5a1 100644 --- a/src/parsers/ArtistParser.ts +++ b/src/parsers/ArtistParser.ts @@ -4,10 +4,29 @@ import SongParser from "./SongParser" import traverse from "../utils/traverse" export default class ArtistParser { - private data: any + public static parse(data: any, artistId: string): YTMusic.ArtistFull { + const artistBasic = { + artistId, + name: traverse(data, "header", "title", "text").at(0) + } - public constructor(data: any) { - this.data = data + const description = traverse(data, "header", "description", "text") + + return { + type: "ARTIST", + ...artistBasic, + thumbnails: traverse(data, "header", "thumbnails"), + description: description instanceof Array ? null : description, + subscribers: Parse.parseNumber(traverse(data, "subscriberCountText", "text")), + topSongs: traverse(data, "musicShelfRenderer", "contents").map((item: any) => + SongParser.parseArtistTopSong(item, artistBasic) + ), + topAlbums: [traverse(data, "musicCarouselShelfRenderer")] + .flat() + .at(0) + .contents + .map((item: any) => AlbumParser.parseArtistTopAlbums(item, artistBasic)) + } } public static parseSearchResult(item: any): YTMusic.ArtistDetailed { @@ -21,28 +40,4 @@ export default class ArtistParser { thumbnails: [thumbnails].flat() } } - - public parse(artistId: string): YTMusic.ArtistFull { - const artistBasic = { - artistId, - name: traverse(this.data, "header", "title", "text").at(0) - } - - const description = traverse(this.data, "header", "description", "text") - - return { - type: "ARTIST", - ...artistBasic, - thumbnails: traverse(this.data, "header", "thumbnails"), - description: description instanceof Array ? null : description, - subscribers: Parse.parseNumber(traverse(this.data, "subscriberCountText", "text")), - topSongs: traverse(this.data, "musicShelfRenderer", "contents").map((item: any) => - SongParser.parseArtistTopSong(item, artistBasic) - ), - topAlbums: [traverse(this.data, "musicCarouselShelfRenderer", "contents")] - .flat() - .at(0) - .map((item: any) => AlbumParser.parseArtistTopAlbums(item, artistBasic)) - } - } } diff --git a/src/parsers/PlaylistParser.ts b/src/parsers/PlaylistParser.ts index b734f69..e07a5fc 100644 --- a/src/parsers/PlaylistParser.ts +++ b/src/parsers/PlaylistParser.ts @@ -1,7 +1,7 @@ import traverse from "../utils/traverse" export default class PlaylistParser { - public static parseSearchResult(item: any, specific: boolean): YTMusic.PlaylistDetailed { + public static parseSearchResult(item: any): YTMusic.PlaylistDetailed { const flexColumns = traverse(item, "flexColumns") const thumbnails = traverse(item, "thumbnails") const artistId = traverse(flexColumns[1], "browseId") @@ -12,7 +12,7 @@ export default class PlaylistParser { name: traverse(flexColumns[0], "runs", "text"), artist: { artistId: artistId instanceof Array ? null : artistId, - name: traverse(flexColumns[1], "runs", "text").at(specific ? 0 : 2) + name: traverse(flexColumns[1], "runs", "text").at(-2) }, trackCount: +traverse(flexColumns[1], "runs", "text").at(-1).split(" ").at(0), thumbnails: [thumbnails].flat() diff --git a/src/parsers/SearchParser.ts b/src/parsers/SearchParser.ts index 245f7cf..3aa703a 100644 --- a/src/parsers/SearchParser.ts +++ b/src/parsers/SearchParser.ts @@ -18,13 +18,13 @@ export default class SearchParser { | "Playlist" return { - Song: () => SongParser.parseSearchResult(item), - Video: () => VideoParser.parseSearchResult(item, true), - Artist: () => ArtistParser.parseSearchResult(item), - EP: () => AlbumParser.parseSearchResult(item), - Single: () => AlbumParser.parseSearchResult(item), - Album: () => AlbumParser.parseSearchResult(item), - Playlist: () => PlaylistParser.parseSearchResult(item, true) - }[type]() + Song: SongParser.parseSearchResult, + Video: VideoParser.parseSearchResult, + Artist: ArtistParser.parseSearchResult, + EP: AlbumParser.parseSearchResult, + Single: AlbumParser.parseSearchResult, + Album: AlbumParser.parseSearchResult, + Playlist: PlaylistParser.parseSearchResult + }[type](item) } } diff --git a/src/parsers/SongParser.ts b/src/parsers/SongParser.ts index 38e23a0..197eca5 100644 --- a/src/parsers/SongParser.ts +++ b/src/parsers/SongParser.ts @@ -27,32 +27,27 @@ export default class SongParser { } } - public static parseArtistSongs(songsData: any, moreSongsData: any): YTMusic.SongDetailed[] { - return [ - ...traverse(songsData, "musicResponsiveListItemRenderer"), - ...traverse(moreSongsData, "musicResponsiveListItemRenderer") - ].map((item: any) => { - const flexColumns = traverse(item, "flexColumns") + public static parseArtistSong(item: any): YTMusic.SongDetailed { + const flexColumns = traverse(item, "flexColumns") - return { - type: "SONG", - videoId: traverse(item, "playlistItemData", "videoId"), - name: traverse(flexColumns[0], "runs", "text"), - artists: [traverse(flexColumns[1], "runs")] - .flat() - .filter((item: any) => "navigationEndpoint" in item) - .map((run: any) => ({ - name: run.text, - artistId: traverse(run, "browseId") - })), - album: { - albumId: traverse(flexColumns[2], "browseId"), - name: traverse(flexColumns[2], "runs", "text") - }, - duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")), - thumbnails: [traverse(item, "thumbnails")].flat() - } - }) + return { + type: "SONG", + videoId: traverse(item, "playlistItemData", "videoId"), + name: traverse(flexColumns[0], "runs", "text"), + artists: [traverse(flexColumns[1], "runs")] + .flat() + .filter((item: any) => "navigationEndpoint" in item) + .map((run: any) => ({ + name: run.text, + artistId: traverse(run, "browseId") + })), + album: { + albumId: traverse(flexColumns[2], "browseId"), + name: traverse(flexColumns[2], "runs", "text") + }, + duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")), + thumbnails: [traverse(item, "thumbnails")].flat() + } } public static parseArtistTopSong( diff --git a/src/parsers/VideoParser.ts b/src/parsers/VideoParser.ts index 4780742..b6ddd02 100644 --- a/src/parsers/VideoParser.ts +++ b/src/parsers/VideoParser.ts @@ -2,7 +2,7 @@ import Parser from "./Parser" import traverse from "../utils/traverse" export default class VideoParser { - public static parseSearchResult(item: any, specific: boolean): YTMusic.VideoDetailed { + public static parseSearchResult(item: any): YTMusic.VideoDetailed { const flexColumns = traverse(item, "flexColumns") const thumbnails = traverse(item, "thumbnails") diff --git a/src/utils/traverse_tests/data.json b/src/tests/traverse/data.json similarity index 100% rename from src/utils/traverse_tests/data.json rename to src/tests/traverse/data.json diff --git a/src/utils/traverse_tests/his.ts b/src/tests/traverse/his.ts similarity index 100% rename from src/utils/traverse_tests/his.ts rename to src/tests/traverse/his.ts diff --git a/src/utils/traverse_tests/mine.ts b/src/tests/traverse/mine.ts similarity index 93% rename from src/utils/traverse_tests/mine.ts rename to src/tests/traverse/mine.ts index 67eb11c..ef186f4 100644 --- a/src/utils/traverse_tests/mine.ts +++ b/src/tests/traverse/mine.ts @@ -3,7 +3,6 @@ const traverse = (data: any, keys: string[], single: boolean = false) => { let res = [] if (data instanceof Object && key in data) { - if (single) return data[key] res.push(data[key]) } @@ -17,7 +16,7 @@ const traverse = (data: any, keys: string[], single: boolean = false) => { ) } - return res + return res.length === 1 ? res[0] : res } let value = data