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 PlaylistParser from "./parsers/PlaylistParser"
|
||||||
import SearchParser from "./parsers/SearchParser"
|
import SearchParser from "./parsers/SearchParser"
|
||||||
import SongParser from "./parsers/SongParser"
|
import SongParser from "./parsers/SongParser"
|
||||||
import traverse from "./traverse"
|
import traverse from "./utils/traverse"
|
||||||
import VideoParser from "./parsers/VideoParser"
|
import VideoParser from "./parsers/VideoParser"
|
||||||
import {
|
import {
|
||||||
AlbumDetailed,
|
AlbumDetailed,
|
||||||
|
|
|
||||||
|
|
@ -1,162 +1,18 @@
|
||||||
import ObjectValidator from "validate-any/dist/validators/ObjectValidator"
|
|
||||||
import Validator from "validate-any/dist/classes/Validator"
|
import Validator from "validate-any/dist/classes/Validator"
|
||||||
import YTMusic, {
|
import YTMusic from ".."
|
||||||
AlbumBasic,
|
|
||||||
AlbumDetailed,
|
|
||||||
AlbumFull,
|
|
||||||
ArtistBasic,
|
|
||||||
ArtistDetailed,
|
|
||||||
ArtistFull,
|
|
||||||
PlaylistFull,
|
|
||||||
SongDetailed,
|
|
||||||
SongFull,
|
|
||||||
ThumbnailFull,
|
|
||||||
VideoDetailed,
|
|
||||||
VideoFull
|
|
||||||
} from ".."
|
|
||||||
import {
|
import {
|
||||||
BOOLEAN,
|
ALBUM_DETAILED,
|
||||||
iValidationError,
|
ALBUM_FULL,
|
||||||
LIST,
|
ARTIST_DETAILED,
|
||||||
NULL,
|
ARTIST_FULL,
|
||||||
NUMBER,
|
PLAYLIST_FULL,
|
||||||
OBJECT,
|
PLAYLIST_VIDEO,
|
||||||
OR,
|
SONG_DETAILED,
|
||||||
STRING,
|
SONG_FULL,
|
||||||
validate
|
VIDEO_DETAILED,
|
||||||
} from "validate-any"
|
VIDEO_FULL
|
||||||
|
} from "../interfaces"
|
||||||
//#region Interfaces
|
import { iValidationError, LIST, STRING, validate } from "validate-any"
|
||||||
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
|
|
||||||
|
|
||||||
const issues: iValidationError[][] = []
|
const issues: iValidationError[][] = []
|
||||||
const queries = ["Lilac", "Weekend", "Eill", "Eminem", "Lisa Hannigan"]
|
const queries = ["Lilac", "Weekend", "Eill", "Eminem", "Lisa Hannigan"]
|
||||||
|
|
@ -231,7 +87,7 @@ queries.forEach(query => {
|
||||||
ytmusic
|
ytmusic
|
||||||
.search(query, "PLAYLIST")
|
.search(query, "PLAYLIST")
|
||||||
.then(playlists => {
|
.then(playlists => {
|
||||||
_expect(playlists, LIST(PLAYLIST_DETAILED))
|
_expect(playlists, LIST(PLAYLIST_FULL))
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(done)
|
.catch(done)
|
||||||
|
|
@ -246,7 +102,7 @@ queries.forEach(query => {
|
||||||
LIST(
|
LIST(
|
||||||
ALBUM_DETAILED,
|
ALBUM_DETAILED,
|
||||||
ARTIST_DETAILED,
|
ARTIST_DETAILED,
|
||||||
PLAYLIST_DETAILED,
|
PLAYLIST_FULL,
|
||||||
SONG_DETAILED,
|
SONG_DETAILED,
|
||||||
VIDEO_DETAILED
|
VIDEO_DETAILED
|
||||||
)
|
)
|
||||||
|
|
@ -327,7 +183,7 @@ queries.forEach(query => {
|
||||||
.search(query, "PLAYLIST")
|
.search(query, "PLAYLIST")
|
||||||
.then(playlists => ytmusic.getPlaylist(playlists[0]!.playlistId!))
|
.then(playlists => ytmusic.getPlaylist(playlists[0]!.playlistId!))
|
||||||
.then(playlist => {
|
.then(playlist => {
|
||||||
_expect(playlist, PLAYLIST_DETAILED)
|
_expect(playlist, PLAYLIST_FULL)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
.catch(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 SongParser from "./SongParser"
|
||||||
import traverse from "../traverse"
|
import traverse from "../utils/traverse"
|
||||||
|
import { ALBUM_DETAILED, ALBUM_FULL } from "../interfaces"
|
||||||
import { AlbumDetailed, AlbumFull, ArtistBasic } from ".."
|
import { AlbumDetailed, AlbumFull, ArtistBasic } from ".."
|
||||||
|
|
||||||
export default class AlbumParser {
|
export default class AlbumParser {
|
||||||
|
|
@ -14,7 +16,8 @@ export default class AlbumParser {
|
||||||
const thumbnails = [traverse(data, "header", "thumbnails")].flat()
|
const thumbnails = [traverse(data, "header", "thumbnails")].flat()
|
||||||
const description = traverse(data, "description", "text")
|
const description = traverse(data, "description", "text")
|
||||||
|
|
||||||
return {
|
return checkType<AlbumFull>(
|
||||||
|
{
|
||||||
type: "ALBUM",
|
type: "ALBUM",
|
||||||
...albumBasic,
|
...albumBasic,
|
||||||
playlistId: traverse(data, "buttonRenderer", "playlistId"),
|
playlistId: traverse(data, "buttonRenderer", "playlistId"),
|
||||||
|
|
@ -27,13 +30,16 @@ export default class AlbumParser {
|
||||||
.map((item: any) =>
|
.map((item: any) =>
|
||||||
SongParser.parseAlbumSong(item, artists, albumBasic, thumbnails)
|
SongParser.parseAlbumSong(item, artists, albumBasic, thumbnails)
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
ALBUM_FULL
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parseSearchResult(item: any): AlbumDetailed {
|
public static parseSearchResult(item: any): AlbumDetailed {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
|
|
||||||
return {
|
return checkType<AlbumDetailed>(
|
||||||
|
{
|
||||||
type: "ALBUM",
|
type: "ALBUM",
|
||||||
albumId: [traverse(item, "browseId")].flat().at(-1),
|
albumId: [traverse(item, "browseId")].flat().at(-1),
|
||||||
playlistId: traverse(item, "overlay", "playlistId"),
|
playlistId: traverse(item, "overlay", "playlistId"),
|
||||||
|
|
@ -43,11 +49,14 @@ export default class AlbumParser {
|
||||||
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: [traverse(item, "thumbnails")].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
},
|
||||||
|
ALBUM_DETAILED
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parseArtistAlbum(item: any, artistBasic: ArtistBasic): AlbumDetailed {
|
public static parseArtistAlbum(item: any, artistBasic: ArtistBasic): AlbumDetailed {
|
||||||
return {
|
return checkType<AlbumDetailed>(
|
||||||
|
{
|
||||||
type: "ALBUM",
|
type: "ALBUM",
|
||||||
albumId: [traverse(item, "browseId")].flat().at(-1),
|
albumId: [traverse(item, "browseId")].flat().at(-1),
|
||||||
playlistId: traverse(item, "thumbnailOverlay", "playlistId"),
|
playlistId: traverse(item, "thumbnailOverlay", "playlistId"),
|
||||||
|
|
@ -55,11 +64,14 @@ export default class AlbumParser {
|
||||||
artists: [artistBasic],
|
artists: [artistBasic],
|
||||||
year: +traverse(item, "subtitle", "text").at(-1),
|
year: +traverse(item, "subtitle", "text").at(-1),
|
||||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
},
|
||||||
|
ALBUM_DETAILED
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parseArtistTopAlbums(item: any, artistBasic: ArtistBasic): AlbumDetailed {
|
public static parseArtistTopAlbums(item: any, artistBasic: ArtistBasic): AlbumDetailed {
|
||||||
return {
|
return checkType<AlbumDetailed>(
|
||||||
|
{
|
||||||
type: "ALBUM",
|
type: "ALBUM",
|
||||||
albumId: traverse(item, "browseId").at(-1),
|
albumId: traverse(item, "browseId").at(-1),
|
||||||
playlistId: traverse(item, "musicPlayButtonRenderer", "playlistId"),
|
playlistId: traverse(item, "musicPlayButtonRenderer", "playlistId"),
|
||||||
|
|
@ -67,6 +79,8 @@ export default class AlbumParser {
|
||||||
artists: [artistBasic],
|
artists: [artistBasic],
|
||||||
year: +traverse(item, "subtitle", "text").at(-1),
|
year: +traverse(item, "subtitle", "text").at(-1),
|
||||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
},
|
||||||
|
ALBUM_DETAILED
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import AlbumParser from "./AlbumParser"
|
import AlbumParser from "./AlbumParser"
|
||||||
|
import checkType from "../utils/checkType"
|
||||||
import Parser from "./Parser"
|
import Parser from "./Parser"
|
||||||
import SongParser from "./SongParser"
|
import SongParser from "./SongParser"
|
||||||
import traverse from "../traverse"
|
import traverse from "../utils/traverse"
|
||||||
|
import { ARTIST_DETAILED, ARTIST_FULL } from "../interfaces"
|
||||||
import { ArtistDetailed, ArtistFull } from ".."
|
import { ArtistDetailed, ArtistFull } from ".."
|
||||||
|
|
||||||
export default class ArtistParser {
|
export default class ArtistParser {
|
||||||
|
|
@ -13,7 +15,8 @@ export default class ArtistParser {
|
||||||
|
|
||||||
const description = traverse(data, "header", "description", "text")
|
const description = traverse(data, "header", "description", "text")
|
||||||
|
|
||||||
return {
|
return checkType<ArtistFull>(
|
||||||
|
{
|
||||||
type: "ARTIST",
|
type: "ARTIST",
|
||||||
...artistBasic,
|
...artistBasic,
|
||||||
thumbnails: [traverse(data, "header", "thumbnails")].flat(),
|
thumbnails: [traverse(data, "header", "thumbnails")].flat(),
|
||||||
|
|
@ -25,18 +28,25 @@ export default class ArtistParser {
|
||||||
topAlbums: [traverse(data, "musicCarouselShelfRenderer")]
|
topAlbums: [traverse(data, "musicCarouselShelfRenderer")]
|
||||||
.flat()
|
.flat()
|
||||||
.at(0)
|
.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 {
|
public static parseSearchResult(item: any): ArtistDetailed {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
|
|
||||||
return {
|
return checkType<ArtistDetailed>(
|
||||||
|
{
|
||||||
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: [traverse(item, "thumbnails")].flat()
|
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 ".."
|
import { PlaylistFull } from ".."
|
||||||
|
|
||||||
export default class PlaylistParser {
|
export default class PlaylistParser {
|
||||||
public static parse(data: any, playlistId: string): PlaylistFull {
|
public static parse(data: any, playlistId: string): PlaylistFull {
|
||||||
return {
|
return checkType<PlaylistFull>(
|
||||||
|
{
|
||||||
type: "PLAYLIST",
|
type: "PLAYLIST",
|
||||||
playlistId,
|
playlistId,
|
||||||
name: traverse(data, "header", "title", "text").at(0),
|
name: traverse(data, "header", "title", "text").at(0),
|
||||||
|
|
@ -17,14 +20,17 @@ export default class PlaylistParser {
|
||||||
.at(0)
|
.at(0)
|
||||||
.replaceAll(",", ""),
|
.replaceAll(",", ""),
|
||||||
thumbnails: traverse(data, "header", "thumbnails")
|
thumbnails: traverse(data, "header", "thumbnails")
|
||||||
}
|
},
|
||||||
|
PLAYLIST_FULL
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parseSearchResult(item: any): PlaylistFull {
|
public static parseSearchResult(item: any): PlaylistFull {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
const artistId = traverse(flexColumns[1], "browseId")
|
const artistId = traverse(flexColumns[1], "browseId")
|
||||||
|
|
||||||
return {
|
return checkType<PlaylistFull>(
|
||||||
|
{
|
||||||
type: "PLAYLIST",
|
type: "PLAYLIST",
|
||||||
playlistId: traverse(item, "overlay", "playlistId"),
|
playlistId: traverse(item, "overlay", "playlistId"),
|
||||||
name: traverse(flexColumns[0], "runs", "text"),
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
|
|
@ -38,6 +44,8 @@ export default class PlaylistParser {
|
||||||
.at(0)
|
.at(0)
|
||||||
.replaceAll(",", ""),
|
.replaceAll(",", ""),
|
||||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
},
|
||||||
|
PLAYLIST_FULL
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ 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 "../traverse"
|
import traverse from "../utils/traverse"
|
||||||
import VideoParser from "./VideoParser"
|
import VideoParser from "./VideoParser"
|
||||||
import { SearchResult } from ".."
|
import { SearchResult } from ".."
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
|
import checkType from "../utils/checkType"
|
||||||
import Parser from "./Parser"
|
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 { AlbumBasic, ArtistBasic, SongDetailed, SongFull, ThumbnailFull } from ".."
|
||||||
|
import { LIST, OBJECT, STRING } from "validate-any"
|
||||||
|
|
||||||
export default class SongParser {
|
export default class SongParser {
|
||||||
public static parse(data: any): SongFull {
|
public static parse(data: any): SongFull {
|
||||||
return {
|
return checkType<SongFull>(
|
||||||
|
{
|
||||||
type: "SONG",
|
type: "SONG",
|
||||||
videoId: traverse(data, "videoDetails", "videoId"),
|
videoId: traverse(data, "videoDetails", "videoId"),
|
||||||
name: traverse(data, "videoDetails", "title"),
|
name: traverse(data, "videoDetails", "title"),
|
||||||
|
|
@ -19,14 +23,17 @@ export default class SongParser {
|
||||||
description: traverse(data, "description"),
|
description: traverse(data, "description"),
|
||||||
formats: traverse(data, "streamingData", "formats"),
|
formats: traverse(data, "streamingData", "formats"),
|
||||||
adaptiveFormats: traverse(data, "streamingData", "adaptiveFormats")
|
adaptiveFormats: traverse(data, "streamingData", "adaptiveFormats")
|
||||||
}
|
},
|
||||||
|
SONG_FULL
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parseSearchResult(item: any): SongDetailed {
|
public static parseSearchResult(item: any): SongDetailed {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||||
|
|
||||||
return {
|
return checkType<SongDetailed>(
|
||||||
|
{
|
||||||
type: "SONG",
|
type: "SONG",
|
||||||
videoId: videoId instanceof Array ? null : videoId,
|
videoId: videoId instanceof Array ? null : videoId,
|
||||||
name: traverse(flexColumns[0], "runs", "text"),
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
|
|
@ -40,14 +47,17 @@ export default class SongParser {
|
||||||
},
|
},
|
||||||
duration: Parser.parseDuration(traverse(flexColumns[1], "runs", "text").at(-1)),
|
duration: Parser.parseDuration(traverse(flexColumns[1], "runs", "text").at(-1)),
|
||||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
},
|
||||||
|
SONG_DETAILED
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parseArtistSong(item: any): SongDetailed {
|
public static parseArtistSong(item: any): SongDetailed {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||||
|
|
||||||
return {
|
return checkType<SongDetailed>(
|
||||||
|
{
|
||||||
type: "SONG",
|
type: "SONG",
|
||||||
videoId: videoId instanceof Array ? null : videoId,
|
videoId: videoId instanceof Array ? null : videoId,
|
||||||
name: traverse(flexColumns[0], "runs", "text"),
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
|
|
@ -64,7 +74,9 @@ export default class SongParser {
|
||||||
},
|
},
|
||||||
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
||||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
thumbnails: [traverse(item, "thumbnails")].flat()
|
||||||
}
|
},
|
||||||
|
SONG_DETAILED
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parseArtistTopSong(
|
public static parseArtistTopSong(
|
||||||
|
|
@ -74,7 +86,8 @@ export default class SongParser {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||||
|
|
||||||
return {
|
return checkType<Omit<SongDetailed, "duration">>(
|
||||||
|
{
|
||||||
type: "SONG",
|
type: "SONG",
|
||||||
videoId: videoId instanceof Array ? null : videoId,
|
videoId: videoId instanceof Array ? null : videoId,
|
||||||
name: traverse(flexColumns[0], "runs", "text"),
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
|
|
@ -84,7 +97,16 @@ export default class SongParser {
|
||||||
name: traverse(flexColumns[2], "browseId")
|
name: traverse(flexColumns[2], "browseId")
|
||||||
},
|
},
|
||||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
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(
|
public static parseAlbumSong(
|
||||||
|
|
@ -96,7 +118,8 @@ export default class SongParser {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
const videoId = traverse(item, "playlistItemData", "videoId")
|
const videoId = traverse(item, "playlistItemData", "videoId")
|
||||||
|
|
||||||
return {
|
return checkType<SongDetailed>(
|
||||||
|
{
|
||||||
type: "SONG",
|
type: "SONG",
|
||||||
videoId: videoId instanceof Array ? null : videoId,
|
videoId: videoId instanceof Array ? null : videoId,
|
||||||
name: traverse(flexColumns[0], "runs", "text"),
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
|
|
@ -104,6 +127,8 @@ export default class SongParser {
|
||||||
album: albumBasic,
|
album: albumBasic,
|
||||||
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
||||||
thumbnails
|
thumbnails
|
||||||
}
|
},
|
||||||
|
SONG_DETAILED
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
import checkType from "../utils/checkType"
|
||||||
import Parser from "./Parser"
|
import Parser from "./Parser"
|
||||||
import traverse from "../traverse"
|
import traverse from "../utils/traverse"
|
||||||
|
import { PLAYLIST_VIDEO } from "../interfaces"
|
||||||
import { VideoDetailed, VideoFull } from ".."
|
import { VideoDetailed, VideoFull } from ".."
|
||||||
|
|
||||||
export default class VideoParser {
|
export default class VideoParser {
|
||||||
|
|
@ -47,7 +49,8 @@ export default class VideoParser {
|
||||||
const flexColumns = traverse(item, "flexColumns")
|
const flexColumns = traverse(item, "flexColumns")
|
||||||
const videoId = traverse(item, "playNavigationEndpoint", "videoId")
|
const videoId = traverse(item, "playNavigationEndpoint", "videoId")
|
||||||
|
|
||||||
return {
|
return checkType<Omit<VideoDetailed, "views">>(
|
||||||
|
{
|
||||||
type: "VIDEO",
|
type: "VIDEO",
|
||||||
videoId: videoId instanceof Array ? null : videoId,
|
videoId: videoId instanceof Array ? null : videoId,
|
||||||
name: traverse(flexColumns[0], "runs", "text"),
|
name: traverse(flexColumns[0], "runs", "text"),
|
||||||
|
|
@ -57,6 +60,8 @@ export default class VideoParser {
|
||||||
.map((run: any) => ({ artistId: traverse(run, "browseId"), name: run.text })),
|
.map((run: any) => ({ artistId: traverse(run, "browseId"), name: run.text })),
|
||||||
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
duration: Parser.parseDuration(traverse(item, "fixedColumns", "runs", "text")),
|
||||||
thumbnails: [traverse(item, "thumbnails")].flat()
|
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