2022-03-16 09:07:40 +00:00
// Code specific to working with the innertube API
// https://www.youtube.com/youtubei/v1
import { Http } from '@capacitor-community/http' ;
import { getBetweenStrings } from './utils' ;
2022-03-20 12:20:13 +00:00
import constants from './constants' ;
2022-03-16 09:07:40 +00:00
class Innertube {
2022-03-17 13:17:32 +00:00
//--- Initiation ---//
2022-03-16 09:07:40 +00:00
constructor ( ErrorCallback ) {
2022-03-17 13:17:32 +00:00
this . ErrorCallback = ErrorCallback || undefined ;
2022-03-16 09:07:40 +00:00
this . retry _count = 0
}
2022-03-17 13:17:32 +00:00
checkErrorCallback ( ) {
return typeof this . ErrorCallback === "function"
}
2022-03-18 06:15:19 +00:00
async initAsync ( ) {
const html = await Http . get ( { url : constants . URLS . YT _URL , params : { hl : "en" } } ) . catch ( ( error ) => error ) ;
try {
if ( html instanceof Error && this . checkErrorCallback ) this . ErrorCallback ( html . message , true ) ;
try {
const data = JSON . parse ( getBetweenStrings ( html . data , 'ytcfg.set(' , ');' ) ) ;
if ( data . INNERTUBE _CONTEXT ) {
this . key = data . INNERTUBE _API _KEY ;
this . context = data . INNERTUBE _CONTEXT ;
2022-03-21 13:21:18 +00:00
this . logged _in = data . LOGGED _IN ;
2022-03-18 06:15:19 +00:00
this . context . client = constants . INNERTUBE _CLIENT ( this . context . client )
2022-03-20 06:05:43 +00:00
this . header = constants . INNERTUBE _HEADER ( this . context . client )
2022-03-17 13:17:32 +00:00
}
2022-03-18 06:15:19 +00:00
} catch ( err ) {
console . log ( err )
if ( this . checkErrorCallback ) this . ErrorCallback ( err , true )
if ( this . retry _count >= 10 ) { this . initAsync ( ) } else { if ( this . checkErrorCallback ) this . ErrorCallback ( "Failed to retrieve Innertube session" , true ) ; }
}
} catch ( error ) {
this . ErrorCallback ( error , true )
} ;
2022-03-16 09:07:40 +00:00
} ;
2022-03-18 06:15:19 +00:00
static async createAsync ( ErrorCallback ) {
2022-03-16 19:47:17 +00:00
const created = new Innertube ( ErrorCallback ) ;
2022-03-18 06:15:19 +00:00
await created . initAsync ( ) ;
2022-03-16 19:47:17 +00:00
return created ;
}
2022-03-17 13:17:32 +00:00
//--- API Calls ---//
2022-03-18 06:15:19 +00:00
async browseAsync ( action _type ) {
2022-03-16 09:07:40 +00:00
let data = { context : this . context }
switch ( action _type ) {
case 'recommendations' :
data . browseId = 'FEwhat_to_watch'
break ;
case 'playlist' :
data . browseId = args . browse _id
break ;
default :
}
2022-03-16 19:47:17 +00:00
console . log ( data )
const response = await Http . post ( {
2022-03-17 05:57:28 +00:00
url : ` ${ constants . URLS . YT _BASE _API } /browse?key= ${ this . key } ` ,
2022-03-16 22:34:58 +00:00
data : data ,
headers : { "Content-Type" : "application/json" }
2022-03-16 09:07:40 +00:00
} ) . catch ( ( error ) => error ) ;
2022-03-18 06:15:19 +00:00
if ( response instanceof Error ) return { success : false , status _code : response . status , message : response . message } ;
2022-03-16 09:07:40 +00:00
return {
success : true ,
status _code : response . status ,
data : response . data
} ;
}
2022-03-18 11:50:44 +00:00
static getThumbnail ( id , resolution ) {
2022-03-20 09:02:52 +00:00
if ( resolution == "max" ) {
const url = ` https://img.youtube.com/vi/ ${ id } /maxresdefault.jpg `
let img = new Image ( ) ;
img . src = url
img . onload = function ( ) {
if ( img . height !== 120 ) return url
} ;
}
return ` https://img.youtube.com/vi/ ${ id } /mqdefault.jpg `
2022-03-18 11:50:44 +00:00
}
2022-03-20 09:02:52 +00:00
2022-03-18 11:50:44 +00:00
2022-03-20 06:05:43 +00:00
async getVidAsync ( id ) {
2022-03-21 13:21:18 +00:00
let data = { context : this . context , videoId : id }
2022-03-20 06:05:43 +00:00
const response = await Http . get ( {
2022-03-21 13:21:18 +00:00
url : ` https://m.youtube.com/watch?v= ${ id } &pbj=1 ` ,
2022-03-20 06:05:43 +00:00
params : { } ,
2022-03-21 13:21:18 +00:00
headers : Object . assign ( this . header , {
referer : ` https://m.youtube.com/watch?v= ${ id } ` ,
'x-youtube-client-name' : constants . YT _API _VALUES . CLIENT _WEB ,
'x-youtube-client-version' : constants . YT _API _VALUES . VERSION _WEB } )
} ) . catch ( ( error ) => error ) ;
const responseMobile = await Http . post ( {
url : ` ${ constants . URLS . YT _BASE _API } /player?key= ${ this . key } ` ,
data : data ,
headers : constants . INNERTUBE _HEADER ( this . context )
2022-03-17 13:17:32 +00:00
} ) . catch ( ( error ) => error ) ;
if ( response instanceof Error ) return { success : false , status _code : response . response . status , message : response . message } ;
return {
success : true ,
status _code : response . status ,
2022-03-21 13:21:18 +00:00
data : { webOutput : response . data , appOutput : responseMobile . data }
2022-03-17 13:17:32 +00:00
} ;
}
// Simple Wrappers
2022-03-18 06:15:19 +00:00
async getRecommendationsAsync ( ) {
const rec = await this . browseAsync ( "recommendations" ) ;
console . log ( rec . data )
2022-03-19 05:35:28 +00:00
return rec ;
2022-03-16 09:07:40 +00:00
}
2022-03-20 06:05:43 +00:00
async VidInfoAsync ( id ) {
let response = await this . getVidAsync ( id )
2022-03-21 13:21:18 +00:00
if ( response . success && ( response . data . webOutput [ 2 ] . playerResponse ? . playabilityStatus ? . status == ( "ERROR" || undefined ) ) )
2022-03-20 07:24:38 +00:00
throw new Error ( ` Could not get information for video: ${ response [ 2 ] . playerResponse ? . playabilityStatus ? . status } - ${ response [ 2 ] . playerResponse ? . playabilityStatus ? . reason } ` )
2022-03-21 13:21:18 +00:00
const responseWeb = response . data . webOutput
const responseApp = response . data . appOutput
const details = responseWeb [ 2 ] . playerResponse ? . videoDetails
const microformat = responseWeb [ 2 ] . playerResponse ? . microformat ? . playerMicroformatRenderer
const renderedPanels = responseWeb [ 3 ] . response ? . engagementPanels
const columnUI = responseWeb [ 3 ] . response ? . contents . singleColumnWatchNextResults ? . results ? . results
const resolutions = responseApp . streamingData
2022-03-20 06:05:43 +00:00
console . log ( ( columnUI . contents ) . length )
return {
id : details . videoId ,
title : details . title || microformat . title ? . runs [ 0 ] . text ,
isLive : details . isLiveContent || microformat . liveBroadcastDetails ? . isLiveNow || false ,
channelName : details . author || microformat . ownerChannelName ,
channelUrl : microformat . ownerProfileUrl ,
2022-03-21 13:21:18 +00:00
availableResolutions : resolutions ? . formats ,
availableResolutionsAdaptive : resolutions ? . adaptiveFormats ,
2022-03-20 06:05:43 +00:00
metadata : {
description : microformat . description ? . runs [ 0 ] . text ,
descriptionShort : details . shortDescription ,
thumbnails : details . thumbnails ? . thumbnails || microformat . thumbnails ? . thumbnails ,
isFamilySafe : microformat . isFamilySafe ,
availableCountries : microformat . availableCountries ,
liveBroadcastDetails : microformat . liveBroadcastDetails ,
uploadDate : microformat . uploadDate ,
publishDate : microformat . publishDate ,
isPrivate : details . isPrivate ,
viewCount : details . viewCount || microformat . viewCount ,
lengthSeconds : details . lengthSeconds || microformat . lengthSeconds ,
likes : parseInt ( columnUI ? . contents [ 1 ]
. slimVideoMetadataSectionRenderer ? . contents [ 1 ] . slimVideoActionBarRenderer ? . buttons [ 0 ]
. slimMetadataToggleButtonRenderer ? . button ? . toggleButtonRenderer ? . defaultText ? . accessibility ? . accessibilityData ? . label ? . replace ( /\D/g , '' ) ) // Yes. I know.
} ,
2022-03-21 00:27:41 +00:00
renderedData : {
2022-03-20 06:05:43 +00:00
description : renderedPanels [ 0 ] . engagementPanelSectionListRenderer ? . content . structuredDescriptionContentRenderer ? . items [ 1 ] . expandableVideoDescriptionBodyRenderer ? . descriptionBodyText . runs ,
2022-03-20 09:50:13 +00:00
recommendations : columnUI ? . contents [ ( columnUI . contents ) . length - 1 ] . itemSectionRenderer ? . contents
2022-03-20 06:05:43 +00:00
}
}
}
2022-03-16 09:07:40 +00:00
}
2022-03-21 13:21:18 +00:00
export default Innertube