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 ;
2022-03-19 05:35:28 +00:00
logger ( constants . LOGGER _NAMES . search , results ) ;
2022-03-16 09:07:40 +00:00
callback ( results ) ;
} else {
try {
results = JSON . parse ( html . split ( '{"itemSectionRenderer":{"contents":' ) [ html . split ( '{"itemSectionRenderer":{"contents":' ) . length - 1 ] . split ( ',"continuations":[{' ) [ 0 ] ) ;
2022-03-19 05:35:28 +00:00
logger ( constants . LOGGER _NAMES . search , results ) ;
2022-03-16 09:07:40 +00:00
callback ( results ) ;
} catch ( e ) { }
try {
results = JSON . parse ( html . split ( '{"itemSectionRenderer":' ) [ html . split ( '{"itemSectionRenderer":' ) . length - 1 ] . split ( '},{"continuationItemRenderer":{' ) [ 0 ] ) . contents ;
2022-03-19 05:35:28 +00:00
logger ( constants . LOGGER _NAMES . search , results ) ;
2022-03-16 09:07:40 +00:00
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 ) => {
2022-03-19 05:35:28 +00:00
logger ( constants . LOGGER _NAMES . search , err , true ) ;
2022-03-16 09:07:40 +00:00
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 ) => {
2022-03-19 05:35:28 +00:00
logger ( constants . LOGGER _NAMES . autoComplete , res ) ;
2022-03-16 09:07:40 +00:00
callback ( res . data ) ;
} )
. catch ( ( err ) => {
2022-03-19 05:35:28 +00:00
logger ( constants . LOGGER _NAMES . autoComplete , err , true ) ;
2022-03-16 09:07:40 +00:00
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 ---//
2022-03-19 05:35:28 +00:00
//logger(constants.LOGGER_NAMES.search, { type: "Error Caught Successfully", error: video }, true);
2022-03-16 09:07:40 +00:00
}
}
} )
callback ( results ) ;
} ,
2022-03-19 06:17:18 +00:00
getRemainingVideoInfo ( id , callback ) {
String . prototype . decodeEscapeSequence = function ( ) {
return this . replace ( /\\x([0-9A-Fa-f]{2})/g , function ( ) {
return String . fromCharCode ( parseInt ( arguments [ 1 ] , 16 ) ) ;
} ) ;
} ;
Http . request ( {
method : 'GET' ,
url : ` ${ constants . URLS . YT _URL } /watch ` ,
params : { v : id }
} )
. then ( ( res ) => {
let dataUpdated = res . data . decodeEscapeSequence ( )
let likes = dataUpdated . split ( ` "defaultIcon":{"iconType":"LIKE"},"defaultText":{"runs":[{"text":" ` ) [ 1 ] . split ( ` "}],"accessibility": ` ) [ 0 ]
let uploadDate = dataUpdated . split ( ` "uploadDate":" ` ) [ 1 ] . split ( ` }},"trackingParams":" ` ) [ 0 ] . slice ( 0 , - 2 ) ;
let data = {
"likes" : likes ,
"uploadDate" : uploadDate
}
logger ( "vidData" , data )
callback ( data )
} )
. catch ( ( err ) => {
logger ( "codeRun" , err , true ) ;
callback ( err ) ;
} ) ;
} ,
getReturnYoutubeDislike ( id , callback ) {
Http . request ( {
method : 'GET' ,
url : ` https://returnyoutubedislikeapi.com/votes ` ,
params : { videoId : id }
} )
. then ( ( res ) => {
logger ( "rydData" , res . data )
callback ( res . data )
} )
. catch ( ( err ) => {
logger ( "codeRun" , err , true ) ;
callback ( err ) ;
} ) ;
2022-03-16 09:07:40 +00:00
}
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 ;
2022-03-19 05:35:28 +00:00
// 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 ) {
2022-03-19 05:35:28 +00:00
InnertubeAPI = await Innertube . createAsync ( ( message , isError ) => { logger ( constants . LOGGER _NAMES . innertube , message , isError ) ; } )
2022-03-18 11:50:44 +00:00
}
2022-03-18 06:15:19 +00:00
return InnertubeAPI ;
} ,
2022-03-17 13:17:32 +00:00
async getVid ( id ) {
2022-03-19 13:15:40 +00:00
// temporary test
const html = await Http . get ( {
url : "https://m.youtube.com/watch?v=U-9M-BjFYMc&t=8s&pbj=1" ,
params : { } ,
headers : {
accept : '*/*' ,
'user-agent' : 'Mozilla/5.0 (Linux; Android 10; WP7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.101 Mobile Safari/537.36' ,
'content-type' : 'application/json' ,
'accept-language' : 'en-US,en;q=0.9' ,
'x-goog-authuser' : 0 ,
'x-goog-visitor-id' : 'CgtsaVdQdGhfbVNOMCiC0taRBg%3D%3D' ,
'x-youtube-client-name' : 2 ,
'x-youtube-client-version' : '2.20220318.00.00' ,
'x-youtube-chrome-connected' : 'source=Chrome,mode=0,enable_account_consistency=true,supervised=false,consistency_enabled_by_default=false' ,
'x-origin' : 'https://m.youtube.com' ,
origin : 'https://m.youtube.com' ,
referer : 'https://m.youtube.com/watch?v=U-9M-BjFYMc'
}
} ) . catch ( ( error ) => error ) ;
console . log ( html . data )
2022-03-19 20:22:03 +00:00
return InnertubeAPI . getVidInfoAsync ( id ) ;
2022-03-17 13:17:32 +00:00
} ,
2022-03-19 13:15:40 +00:00
// It just works™
2022-03-17 05:57:28 +00:00
async recommend ( ) {
2022-03-19 13:15:40 +00:00
const response = await InnertubeAPI . getRecommendationsAsync ( ) ;
if ( ! response . success ) throw new Error ( "An error occurred and innertube failed to respond" )
const contents = response . data . contents . singleColumnBrowseResultsRenderer . tabs [ 0 ] . tabRenderer . content . sectionListRenderer . contents
return contents . map ( ( shelves ) => {
const video = shelves . shelfRenderer ? . content ? . horizontalListRenderer ? . items
if ( video ) return video . map ( ( item ) => {
item = item . gridVideoRenderer
if ( item ) return {
id : item . videoId ,
title : item . title ? . runs [ 0 ] . text ,
thumbnail : this . getThumbnail ( item . videoId ) ,
2022-03-19 13:47:08 +00:00
channel : item . shortBylineText ? . runs [ 0 ] ? item . shortBylineText . runs [ 0 ] . text : item . longBylineText ? . runs [ 0 ] . text ,
channelURL : ` ${ constants . YT _URL } / ${ ( item . shortBylineText ? . runs [ 0 ] ? item . shortBylineText . runs [ 0 ] : item . longBylineText ? . runs [ 0 ] ) . navigationEndpoint ? . browseEndpoint ? . canonicalBaseUrl } ` ,
2022-03-19 13:15:40 +00:00
channelThumbnail : item . channelThumbnail ? . thumbnails [ 0 ] ,
metadata : {
published : item . publishedTimeText ? . runs [ 0 ] . text ,
views : item . shortViewCountText ? . runs [ 0 ] . text ,
length : item . publishedTimeText ? . runs [ 0 ] . text ,
2022-03-19 13:47:08 +00:00
overlayStyle : item . thumbnailOverlays ? . map ( overlay => overlay . thumbnailOverlayTimeStatusRenderer ? . style ) ,
overlay : item . thumbnailOverlays ? . map ( overlay => overlay . thumbnailOverlayTimeStatusRenderer ? . text . runs [ 0 ] . text ) ,
2022-03-19 13:15:40 +00:00
} ,
} ;
else return undefined
} )
} )
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-19 18:26:20 +00:00
logger ( constants . LOGGER _NAMES . init , "Program Started" ) ;