2022-03-16 09:07:40 +00:00
// Code specific to working with the innertube API
// https://www.youtube.com/youtubei/v1
2022-03-21 23:47:11 +00:00
import { Http } from "@capacitor-community/http" ;
import { getBetweenStrings } from "./utils" ;
import constants from "./constants" ;
2022-03-16 09:07:40 +00:00
class Innertube {
2022-03-21 23:47:11 +00:00
//--- Initiation ---//
constructor ( ErrorCallback ) {
this . ErrorCallback = ErrorCallback || undefined ;
this . retry _count = 0 ;
}
checkErrorCallback ( ) {
return typeof this . ErrorCallback === "function" ;
}
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 ;
this . logged _in = data . LOGGED _IN ;
this . context . client = constants . INNERTUBE _CLIENT ( this . context . client ) ;
this . header = constants . INNERTUBE _HEADER ( this . context . client ) ;
2022-03-16 09:07:40 +00:00
}
2022-03-21 23:47:11 +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 ) ;
2022-03-18 11:50:44 +00:00
}
2022-03-21 23:47:11 +00:00
}
} catch ( error ) {
this . ErrorCallback ( error , true ) ;
2022-03-17 13:17:32 +00:00
}
2022-03-21 23:47:11 +00:00
}
static async createAsync ( ErrorCallback ) {
const created = new Innertube ( ErrorCallback ) ;
await created . initAsync ( ) ;
return created ;
}
//--- API Calls ---//
async browseAsync ( action _type ) {
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 09:07:40 +00:00
}
2022-03-21 23:47:11 +00:00
console . log ( data ) ;
const response = await Http . post ( {
url : ` ${ constants . URLS . YT _BASE _API } /browse?key= ${ this . key } ` ,
data : data ,
headers : { "Content-Type" : "application/json" } ,
} ) . catch ( ( error ) => error ) ;
if ( response instanceof Error )
return {
success : false ,
status _code : response . status ,
message : response . message ,
} ;
return {
success : true ,
status _code : response . status ,
data : response . data ,
} ;
}
static getThumbnail ( id , resolution ) {
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 ;
} ;
2022-03-20 06:05:43 +00:00
}
2022-03-21 23:47:11 +00:00
return ` https://img.youtube.com/vi/ ${ id } /mqdefault.jpg ` ;
}
async getVidAsync ( id ) {
let data = { context : this . context , videoId : id } ;
2022-03-22 23:07:01 +00:00
const responseNext = await Http . post ( {
url : ` ${ constants . URLS . YT _BASE _API } /next?v= ${ id } ` ,
data : data ,
headers : constants . INNERTUBE _HEADER ( this . context . client ) ,
2022-03-21 23:47:11 +00:00
} ) . catch ( ( error ) => error ) ;
2022-03-22 23:07:01 +00:00
const response = await Http . post ( {
2022-03-21 23:47:11 +00:00
url : ` ${ constants . URLS . YT _BASE _API } /player?key= ${ this . key } ` ,
data : data ,
2022-03-22 23:07:01 +00:00
headers : constants . INNERTUBE _HEADER ( this . context . client ) ,
2022-03-21 23:47:11 +00:00
} ) . catch ( ( error ) => error ) ;
2022-03-22 23:07:01 +00:00
if ( response . error )
2022-03-21 23:47:11 +00:00
return {
success : false ,
2022-03-22 23:07:01 +00:00
status _code : response . status ,
2022-03-21 23:47:11 +00:00
message : response . message ,
2022-03-22 23:07:01 +00:00
}
else if ( responseNext . error )
return {
success : false ,
status _code : responseNext . status ,
message : responseNext . message ,
}
2022-03-21 23:47:11 +00:00
return {
success : true ,
status _code : response . status ,
2022-03-22 23:07:01 +00:00
data : { output : response . data , outputNext : responseNext . data } ,
2022-03-21 23:47:11 +00:00
} ;
}
// Simple Wrappers
async getRecommendationsAsync ( ) {
const rec = await this . browseAsync ( "recommendations" ) ;
console . log ( rec . data ) ;
return rec ;
}
async VidInfoAsync ( id ) {
let response = await this . getVidAsync ( id ) ;
if (
2022-03-22 23:07:01 +00:00
response . success == false ||
response . data . output ? . playabilityStatus ? . status ==
2022-03-21 23:47:11 +00:00
( "ERROR" || undefined )
)
throw new Error (
2022-03-22 23:07:01 +00:00
` Could not get information for video: ${ response . status _code || response . data . output ? . playabilityStatus ? . status } - ${ response . message || response . data . output ? . playabilityStatus ? . reason } `
2022-03-21 23:47:11 +00:00
) ;
2022-03-22 23:07:01 +00:00
const responseInfo = response . data . output ;
const responseNext = response . data . outputNext ;
const details = responseInfo . videoDetails ;
// const columnUI =
// responseInfo[3].response?.contents.singleColumnWatchNextResults?.results
// ?.results;
const resolutions = responseInfo . streamingData ;
const columnUI = responseNext . contents . singleColumnWatchNextResults . results . results
const vidData = {
2022-03-21 23:47:11 +00:00
id : details . videoId ,
2022-03-22 23:07:01 +00:00
title : details . title ,
isLive : details . isLiveContent ,
channelName : details . author ,
channelUrl : columnUI ? . contents [ 0 ] . slimVideoMetadataSectionRenderer ? . contents . find ( contents => contents . elementRenderer ) ? . newElement ? . type ? . componentType ? . model ? . channelBarModel ? . videoChannelBarData
? . onTap ? . innertubeCommand ? . browseEndpoint ? . canonicalBaseUrl ,
channelImg : columnUI ? . contents [ 0 ] . slimVideoMetadataSectionRenderer ? . contents . find ( contents => contents . elementRenderer ) ? . newElement ? . type ? . componentType ? . model ? . channelBarModel ? . videoChannelBarData
? . avatar ? . image ? . sources [ 0 ] . url ,
2022-03-21 23:47:11 +00:00
availableResolutions : resolutions ? . formats ,
availableResolutionsAdaptive : resolutions ? . adaptiveFormats ,
metadata : {
2022-03-22 23:07:01 +00:00
description : details . shortDescription ,
thumbnails : details . thumbnails ? . thumbnails ,
uploadDate : columnUI ? . contents [ 0 ] . slimVideoMetadataSectionRenderer ? . contents . find ( contents => contents . slimVideoDescriptionRenderer ) ? . slimVideoDescriptionRenderer . publishDate . runs [ 0 ] . text ,
2022-03-21 23:47:11 +00:00
isPrivate : details . isPrivate ,
2022-03-22 23:07:01 +00:00
viewCount : details . viewCount ,
lengthSeconds : details . lengthSeconds ,
likes : parseInt ( columnUI ? . contents [ 0 ] . slimVideoMetadataSectionRenderer ? . contents . find ( contents => contents . slimVideoScrollableActionBarRenderer ) ? . slimVideoScrollableActionBarRenderer
. buttons . find ( button => button . slimMetadataToggleButtonRenderer . isLike == true ) ? . slimMetadataToggleButtonRenderer ? . button
. toggleButtonRenderer ? . defaultText ? . accessibility ? . accessibilityData ? . label ? . replace ( /\D/g , "" ) ) , // Yes. I know.
2022-03-21 23:47:11 +00:00
} ,
renderedData : {
2022-03-22 23:07:01 +00:00
description : columnUI ? . contents . find ( contents => contents . slimVideoMetadataSectionRenderer ) . slimVideoMetadataSectionRenderer ? . contents . find ( contents => contents . slimVideoDescriptionRenderer ) ? . slimVideoDescriptionRenderer . description . runs ,
recommendations : columnUI ? . contents . find ( contents => contents . shelfRenderer ) . shelfRenderer ? . content ? . horizontalListRenderer ? . items ,
recommendationsContinuation : columnUI ? . continuations [ 0 ] . reloadContinuationData ? . continuation
2022-03-21 23:47:11 +00:00
} ,
} ;
2022-03-22 23:07:01 +00:00
console . log ( vidData )
return vidData
2022-03-21 23:47:11 +00:00
}
2022-03-22 23:07:01 +00:00
2022-03-16 09:07:40 +00:00
}
2022-03-21 23:47:11 +00:00
export default Innertube ;