Added more consistency
This commit is contained in:
		
							parent
							
								
									f5b5047c31
								
							
						
					
					
						commit
						cdbf3da1aa
					
				|  | @ -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<string, any> = {}, | ||||
| 		query: Record<string, string> = {} | ||||
| 	) { | ||||
| 		const config = this.assertInitialized() | ||||
| 		if (!this.config) { | ||||
| 			throw new Error("API not initialized. Make sure to call the initialize() method first") | ||||
| 		} | ||||
| 
 | ||||
| 		const headers: Record<string, any> = { | ||||
| 			...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<YTMusic.ArtistFull> { | ||||
| 		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) { | ||||
|  |  | |||
|  | @ -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 => {
 | ||||
| 			 | ||||
| 		}) | ||||
| 		// })
 | ||||
| 	}) | ||||
| }) | ||||
|  |  | |||
|  | @ -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( | ||||
|  |  | |||
|  | @ -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)) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -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( | ||||
|  |  | |||
|  | @ -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") | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
		Loading…
	
		Reference in New Issue