From 9479811d4c5125cd01f28955379bcfb53e8f3581 Mon Sep 17 00:00:00 2001 From: Zechariah Date: Sun, 26 Dec 2021 03:22:59 +0800 Subject: [PATCH] Song and Video data fetching works --- src/YTMusic.ts | 25 ++++++++++++++++++------- src/index.ts | 10 ---------- src/parsers/SongParser.ts | 19 +++++++++++++++++++ src/parsers/VideoParser.ts | 22 ++++++++++++++++++++++ src/tests/{testing.ts => all.ts} | 14 ++++++++------ src/tests/interfaces.ts | 29 ++++++++++++++++++++++++++++- src/tests/run.ts | 10 ++++++++++ src/types.d.ts | 14 ++++++++++++++ 8 files changed, 119 insertions(+), 24 deletions(-) delete mode 100644 src/index.ts rename src/tests/{testing.ts => all.ts} (88%) create mode 100644 src/tests/run.ts diff --git a/src/YTMusic.ts b/src/YTMusic.ts index f919a8a..b1fe968 100644 --- a/src/YTMusic.ts +++ b/src/YTMusic.ts @@ -1,7 +1,6 @@ import AlbumParser from "./parsers/AlbumParser" import ArtistParser from "./parsers/ArtistParser" import axios, { AxiosInstance } from "axios" -import fs from "fs" import PlaylistParser from "./parsers/PlaylistParser" import SearchParser from "./parsers/SearchParser" import SongParser from "./parsers/SongParser" @@ -237,16 +236,28 @@ export default class YTMusic { ) } - public async getSong(videoId: string) { + /** + * Get all possible information of a Song + * + * @param videoId Video ID + * @returns Song Data + */ + public async getSong(videoId: string): Promise { const data = await this.constructRequest("player", { videoId }) - fs.writeFileSync("data.json", JSON.stringify(data)) + return SongParser.parse(data) } - public async getVideo(videoId: string) { + /** + * Get all possible information of a Video + * + * @param videoId Video ID + * @returns Video Data + */ + public async getVideo(videoId: string): Promise { const data = await this.constructRequest("player", { videoId }) - fs.writeFileSync("data.json", JSON.stringify(data)) + return VideoParser.parse(data) } /** @@ -320,7 +331,7 @@ export default class YTMusic { /** * Get all possible information of a Playlist except the tracks - * + * * @param playlistId Playlist ID * @returns Playlist Data */ @@ -333,7 +344,7 @@ export default class YTMusic { /** * Get all videos in a Playlist - * + * * @param playlistId Playlist ID * @returns Playlist's Videos */ diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index ff0c946..0000000 --- a/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import YTMusic from "./YTMusic" - -const ytmusic = new YTMusic() -ytmusic.initialize().then(() => { - ytmusic.search("Lilac", "PLAYLIST").then(res => { - ytmusic.getPlaylist(res[0].playlistId).then(res => { - console.log(res) - }) - }) -}) diff --git a/src/parsers/SongParser.ts b/src/parsers/SongParser.ts index ca403b1..4e6b0b0 100644 --- a/src/parsers/SongParser.ts +++ b/src/parsers/SongParser.ts @@ -2,6 +2,25 @@ import Parser from "./Parser" import traverse from "../utils/traverse" export default class SongParser { + public static parse(data: any): YTMusic.SongFull { + return { + type: "SONG", + videoId: traverse(data, "videoDetails", "videoId"), + name: traverse(data, "videoDetails", "title"), + artists: [ + { + artistId: traverse(data, "videoDetails", "channelId"), + name: traverse(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") + } + } + public static parseSearchResult(item: any): YTMusic.SongDetailed { const flexColumns = traverse(item, "flexColumns") const videoId = traverse(item, "playlistItemData", "videoId") diff --git a/src/parsers/VideoParser.ts b/src/parsers/VideoParser.ts index a59d529..527fdda 100644 --- a/src/parsers/VideoParser.ts +++ b/src/parsers/VideoParser.ts @@ -2,6 +2,28 @@ import Parser from "./Parser" import traverse from "../utils/traverse" export default class VideoParser { + public static parse(data: any): YTMusic.VideoFull { + return { + type: "VIDEO", + videoId: traverse(data, "videoDetails", "videoId"), + name: traverse(data, "videoDetails", "title"), + artists: [ + { + artistId: traverse(data, "videoDetails", "channelId"), + name: traverse(data, "author") + } + ], + views: +traverse(data, "videoDetails", "viewCount"), + duration: +traverse(data, "videoDetails", "lengthSeconds"), + thumbnails: [traverse(data, "videoDetails", "thumbnails")].flat(), + description: traverse(data, "description"), + unlisted: traverse(data, "unlisted"), + familySafe: traverse(data, "familySafe"), + paid: traverse(data, "paid"), + tags: traverse(data, "tags") + } + } + public static parseSearchResult(item: any): YTMusic.VideoDetailed { const flexColumns = traverse(item, "flexColumns") const videoId = traverse(item, "playNavigationEndpoint", "videoId") diff --git a/src/tests/testing.ts b/src/tests/all.ts similarity index 88% rename from src/tests/testing.ts rename to src/tests/all.ts index 3a4c0bd..1f3133b 100644 --- a/src/tests/testing.ts +++ b/src/tests/all.ts @@ -8,7 +8,9 @@ import { PLAYLIST_DETAILED, PLAYLIST_VIDEO, SONG_DETAILED, - VIDEO_DETAILED + SONG_FULL, + VIDEO_DETAILED, + VIDEO_FULL } from "./interfaces" import { LIST, validate } from "validate-any" @@ -26,10 +28,10 @@ ytmusic.initialize().then(() => ytmusic.search(query) ]) - const [artist, artistSongs, artistAlbums, album, playlist, playlistVideos] = + const [song, video, artist, artistSongs, artistAlbums, album, playlist, playlistVideos] = await Promise.all([ - // ytmusic.getSong(songs[0].videoId), - // ytmusic.getVideo(videos[0].videoId), + ytmusic.getSong(songs[0].videoId!), + ytmusic.getVideo(videos[0].videoId!), ytmusic.getArtist(artists[0].artistId), ytmusic.getArtistSongs(artists[0].artistId), ytmusic.getArtistAlbums(artists[0].artistId), @@ -54,8 +56,8 @@ ytmusic.initialize().then(() => VIDEO_DETAILED ) ], - // [song, SONG_DETAILED], - // [video, VIDEO_DETAILED], + [song, SONG_FULL], + [video, VIDEO_FULL], [artist, ARTIST_FULL], [artistSongs, LIST(SONG_DETAILED)], [artistAlbums, LIST(ALBUM_DETAILED)], diff --git a/src/tests/interfaces.ts b/src/tests/interfaces.ts index 28563fd..0a69542 100644 --- a/src/tests/interfaces.ts +++ b/src/tests/interfaces.ts @@ -1,5 +1,5 @@ import ObjectValidator from "validate-any/build/validators/ObjectValidator" -import { LIST, NULL, NUMBER, OBJECT, OR, STRING } from "validate-any" +import { BOOLEAN, LIST, NULL, NUMBER, OBJECT, OR, STRING } from "validate-any" export const THUMBNAIL_FULL: ObjectValidator = OBJECT({ url: STRING(), @@ -54,6 +54,33 @@ export const ALBUM_DETAILED: ObjectValidator = OBJECT({ thumbnails: LIST(THUMBNAIL_FULL) }) +export const SONG_FULL: ObjectValidator = 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 = 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 = OBJECT({ artistId: STRING(), name: STRING(), diff --git a/src/tests/run.ts b/src/tests/run.ts new file mode 100644 index 0000000..9c016b1 --- /dev/null +++ b/src/tests/run.ts @@ -0,0 +1,10 @@ +import YTMusic from "../YTMusic" + +const ytmusic = new YTMusic() +ytmusic.initialize().then(() => { + ytmusic.search("Lilac", "SONG").then(res => { + ytmusic.getSong(res.find(r => !!r.videoId)!.videoId!).then(res => { + console.log(res) + }) + }) +}) diff --git a/src/types.d.ts b/src/types.d.ts index b200096..ece05d5 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -15,6 +15,12 @@ declare namespace YTMusic { thumbnails: ThumbnailFull[] } + interface SongFull extends Omit { + description: string + formats: any[] + adaptiveFormats: any[] + } + interface VideoDetailed { type: "VIDEO" videoId: string | null @@ -25,6 +31,14 @@ declare namespace YTMusic { thumbnails: ThumbnailFull[] } + interface VideoFull extends VideoDetailed { + description: string + unlisted: boolean + familySafe: boolean + paid: boolean + tags: string[] + } + interface ArtistBasic { artistId: string | null name: string