Fetching playlist by id works
This commit is contained in:
parent
10c15a85af
commit
7471f1e70c
|
|
@ -318,9 +318,45 @@ export default class YTMusic {
|
||||||
return AlbumParser.parse(data, albumId)
|
return AlbumParser.parse(data, albumId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPlaylist(playlistId: string) {
|
/**
|
||||||
|
* Get all possible information of a Playlist except the tracks
|
||||||
|
*
|
||||||
|
* @param playlistId Playlist ID
|
||||||
|
* @returns Playlist Data
|
||||||
|
*/
|
||||||
|
public async getPlaylist(playlistId: string): Promise<YTMusic.PlaylistDetailed> {
|
||||||
|
if (playlistId.startsWith("PL")) playlistId = "VL" + playlistId
|
||||||
const data = await this.constructRequest("browse", { browseId: playlistId })
|
const data = await this.constructRequest("browse", { browseId: playlistId })
|
||||||
|
|
||||||
fs.writeFileSync("data.json", JSON.stringify(data))
|
return PlaylistParser.parse(data, playlistId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all videos in a Playlist
|
||||||
|
*
|
||||||
|
* @param playlistId Playlist ID
|
||||||
|
* @returns Playlist's Videos
|
||||||
|
*/
|
||||||
|
public async getPlaylistVideos(
|
||||||
|
playlistId: string
|
||||||
|
): Promise<Omit<YTMusic.VideoDetailed, "views">[]> {
|
||||||
|
if (playlistId.startsWith("PL")) playlistId = "VL" + playlistId
|
||||||
|
const playlistData = await this.constructRequest("browse", { browseId: playlistId })
|
||||||
|
|
||||||
|
const songs = traverse(
|
||||||
|
playlistData,
|
||||||
|
"musicPlaylistShelfRenderer",
|
||||||
|
"musicResponsiveListItemRenderer"
|
||||||
|
)
|
||||||
|
let continuation = traverse(playlistData, "musicPlaylistShelfRenderer", "continuation")
|
||||||
|
while (true) {
|
||||||
|
if (continuation instanceof Array) break
|
||||||
|
|
||||||
|
const songsData = await this.constructRequest("browse", {}, { continuation })
|
||||||
|
songs.push(...traverse(songsData, "musicResponsiveListItemRenderer"))
|
||||||
|
continuation = traverse(songsData, "continuation")
|
||||||
|
}
|
||||||
|
|
||||||
|
return songs.map(VideoParser.parsePlaylistVideo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,24 @@
|
||||||
import traverse from "../utils/traverse"
|
import traverse from "../utils/traverse"
|
||||||
|
|
||||||
export default class PlaylistParser {
|
export default class PlaylistParser {
|
||||||
|
public static parse(data: any, playlistId: string): YTMusic.PlaylistDetailed {
|
||||||
|
return {
|
||||||
|
type: "PLAYLIST",
|
||||||
|
playlistId,
|
||||||
|
name: traverse(data, "header", "title", "text").at(0),
|
||||||
|
artist: {
|
||||||
|
artistId: traverse(data, "header", "subtitle", "browseId"),
|
||||||
|
name: traverse(data, "header", "subtitle", "text").at(2)
|
||||||
|
},
|
||||||
|
videoCount: +traverse(data, "header", "secondSubtitle", "text")
|
||||||
|
.at(0)
|
||||||
|
.split(" ")
|
||||||
|
.at(0)
|
||||||
|
.replaceAll(",", ""),
|
||||||
|
thumbnails: traverse(data, "header", "thumbnails")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static parseSearchResult(item: any): YTMusic.PlaylistDetailed {
|
public static parseSearchResult(item: any): YTMusic.PlaylistDetailed {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
const artistId = traverse(flexColumns[1], "browseId")
|
const artistId = traverse(flexColumns[1], "browseId")
|
||||||
|
|
@ -13,7 +31,11 @@ export default class PlaylistParser {
|
||||||
artistId: artistId instanceof Array ? null : artistId,
|
artistId: artistId instanceof Array ? null : artistId,
|
||||||
name: traverse(flexColumns[1], "runs", "text").at(-2)
|
name: traverse(flexColumns[1], "runs", "text").at(-2)
|
||||||
},
|
},
|
||||||
songCount: +traverse(flexColumns[1], "runs", "text").at(-1).split(" ").at(0),
|
videoCount: +traverse(flexColumns[1], "runs", "text")
|
||||||
|
.at(-1)
|
||||||
|
.split(" ")
|
||||||
|
.at(0)
|
||||||
|
.replaceAll(",", ""),
|
||||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@ import traverse from "../utils/traverse"
|
||||||
export default class VideoParser {
|
export default class VideoParser {
|
||||||
public static parseSearchResult(item: any): YTMusic.VideoDetailed {
|
public static parseSearchResult(item: any): YTMusic.VideoDetailed {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
|
const videoId = traverse(item, "playNavigationEndpoint", "videoId")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "VIDEO",
|
type: "VIDEO",
|
||||||
videoId: traverse(item, "playNavigationEndpoint", "videoId"),
|
videoId: videoId instanceof Array ? null : videoId,
|
||||||
name: traverse(flexColumns[0], "runs", "text"),
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
artists: traverse(flexColumns[1], "runs")
|
artists: [traverse(flexColumns[1], "runs")]
|
||||||
|
.flat()
|
||||||
.filter((run: any) => "navigationEndpoint" in run)
|
.filter((run: any) => "navigationEndpoint" in run)
|
||||||
.map((run: any) => ({ artistId: traverse(run, "browseId"), name: run.text })),
|
.map((run: any) => ({ artistId: traverse(run, "browseId"), name: run.text })),
|
||||||
views: Parser.parseNumber(traverse(flexColumns[1], "runs", "text").at(-3).slice(0, -6)),
|
views: Parser.parseNumber(traverse(flexColumns[1], "runs", "text").at(-3).slice(0, -6)),
|
||||||
|
|
@ -17,4 +19,21 @@ export default class VideoParser {
|
||||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static parsePlaylistVideo(item: any): Omit<YTMusic.VideoDetailed, "views"> {
|
||||||
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
|
const videoId = traverse(item, "playNavigationEndpoint", "videoId")
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "VIDEO",
|
||||||
|
videoId: videoId instanceof Array ? null : videoId,
|
||||||
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
|
artists: [traverse(flexColumns[1], "runs")]
|
||||||
|
.flat()
|
||||||
|
.filter((run: any) => "navigationEndpoint" in run)
|
||||||
|
.map((run: any) => ({ artistId: traverse(run, "browseId"), name: run.text })),
|
||||||
|
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
||||||
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export const SONG_DETAILED: ObjectValidator<YTMusic.SongDetailed> = OBJECT({
|
||||||
|
|
||||||
export const VIDEO_DETAILED: ObjectValidator<YTMusic.VideoDetailed> = OBJECT({
|
export const VIDEO_DETAILED: ObjectValidator<YTMusic.VideoDetailed> = OBJECT({
|
||||||
type: STRING("VIDEO"),
|
type: STRING("VIDEO"),
|
||||||
videoId: STRING(),
|
videoId: OR(STRING(), NULL()),
|
||||||
name: STRING(),
|
name: STRING(),
|
||||||
artists: LIST(ARTIST_BASIC),
|
artists: LIST(ARTIST_BASIC),
|
||||||
views: NUMBER(),
|
views: NUMBER(),
|
||||||
|
|
@ -91,6 +91,15 @@ export const PLAYLIST_DETAILED: ObjectValidator<YTMusic.PlaylistDetailed> = OBJE
|
||||||
playlistId: STRING(),
|
playlistId: STRING(),
|
||||||
name: STRING(),
|
name: STRING(),
|
||||||
artist: ARTIST_BASIC,
|
artist: ARTIST_BASIC,
|
||||||
songCount: NUMBER(),
|
videoCount: NUMBER(),
|
||||||
|
thumbnails: LIST(THUMBNAIL_FULL)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const PLAYLIST_VIDEO: ObjectValidator<Omit<YTMusic.VideoDetailed, "views">> = OBJECT({
|
||||||
|
type: STRING("VIDEO"),
|
||||||
|
videoId: OR(STRING(), NULL()),
|
||||||
|
name: STRING(),
|
||||||
|
artists: LIST(ARTIST_BASIC),
|
||||||
|
duration: NUMBER(),
|
||||||
thumbnails: LIST(THUMBNAIL_FULL)
|
thumbnails: LIST(THUMBNAIL_FULL)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@ import {
|
||||||
ARTIST_DETAILED,
|
ARTIST_DETAILED,
|
||||||
ARTIST_FULL,
|
ARTIST_FULL,
|
||||||
PLAYLIST_DETAILED,
|
PLAYLIST_DETAILED,
|
||||||
|
PLAYLIST_VIDEO,
|
||||||
SONG_DETAILED,
|
SONG_DETAILED,
|
||||||
VIDEO_DETAILED
|
VIDEO_DETAILED
|
||||||
} from "./interfaces"
|
} from "./interfaces"
|
||||||
import { LIST, validate } from "validate-any"
|
import { LIST, validate } from "validate-any"
|
||||||
|
|
||||||
const queries = ["Lilac", "Weekend", "Yours Raiden", "Eminem", "IU"]
|
const queries = ["Lilac", "Weekend", "Yours Raiden", "Eminem", "Lisa Hannigan"]
|
||||||
const ytmusic = new YTMusic()
|
const ytmusic = new YTMusic()
|
||||||
|
|
||||||
ytmusic.initialize().then(() =>
|
ytmusic.initialize().then(() =>
|
||||||
|
|
@ -25,15 +26,17 @@ ytmusic.initialize().then(() =>
|
||||||
ytmusic.search(query)
|
ytmusic.search(query)
|
||||||
])
|
])
|
||||||
|
|
||||||
const [artist, artistSongs, artistAlbums, album] = await Promise.all([
|
const [artist, artistSongs, artistAlbums, album, playlist, playlistVideos] =
|
||||||
// ytmusic.getSong(songs[0].videoId),
|
await Promise.all([
|
||||||
// ytmusic.getVideo(videos[0].videoId),
|
// ytmusic.getSong(songs[0].videoId),
|
||||||
ytmusic.getArtist(artists[0].artistId),
|
// ytmusic.getVideo(videos[0].videoId),
|
||||||
ytmusic.getArtistSongs(artists[0].artistId),
|
ytmusic.getArtist(artists[0].artistId),
|
||||||
ytmusic.getArtistAlbums(artists[0].artistId),
|
ytmusic.getArtistSongs(artists[0].artistId),
|
||||||
ytmusic.getAlbum(albums[0].albumId)
|
ytmusic.getArtistAlbums(artists[0].artistId),
|
||||||
// ytmusic.getPlaylist(playlists[0].playlistId)
|
ytmusic.getAlbum(albums[0].albumId),
|
||||||
])
|
ytmusic.getPlaylist(playlists[0].playlistId),
|
||||||
|
ytmusic.getPlaylistVideos(playlists[0].playlistId)
|
||||||
|
])
|
||||||
|
|
||||||
const tests: [any, Validator<any>][] = [
|
const tests: [any, Validator<any>][] = [
|
||||||
[songs, LIST(SONG_DETAILED)],
|
[songs, LIST(SONG_DETAILED)],
|
||||||
|
|
@ -56,8 +59,9 @@ ytmusic.initialize().then(() =>
|
||||||
[artist, ARTIST_FULL],
|
[artist, ARTIST_FULL],
|
||||||
[artistSongs, LIST(SONG_DETAILED)],
|
[artistSongs, LIST(SONG_DETAILED)],
|
||||||
[artistAlbums, LIST(ALBUM_DETAILED)],
|
[artistAlbums, LIST(ALBUM_DETAILED)],
|
||||||
[album, ALBUM_FULL]
|
[album, ALBUM_FULL],
|
||||||
// [playlist, PLAYLIST_DETAILED]
|
[playlist, PLAYLIST_DETAILED],
|
||||||
|
[playlistVideos, LIST(PLAYLIST_VIDEO)]
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const [value, validator] of tests) {
|
for (const [value, validator] of tests) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ declare namespace YTMusic {
|
||||||
|
|
||||||
interface VideoDetailed {
|
interface VideoDetailed {
|
||||||
type: "VIDEO"
|
type: "VIDEO"
|
||||||
videoId: string
|
videoId: string | null
|
||||||
name: string
|
name: string
|
||||||
artists: ArtistBasic[]
|
artists: ArtistBasic[]
|
||||||
views: number
|
views: number
|
||||||
|
|
@ -66,7 +66,7 @@ declare namespace YTMusic {
|
||||||
playlistId: string
|
playlistId: string
|
||||||
name: string
|
name: string
|
||||||
artist: ArtistBasic
|
artist: ArtistBasic
|
||||||
songCount: number
|
videoCount: number
|
||||||
thumbnails: ThumbnailFull[]
|
thumbnails: ThumbnailFull[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue