2022-02-28 15:24:06 +00:00
//--- Modules/Imports ---//
2022-02-25 18:54:15 +00:00
import { Http } from '@capacitor-community/http' ;
2022-03-16 09:07:40 +00:00
import Innertube from './innertube'
2022-03-17 05:57:28 +00:00
import constants from '../static/constants' ;
2022-02-25 18:54:15 +00:00
2022-02-28 15:24:06 +00:00
//--- Logger Function ---//
2022-03-16 09:07:40 +00:00
function logger ( func , data , isError = false ) {
searchModule . logs . unshift ( {
name : func ,
time : Date . now ( ) ,
data : data ,
error : isError
} )
2022-02-28 15:24:06 +00:00
}
2022-03-13 23:21:41 +00:00
//--- Youtube Base Parser ---//
function youtubeParse ( html , callback ) {
2022-03-16 09:07:40 +00:00
//--- Replace Encoded Characters ---///
html = html . replace ( /\\x([0-9A-F]{2})/ig , ( ... items ) => { return String . fromCharCode ( parseInt ( items [ 1 ] , 16 ) ) ; } ) ;
//--- Properly Format JSON ---//
html = html . replaceAll ( "\\\\\"" , "" ) ;
//--- Parse JSON ---//
html = JSON . parse ( html ) ;
//--- Get Results ---// ( Thanks To appit-online On Github ) -> https://github.com/appit-online/youtube-search/blob/master/src/lib/search.ts
let results ;
if ( html && html . contents && html . contents . sectionListRenderer && html . contents . sectionListRenderer . contents &&
html . contents . sectionListRenderer . contents . length > 0 &&
html . contents . sectionListRenderer . contents [ 0 ] . itemSectionRenderer &&
html . contents . sectionListRenderer . contents [ 0 ] . itemSectionRenderer . contents . length > 0 ) {
results = html . contents . sectionListRenderer . contents [ 0 ] . itemSectionRenderer . contents ;
logger ( "search" , results ) ;
callback ( results ) ;
} else {
try {
results = JSON . parse ( html . split ( '{"itemSectionRenderer":{"contents":' ) [ html . split ( '{"itemSectionRenderer":{"contents":' ) . length - 1 ] . split ( ',"continuations":[{' ) [ 0 ] ) ;
logger ( "search" , results ) ;
callback ( results ) ;
} catch ( e ) { }
try {
results = JSON . parse ( html . split ( '{"itemSectionRenderer":' ) [ html . split ( '{"itemSectionRenderer":' ) . length - 1 ] . split ( '},{"continuationItemRenderer":{' ) [ 0 ] ) . contents ;
logger ( "search" , results ) ;
callback ( results ) ;
} catch ( e ) { }
}
2022-03-13 23:21:41 +00:00
}
2022-03-04 02:07:53 +00:00
//--- Search Main Function ---//
function youtubeSearch ( text , callback ) {
2022-03-16 09:07:40 +00:00
Http . request ( {
method : 'GET' ,
2022-03-17 05:57:28 +00:00
url : ` ${ constants . URLS . YT _URL } /results ` ,
2022-03-16 09:07:40 +00:00
params : { q : text , hl : "en" }
} )
. then ( ( res ) => {
//--- Get HTML Only ---//
let html = res . data ;
//--- Isolate The Script Containing Video Information ---//
html = html . split ( "var ytInitialData = '" ) [ 1 ] . split ( "';</script>" ) [ 0 ] ;
youtubeParse ( html , ( data ) => {
callback ( data ) ;
} )
} )
. catch ( ( err ) => {
logger ( "search" , err , true ) ;
callback ( err ) ;
} ) ;
2022-03-04 02:07:53 +00:00
}
2022-03-16 09:07:40 +00:00
const searchModule = {
logs : new Array ( ) ,
//--- Get YouTube's Search Auto Complete ---//
autoComplete ( text , callback ) {
Http . request ( {
method : 'GET' ,
2022-03-17 05:57:28 +00:00
url : ` ${ constants . URLS . YT _SUGGESTIONS } /search ` ,
2022-03-16 09:07:40 +00:00
params : { client : 'youtube' , q : text }
} )
. then ( ( res ) => {
logger ( "autoComplete" , res ) ;
callback ( res . data ) ;
} )
. catch ( ( err ) => {
logger ( "autoComplete" , err , true ) ;
callback ( err ) ;
} ) ;
} ,
search ( text , callback ) {
let results = new Array ( ) ;
youtubeSearch ( text , ( videos ) => {
for ( const i in videos ) {
const video = videos [ i ] ;
if ( video . compactVideoRenderer ) {
//--- If Entry Is A Video ---//
results . push ( {
id : video . compactVideoRenderer . videoId ,
title : video . compactVideoRenderer . title . runs [ 0 ] . text ,
runtime : video . compactVideoRenderer . lengthText . runs [ 0 ] . text ,
uploaded : video . compactVideoRenderer . publishedTimeText . runs [ 0 ] . text ,
views : video . compactVideoRenderer . viewCountText . runs [ 0 ] . text ,
thumbnails : video . compactVideoRenderer . thumbnail . thumbnails
} )
} else {
//--- If Entry Is Not A Video ---//
//logger("search", { type: "Error Caught Successfully", error: video }, true);
}
}
} )
callback ( results ) ;
} ,
getVideo ( id ) {
return id ;
}
2022-02-25 18:54:15 +00:00
2022-03-16 09:07:40 +00:00
}
2022-03-13 23:45:04 +00:00
2022-03-16 09:07:40 +00:00
//--- Recommendations --//
2022-03-17 13:17:32 +00:00
2022-03-18 06:15:19 +00:00
let InnertubeAPI ;
// Lazy loads Innertube object. This will be the object used in all future Innertube API calls. Code provided by Lightfire228 (https://github.com/Lightfire228)
2022-03-17 13:17:32 +00:00
// These are just a way for the backend Javascript to communicate with the front end Vue scripts. Essentially a wrapper inside a wrapper
2022-03-16 09:07:40 +00:00
const recommendationModule = {
2022-03-18 06:15:19 +00:00
async getAPI ( ) {
2022-03-18 11:50:44 +00:00
if ( ! InnertubeAPI ) {
InnertubeAPI = await Innertube . createAsync ( ( message , isError ) => { logger ( "Innertube" , message , isError ) ; } )
}
2022-03-18 06:15:19 +00:00
return InnertubeAPI ;
} ,
2022-03-17 13:17:32 +00:00
async getVid ( id ) {
2022-03-18 06:15:19 +00:00
return InnertubeAPI . getVidInfoAsync ( id ) . data ;
2022-03-17 13:17:32 +00:00
} ,
2022-03-17 05:57:28 +00:00
async recommend ( ) {
2022-03-18 06:15:19 +00:00
return InnertubeAPI . getRecommendationsAsync ( ) ;
2022-03-17 05:57:28 +00:00
} ,
2022-03-18 11:50:44 +00:00
getThumbnail : ( id , resolution ) => Innertube . getThumbnail ( id , resolution )
2022-02-25 18:39:17 +00:00
}
2022-02-28 15:24:06 +00:00
//--- Start ---//
2022-02-25 18:39:17 +00:00
export default ( { app } , inject ) => {
2022-03-18 11:50:44 +00:00
inject ( 'youtube' , { ... searchModule , ... recommendationModule , } )
2022-03-18 06:15:19 +00:00
inject ( "logger" , logger )
2022-02-25 18:39:17 +00:00
}
2022-03-16 09:07:40 +00:00
logger ( "Initialize" , "Program Started" ) ;