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,11 +27,7 @@ export default class SongParser {
|
|||
}
|
||||
}
|
||||
|
||||
public static parseArtistSongs(songsData: any, moreSongsData: any): YTMusic.SongDetailed[] {
|
||||
return [
|
||||
...traverse(songsData, "musicResponsiveListItemRenderer"),
|
||||
...traverse(moreSongsData, "musicResponsiveListItemRenderer")
|
||||
].map((item: any) => {
|
||||
public static parseArtistSong(item: any): YTMusic.SongDetailed {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
|
||||
return {
|
||||
|
|
@ -52,7 +48,6 @@ export default class SongParser {
|
|||
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