2021-08-12 13:28:17 +05:30
import { url_get } from './request'
2021-08-12 11:10:18 +05:30
import { format_decipher , js_tokens } from './cipher'
2021-08-13 13:16:34 +05:30
import { Video } from '../classes/Video'
import { PlayList } from '../classes/Playlist'
2021-08-07 15:53:18 +05:30
2021-08-13 13:16:34 +05:30
const DEFAULT_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8" ;
2021-08-20 17:31:57 +05:30
const video_pattern = /^((?:https?:)?\/\/)?(?:(?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/ ;
2021-08-31 10:16:14 +05:30
const playlist_pattern = /^((?:https?:)?\/\/)?(?:(?:www|m)\.)?(youtube\.com)\/(?:(playlist|watch))(.*)?((\?|\&)list=)/
2021-08-07 15:53:18 +05:30
2021-09-05 18:50:57 +05:30
export function yt_validate ( url : string ) : "playlist" | "video" | boolean {
if ( url . indexOf ( 'list=' ) === - 1 ) {
if ( ! url . match ( video_pattern ) ) return false
else return "video"
}
else {
if ( ! url . match ( playlist_pattern ) ) return false
let Playlist_id = url . split ( 'list=' ) [ 1 ] . split ( '&' ) [ 0 ]
if ( Playlist_id . length !== 34 || ! Playlist_id . startsWith ( 'PL' ) ) {
return false
}
return "playlist"
2021-08-31 10:16:14 +05:30
}
}
2021-09-06 10:33:37 +05:30
export function extractID ( url : string ) : string {
if ( url . startsWith ( 'https' ) ) {
if ( url . indexOf ( 'list=' ) === - 1 ) {
let video_id : string ;
if ( url . includes ( 'youtu.be/' ) ) video_id = url . split ( 'youtu.be/' ) [ 1 ] . split ( '/' ) [ 0 ]
else if ( url . includes ( 'youtube.com/embed/' ) ) video_id = url . split ( 'youtube.com/embed/' ) [ 1 ] . split ( '/' ) [ 0 ]
else video_id = url . split ( 'watch?v=' ) [ 1 ] . split ( '&' ) [ 0 ] ;
return video_id
}
else {
return url . split ( 'list=' ) [ 1 ] . split ( '&' ) [ 0 ]
}
}
else return url
}
2021-09-02 09:23:56 +05:30
export async function video_basic_info ( url : string , cookie? : string ) {
2021-08-23 23:34:10 +05:30
let video_id : string ;
2021-09-06 10:33:37 +05:30
if ( url . startsWith ( 'https' ) ) {
if ( yt_validate ( url ) !== 'video' ) throw new Error ( 'This is not a YouTube Watch URL' )
video_id = extractID ( url )
}
else video_id = url
2021-09-08 01:12:09 +09:00
let new_url = ` https://www.youtube.com/watch?v= ${ video_id } `
2021-09-02 09:23:56 +05:30
let body = await url_get ( new_url , {
2021-09-09 12:11:11 +05:30
headers : ( cookie ) ? { 'cookie' : cookie , 'accept-language' : 'en-US,en-IN;q=0.9,en;q=0.8,hi;q=0.7' } : { 'accept-language' : 'en-US,en-IN;q=0.9,en;q=0.8,hi;q=0.7' }
2021-09-02 09:23:56 +05:30
} )
2021-08-20 12:06:12 +05:30
let player_response = JSON . parse ( body . split ( "var ytInitialPlayerResponse = " ) [ 1 ] . split ( "}};" ) [ 0 ] + '}}' )
2021-08-31 15:08:37 +05:30
let initial_response = JSON . parse ( body . split ( "var ytInitialData = " ) [ 1 ] . split ( "}};" ) [ 0 ] + '}}' )
let badge = initial_response . contents . twoColumnWatchNextResults . results . results . contents [ 1 ] ? . videoSecondaryInfoRenderer ? . owner ? . videoOwnerRenderer ? . badges && initial_response . contents . twoColumnWatchNextResults . results . results . contents [ 1 ] ? . videoSecondaryInfoRenderer ? . owner ? . videoOwnerRenderer ? . badges [ 0 ]
2021-09-08 21:19:55 +05:30
if ( player_response . playabilityStatus . status !== 'OK' ) throw new Error ( ` While getting info from url \ n ${ player_response . playabilityStatus . errorScreen . playerErrorMessageRenderer ? . reason . simpleText ? ? player_response . playabilityStatus . errorScreen . playerKavRenderer ? . reason . simpleText } ` )
2021-09-08 01:12:09 +09:00
let html5player = ` https://www.youtube.com ${ body . split ( '"jsUrl":"' ) [ 1 ] . split ( '"' ) [ 0 ] } `
2021-08-12 11:10:18 +05:30
let format = [ ]
let vid = player_response . videoDetails
let microformat = player_response . microformat . playerMicroformatRenderer
2021-08-13 14:58:57 +05:30
let video_details = {
2021-08-12 11:10:18 +05:30
id : vid.videoId ,
2021-09-08 01:12:09 +09:00
url : ` https://www.youtube.com/watch?v= ${ vid . videoId } ` ,
2021-08-12 11:10:18 +05:30
title : vid.title ,
description : vid.shortDescription ,
2021-08-23 13:05:40 +05:30
durationInSec : vid.lengthSeconds ,
durationRaw : parseSeconds ( vid . lengthSeconds ) ,
2021-08-12 11:10:18 +05:30
uploadedDate : microformat.publishDate ,
2021-08-30 14:42:51 +05:30
thumbnail : vid.thumbnail.thumbnails [ vid . thumbnail . thumbnails . length - 1 ] ,
2021-08-12 11:10:18 +05:30
channel : {
name : vid.author ,
id : vid.channelId ,
2021-08-31 15:08:37 +05:30
url : ` https://www.youtube.com/channel/ ${ vid . channelId } ` ,
verified : Boolean ( badge ? . metadataBadgeRenderer ? . style ? . toLowerCase ( ) . includes ( 'verified' ) )
2021-08-12 11:10:18 +05:30
} ,
views : vid.viewCount ,
tags : vid.keywords ,
averageRating : vid.averageRating ,
live : vid.isLiveContent ,
private : vid . isPrivate
2021-08-13 14:58:57 +05:30
}
2021-08-18 17:11:37 +05:30
if ( ! video_details . live ) format . push ( player_response . streamingData . formats [ 0 ] )
format . push ( . . . player_response . streamingData . adaptiveFormats )
2021-08-20 12:06:12 +05:30
let LiveStreamData = {
isLive : video_details.live ,
2021-09-08 00:36:08 +09:00
dashManifestUrl : player_response.streamingData?.dashManifestUrl ? ? null ,
hlsManifestUrl : player_response.streamingData?.hlsManifestUrl ? ? null
2021-08-20 12:06:12 +05:30
}
2021-08-13 13:16:34 +05:30
return {
2021-08-20 12:06:12 +05:30
LiveStreamData ,
2021-08-12 11:10:18 +05:30
html5player ,
format ,
video_details
2021-08-07 15:53:18 +05:30
}
}
2021-08-23 13:05:40 +05:30
function parseSeconds ( seconds : number ) : string {
let d = Number ( seconds ) ;
var h = Math . floor ( d / 3600 ) ;
var m = Math . floor ( d % 3600 / 60 ) ;
var s = Math . floor ( d % 3600 % 60 ) ;
var hDisplay = h > 0 ? ( h < 10 ? ` 0 ${ h } ` : h ) + ':' : "" ;
var mDisplay = m > 0 ? ( m < 10 ? ` 0 ${ m } ` : m ) + ':' : "00:" ;
var sDisplay = s > 0 ? ( s < 10 ? ` 0 ${ s } ` : s ) : "00" ;
return hDisplay + mDisplay + sDisplay ;
}
2021-09-02 09:23:56 +05:30
export async function video_info ( url : string , cookie? : string ) {
let data = await video_basic_info ( url , cookie )
2021-08-21 18:41:05 +05:30
if ( data . LiveStreamData . isLive === true && data . LiveStreamData . hlsManifestUrl !== null ) {
2021-08-20 12:06:12 +05:30
return data
}
2021-08-21 18:41:05 +05:30
else if ( data . format [ 0 ] . signatureCipher || data . format [ 0 ] . cipher ) {
data . format = await format_decipher ( data . format , data . html5player )
return data
}
2021-08-12 11:10:18 +05:30
else {
return data
}
2021-08-07 15:53:18 +05:30
}
2021-08-13 13:16:34 +05:30
2021-08-24 21:30:48 +05:30
export async function playlist_info ( url : string , parseIncomplete : boolean = false ) {
2021-08-13 13:16:34 +05:30
if ( ! url || typeof url !== "string" ) throw new Error ( ` Expected playlist url, received ${ typeof url } ! ` ) ;
2021-09-06 10:33:37 +05:30
let Playlist_id : string
if ( url . startsWith ( 'https' ) ) {
if ( yt_validate ( url ) !== 'playlist' ) throw new Error ( 'This is not a Playlist URL' )
Playlist_id = extractID ( url )
}
else Playlist_id = url
2021-08-13 13:16:34 +05:30
let new_url = ` https://www.youtube.com/playlist?list= ${ Playlist_id } `
2021-09-09 12:11:11 +05:30
let body = await url_get ( new_url , {
headers : { 'accept-language' : 'en-US,en-IN;q=0.9,en;q=0.8,hi;q=0.7' }
} )
2021-08-13 13:16:34 +05:30
let response = JSON . parse ( body . split ( "var ytInitialData = " ) [ 1 ] . split ( ";</script>" ) [ 0 ] )
2021-08-23 23:34:10 +05:30
if ( response . alerts ) {
2021-08-24 21:11:54 +05:30
if ( response . alerts [ 0 ] . alertWithButtonRenderer ? . type === 'INFO' ) {
2021-09-08 01:12:09 +09:00
if ( ! parseIncomplete ) throw new Error ( ` While parsing playlist url \ n ${ response . alerts [ 0 ] . alertWithButtonRenderer . text . simpleText } ` )
2021-08-24 21:11:54 +05:30
}
2021-09-08 00:36:08 +09:00
else if ( response . alerts [ 0 ] . alertRenderer ? . type === 'ERROR' ) throw new Error ( ` While parsing playlist url \ n ${ response . alerts [ 0 ] . alertRenderer . text . runs [ 0 ] . text } ` )
2021-09-08 01:12:09 +09:00
else throw new Error ( 'While parsing playlist url\nUnknown Playlist Error' )
2021-08-23 23:34:10 +05:30
}
2021-08-13 13:16:34 +05:30
let rawJSON = ` ${ body . split ( '{"playlistVideoListRenderer":{"contents":' ) [ 1 ] . split ( '}],"playlistId"' ) [ 0 ] } }] ` ;
let parsed = JSON . parse ( rawJSON ) ;
let playlistDetails = JSON . parse ( body . split ( '{"playlistSidebarRenderer":' ) [ 1 ] . split ( "}};</script>" ) [ 0 ] ) . items ;
let API_KEY = body . split ( 'INNERTUBE_API_KEY":"' ) [ 1 ] ? . split ( '"' ) [ 0 ] ? ? body . split ( 'innertubeApiKey":"' ) [ 1 ] ? . split ( '"' ) [ 0 ] ? ? DEFAULT_API_KEY ;
2021-08-13 14:58:57 +05:30
let videos = getPlaylistVideos ( parsed , 100 ) ;
2021-08-13 13:16:34 +05:30
let data = playlistDetails [ 0 ] . playlistSidebarPrimaryInfoRenderer ;
if ( ! data . title . runs || ! data . title . runs . length ) return undefined ;
let author = playlistDetails [ 1 ] ? . playlistSidebarSecondaryInfoRenderer . videoOwner ;
let views = data . stats . length === 3 ? data . stats [ 1 ] . simpleText . replace ( /[^0-9]/g , "" ) : 0 ;
let lastUpdate = data . stats . find ( ( x : any ) = > "runs" in x && x [ "runs" ] . find ( ( y : any ) = > y . text . toLowerCase ( ) . includes ( "last update" ) ) ) ? . runs . pop ( ) ? . text ? ? null ;
let videosCount = data . stats [ 0 ] . runs [ 0 ] . text . replace ( /[^0-9]/g , "" ) || 0 ;
let res = new PlayList ( {
continuation : {
api : API_KEY ,
token : getContinuationToken ( parsed ) ,
clientVersion : body.split ( '"INNERTUBE_CONTEXT_CLIENT_VERSION":"' ) [ 1 ] ? . split ( '"' ) [ 0 ] ? ? body . split ( '"innertube_context_client_version":"' ) [ 1 ] ? . split ( '"' ) [ 0 ] ? ? "<some version>"
} ,
id : data.title.runs [ 0 ] . navigationEndpoint . watchEndpoint . playlistId ,
title : data.title.runs [ 0 ] . text ,
videoCount : parseInt ( videosCount ) || 0 ,
lastUpdate : lastUpdate ,
views : parseInt ( views ) || 0 ,
videos : videos ,
url : ` https://www.youtube.com/playlist?list= ${ data . title . runs [ 0 ] . navigationEndpoint . watchEndpoint . playlistId } ` ,
link : ` https://www.youtube.com ${ data . title . runs [ 0 ] . navigationEndpoint . commandMetadata . webCommandMetadata . url } ` ,
author : author
? {
name : author.videoOwnerRenderer.title.runs [ 0 ] . text ,
id : author.videoOwnerRenderer.title.runs [ 0 ] . navigationEndpoint . browseEndpoint . browseId ,
url : ` https://www.youtube.com ${ author . videoOwnerRenderer . navigationEndpoint . commandMetadata . webCommandMetadata . url || author . videoOwnerRenderer . navigationEndpoint . browseEndpoint . canonicalBaseUrl } ` ,
icon : author.videoOwnerRenderer.thumbnail.thumbnails.length ? author . videoOwnerRenderer . thumbnail . thumbnails [ author . videoOwnerRenderer . thumbnail . thumbnails . length - 1 ] . url : null
}
: { } ,
thumbnail : data.thumbnailRenderer.playlistVideoThumbnailRenderer?.thumbnail.thumbnails.length ? data . thumbnailRenderer . playlistVideoThumbnailRenderer . thumbnail . thumbnails [ data . thumbnailRenderer . playlistVideoThumbnailRenderer . thumbnail . thumbnails . length - 1 ] . url : null
} ) ;
return res ;
}
export function getPlaylistVideos ( data :any , limit : number = Infinity ) : Video [ ] {
const videos = [ ] ;
for ( let i = 0 ; i < data . length ; i ++ ) {
if ( limit === videos . length ) break ;
const info = data [ i ] . playlistVideoRenderer ;
if ( ! info || ! info . shortBylineText ) continue ;
videos . push (
new Video ( {
id : info.videoId ,
index : parseInt ( info . index ? . simpleText ) || 0 ,
duration : parseDuration ( info . lengthText ? . simpleText ) || 0 ,
duration_raw : info.lengthText?.simpleText ? ? "0:00" ,
thumbnail : {
id : info.videoId ,
url : info.thumbnail.thumbnails [ info . thumbnail . thumbnails . length - 1 ] . url ,
height : info.thumbnail.thumbnails [ info . thumbnail . thumbnails . length - 1 ] . height ,
width : info.thumbnail.thumbnails [ info . thumbnail . thumbnails . length - 1 ] . width
} ,
title : info.title.runs [ 0 ] . text ,
channel : {
id : info.shortBylineText.runs [ 0 ] . navigationEndpoint . browseEndpoint . browseId || undefined ,
name : info.shortBylineText.runs [ 0 ] . text || undefined ,
url : ` https://www.youtube.com ${ info . shortBylineText . runs [ 0 ] . navigationEndpoint . browseEndpoint . canonicalBaseUrl || info . shortBylineText . runs [ 0 ] . navigationEndpoint . commandMetadata . webCommandMetadata . url } ` ,
icon : undefined
}
} )
) ;
}
return videos
}
function parseDuration ( duration : string ) : number {
duration ? ? = "0:00" ;
const args = duration . split ( ":" ) ;
let dur = 0 ;
switch ( args . length ) {
case 3 :
dur = parseInt ( args [ 0 ] ) * 60 * 60 + parseInt ( args [ 1 ] ) * 60 + parseInt ( args [ 2 ] ) ;
break ;
case 2 :
dur = parseInt ( args [ 0 ] ) * 60 + parseInt ( args [ 1 ] ) ;
break ;
default :
dur = parseInt ( args [ 0 ] ) ;
}
return dur ;
}
export function getContinuationToken ( data :any ) : string {
const continuationToken = data . find ( ( x : any ) = > Object . keys ( x ) [ 0 ] === "continuationItemRenderer" ) ? . continuationItemRenderer . continuationEndpoint ? . continuationCommand ? . token ;
return continuationToken ;
2021-09-07 22:05:01 +09:00
}