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