Validate all types before returning to user
This commit is contained in:
parent
892fd5f82f
commit
45692dcaa8
|
|
@ -4,7 +4,7 @@ import axios, { AxiosInstance } from "axios"
|
|||
import PlaylistParser from "./parsers/PlaylistParser"
|
||||
import SearchParser from "./parsers/SearchParser"
|
||||
import SongParser from "./parsers/SongParser"
|
||||
import traverse from "./traverse"
|
||||
import traverse from "./utils/traverse"
|
||||
import VideoParser from "./parsers/VideoParser"
|
||||
import {
|
||||
AlbumDetailed,
|
||||
|
|
|
|||
|
|
@ -1,162 +1,18 @@
|
|||
import ObjectValidator from "validate-any/dist/validators/ObjectValidator"
|
||||
import Validator from "validate-any/dist/classes/Validator"
|
||||
import YTMusic, {
|
||||
AlbumBasic,
|
||||
AlbumDetailed,
|
||||
AlbumFull,
|
||||
ArtistBasic,
|
||||
ArtistDetailed,
|
||||
ArtistFull,
|
||||
PlaylistFull,
|
||||
SongDetailed,
|
||||
SongFull,
|
||||
ThumbnailFull,
|
||||
VideoDetailed,
|
||||
VideoFull
|
||||
} from ".."
|
||||
import YTMusic from ".."
|
||||
import {
|
||||
BOOLEAN,
|
||||
iValidationError,
|
||||
LIST,
|
||||
NULL,
|
||||
NUMBER,
|
||||
OBJECT,
|
||||
OR,
|
||||
STRING,
|
||||
validate
|
||||
} from "validate-any"
|
||||
|
||||
//#region Interfaces
|
||||
const THUMBNAIL_FULL: ObjectValidator<ThumbnailFull> = OBJECT({
|
||||
url: STRING(),
|
||||
width: NUMBER(),
|
||||
height: NUMBER()
|
||||
})
|
||||
|
||||
const ARTIST_BASIC: ObjectValidator<ArtistBasic> = OBJECT({
|
||||
artistId: OR(STRING(), NULL()),
|
||||
name: STRING()
|
||||
})
|
||||
|
||||
const ALBUM_BASIC: ObjectValidator<AlbumBasic> = OBJECT({
|
||||
albumId: STRING(),
|
||||
name: STRING()
|
||||
})
|
||||
|
||||
const SONG_DETAILED: ObjectValidator<SongDetailed> = OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
album: ALBUM_BASIC,
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
const VIDEO_DETAILED: ObjectValidator<VideoDetailed> = OBJECT({
|
||||
type: STRING("VIDEO"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
views: NUMBER(),
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
const ARTIST_DETAILED: ObjectValidator<ArtistDetailed> = OBJECT({
|
||||
artistId: STRING(),
|
||||
name: STRING(),
|
||||
type: STRING("ARTIST"),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
const ALBUM_DETAILED: ObjectValidator<AlbumDetailed> = OBJECT({
|
||||
type: STRING("ALBUM"),
|
||||
albumId: STRING(),
|
||||
playlistId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
year: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
const SONG_FULL: ObjectValidator<SongFull> = OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: STRING(),
|
||||
formats: LIST(OBJECT()),
|
||||
adaptiveFormats: LIST(OBJECT())
|
||||
})
|
||||
|
||||
const VIDEO_FULL: ObjectValidator<VideoFull> = OBJECT({
|
||||
type: STRING("VIDEO"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
views: NUMBER(),
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: STRING(),
|
||||
unlisted: BOOLEAN(),
|
||||
familySafe: BOOLEAN(),
|
||||
paid: BOOLEAN(),
|
||||
tags: LIST(STRING())
|
||||
})
|
||||
|
||||
const ARTIST_FULL: ObjectValidator<ArtistFull> = OBJECT({
|
||||
artistId: STRING(),
|
||||
name: STRING(),
|
||||
type: STRING("ARTIST"),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: OR(STRING(), NULL()),
|
||||
subscribers: NUMBER(),
|
||||
topSongs: LIST(
|
||||
OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
album: ALBUM_BASIC,
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
),
|
||||
topAlbums: LIST(ALBUM_DETAILED)
|
||||
})
|
||||
|
||||
const ALBUM_FULL: ObjectValidator<AlbumFull> = OBJECT({
|
||||
type: STRING("ALBUM"),
|
||||
albumId: STRING(),
|
||||
playlistId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
year: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: OR(STRING(), NULL()),
|
||||
songs: LIST(SONG_DETAILED)
|
||||
})
|
||||
|
||||
const PLAYLIST_DETAILED: ObjectValidator<PlaylistFull> = OBJECT({
|
||||
type: STRING("PLAYLIST"),
|
||||
playlistId: STRING(),
|
||||
name: STRING(),
|
||||
artist: ARTIST_BASIC,
|
||||
videoCount: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
const PLAYLIST_VIDEO: ObjectValidator<Omit<VideoDetailed, "views">> = OBJECT({
|
||||
type: STRING("VIDEO"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
//#endregion
|
||||
ALBUM_DETAILED,
|
||||
ALBUM_FULL,
|
||||
ARTIST_DETAILED,
|
||||
ARTIST_FULL,
|
||||
PLAYLIST_FULL,
|
||||
PLAYLIST_VIDEO,
|
||||
SONG_DETAILED,
|
||||
SONG_FULL,
|
||||
VIDEO_DETAILED,
|
||||
VIDEO_FULL
|
||||
} from "../interfaces"
|
||||
import { iValidationError, LIST, STRING, validate } from "validate-any"
|
||||
|
||||
const issues: iValidationError[][] = []
|
||||
const queries = ["Lilac", "Weekend", "Eill", "Eminem", "Lisa Hannigan"]
|
||||
|
|
@ -231,7 +87,7 @@ queries.forEach(query => {
|
|||
ytmusic
|
||||
.search(query, "PLAYLIST")
|
||||
.then(playlists => {
|
||||
_expect(playlists, LIST(PLAYLIST_DETAILED))
|
||||
_expect(playlists, LIST(PLAYLIST_FULL))
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
|
|
@ -246,7 +102,7 @@ queries.forEach(query => {
|
|||
LIST(
|
||||
ALBUM_DETAILED,
|
||||
ARTIST_DETAILED,
|
||||
PLAYLIST_DETAILED,
|
||||
PLAYLIST_FULL,
|
||||
SONG_DETAILED,
|
||||
VIDEO_DETAILED
|
||||
)
|
||||
|
|
@ -327,7 +183,7 @@ queries.forEach(query => {
|
|||
.search(query, "PLAYLIST")
|
||||
.then(playlists => ytmusic.getPlaylist(playlists[0]!.playlistId!))
|
||||
.then(playlist => {
|
||||
_expect(playlist, PLAYLIST_DETAILED)
|
||||
_expect(playlist, PLAYLIST_FULL)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
import ObjectValidator from "validate-any/dist/validators/ObjectValidator"
|
||||
import {
|
||||
AlbumBasic,
|
||||
AlbumDetailed,
|
||||
AlbumFull,
|
||||
ArtistBasic,
|
||||
ArtistDetailed,
|
||||
ArtistFull,
|
||||
PlaylistFull,
|
||||
SongDetailed,
|
||||
SongFull,
|
||||
ThumbnailFull,
|
||||
VideoDetailed,
|
||||
VideoFull
|
||||
} from "."
|
||||
import { BOOLEAN, LIST, NULL, NUMBER, OBJECT, OR, STRING } from "validate-any"
|
||||
|
||||
export const THUMBNAIL_FULL: ObjectValidator<ThumbnailFull> = OBJECT({
|
||||
url: STRING(),
|
||||
width: NUMBER(),
|
||||
height: NUMBER()
|
||||
})
|
||||
|
||||
export const ARTIST_BASIC: ObjectValidator<ArtistBasic> = OBJECT({
|
||||
artistId: OR(STRING(), NULL()),
|
||||
name: STRING()
|
||||
})
|
||||
|
||||
export const ALBUM_BASIC: ObjectValidator<AlbumBasic> = OBJECT({
|
||||
albumId: STRING(),
|
||||
name: STRING()
|
||||
})
|
||||
|
||||
export const SONG_DETAILED: ObjectValidator<SongDetailed> = OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
album: ALBUM_BASIC,
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
export const VIDEO_DETAILED: ObjectValidator<VideoDetailed> = OBJECT({
|
||||
type: STRING("VIDEO"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
views: NUMBER(),
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
export const ARTIST_DETAILED: ObjectValidator<ArtistDetailed> = OBJECT({
|
||||
artistId: STRING(),
|
||||
name: STRING(),
|
||||
type: STRING("ARTIST"),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
export const ALBUM_DETAILED: ObjectValidator<AlbumDetailed> = OBJECT({
|
||||
type: STRING("ALBUM"),
|
||||
albumId: STRING(),
|
||||
playlistId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
year: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
export const SONG_FULL: ObjectValidator<SongFull> = OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: STRING(),
|
||||
formats: LIST(OBJECT()),
|
||||
adaptiveFormats: LIST(OBJECT())
|
||||
})
|
||||
|
||||
export const VIDEO_FULL: ObjectValidator<VideoFull> = OBJECT({
|
||||
type: STRING("VIDEO"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
views: NUMBER(),
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: STRING(),
|
||||
unlisted: BOOLEAN(),
|
||||
familySafe: BOOLEAN(),
|
||||
paid: BOOLEAN(),
|
||||
tags: LIST(STRING())
|
||||
})
|
||||
|
||||
export const ARTIST_FULL: ObjectValidator<ArtistFull> = OBJECT({
|
||||
artistId: STRING(),
|
||||
name: STRING(),
|
||||
type: STRING("ARTIST"),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: OR(STRING(), NULL()),
|
||||
subscribers: NUMBER(),
|
||||
topSongs: LIST(
|
||||
OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
album: ALBUM_BASIC,
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
),
|
||||
topAlbums: LIST(ALBUM_DETAILED)
|
||||
})
|
||||
|
||||
export const ALBUM_FULL: ObjectValidator<AlbumFull> = OBJECT({
|
||||
type: STRING("ALBUM"),
|
||||
albumId: STRING(),
|
||||
playlistId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
year: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL),
|
||||
description: OR(STRING(), NULL()),
|
||||
songs: LIST(SONG_DETAILED)
|
||||
})
|
||||
|
||||
export const PLAYLIST_FULL: ObjectValidator<PlaylistFull> = OBJECT({
|
||||
type: STRING("PLAYLIST"),
|
||||
playlistId: STRING(),
|
||||
name: STRING(),
|
||||
artist: ARTIST_BASIC,
|
||||
videoCount: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
||||
export const PLAYLIST_VIDEO: ObjectValidator<Omit<VideoDetailed, "views">> = OBJECT({
|
||||
type: STRING("VIDEO"),
|
||||
videoId: OR(STRING(), NULL()),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
duration: NUMBER(),
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import checkType from "../utils/checkType"
|
||||
import SongParser from "./SongParser"
|
||||
import traverse from "../traverse"
|
||||
import traverse from "../utils/traverse"
|
||||
import { ALBUM_DETAILED, ALBUM_FULL } from "../interfaces"
|
||||
import { AlbumDetailed, AlbumFull, ArtistBasic } from ".."
|
||||
|
||||
export default class AlbumParser {
|
||||
|
|
@ -14,7 +16,8 @@ export default class AlbumParser {
|
|||
const thumbnails = [traverse(data, "header", "thumbnails")].flat()
|
||||
const description = traverse(data, "description", "text")
|
||||
|
||||
return {
|
||||
return checkType<AlbumFull>(
|
||||
{
|
||||
type: "ALBUM",
|
||||
...albumBasic,
|
||||
playlistId: traverse(data, "buttonRenderer", "playlistId"),
|
||||
|
|
@ -27,13 +30,16 @@ export default class AlbumParser {
|
|||
.map((item: any) =>
|
||||
SongParser.parseAlbumSong(item, artists, albumBasic, thumbnails)
|
||||
)
|
||||
}
|
||||
},
|
||||
ALBUM_FULL
|
||||
)
|
||||
}
|
||||
|
||||
public static parseSearchResult(item: any): AlbumDetailed {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
|
||||
return {
|
||||
return checkType<AlbumDetailed>(
|
||||
{
|
||||
type: "ALBUM",
|
||||
albumId: [traverse(item, "browseId")].flat().at(-1),
|
||||
playlistId: traverse(item, "overlay", "playlistId"),
|
||||
|
|
@ -43,11 +49,14 @@ export default class AlbumParser {
|
|||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
year: +traverse(flexColumns[1], "runs", "text").at(-1),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
}
|
||||
},
|
||||
ALBUM_DETAILED
|
||||
)
|
||||
}
|
||||
|
||||
public static parseArtistAlbum(item: any, artistBasic: ArtistBasic): AlbumDetailed {
|
||||
return {
|
||||
return checkType<AlbumDetailed>(
|
||||
{
|
||||
type: "ALBUM",
|
||||
albumId: [traverse(item, "browseId")].flat().at(-1),
|
||||
playlistId: traverse(item, "thumbnailOverlay", "playlistId"),
|
||||
|
|
@ -55,11 +64,14 @@ export default class AlbumParser {
|
|||
artists: [artistBasic],
|
||||
year: +traverse(item, "subtitle", "text").at(-1),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
}
|
||||
},
|
||||
ALBUM_DETAILED
|
||||
)
|
||||
}
|
||||
|
||||
public static parseArtistTopAlbums(item: any, artistBasic: ArtistBasic): AlbumDetailed {
|
||||
return {
|
||||
return checkType<AlbumDetailed>(
|
||||
{
|
||||
type: "ALBUM",
|
||||
albumId: traverse(item, "browseId").at(-1),
|
||||
playlistId: traverse(item, "musicPlayButtonRenderer", "playlistId"),
|
||||
|
|
@ -67,6 +79,8 @@ export default class AlbumParser {
|
|||
artists: [artistBasic],
|
||||
year: +traverse(item, "subtitle", "text").at(-1),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
}
|
||||
},
|
||||
ALBUM_DETAILED
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import AlbumParser from "./AlbumParser"
|
||||
import checkType from "../utils/checkType"
|
||||
import Parser from "./Parser"
|
||||
import SongParser from "./SongParser"
|
||||
import traverse from "../traverse"
|
||||
import traverse from "../utils/traverse"
|
||||
import { ARTIST_DETAILED, ARTIST_FULL } from "../interfaces"
|
||||
import { ArtistDetailed, ArtistFull } from ".."
|
||||
|
||||
export default class ArtistParser {
|
||||
|
|
@ -13,7 +15,8 @@ export default class ArtistParser {
|
|||
|
||||
const description = traverse(data, "header", "description", "text")
|
||||
|
||||
return {
|
||||
return checkType<ArtistFull>(
|
||||
{
|
||||
type: "ARTIST",
|
||||
...artistBasic,
|
||||
thumbnails: [traverse(data, "header", "thumbnails")].flat(),
|
||||
|
|
@ -25,18 +28,25 @@ export default class ArtistParser {
|
|||
topAlbums: [traverse(data, "musicCarouselShelfRenderer")]
|
||||
.flat()
|
||||
.at(0)
|
||||
.contents.map((item: any) => AlbumParser.parseArtistTopAlbums(item, artistBasic))
|
||||
}
|
||||
.contents.map((item: any) =>
|
||||
AlbumParser.parseArtistTopAlbums(item, artistBasic)
|
||||
)
|
||||
},
|
||||
ARTIST_FULL
|
||||
)
|
||||
}
|
||||
|
||||
public static parseSearchResult(item: any): ArtistDetailed {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
|
||||
return {
|
||||
return checkType<ArtistDetailed>(
|
||||
{
|
||||
type: "ARTIST",
|
||||
artistId: traverse(item, "browseId"),
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
}
|
||||
},
|
||||
ARTIST_DETAILED
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import traverse from "../traverse"
|
||||
import checkType from "../utils/checkType"
|
||||
import traverse from "../utils/traverse"
|
||||
import { PLAYLIST_FULL } from "../interfaces"
|
||||
import { PlaylistFull } from ".."
|
||||
|
||||
export default class PlaylistParser {
|
||||
public static parse(data: any, playlistId: string): PlaylistFull {
|
||||
return {
|
||||
return checkType<PlaylistFull>(
|
||||
{
|
||||
type: "PLAYLIST",
|
||||
playlistId,
|
||||
name: traverse(data, "header", "title", "text").at(0),
|
||||
|
|
@ -17,14 +20,17 @@ export default class PlaylistParser {
|
|||
.at(0)
|
||||
.replaceAll(",", ""),
|
||||
thumbnails: traverse(data, "header", "thumbnails")
|
||||
}
|
||||
},
|
||||
PLAYLIST_FULL
|
||||
)
|
||||
}
|
||||
|
||||
public static parseSearchResult(item: any): PlaylistFull {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const artistId = traverse(flexColumns[1], "browseId")
|
||||
|
||||
return {
|
||||
return checkType<PlaylistFull>(
|
||||
{
|
||||
type: "PLAYLIST",
|
||||
playlistId: traverse(item, "overlay", "playlistId"),
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
|
|
@ -38,6 +44,8 @@ export default class PlaylistParser {
|
|||
.at(0)
|
||||
.replaceAll(",", ""),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
}
|
||||
},
|
||||
PLAYLIST_FULL
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import AlbumParser from "./AlbumParser"
|
|||
import ArtistParser from "./ArtistParser"
|
||||
import PlaylistParser from "./PlaylistParser"
|
||||
import SongParser from "./SongParser"
|
||||
import traverse from "../traverse"
|
||||
import traverse from "../utils/traverse"
|
||||
import VideoParser from "./VideoParser"
|
||||
import { SearchResult } from ".."
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
import checkType from "../utils/checkType"
|
||||
import Parser from "./Parser"
|
||||
import traverse from "../traverse"
|
||||
import traverse from "../utils/traverse"
|
||||
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"
|
||||
|
||||
export default class SongParser {
|
||||
public static parse(data: any): SongFull {
|
||||
return {
|
||||
return checkType<SongFull>(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: traverse(data, "videoDetails", "videoId"),
|
||||
name: traverse(data, "videoDetails", "title"),
|
||||
|
|
@ -19,14 +23,17 @@ export default class SongParser {
|
|||
description: traverse(data, "description"),
|
||||
formats: traverse(data, "streamingData", "formats"),
|
||||
adaptiveFormats: traverse(data, "streamingData", "adaptiveFormats")
|
||||
}
|
||||
},
|
||||
SONG_FULL
|
||||
)
|
||||
}
|
||||
|
||||
public static parseSearchResult(item: any): SongDetailed {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||
|
||||
return {
|
||||
return checkType<SongDetailed>(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: videoId instanceof Array ? null : videoId,
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
|
|
@ -40,14 +47,17 @@ export default class SongParser {
|
|||
},
|
||||
duration: Parser.parseDuration(traverse(flexColumns[1], "runs", "text").at(-1)),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
}
|
||||
},
|
||||
SONG_DETAILED
|
||||
)
|
||||
}
|
||||
|
||||
public static parseArtistSong(item: any): SongDetailed {
|
||||
const flexColumns = traverse(item, "flexColumns")
|
||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||
|
||||
return {
|
||||
return checkType<SongDetailed>(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: videoId instanceof Array ? null : videoId,
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
|
|
@ -64,7 +74,9 @@ export default class SongParser {
|
|||
},
|
||||
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
}
|
||||
},
|
||||
SONG_DETAILED
|
||||
)
|
||||
}
|
||||
|
||||
public static parseArtistTopSong(
|
||||
|
|
@ -74,7 +86,8 @@ export default class SongParser {
|
|||
const flexColumns = traverse(item, "flexColumns")
|
||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||
|
||||
return {
|
||||
return checkType<Omit<SongDetailed, "duration">>(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: videoId instanceof Array ? null : videoId,
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
|
|
@ -84,7 +97,16 @@ export default class SongParser {
|
|||
name: traverse(flexColumns[2], "browseId")
|
||||
},
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
}
|
||||
},
|
||||
OBJECT({
|
||||
type: STRING("SONG"),
|
||||
videoId: STRING(),
|
||||
name: STRING(),
|
||||
artists: LIST(ARTIST_BASIC),
|
||||
album: ALBUM_BASIC,
|
||||
thumbnails: LIST(THUMBNAIL_FULL)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
public static parseAlbumSong(
|
||||
|
|
@ -96,7 +118,8 @@ export default class SongParser {
|
|||
const flexColumns = traverse(item, "flexColumns")
|
||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||
|
||||
return {
|
||||
return checkType<SongDetailed>(
|
||||
{
|
||||
type: "SONG",
|
||||
videoId: videoId instanceof Array ? null : videoId,
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
|
|
@ -104,6 +127,8 @@ export default class SongParser {
|
|||
album: albumBasic,
|
||||
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
||||
thumbnails
|
||||
}
|
||||
},
|
||||
SONG_DETAILED
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import checkType from "../utils/checkType"
|
||||
import Parser from "./Parser"
|
||||
import traverse from "../traverse"
|
||||
import traverse from "../utils/traverse"
|
||||
import { PLAYLIST_VIDEO } from "../interfaces"
|
||||
import { VideoDetailed, VideoFull } from ".."
|
||||
|
||||
export default class VideoParser {
|
||||
|
|
@ -47,7 +49,8 @@ export default class VideoParser {
|
|||
const flexColumns = traverse(item, "flexColumns")
|
||||
const videoId = traverse(item, "playNavigationEndpoint", "videoId")
|
||||
|
||||
return {
|
||||
return checkType<Omit<VideoDetailed, "views">>(
|
||||
{
|
||||
type: "VIDEO",
|
||||
videoId: videoId instanceof Array ? null : videoId,
|
||||
name: traverse(flexColumns[0], "runs", "text"),
|
||||
|
|
@ -57,6 +60,8 @@ export default class VideoParser {
|
|||
.map((run: any) => ({ artistId: traverse(run, "browseId"), name: run.text })),
|
||||
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||
}
|
||||
},
|
||||
PLAYLIST_VIDEO
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import Validator from "validate-any/dist/classes/Validator"
|
||||
import { validate } from "validate-any"
|
||||
|
||||
export default <T>(data: T, validator: Validator<T>): T => {
|
||||
const result = validate(data, validator)
|
||||
if (result.success) {
|
||||
return result.data
|
||||
} else {
|
||||
console.error("Invalid data schema, please report as an issue", {
|
||||
expected: validator.formatSchema(),
|
||||
actual: data,
|
||||
errors: result.errors
|
||||
})
|
||||
return data
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue