diff --git a/src/core/Music.ts b/src/core/Music.ts index c8b7385f..4d01fa92 100644 --- a/src/core/Music.ts +++ b/src/core/Music.ts @@ -1,4 +1,7 @@ import Session from './Session'; + +import TrackInfo from '../parser/ytmusic/TrackInfo'; + import Search from '../parser/ytmusic/Search'; import HomeFeed from '../parser/ytmusic/HomeFeed'; import Explore from '../parser/ytmusic/Explore'; @@ -31,6 +34,17 @@ class Music { this.#actions = session.actions; } + /** + * Retrieves track info. + */ + async getInfo(video_id: string) { + const initial_info = this.#actions.execute('/player', { client: 'YTMUSIC', videoId: video_id }); + const continuation = this.#actions.execute('/next', { client: 'YTMUSIC', videoId: video_id }); + + const response = await Promise.all([ initial_info, continuation ]); + return new TrackInfo(response, this.#actions); + } + /** * Searches on YouTube Music. */ diff --git a/src/parser/index.ts b/src/parser/index.ts index 53fa9556..b355bb85 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -3,6 +3,7 @@ import VideoDetails from './classes/misc/VideoDetails'; import GetParserByName from './map'; import Endscreen from './classes/Endscreen'; import CardCollection from './classes/CardCollection'; +import NavigationEndpoint from './classes/NavigationEndpoint'; import { InnertubeError, ParsingError } from '../utils/Utils'; import { YTNode, YTNodeConstructor, SuperParsedResult, ObservedArray, observe, Memo } from './helpers'; @@ -197,6 +198,7 @@ export default class Parser { dash_manifest_url: data.streamingData?.dashManifestUrl || null, dls_manifest_url: data.streamingData?.dashManifestUrl || null } : undefined, + current_video_endpoint: data.currentVideoEndpoint ? new NavigationEndpoint(data.currentVideoEndpoint) : null, // TODO: PlayerCaptionsTracklist ? captions: Parser.parse(data.captions), video_details: data.videoDetails ? new VideoDetails(data.videoDetails) : undefined, diff --git a/src/parser/ytmusic/TrackInfo.ts b/src/parser/ytmusic/TrackInfo.ts new file mode 100644 index 00000000..a4879be5 --- /dev/null +++ b/src/parser/ytmusic/TrackInfo.ts @@ -0,0 +1,73 @@ +import Parser, { ParsedResponse } from '..'; +import Actions, { AxioslikeResponse } from '../../core/Actions'; +import { InnertubeError } from '../../utils/Utils'; + +import Tab from '../classes/Tab'; +import Tabbed from '../classes/Tabbed'; +import WatchNextTabbedResults from '../classes/WatchNextTabbedResults'; +import SingleColumnMusicWatchNextResults from '../classes/SingleColumnMusicWatchNextResults'; +import MicroformatData from '../classes/MicroformatData'; +import PlayerOverlay from '../classes/PlayerOverlay'; + +// TODO: add a way to get specific tabs +class TrackInfo { + #page: [ ParsedResponse, ParsedResponse? ]; + #actions: Actions; + + basic_info; + streaming_data; + playability_status; + storyboards; + endscreen; + + tabs; + current_video_endpoint; + + constructor(data: [AxioslikeResponse, AxioslikeResponse?], actions: Actions) { + this.#actions = actions; + + const info = Parser.parseResponse(data[0].data); + const next = data?.[1]?.data ? Parser.parseResponse(data[1].data) : undefined; + + this.#page = [ info, next ]; + + if (info.playability_status?.status === 'ERROR') + throw new InnertubeError('This video is unavailable', info.playability_status); + + if (!info.microformat?.is(MicroformatData)) + throw new InnertubeError('Invalid microformat', info.microformat); + + this.basic_info = { + ...info.video_details, + ...{ + description: info.microformat?.description, + is_unlisted: info.microformat?.is_unlisted, + is_family_safe: info.microformat?.is_family_safe, + url_canonical: info.microformat?.url_canonical, + tags: info.microformat?.tags + } + }; + + this.streaming_data = info.streaming_data; + this.playability_status = info.playability_status; + this.storyboards = info.storyboards; + this.endscreen = info.endscreen; + + if (next) { + const single_col = next.contents.item().as(SingleColumnMusicWatchNextResults); + const tabbed_results = single_col.contents.item().as(Tabbed).contents.item().as(WatchNextTabbedResults); + + this.tabs = tabbed_results.tabs.array().as(Tab); + this.current_video_endpoint = next.current_video_endpoint; + + // TODO: update PlayerOverlay, YTMusic's is a little bit different. + this.player_overlays = next.player_overlays.item().as(PlayerOverlay); + } + } + + get page() { + return this.#page; + } +} + +export default TrackInfo; \ No newline at end of file