Fetching album by id works
This commit is contained in:
parent
cdbf3da1aa
commit
e3579a90b5
|
|
@ -14,16 +14,6 @@
|
||||||
"runtimeExecutable": "node",
|
"runtimeExecutable": "node",
|
||||||
"runtimeArgs": ["--require", "ts-node/register/files"]
|
"runtimeArgs": ["--require", "ts-node/register/files"]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "YTMusic.ts",
|
|
||||||
"program": "${workspaceFolder}/src/YTMusic.ts",
|
|
||||||
"sourceMaps": true,
|
|
||||||
"skipFiles": ["<node_internals>/**"],
|
|
||||||
"runtimeExecutable": "node",
|
|
||||||
"runtimeArgs": ["--require", "ts-node/register/files"]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,7 @@ export default class YTMusic {
|
||||||
}[category!] || null
|
}[category!] || null
|
||||||
})
|
})
|
||||||
|
|
||||||
return traverse(searchData, "musicResponsiveListItemRenderer").map(
|
return [traverse(searchData, "musicResponsiveListItemRenderer")].flat().map(
|
||||||
{
|
{
|
||||||
SONG: SongParser.parseSearchResult,
|
SONG: SongParser.parseSearchResult,
|
||||||
VIDEO: VideoParser.parseSearchResult,
|
VIDEO: VideoParser.parseSearchResult,
|
||||||
|
|
@ -306,10 +306,16 @@ export default class YTMusic {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAlbum(albumId: string) {
|
/**
|
||||||
|
* Get all possible information of an Album
|
||||||
|
*
|
||||||
|
* @param albumId Album ID
|
||||||
|
* @returns Album Data
|
||||||
|
*/
|
||||||
|
public async getAlbum(albumId: string): Promise<YTMusic.AlbumFull> {
|
||||||
const data = await this.constructRequest("browse", { browseId: albumId })
|
const data = await this.constructRequest("browse", { browseId: albumId })
|
||||||
|
|
||||||
fs.writeFileSync("data.json", JSON.stringify(data))
|
return AlbumParser.parse(data, albumId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPlaylist(playlistId: string) {
|
public async getPlaylist(playlistId: string) {
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ import YTMusic from "./YTMusic"
|
||||||
|
|
||||||
const ytmusic = new YTMusic()
|
const ytmusic = new YTMusic()
|
||||||
ytmusic.initialize().then(() => {
|
ytmusic.initialize().then(() => {
|
||||||
ytmusic.search("Lilac").then(res => {
|
ytmusic.search("Lilac", "ALBUM").then(res => {
|
||||||
// ytmusic.getPlaylist(res[0].playlistId).then(res => {
|
ytmusic.getAlbum(res[0].albumId).then(res => {
|
||||||
|
console.log(res)
|
||||||
// })
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,36 @@
|
||||||
|
import SongParser from "./SongParser"
|
||||||
import traverse from "../utils/traverse"
|
import traverse from "../utils/traverse"
|
||||||
|
|
||||||
export default class AlbumParser {
|
export default class AlbumParser {
|
||||||
|
public static parse(data: any, albumId: string): YTMusic.AlbumFull {
|
||||||
|
const albumBasic = {
|
||||||
|
albumId,
|
||||||
|
name: traverse(data, "header", "title", "text").at(0)
|
||||||
|
}
|
||||||
|
const artists = traverse(data, "header", "subtitle", "runs")
|
||||||
|
.filter((run: any) => "navigationEndpoint" in run)
|
||||||
|
.map((run: any) => ({ artistId: traverse(run, "browseId"), name: run.text }))
|
||||||
|
const thumbnails = [traverse(data, "header", "thumbnails")].flat()
|
||||||
|
const description = traverse(data, "description", "text")
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "ALBUM",
|
||||||
|
...albumBasic,
|
||||||
|
playlistId: traverse(data, "buttonRenderer", "playlistId"),
|
||||||
|
artists,
|
||||||
|
year: +traverse(data, "header", "subtitle", "text").at(-1),
|
||||||
|
thumbnails,
|
||||||
|
description: description instanceof Array ? null : description,
|
||||||
|
songs: [traverse(data, "musicResponsiveListItemRenderer")]
|
||||||
|
.flat()
|
||||||
|
.map((item: any) =>
|
||||||
|
SongParser.parseAlbumSong(item, artists, albumBasic, thumbnails)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static parseSearchResult(item: any): YTMusic.AlbumDetailed {
|
public static parseSearchResult(item: any): YTMusic.AlbumDetailed {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
const thumbnails = traverse(item, "thumbnails")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "ALBUM",
|
type: "ALBUM",
|
||||||
|
|
@ -14,7 +41,7 @@ export default class AlbumParser {
|
||||||
.map((run: any) => ({ artistId: traverse(run, "browseId"), name: run.text })),
|
.map((run: any) => ({ artistId: traverse(run, "browseId"), name: run.text })),
|
||||||
name: traverse(flexColumns[0], "runs", "text"),
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
year: +traverse(flexColumns[1], "runs", "text").at(-1),
|
year: +traverse(flexColumns[1], "runs", "text").at(-1),
|
||||||
thumbnails: [thumbnails].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export default class ArtistParser {
|
||||||
return {
|
return {
|
||||||
type: "ARTIST",
|
type: "ARTIST",
|
||||||
...artistBasic,
|
...artistBasic,
|
||||||
thumbnails: traverse(data, "header", "thumbnails"),
|
thumbnails: [traverse(data, "header", "thumbnails")].flat(),
|
||||||
description: description instanceof Array ? null : description,
|
description: description instanceof Array ? null : description,
|
||||||
subscribers: Parse.parseNumber(traverse(data, "subscriberCountText", "text")),
|
subscribers: Parse.parseNumber(traverse(data, "subscriberCountText", "text")),
|
||||||
topSongs: traverse(data, "musicShelfRenderer", "contents").map((item: any) =>
|
topSongs: traverse(data, "musicShelfRenderer", "contents").map((item: any) =>
|
||||||
|
|
@ -24,20 +24,18 @@ export default class ArtistParser {
|
||||||
topAlbums: [traverse(data, "musicCarouselShelfRenderer")]
|
topAlbums: [traverse(data, "musicCarouselShelfRenderer")]
|
||||||
.flat()
|
.flat()
|
||||||
.at(0)
|
.at(0)
|
||||||
.contents
|
.contents.map((item: any) => AlbumParser.parseArtistTopAlbums(item, artistBasic))
|
||||||
.map((item: any) => AlbumParser.parseArtistTopAlbums(item, artistBasic))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parseSearchResult(item: any): YTMusic.ArtistDetailed {
|
public static parseSearchResult(item: any): YTMusic.ArtistDetailed {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
const thumbnails = traverse(item, "thumbnails")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "ARTIST",
|
type: "ARTIST",
|
||||||
artistId: traverse(item, "browseId"),
|
artistId: traverse(item, "browseId"),
|
||||||
name: traverse(flexColumns[0], "runs", "text"),
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
thumbnails: [thumbnails].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import traverse from "../utils/traverse"
|
||||||
export default class PlaylistParser {
|
export default class PlaylistParser {
|
||||||
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 thumbnails = traverse(item, "thumbnails")
|
|
||||||
const artistId = traverse(flexColumns[1], "browseId")
|
const artistId = traverse(flexColumns[1], "browseId")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -14,8 +13,8 @@ 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)
|
||||||
},
|
},
|
||||||
trackCount: +traverse(flexColumns[1], "runs", "text").at(-1).split(" ").at(0),
|
songCount: +traverse(flexColumns[1], "runs", "text").at(-1).split(" ").at(0),
|
||||||
thumbnails: [thumbnails].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,20 @@ import traverse from "../utils/traverse"
|
||||||
export default class SongParser {
|
export default class SongParser {
|
||||||
public static parseSearchResult(item: any): YTMusic.SongDetailed {
|
public static parseSearchResult(item: any): YTMusic.SongDetailed {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
const thumbnails = traverse(item, "thumbnails")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "SONG",
|
type: "SONG",
|
||||||
videoId: traverse(item, "playlistItemData", "videoId"),
|
videoId: traverse(item, "playlistItemData", "videoId"),
|
||||||
name: traverse(flexColumns[0], "runs", "text"),
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
artists: traverse(flexColumns[1], "runs")
|
artists: traverse(flexColumns[1], "runs")
|
||||||
.map((run: any) =>
|
.filter((run: any) => "navigationEndpoint" in run)
|
||||||
"navigationEndpoint" in run
|
.map((run: any) => ({ name: run.text, artistId: traverse(run, "browseId") })),
|
||||||
? { name: run.text, artistId: traverse(run, "browseId") }
|
|
||||||
: null
|
|
||||||
)
|
|
||||||
.slice(0, -3)
|
|
||||||
.filter(Boolean),
|
|
||||||
album: {
|
album: {
|
||||||
albumId: traverse(item, "browseId").at(-1),
|
albumId: traverse(item, "browseId").at(-1),
|
||||||
name: traverse(flexColumns[1], "runs", "text").at(-3)
|
name: traverse(flexColumns[1], "runs", "text").at(-3)
|
||||||
},
|
},
|
||||||
duration: Parser.parseDuration(traverse(flexColumns[1], "runs", "text").at(-1)),
|
duration: Parser.parseDuration(traverse(flexColumns[1], "runs", "text").at(-1)),
|
||||||
thumbnails: thumbnails
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,4 +62,23 @@ export default class SongParser {
|
||||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static parseAlbumSong(
|
||||||
|
item: any,
|
||||||
|
artists: YTMusic.ArtistBasic[],
|
||||||
|
albumBasic: YTMusic.AlbumBasic,
|
||||||
|
thumbnails: YTMusic.ThumbnailFull[]
|
||||||
|
): YTMusic.SongDetailed {
|
||||||
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "SONG",
|
||||||
|
videoId: traverse(item, "playlistItemData", "videoId"),
|
||||||
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
|
artists,
|
||||||
|
album: albumBasic,
|
||||||
|
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
||||||
|
thumbnails
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ 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 thumbnails = traverse(item, "thumbnails")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "VIDEO",
|
type: "VIDEO",
|
||||||
|
|
@ -15,7 +14,7 @@ export default class VideoParser {
|
||||||
.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)),
|
||||||
duration: Parser.parseDuration(traverse(flexColumns[1], "text").at(-1)),
|
duration: Parser.parseDuration(traverse(flexColumns[1], "text").at(-1)),
|
||||||
thumbnails: [thumbnails].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,8 +82,8 @@ export const ALBUM_FULL: ObjectValidator<YTMusic.AlbumFull> = OBJECT({
|
||||||
artists: LIST(ARTIST_BASIC),
|
artists: LIST(ARTIST_BASIC),
|
||||||
year: NUMBER(),
|
year: NUMBER(),
|
||||||
thumbnails: LIST(THUMBNAIL_FULL),
|
thumbnails: LIST(THUMBNAIL_FULL),
|
||||||
description: STRING(),
|
description: OR(STRING(), NULL()),
|
||||||
tracks: LIST(SONG_DETAILED)
|
songs: LIST(SONG_DETAILED)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const PLAYLIST_DETAILED: ObjectValidator<YTMusic.PlaylistDetailed> = OBJECT({
|
export const PLAYLIST_DETAILED: ObjectValidator<YTMusic.PlaylistDetailed> = OBJECT({
|
||||||
|
|
@ -91,6 +91,6 @@ export const PLAYLIST_DETAILED: ObjectValidator<YTMusic.PlaylistDetailed> = OBJE
|
||||||
playlistId: STRING(),
|
playlistId: STRING(),
|
||||||
name: STRING(),
|
name: STRING(),
|
||||||
artist: ARTIST_BASIC,
|
artist: ARTIST_BASIC,
|
||||||
trackCount: NUMBER(),
|
songCount: NUMBER(),
|
||||||
thumbnails: LIST(THUMBNAIL_FULL)
|
thumbnails: LIST(THUMBNAIL_FULL)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import Validator from "validate-any/build/classes/Validator"
|
||||||
import YTMusic from "../YTMusic"
|
import YTMusic from "../YTMusic"
|
||||||
import {
|
import {
|
||||||
ALBUM_DETAILED,
|
ALBUM_DETAILED,
|
||||||
|
ALBUM_FULL,
|
||||||
ARTIST_DETAILED,
|
ARTIST_DETAILED,
|
||||||
ARTIST_FULL,
|
ARTIST_FULL,
|
||||||
PLAYLIST_DETAILED,
|
PLAYLIST_DETAILED,
|
||||||
|
|
@ -10,31 +11,56 @@ import {
|
||||||
} 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 ytmusic = new YTMusic()
|
const ytmusic = new YTMusic()
|
||||||
const tests: (query: string) => [() => Promise<any>, Validator<any>][] = query => [
|
|
||||||
[() => ytmusic.search(query, "SONG"), LIST(SONG_DETAILED)],
|
ytmusic.initialize().then(() =>
|
||||||
[() => ytmusic.search(query, "VIDEO"), LIST(VIDEO_DETAILED)],
|
queries.forEach(async query => {
|
||||||
[() => ytmusic.search(query, "ARTIST"), LIST(ARTIST_DETAILED)],
|
const [songs, videos, artists, albums, playlists, results] = await Promise.all([
|
||||||
[() => ytmusic.search(query, "ALBUM"), LIST(ALBUM_DETAILED)],
|
ytmusic.search(query, "SONG"),
|
||||||
[() => ytmusic.search(query, "PLAYLIST"), LIST(PLAYLIST_DETAILED)],
|
ytmusic.search(query, "VIDEO"),
|
||||||
|
ytmusic.search(query, "ARTIST"),
|
||||||
|
ytmusic.search(query, "ALBUM"),
|
||||||
|
ytmusic.search(query, "PLAYLIST"),
|
||||||
|
ytmusic.search(query)
|
||||||
|
])
|
||||||
|
|
||||||
|
const [artist, artistSongs, artistAlbums, album] = await Promise.all([
|
||||||
|
// ytmusic.getSong(songs[0].videoId),
|
||||||
|
// ytmusic.getVideo(videos[0].videoId),
|
||||||
|
ytmusic.getArtist(artists[0].artistId),
|
||||||
|
ytmusic.getArtistSongs(artists[0].artistId),
|
||||||
|
ytmusic.getArtistAlbums(artists[0].artistId),
|
||||||
|
ytmusic.getAlbum(albums[0].albumId)
|
||||||
|
// ytmusic.getPlaylist(playlists[0].playlistId)
|
||||||
|
])
|
||||||
|
|
||||||
|
const tests: [any, Validator<any>][] = [
|
||||||
|
[songs, LIST(SONG_DETAILED)],
|
||||||
|
[videos, LIST(VIDEO_DETAILED)],
|
||||||
|
[artists, LIST(ARTIST_DETAILED)],
|
||||||
|
[albums, LIST(ALBUM_DETAILED)],
|
||||||
|
[playlists, LIST(PLAYLIST_DETAILED)],
|
||||||
[
|
[
|
||||||
() => ytmusic.search(query),
|
results,
|
||||||
LIST(ALBUM_DETAILED, ARTIST_DETAILED, PLAYLIST_DETAILED, SONG_DETAILED, VIDEO_DETAILED)
|
LIST(
|
||||||
|
ALBUM_DETAILED,
|
||||||
|
ARTIST_DETAILED,
|
||||||
|
PLAYLIST_DETAILED,
|
||||||
|
SONG_DETAILED,
|
||||||
|
VIDEO_DETAILED
|
||||||
|
)
|
||||||
],
|
],
|
||||||
[() => ytmusic.getArtist("UCUCF7BJBzLcu_6qvgSBk7dA"), ARTIST_FULL],
|
// [song, SONG_DETAILED],
|
||||||
[() => ytmusic.getArtist("UCTUR0sVEkD8T5MlSHqgaI_Q"), ARTIST_FULL],
|
// [video, VIDEO_DETAILED],
|
||||||
[() => ytmusic.getArtistSongs("UCUCF7BJBzLcu_6qvgSBk7dA"), LIST(SONG_DETAILED)],
|
[artist, ARTIST_FULL],
|
||||||
[() => ytmusic.getArtistSongs("UCTUR0sVEkD8T5MlSHqgaI_Q"), LIST(SONG_DETAILED)],
|
[artistSongs, LIST(SONG_DETAILED)],
|
||||||
[() => ytmusic.getArtistAlbums("UCUCF7BJBzLcu_6qvgSBk7dA"), LIST(ALBUM_DETAILED)],
|
[artistAlbums, LIST(ALBUM_DETAILED)],
|
||||||
[() => ytmusic.getArtistAlbums("UCTUR0sVEkD8T5MlSHqgaI_Q"), LIST(ALBUM_DETAILED)]
|
[album, ALBUM_FULL]
|
||||||
|
// [playlist, PLAYLIST_DETAILED]
|
||||||
]
|
]
|
||||||
|
|
||||||
const queries = ["Lilac", "Weekend", "Yours Raiden", "Eminem"]
|
for (const [value, validator] of tests) {
|
||||||
|
|
||||||
ytmusic.initialize().then(async () => {
|
|
||||||
queries.forEach(query => {
|
|
||||||
tests(query).forEach(async ([fetch, validator]) => {
|
|
||||||
const value = await fetch()
|
|
||||||
const result = validate(value, validator)
|
const result = validate(value, validator)
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
console.log(JSON.stringify(value))
|
console.log(JSON.stringify(value))
|
||||||
|
|
@ -42,6 +68,8 @@ ytmusic.initialize().then(async () => {
|
||||||
console.log(result.errors)
|
console.log(result.errors)
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Valid 🎉 - ${query}`)
|
||||||
})
|
})
|
||||||
})
|
)
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ declare namespace YTMusic {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AlbumFull extends AlbumDetailed {
|
interface AlbumFull extends AlbumDetailed {
|
||||||
description: string
|
description: string | null
|
||||||
tracks: SongDetailed[]
|
songs: SongDetailed[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlaylistDetailed {
|
interface PlaylistDetailed {
|
||||||
|
|
@ -66,7 +66,7 @@ declare namespace YTMusic {
|
||||||
playlistId: string
|
playlistId: string
|
||||||
name: string
|
name: string
|
||||||
artist: ArtistBasic
|
artist: ArtistBasic
|
||||||
trackCount: number
|
songCount: number
|
||||||
thumbnails: ThumbnailFull[]
|
thumbnails: ThumbnailFull[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue