Added more consistency

This commit is contained in:
Zechariah 2021-12-24 22:30:17 +08:00
parent f5b5047c31
commit cdbf3da1aa
11 changed files with 90 additions and 107 deletions

View File

@ -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) {

View File

@ -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 => {
})
// })
})
})

View File

@ -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(

View File

@ -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))
}
}
}

View File

@ -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()

View File

@ -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)
}
}

View File

@ -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(

View File

@ -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")

View File

@ -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