1+ import { HorizontalListContinuation , type IBrowseResponse , Parser } from '../../parser/index.js' ;
2+ import type { Actions , Session } from '../index.js' ;
3+ import type { InnerTubeClient } from '../../types/index.js' ;
4+ import NavigationEndpoint from '../../parser/classes/NavigationEndpoint.js' ;
5+ import { HomeFeed , VideoInfo , MyYoutubeFeed } from '../../parser/yttv/index.js' ;
6+ import { generateRandomString , InnertubeError , throwIfMissing } from '../../utils/Utils.js' ;
7+ import HorizontalList from '../../parser/classes/HorizontalList.js' ;
8+ import type { YTNode } from '../../parser/helpers.js' ;
9+ import Playlist from '../../parser/yttv/Playlist.js' ;
10+ import Library from '../../parser/yttv/Library.js' ;
11+ import SubscriptionsFeed from '../../parser/yttv/SubscriptionsFeed.js' ;
12+ import PlaylistsFeed from '../../parser/yttv/PlaylistsFeed.js' ;
13+
14+ export default class TV {
15+ #session: Session ;
16+ readonly #actions: Actions ;
17+
18+ constructor ( session : Session ) {
19+ this . #session = session ;
20+ this . #actions = session . actions ;
21+ }
22+
23+ async getInfo ( target : string | NavigationEndpoint , client ?: InnerTubeClient ) : Promise < VideoInfo > {
24+ throwIfMissing ( { target } ) ;
25+
26+ const payload = {
27+ videoId : target instanceof NavigationEndpoint ? target . payload ?. videoId : target ,
28+ playlistId : target instanceof NavigationEndpoint ? target . payload ?. playlistId : undefined ,
29+ playlistIndex : target instanceof NavigationEndpoint ? target . payload ?. playlistIndex : undefined ,
30+ params : target instanceof NavigationEndpoint ? target . payload ?. params : undefined ,
31+ racyCheckOk : true ,
32+ contentCheckOk : true
33+ } ;
34+
35+ const watch_endpoint = new NavigationEndpoint ( { watchEndpoint : payload } ) ;
36+ const watch_next_endpoint = new NavigationEndpoint ( { watchNextEndpoint : payload } ) ;
37+
38+ const watch_response = watch_endpoint . call ( this . #actions, {
39+ playbackContext : {
40+ contentPlaybackContext : {
41+ vis : 0 ,
42+ splay : false ,
43+ lactMilliseconds : '-1' ,
44+ signatureTimestamp : this . #session. player ?. sts
45+ }
46+ } ,
47+ serviceIntegrityDimensions : {
48+ poToken : this . #session. po_token
49+ } ,
50+ client
51+ } ) ;
52+
53+ const watch_next_response = await watch_next_endpoint . call ( this . #actions, {
54+ client
55+ } ) ;
56+
57+ const response = await Promise . all ( [ watch_response , watch_next_response ] ) ;
58+
59+ const cpn = generateRandomString ( 16 ) ;
60+
61+ return new VideoInfo ( response , this . #actions, cpn ) ;
62+ }
63+
64+ async getHomeFeed ( ) : Promise < HomeFeed > {
65+ const client : InnerTubeClient = 'TV' ;
66+ const home_feed = new NavigationEndpoint ( { browseEndpoint : {
67+ browseId : 'default'
68+ } } ) ;
69+ const response = await home_feed . call ( this . #actions, {
70+ client
71+ } ) ;
72+ return new HomeFeed ( response , this . #actions) ;
73+ }
74+
75+ async getLibrary ( ) : Promise < Library > {
76+ const browse_endpoint = new NavigationEndpoint ( { browseEndpoint : { browseId : 'FElibrary' } } ) ;
77+ const response = await browse_endpoint . call ( this . #actions, {
78+ client : 'TV'
79+ } ) ;
80+ return new Library ( response , this . #actions) ;
81+ }
82+
83+ async getSubscriptionsFeed ( ) : Promise < SubscriptionsFeed > {
84+ const browse_endpoint = new NavigationEndpoint ( { browseEndpoint : { browseId : 'FEsubscriptions' } } ) ;
85+ const response = await browse_endpoint . call ( this . #actions, { client : 'TV' } ) ;
86+ return new SubscriptionsFeed ( response , this . #actions) ;
87+ }
88+
89+ /**
90+ * Retrieves the user's playlists.
91+ */
92+ async getPlaylists ( ) : Promise < PlaylistsFeed > {
93+ const browse_endpoint = new NavigationEndpoint ( { browseEndpoint : { browseId : 'FEplaylist_aggregation' } } ) ;
94+ const response = await browse_endpoint . call ( this . #actions, { client : 'TV' } ) ;
95+ return new PlaylistsFeed ( response , this . #actions) ;
96+ }
97+
98+ /**
99+ * Retrieves the user's My YouTube page.
100+ */
101+ async getMyYoutubeFeed ( ) : Promise < MyYoutubeFeed > {
102+ const browse_endpoint = new NavigationEndpoint ( { browseEndpoint : { browseId : 'FEmy_youtube' } } ) ;
103+ const response = await browse_endpoint . call ( this . #actions, { client : 'TV' } ) ;
104+ return new MyYoutubeFeed ( response , this . #actions) ;
105+ }
106+
107+ async getPlaylist ( id : string ) : Promise < Playlist > {
108+ throwIfMissing ( { id } ) ;
109+
110+ if ( ! id . startsWith ( 'VL' ) ) {
111+ id = `VL${ id } ` ;
112+ }
113+
114+ const browse_endpoint = new NavigationEndpoint ( { browseEndpoint : { browseId : id } } ) ;
115+ const response = await browse_endpoint . call ( this . #actions, {
116+ client : 'TV'
117+ } ) ;
118+
119+ return new Playlist ( response , this . #actions) ;
120+ }
121+
122+ // Utils
123+
124+ async fetchContinuationData ( item : YTNode , client ?: InnerTubeClient ) {
125+ let continuation : string | undefined ;
126+
127+ if ( item . is ( HorizontalList ) ) {
128+ continuation = item . continuations ?. first ( ) ?. continuation ;
129+ } else if ( item . is ( HorizontalListContinuation ) ) {
130+ continuation = item . continuation ;
131+ } else {
132+ throw new InnertubeError ( `No supported YTNode supplied. Type: ${ item . type } ` ) ;
133+ }
134+
135+ if ( ! continuation ) {
136+ throw new InnertubeError ( 'No continuation data available.' ) ;
137+ }
138+
139+ const data = await this . #actions. execute ( '/browse' , {
140+ client : client ?? 'TV' ,
141+ continuation : continuation
142+ } ) ;
143+
144+ const parser = Parser . parseResponse < IBrowseResponse > ( data . data ) ;
145+ return parser . continuation_contents ;
146+ }
147+ }
0 commit comments