More type safe traversing
This commit is contained in:
parent
12f3cd9d70
commit
dea7d8883b
10
src/index.ts
10
src/index.ts
|
|
@ -8,7 +8,7 @@ export interface ThumbnailFull {
|
|||
|
||||
export interface SongDetailed {
|
||||
type: "SONG"
|
||||
videoId: string | null
|
||||
videoId: string
|
||||
name: string
|
||||
artists: ArtistBasic[]
|
||||
album: AlbumBasic
|
||||
|
|
@ -24,7 +24,7 @@ export interface SongFull extends Omit<SongDetailed, "album"> {
|
|||
|
||||
export interface VideoDetailed {
|
||||
type: "VIDEO"
|
||||
videoId: string | null
|
||||
videoId: string
|
||||
name: string
|
||||
artists: ArtistBasic[]
|
||||
views: number
|
||||
|
|
@ -41,7 +41,7 @@ export interface VideoFull extends VideoDetailed {
|
|||
}
|
||||
|
||||
export interface ArtistBasic {
|
||||
artistId: string | null
|
||||
artistId: string
|
||||
name: string
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ export interface ArtistDetailed extends ArtistBasic {
|
|||
}
|
||||
|
||||
export interface ArtistFull extends ArtistDetailed {
|
||||
description: string | null
|
||||
description: string
|
||||
subscribers: number
|
||||
topSongs: Omit<SongDetailed, "duration">[]
|
||||
topAlbums: AlbumDetailed[]
|
||||
|
|
@ -72,7 +72,7 @@ export interface AlbumDetailed extends AlbumBasic {
|
|||
}
|
||||
|
||||
export interface AlbumFull extends AlbumDetailed {
|
||||
description: string | null
|
||||
description: string
|
||||
songs: SongDetailed[]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export const THUMBNAIL_FULL: ObjectValidator<ThumbnailFull> = OBJECT({
|
|||
})
|
||||
|
||||
export const ARTIST_BASIC: ObjectValidator<ArtistBasic> = OBJECT({
|
||||
artistId: OR(STRING(), NULL()),
|
||||
artistId: STRING(),
|
||||
name: STRING()
|
||||
})
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ export const ALBUM_BASIC: ObjectValidator<AlbumBasic> = OBJECT({
|
|||
|
||||
export const SONG_DETAILED: ObjectValidator<SongDetailed> = OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
album: ALBUM_BASIC,
|
||||
|
|
@ -43,7 +43,7 @@ export const SONG_DETAILED: ObjectValidator<SongDetailed> = OBJECT({
|
|||
|
||||
export const VIDEO_DETAILED: ObjectValidator<VideoDetailed> = OBJECT({
|
||||
type: STRING("VIDEO"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
views: NUMBER(),
|
||||
|
|
@ -70,7 +70,7 @@ export const ALBUM_DETAILED: ObjectValidator<AlbumDetailed> = OBJECT({
|
|||
|
||||
export const SONG_FULL: ObjectValidator<SongFull> = OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
duration: NUMBER(),
|
||||
|
|
@ -82,7 +82,7 @@ export const SONG_FULL: ObjectValidator<SongFull> = OBJECT({
|
|||
|
||||
export const VIDEO_FULL: ObjectValidator<VideoFull> = OBJECT({
|
||||
type: STRING("VIDEO"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
views: NUMBER(),
|
||||
|
|
@ -100,7 +100,7 @@ export const ARTIST_FULL: ObjectValidator<ArtistFull> = OBJECT({
|
|||
name: STRING(),
|
||||
type: STRING("ARTIST"),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: OR(STRING(), NULL()),
|
||||
description: STRING(),
|
||||
subscribers: NUMBER(),
|
||||
topSongs: LIST(
|
||||
OBJECT({
|
||||
|
|
@ -123,7 +123,7 @@ export const ALBUM_FULL: ObjectValidator<AlbumFull> = OBJECT({
|
|||
artists: LIST(ARTIST_BASIC),
|
||||
year: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: OR(STRING(), NULL()),
|
||||
description: STRING(),
|
||||
songs: LIST(SONG_DETAILED)
|
||||
})
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ export const PLAYLIST_FULL: ObjectValidator<PlaylistFull> = OBJECT({
|
|||
|
||||
export const PLAYLIST_VIDEO: ObjectValidator<Omit<VideoDetailed, "views">> = OBJECT({
|
||||
type: STRING("VIDEO"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
duration: NUMBER(),
|
||||
|
|
|
|||
|
|
@ -1,54 +1,58 @@
|
|||
import checkType from "../utils/checkType"
|
||||
import SongParser from "./SongParser"
|
||||
import traverse from "../utils/traverse"
|
||||
import traverseList from "../utils/traverseList"
|
||||
import traverseString from "../utils/traverseString"
|
||||
import { ALBUM_DETAILED, ALBUM_FULL } from "../interfaces"
|
||||
import { AlbumDetailed, AlbumFull, ArtistBasic } from ".."
|
||||
import { AlbumBasic, AlbumDetailed, AlbumFull, ArtistBasic } from ".."
|
||||
|
||||
export default class AlbumParser {
|
||||
public static parse(data: any, albumId: string): AlbumFull {
|
||||
const albumBasic = {
|
||||
const albumBasic: AlbumBasic = {
|
||||
albumId,
|
||||
name: traverse(data, "header", "title", "text").at(0)
|
||||
name: traverseString(data, "header", "title", "text")()
|
||||
}
|
||||
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")
|
||||
const artists: ArtistBasic[] = traverseList(data, "header", "subtitle", "runs")
|
||||
.filter(run => "navigationEndpoint" in run)
|
||||
.map(run => ({
|
||||
artistId: traverseString(run, "browseId")(),
|
||||
name: traverseString(run, "text")()
|
||||
}))
|
||||
const thumbnails = traverseList(data, "header", "thumbnails")
|
||||
|
||||
return checkType<AlbumFull>(
|
||||
{
|
||||
type: "ALBUM",
|
||||
...albumBasic,
|
||||
playlistId: traverse(data, "buttonRenderer", "playlistId"),
|
||||
playlistId: traverseString(data, "buttonRenderer", "playlistId")(),
|
||||
artists,
|
||||
year: +traverse(data, "header", "subtitle", "text").at(-1),
|
||||
year: +traverseString(data, "header", "subtitle", "text")(-1),
|
||||
thumbnails,
|
||||
description: description instanceof Array ? null : description,
|
||||
songs: [traverse(data, "musicResponsiveListItemRenderer")]
|
||||
.flat()
|
||||
.map((item: any) =>
|
||||
SongParser.parseAlbumSong(item, artists, albumBasic, thumbnails)
|
||||
)
|
||||
description: traverseString(data, "description", "text")(),
|
||||
songs: traverseList(data, "musicResponsiveListItemRenderer").map(item =>
|
||||
SongParser.parseAlbumSong(item, artists, albumBasic, thumbnails)
|
||||
)
|
||||
},
|
||||
ALBUM_FULL
|
||||
)
|
||||
}
|
||||
|
||||
public static parseSearchResult(item: any): AlbumDetailed {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
|
||||
return checkType<AlbumDetailed>(
|
||||
{
|
||||
type: "ALBUM",
|
||||
albumId: [traverse(item, "browseId")].flat().at(-1),
|
||||
playlistId: traverse(item, "overlay", "playlistId"),
|
||||
artists: traverse(flexColumns[1], "runs")
|
||||
.filter((run: any) => "navigationEndpoint" in run)
|
||||
.map((run: any) => ({ artistId: traverse(run, "browseId"), name: run.text })),
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
year: +traverse(flexColumns[1], "runs", "text").at(-1),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
albumId: traverseString(item, "browseId")(-1),
|
||||
playlistId: traverseString(item, "overlay", "playlistId")(),
|
||||
artists: traverseList(flexColumns[1], "runs")
|
||||
.filter(run => "navigationEndpoint" in run)
|
||||
.map(run => ({
|
||||
artistId: traverseString(run, "browseId")(),
|
||||
name: traverseString(run, "text")()
|
||||
})),
|
||||
name: traverseString(flexColumns[0], "runs", "text")(),
|
||||
year: +traverseString(flexColumns[1], "runs", "text")(-1),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
ALBUM_DETAILED
|
||||
)
|
||||
|
|
@ -58,12 +62,12 @@ export default class AlbumParser {
|
|||
return checkType<AlbumDetailed>(
|
||||
{
|
||||
type: "ALBUM",
|
||||
albumId: [traverse(item, "browseId")].flat().at(-1),
|
||||
playlistId: traverse(item, "thumbnailOverlay", "playlistId"),
|
||||
name: traverse(item, "title", "text").at(0),
|
||||
albumId: traverseString(item, "browseId")(-1),
|
||||
playlistId: traverseString(item, "thumbnailOverlay", "playlistId")(),
|
||||
name: traverseString(item, "title", "text")(),
|
||||
artists: [artistBasic],
|
||||
year: +traverse(item, "subtitle", "text").at(-1),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
year: +traverseString(item, "subtitle", "text")(-1),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
ALBUM_DETAILED
|
||||
)
|
||||
|
|
@ -73,12 +77,12 @@ export default class AlbumParser {
|
|||
return checkType<AlbumDetailed>(
|
||||
{
|
||||
type: "ALBUM",
|
||||
albumId: traverse(item, "browseId").at(-1),
|
||||
playlistId: traverse(item, "musicPlayButtonRenderer", "playlistId"),
|
||||
name: traverse(item, "title", "text").at(0),
|
||||
albumId: traverseString(item, "browseId")(-1),
|
||||
playlistId: traverseString(item, "musicPlayButtonRenderer", "playlistId")(),
|
||||
name: traverseString(item, "title", "text")(),
|
||||
artists: [artistBasic],
|
||||
year: +traverse(item, "subtitle", "text").at(-1),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
year: +traverseString(item, "subtitle", "text")(-1),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
ALBUM_DETAILED
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,31 +2,33 @@ import AlbumParser from "./AlbumParser"
|
|||
import checkType from "../utils/checkType"
|
||||
import Parser from "./Parser"
|
||||
import SongParser from "./SongParser"
|
||||
import traverse from "../utils/traverse"
|
||||
import traverseList from "../utils/traverseList"
|
||||
import traverseString from "../utils/traverseString"
|
||||
import { ARTIST_DETAILED, ARTIST_FULL } from "../interfaces"
|
||||
import { ArtistDetailed, ArtistFull } from ".."
|
||||
import { ArtistBasic, ArtistDetailed, ArtistFull } from ".."
|
||||
|
||||
export default class ArtistParser {
|
||||
public static parse(data: any, artistId: string): ArtistFull {
|
||||
const artistBasic = {
|
||||
const artistBasic: ArtistBasic = {
|
||||
artistId,
|
||||
name: traverse(data, "header", "title", "text").at(0)
|
||||
name: traverseString(data, "header", "title", "text")()
|
||||
}
|
||||
|
||||
const description = traverse(data, "header", "description", "text")
|
||||
const description = traverseString(data, "header", "description", "text")()
|
||||
|
||||
return checkType<ArtistFull>(
|
||||
{
|
||||
type: "ARTIST",
|
||||
...artistBasic,
|
||||
thumbnails: [traverse(data, "header", "thumbnails")].flat(),
|
||||
description: description instanceof Array ? null : description,
|
||||
subscribers: Parser.parseNumber(traverse(data, "subscriberCountText", "text")),
|
||||
topSongs: traverse(data, "musicShelfRenderer", "contents").map((item: any) =>
|
||||
thumbnails: traverseList(data, "header", "thumbnails"),
|
||||
description,
|
||||
subscribers: Parser.parseNumber(
|
||||
traverseString(data, "subscriberCountText", "text")()
|
||||
),
|
||||
topSongs: traverseList(data, "musicShelfRenderer", "contents").map(item =>
|
||||
SongParser.parseArtistTopSong(item, artistBasic)
|
||||
),
|
||||
topAlbums: [traverse(data, "musicCarouselShelfRenderer")]
|
||||
.flat()
|
||||
topAlbums: traverseList(data, "musicCarouselShelfRenderer")
|
||||
.at(0)
|
||||
.contents.map((item: any) =>
|
||||
AlbumParser.parseArtistTopAlbums(item, artistBasic)
|
||||
|
|
@ -37,14 +39,14 @@ export default class ArtistParser {
|
|||
}
|
||||
|
||||
public static parseSearchResult(item: any): ArtistDetailed {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
|
||||
return checkType<ArtistDetailed>(
|
||||
{
|
||||
type: "ARTIST",
|
||||
artistId: traverse(item, "browseId"),
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
artistId: traverseString(item, "browseId")(),
|
||||
name: traverseString(flexColumns[0], "runs", "text")(),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
ARTIST_DETAILED
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import checkType from "../utils/checkType"
|
||||
import traverse from "../utils/traverse"
|
||||
import traverseList from "../utils/traverseList"
|
||||
import traverseString from "../utils/traverseString"
|
||||
import { PLAYLIST_FULL } from "../interfaces"
|
||||
import { PlaylistFull } from ".."
|
||||
|
||||
|
|
@ -9,41 +10,41 @@ export default class PlaylistParser {
|
|||
{
|
||||
type: "PLAYLIST",
|
||||
playlistId,
|
||||
name: traverse(data, "header", "title", "text").at(0),
|
||||
name: traverseString(data, "header", "title", "text")(),
|
||||
artist: {
|
||||
artistId: traverse(data, "header", "subtitle", "browseId"),
|
||||
name: traverse(data, "header", "subtitle", "text").at(2)
|
||||
artistId: traverseString(data, "header", "subtitle", "browseId")(),
|
||||
name: traverseString(data, "header", "subtitle", "text")(2)
|
||||
},
|
||||
videoCount: +traverse(data, "header", "secondSubtitle", "text")
|
||||
videoCount: +traverseList(data, "header", "secondSubtitle", "text")
|
||||
.at(0)
|
||||
.split(" ")
|
||||
.at(0)
|
||||
.replaceAll(",", ""),
|
||||
thumbnails: traverse(data, "header", "thumbnails")
|
||||
thumbnails: traverseList(data, "header", "thumbnails")
|
||||
},
|
||||
PLAYLIST_FULL
|
||||
)
|
||||
}
|
||||
|
||||
public static parseSearchResult(item: any): PlaylistFull {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const artistId = traverse(flexColumns[1], "browseId")
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
const artistId = traverseString(flexColumns[1], "browseId")()
|
||||
|
||||
return checkType<PlaylistFull>(
|
||||
{
|
||||
type: "PLAYLIST",
|
||||
playlistId: traverse(item, "overlay", "playlistId"),
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
playlistId: traverseString(item, "overlay", "playlistId")(),
|
||||
name: traverseString(flexColumns[0], "runs", "text")(),
|
||||
artist: {
|
||||
artistId: artistId instanceof Array ? null : artistId,
|
||||
name: traverse(flexColumns[1], "runs", "text").at(-2)
|
||||
artistId,
|
||||
name: traverseString(flexColumns[1], "runs", "text")(-2)
|
||||
},
|
||||
videoCount: +traverse(flexColumns[1], "runs", "text")
|
||||
videoCount: +traverseList(flexColumns[1], "runs", "text")
|
||||
.at(-1)
|
||||
.split(" ")
|
||||
.at(0)
|
||||
.replaceAll(",", ""),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
PLAYLIST_FULL
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@ import AlbumParser from "./AlbumParser"
|
|||
import ArtistParser from "./ArtistParser"
|
||||
import PlaylistParser from "./PlaylistParser"
|
||||
import SongParser from "./SongParser"
|
||||
import traverse from "../utils/traverse"
|
||||
import traverseList from "../utils/traverseList"
|
||||
import VideoParser from "./VideoParser"
|
||||
import { SearchResult } from ".."
|
||||
|
||||
export default class SearchParser {
|
||||
public static parse(item: any): SearchResult {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const type = [traverse(flexColumns[1], "runs", "text")].flat().at(0) as
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
const type = traverseList(flexColumns[1], "runs", "text").at(0) as
|
||||
| "Song"
|
||||
| "Video"
|
||||
| "Artist"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import checkType from "../utils/checkType"
|
||||
import Parser from "./Parser"
|
||||
import traverse from "../utils/traverse"
|
||||
import traverseList from "../utils/traverseList"
|
||||
import traverseString from "../utils/traverseString"
|
||||
import { ALBUM_BASIC, ARTIST_BASIC, SONG_DETAILED, SONG_FULL, THUMBNAIL_FULL } from "../interfaces"
|
||||
import { AlbumBasic, ArtistBasic, SongDetailed, SongFull, ThumbnailFull } from ".."
|
||||
import { LIST, OBJECT, STRING } from "validate-any"
|
||||
|
|
@ -10,70 +11,73 @@ export default class SongParser {
|
|||
return checkType<SongFull>(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: traverse(data, "videoDetails", "videoId"),
|
||||
name: traverse(data, "videoDetails", "title"),
|
||||
videoId: traverseString(data, "videoDetails", "videoId")(),
|
||||
name: traverseString(data, "videoDetails", "title")(),
|
||||
artists: [
|
||||
{
|
||||
artistId: traverse(data, "videoDetails", "channelId"),
|
||||
name: traverse(data, "author")
|
||||
artistId: traverseString(data, "videoDetails", "channelId")(),
|
||||
name: traverseString(data, "author")()
|
||||
}
|
||||
],
|
||||
duration: +traverse(data, "videoDetails", "lengthSeconds"),
|
||||
thumbnails: [traverse(data, "videoDetails", "thumbnails")].flat(),
|
||||
description: traverse(data, "description"),
|
||||
formats: traverse(data, "streamingData", "formats"),
|
||||
adaptiveFormats: traverse(data, "streamingData", "adaptiveFormats")
|
||||
duration: +traverseString(data, "videoDetails", "lengthSeconds"),
|
||||
thumbnails: traverseList(data, "videoDetails", "thumbnails"),
|
||||
description: traverseString(data, "description")(),
|
||||
formats: traverseList(data, "streamingData", "formats"),
|
||||
adaptiveFormats: traverseList(data, "streamingData", "adaptiveFormats")
|
||||
},
|
||||
SONG_FULL
|
||||
)
|
||||
}
|
||||
|
||||
public static parseSearchResult(item: any): SongDetailed {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
|
||||
return checkType<SongDetailed>(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: videoId instanceof Array ? null : videoId,
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
artists: traverse(flexColumns[1], "runs")
|
||||
.filter((run: any) => "navigationEndpoint" in run)
|
||||
.map((run: any) => ({ artistId: traverse(run, "browseId"), name: run.text }))
|
||||
videoId: traverseString(item, "playlistItemData", "videoId")(),
|
||||
name: traverseString(flexColumns[0], "runs", "text")(),
|
||||
artists: traverseList(flexColumns[1], "runs")
|
||||
.filter(run => "navigationEndpoint" in run)
|
||||
.map(run => ({
|
||||
artistId: traverseString(run, "browseId")(),
|
||||
name: traverseString(run, "text")()
|
||||
}))
|
||||
.slice(0, -1),
|
||||
album: {
|
||||
albumId: traverse(item, "browseId").at(-1),
|
||||
name: traverse(flexColumns[1], "runs", "text").at(-3)
|
||||
albumId: traverseString(item, "browseId")(-1),
|
||||
name: traverseString(flexColumns[1], "runs", "text")(-3)
|
||||
},
|
||||
duration: Parser.parseDuration(traverse(flexColumns[1], "runs", "text").at(-1)),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
duration: Parser.parseDuration(traverseString(flexColumns[1], "runs", "text")(-1)),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
SONG_DETAILED
|
||||
)
|
||||
}
|
||||
|
||||
public static parseArtistSong(item: any): SongDetailed {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
const videoId = traverseString(item, "playlistItemData", "videoId")()
|
||||
|
||||
return checkType<SongDetailed>(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: videoId instanceof Array ? null : videoId,
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
artists: [traverse(flexColumns[1], "runs")]
|
||||
.flat()
|
||||
.filter((item: any) => "navigationEndpoint" in item)
|
||||
.map((run: any) => ({
|
||||
artistId: traverse(run, "browseId"),
|
||||
name: run.text
|
||||
videoId,
|
||||
name: traverseString(flexColumns[0], "runs", "text")(),
|
||||
artists: traverseList(flexColumns[1], "runs")
|
||||
.filter(run => "navigationEndpoint" in run)
|
||||
.map(run => ({
|
||||
artistId: traverseString(run, "browseId")(),
|
||||
name: traverseString(run, "text")()
|
||||
})),
|
||||
album: {
|
||||
albumId: traverse(flexColumns[2], "browseId"),
|
||||
name: traverse(flexColumns[2], "runs", "text")
|
||||
albumId: traverseString(flexColumns[2], "browseId")(),
|
||||
name: traverseString(flexColumns[2], "runs", "text")()
|
||||
},
|
||||
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
duration: Parser.parseDuration(
|
||||
traverseString(item, "fixedColumns", "runs", "text")()
|
||||
),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
SONG_DETAILED
|
||||
)
|
||||
|
|
@ -83,20 +87,20 @@ export default class SongParser {
|
|||
item: any,
|
||||
artistBasic: ArtistBasic
|
||||
): Omit<SongDetailed, "duration"> {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
const videoId = traverseString(item, "playlistItemData", "videoId")()
|
||||
|
||||
return checkType<Omit<SongDetailed, "duration">>(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: videoId instanceof Array ? null : videoId,
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
videoId,
|
||||
name: traverseString(flexColumns[0], "runs", "text")(),
|
||||
artists: [artistBasic],
|
||||
album: {
|
||||
albumId: traverse(flexColumns[2], "runs", "text"),
|
||||
name: traverse(flexColumns[2], "browseId")
|
||||
albumId: traverseString(flexColumns[2], "runs", "text")(),
|
||||
name: traverseString(flexColumns[2], "browseId")()
|
||||
},
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
OBJECT({
|
||||
type: STRING("SONG"),
|
||||
|
|
@ -115,17 +119,19 @@ export default class SongParser {
|
|||
albumBasic: AlbumBasic,
|
||||
thumbnails: ThumbnailFull[]
|
||||
): SongDetailed {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
const videoId = traverseString(item, "playlistItemData", "videoId")()
|
||||
|
||||
return checkType<SongDetailed>(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: videoId instanceof Array ? null : videoId,
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
videoId,
|
||||
name: traverseString(flexColumns[0], "runs", "text")(),
|
||||
artists,
|
||||
album: albumBasic,
|
||||
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
||||
duration: Parser.parseDuration(
|
||||
traverseString(item, "fixedColumns", "runs", "text")()
|
||||
),
|
||||
thumbnails
|
||||
},
|
||||
SONG_DETAILED
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import checkType from "../utils/checkType"
|
||||
import Parser from "./Parser"
|
||||
import traverse from "../utils/traverse"
|
||||
import traverseList from "../utils/traverseList"
|
||||
import traverseString from "../utils/traverseString"
|
||||
import { PLAYLIST_VIDEO } from "../interfaces"
|
||||
import { VideoDetailed, VideoFull } from ".."
|
||||
|
||||
|
|
@ -8,58 +10,66 @@ export default class VideoParser {
|
|||
public static parse(data: any): VideoFull {
|
||||
return {
|
||||
type: "VIDEO",
|
||||
videoId: traverse(data, "videoDetails", "videoId"),
|
||||
name: traverse(data, "videoDetails", "title"),
|
||||
videoId: traverseString(data, "videoDetails", "videoId")(),
|
||||
name: traverseString(data, "videoDetails", "title")(),
|
||||
artists: [
|
||||
{
|
||||
artistId: traverse(data, "videoDetails", "channelId"),
|
||||
name: traverse(data, "author")
|
||||
artistId: traverseString(data, "videoDetails", "channelId")(),
|
||||
name: traverseString(data, "author")()
|
||||
}
|
||||
],
|
||||
views: +traverse(data, "videoDetails", "viewCount"),
|
||||
duration: +traverse(data, "videoDetails", "lengthSeconds"),
|
||||
thumbnails: [traverse(data, "videoDetails", "thumbnails")].flat(),
|
||||
description: traverse(data, "description"),
|
||||
views: +traverseString(data, "videoDetails", "viewCount")(),
|
||||
duration: +traverseString(data, "videoDetails", "lengthSeconds")(),
|
||||
thumbnails: traverseList(data, "videoDetails", "thumbnails"),
|
||||
description: traverseString(data, "description")(),
|
||||
unlisted: traverse(data, "unlisted"),
|
||||
familySafe: traverse(data, "familySafe"),
|
||||
paid: traverse(data, "paid"),
|
||||
tags: traverse(data, "tags")
|
||||
tags: traverseList(data, "tags")
|
||||
}
|
||||
}
|
||||
|
||||
public static parseSearchResult(item: any): VideoDetailed {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const videoId = traverse(item, "playNavigationEndpoint", "videoId")
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
const videoId = traverseString(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 })),
|
||||
views: Parser.parseNumber(traverse(flexColumns[1], "runs", "text").at(-3).slice(0, -6)),
|
||||
duration: Parser.parseDuration(traverse(flexColumns[1], "text").at(-1)),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
videoId,
|
||||
name: traverseString(flexColumns[0], "runs", "text")(),
|
||||
artists: traverseList(flexColumns[1], "runs")
|
||||
.filter(run => "navigationEndpoint" in run)
|
||||
.map(run => ({
|
||||
artistId: traverseString(run, "browseId")(),
|
||||
name: traverseString(run, "text")()
|
||||
})),
|
||||
views: Parser.parseNumber(
|
||||
traverseString(flexColumns[1], "runs", "text")(-3).slice(0, -6)
|
||||
),
|
||||
duration: Parser.parseDuration(traverseString(flexColumns[1], "text")(-1)),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
}
|
||||
}
|
||||
|
||||
public static parsePlaylistVideo(item: any): Omit<VideoDetailed, "views"> {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const videoId = traverse(item, "playNavigationEndpoint", "videoId")
|
||||
const flexColumns = traverseList(item, "flexColumns")
|
||||
const videoId = traverseString(item, "playNavigationEndpoint", "videoId")()
|
||||
|
||||
return checkType<Omit<VideoDetailed, "views">>(
|
||||
{
|
||||
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()
|
||||
videoId,
|
||||
name: traverseString(flexColumns[0], "runs", "text")(),
|
||||
artists: traverseList(flexColumns[1], "runs")
|
||||
.filter(run => "navigationEndpoint" in run)
|
||||
.map(run => ({
|
||||
artistId: traverseString(run, "browseId")(),
|
||||
name: traverseString(run, "text")()
|
||||
})),
|
||||
duration: Parser.parseDuration(
|
||||
traverseString(item, "fixedColumns", "runs", "text")()
|
||||
),
|
||||
thumbnails: traverseList(item, "thumbnails")
|
||||
},
|
||||
PLAYLIST_VIDEO
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import traverse from "./traverse"
|
||||
|
||||
export default (data: any, ...keys: string[]): any[] => {
|
||||
const value = traverse(data, ...keys)
|
||||
const flatValue = [value].flat()
|
||||
return flatValue
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import traverse from "./traverse"
|
||||
|
||||
export default (data: any, ...keys: string[]) => (index = 0): string => {
|
||||
const value = traverse(data, ...keys)
|
||||
const flatValue = [value].flat().at(index)
|
||||
return flatValue || ""
|
||||
}
|
||||
Loading…
Reference in New Issue