YouTube : Completed
This commit is contained in:
parent
455c7dfb69
commit
04770cc929
@ -6,10 +6,10 @@ export interface ChannelIconInterface {
|
|||||||
|
|
||||||
export class Channel {
|
export class Channel {
|
||||||
name?: string;
|
name?: string;
|
||||||
verified!: boolean;
|
verified?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
icon!: ChannelIconInterface;
|
icon?: ChannelIconInterface;
|
||||||
subscribers?: string;
|
subscribers?: string;
|
||||||
|
|
||||||
constructor(data: any) {
|
constructor(data: any) {
|
||||||
@ -36,7 +36,7 @@ export class Channel {
|
|||||||
*/
|
*/
|
||||||
iconURL(options = { size: 0 }): string | undefined{
|
iconURL(options = { size: 0 }): string | undefined{
|
||||||
if (typeof options.size !== "number" || options.size < 0) throw new Error("invalid icon size");
|
if (typeof options.size !== "number" || options.size < 0) throw new Error("invalid icon size");
|
||||||
if (!this.icon.url) return undefined;
|
if (!this.icon?.url) return undefined;
|
||||||
const def = this.icon.url.split("=s")[1].split("-c")[0];
|
const def = this.icon.url.split("=s")[1].split("-c")[0];
|
||||||
return this.icon.url.replace(`=s${def}-c`, `=s${options.size}-c`);
|
return this.icon.url.replace(`=s${def}-c`, `=s${options.size}-c`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,41 +1,47 @@
|
|||||||
import { getContinuationToken, getPlaylistVideos } from "../utils/parser";
|
import { getPlaylistVideos, getContinuationToken } from "../utils/extractor";
|
||||||
import { url_get } from "../utils/request";
|
import { url_get } from "../utils/request";
|
||||||
import { Thumbnail } from "./Thumbnail";
|
import { Thumbnail } from "./Thumbnail";
|
||||||
import { Channel } from "./Channel";
|
import { Channel } from "./Channel";
|
||||||
import { Video } from "./Video";
|
import { Video } from "./Video";
|
||||||
|
import fs from 'fs'
|
||||||
const BASE_API = "https://www.youtube.com/youtubei/v1/browse?key=";
|
const BASE_API = "https://www.youtube.com/youtubei/v1/browse?key=";
|
||||||
|
|
||||||
export class PlayList{
|
export class PlayList{
|
||||||
id?: string;
|
id?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
videoCount!: number;
|
videoCount?: number;
|
||||||
lastUpdate?: string;
|
lastUpdate?: string;
|
||||||
views?: number;
|
views?: number;
|
||||||
url?: string;
|
url?: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
channel?: Channel;
|
channel?: Channel;
|
||||||
thumbnail?: Thumbnail;
|
thumbnail?: Thumbnail;
|
||||||
videos!: [];
|
videos?: [];
|
||||||
|
private fetched_videos : Map<string, Video[]>
|
||||||
private _continuation: { api?: string; token?: string; clientVersion?: string } = {};
|
private _continuation: { api?: string; token?: string; clientVersion?: string } = {};
|
||||||
|
private __count : number
|
||||||
|
|
||||||
constructor(data : any, searchResult : Boolean = false){
|
constructor(data : any, searchResult : Boolean = false){
|
||||||
if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);
|
if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);
|
||||||
|
this.__count = 0
|
||||||
|
this.fetched_videos = new Map()
|
||||||
if(searchResult) this.__patchSearch(data)
|
if(searchResult) this.__patchSearch(data)
|
||||||
else this.__patch(data)
|
else this.__patch(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private __patch(data:any){
|
private __patch(data:any){
|
||||||
this.id = data.id || undefined;
|
this.id = data.id || undefined;
|
||||||
|
this.url = data.url || undefined;
|
||||||
this.title = data.title || undefined;
|
this.title = data.title || undefined;
|
||||||
this.videoCount = data.videoCount || 0;
|
this.videoCount = data.videoCount || 0;
|
||||||
this.lastUpdate = data.lastUpdate || undefined;
|
this.lastUpdate = data.lastUpdate || undefined;
|
||||||
this.views = data.views || 0;
|
this.views = data.views || 0;
|
||||||
this.url = data.url || undefined;
|
|
||||||
this.link = data.link || undefined;
|
this.link = data.link || undefined;
|
||||||
this.channel = data.author || undefined;
|
this.channel = data.author || undefined;
|
||||||
this.thumbnail = data.thumbnail || undefined;
|
this.thumbnail = data.thumbnail || undefined;
|
||||||
this.videos = data.videos || [];
|
this.videos = data.videos || [];
|
||||||
|
this.__count ++
|
||||||
|
this.fetched_videos.set(`page${this.__count}`, this.videos as Video[])
|
||||||
this._continuation.api = data.continuation?.api ?? undefined;
|
this._continuation.api = data.continuation?.api ?? undefined;
|
||||||
this._continuation.token = data.continuation?.token ?? undefined;
|
this._continuation.token = data.continuation?.token ?? undefined;
|
||||||
this._continuation.clientVersion = data.continuation?.clientVersion ?? "<important data>";
|
this._continuation.clientVersion = data.continuation?.clientVersion ?? "<important data>";
|
||||||
@ -43,12 +49,12 @@ export class PlayList{
|
|||||||
|
|
||||||
private __patchSearch(data: any){
|
private __patchSearch(data: any){
|
||||||
this.id = data.id || undefined;
|
this.id = data.id || undefined;
|
||||||
|
this.url = this.id ? `https://www.youtube.com/playlist?list=${this.id}` : undefined;
|
||||||
this.title = data.title || undefined;
|
this.title = data.title || undefined;
|
||||||
this.thumbnail = data.thumbnail || undefined;
|
this.thumbnail = data.thumbnail || undefined;
|
||||||
this.channel = data.channel || undefined;
|
this.channel = data.channel || undefined;
|
||||||
this.videos = [];
|
this.videos = [];
|
||||||
this.videoCount = data.videos || 0;
|
this.videoCount = data.videos || 0;
|
||||||
this.url = this.id ? `https://www.youtube.com/playlist?list=${this.id}` : undefined;
|
|
||||||
this.link = undefined;
|
this.link = undefined;
|
||||||
this.lastUpdate = undefined;
|
this.lastUpdate = undefined;
|
||||||
this.views = 0;
|
this.views = 0;
|
||||||
@ -79,8 +85,8 @@ export class PlayList{
|
|||||||
if(!contents) return []
|
if(!contents) return []
|
||||||
|
|
||||||
let playlist_videos = getPlaylistVideos(contents, limit)
|
let playlist_videos = getPlaylistVideos(contents, limit)
|
||||||
|
this.fetched_videos.set(`page${this.__count}`, playlist_videos)
|
||||||
this._continuation.token = getContinuationToken(contents)
|
this._continuation.token = getContinuationToken(contents)
|
||||||
|
|
||||||
return playlist_videos
|
return playlist_videos
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +96,8 @@ export class PlayList{
|
|||||||
if (max < 1) max = Infinity;
|
if (max < 1) max = Infinity;
|
||||||
|
|
||||||
while (typeof this._continuation.token === "string" && this._continuation.token.length) {
|
while (typeof this._continuation.token === "string" && this._continuation.token.length) {
|
||||||
if (this.videos.length >= max) break;
|
if (this.videos?.length as number >= max) break;
|
||||||
|
this.__count++
|
||||||
const res = await this.next();
|
const res = await this.next();
|
||||||
if (!res.length) break;
|
if (!res.length) break;
|
||||||
}
|
}
|
||||||
@ -102,6 +109,21 @@ export class PlayList{
|
|||||||
return "playlist";
|
return "playlist";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page(number : number): Video[]{
|
||||||
|
if(!number) throw new Error('Given Page number is not provided')
|
||||||
|
if(!this.fetched_videos.has(`page${number}`)) throw new Error('Given Page number is invalid')
|
||||||
|
return this.fetched_videos.get(`page${number}`) as Video[]
|
||||||
|
}
|
||||||
|
|
||||||
|
get total_pages(){
|
||||||
|
return this.fetched_videos.size
|
||||||
|
}
|
||||||
|
|
||||||
|
get total_videos(){
|
||||||
|
let page_number: number = this.total_pages
|
||||||
|
return (page_number - 1) * 100 + (this.fetched_videos.get(`page${page_number}`) as Video[]).length
|
||||||
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
|||||||
@ -2,8 +2,8 @@ type ThumbnailType = "default" | "hqdefault" | "mqdefault" | "sddefault" | "maxr
|
|||||||
|
|
||||||
export class Thumbnail {
|
export class Thumbnail {
|
||||||
id?: string;
|
id?: string;
|
||||||
width!: number;
|
width?: number;
|
||||||
height!: number;
|
height?: number;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
|
||||||
constructor(data: any) {
|
constructor(data: any) {
|
||||||
|
|||||||
@ -12,8 +12,8 @@ interface VideoOptions {
|
|||||||
views: number;
|
views: number;
|
||||||
thumbnail?: {
|
thumbnail?: {
|
||||||
id: string | undefined;
|
id: string | undefined;
|
||||||
width: number;
|
width: number | undefined ;
|
||||||
height: number;
|
height: number | undefined;
|
||||||
url: string | undefined;
|
url: string | undefined;
|
||||||
};
|
};
|
||||||
channel?: {
|
channel?: {
|
||||||
@ -34,6 +34,7 @@ interface VideoOptions {
|
|||||||
|
|
||||||
export class Video {
|
export class Video {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
url? : string;
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
durationFormatted: string;
|
durationFormatted: string;
|
||||||
@ -53,6 +54,7 @@ export class Video {
|
|||||||
if(!data) throw new Error(`Can not initiate ${this.constructor.name} without data`)
|
if(!data) throw new Error(`Can not initiate ${this.constructor.name} without data`)
|
||||||
|
|
||||||
this.id = data.id || undefined;
|
this.id = data.id || undefined;
|
||||||
|
this.url = `https://www.youtube.com/watch?v=${this.id}`
|
||||||
this.title = data.title || undefined;
|
this.title = data.title || undefined;
|
||||||
this.description = data.description || undefined;
|
this.description = data.description || undefined;
|
||||||
this.durationFormatted = data.duration_raw || "0:00";
|
this.durationFormatted = data.duration_raw || "0:00";
|
||||||
@ -68,11 +70,6 @@ export class Video {
|
|||||||
this.tags = data.tags || [];
|
this.tags = data.tags || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get url(){
|
|
||||||
if(!this.id) return undefined
|
|
||||||
else return `https://www.youtube.com/watch?v=${this.id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get type(): "video" {
|
get type(): "video" {
|
||||||
return "video";
|
return "video";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1,3 @@
|
|||||||
export { search } from './search'
|
export { search } from './search'
|
||||||
|
|
||||||
|
export * from './utils'
|
||||||
@ -6,8 +6,29 @@ import { Channel } from "./classes/Channel";
|
|||||||
import { PlayList } from "./classes/Playlist";
|
import { PlayList } from "./classes/Playlist";
|
||||||
|
|
||||||
|
|
||||||
export async function search(url:string, options? : ParseSearchInterface): Promise<(Video | Channel | PlayList)[]> {
|
enum SearchType {
|
||||||
|
Video = 'EgIQAQ%253D%253D',
|
||||||
|
PlayList = 'EgIQAw%253D%253D',
|
||||||
|
Channel = 'EgIQAg%253D%253D',
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function search(search :string, options? : ParseSearchInterface): Promise<(Video | Channel | PlayList)[]> {
|
||||||
|
let url = 'https://www.youtube.com/results?search_query=' + search.replaceAll(' ', '+')
|
||||||
|
if(!url.match('&sp=')){
|
||||||
|
url += '&sp='
|
||||||
|
switch(options?.type){
|
||||||
|
case 'channel':
|
||||||
|
url += SearchType.Channel
|
||||||
|
break
|
||||||
|
case 'playlist':
|
||||||
|
url += SearchType.PlayList
|
||||||
|
break
|
||||||
|
case 'video':
|
||||||
|
url += SearchType.Video
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
let body = await url_get(url)
|
let body = await url_get(url)
|
||||||
let data = ParseSearchResult(body)
|
let data = ParseSearchResult(body, options)
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
@ -1,10 +1,24 @@
|
|||||||
import { url_get } from './request'
|
import { url_get } from './request'
|
||||||
import { format_decipher, js_tokens } from './cipher'
|
import { format_decipher, js_tokens } from './cipher'
|
||||||
|
import { Video } from '../classes/Video'
|
||||||
|
import { RequestInit } from 'node-fetch'
|
||||||
|
import { PlayList } from '../classes/Playlist'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
|
const DEFAULT_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
|
||||||
|
const youtube_url = /https:\/\/www.youtube.com\//g
|
||||||
|
const video_pattern = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/;
|
||||||
|
|
||||||
export async function yt_initial_data(url : string){
|
export interface PlaylistOptions {
|
||||||
|
limit?: number;
|
||||||
|
requestOptions?: RequestInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function video_basic_info(url : string){
|
||||||
|
if(!url.match(youtube_url) || !url.match(video_pattern)) throw new Error('This is not a YouTube URL')
|
||||||
let body = await url_get(url)
|
let body = await url_get(url)
|
||||||
let player_response = JSON.parse(body.split("var ytInitialPlayerResponse = ")[1].split(";</script>")[0])
|
let player_response = JSON.parse(body.split("var ytInitialPlayerResponse = ")[1].split(";</script>")[0])
|
||||||
|
if(player_response.playabilityStatus.status === 'ERROR') throw new Error(`While getting info from url \n ${player_response.playabilityStatus.reason}`)
|
||||||
let response = JSON.parse(body.split("var ytInitialData = ")[1].split(";</script>")[0])
|
let response = JSON.parse(body.split("var ytInitialData = ")[1].split(";</script>")[0])
|
||||||
let html5player = 'https://www.youtube.com' + body.split('"jsUrl":"')[1].split('"')[0]
|
let html5player = 'https://www.youtube.com' + body.split('"jsUrl":"')[1].split('"')[0]
|
||||||
let format = []
|
let format = []
|
||||||
@ -12,14 +26,18 @@ export async function yt_initial_data(url : string){
|
|||||||
format.push(...player_response.streamingData.adaptiveFormats)
|
format.push(...player_response.streamingData.adaptiveFormats)
|
||||||
let vid = player_response.videoDetails
|
let vid = player_response.videoDetails
|
||||||
let microformat = player_response.microformat.playerMicroformatRenderer
|
let microformat = player_response.microformat.playerMicroformatRenderer
|
||||||
let video_details = {
|
let video_details = new Video ({
|
||||||
id : vid.videoId,
|
id : vid.videoId,
|
||||||
url : 'https://www.youtube.com/watch?v=' + vid.videoId,
|
url : 'https://www.youtube.com/watch?v=' + vid.videoId,
|
||||||
title : vid.title,
|
title : vid.title,
|
||||||
description : vid.shortDescription,
|
description : vid.shortDescription,
|
||||||
duration : vid.lengthSeconds,
|
duration : vid.lengthSeconds,
|
||||||
uploadedDate : microformat.publishDate,
|
uploadedDate : microformat.publishDate,
|
||||||
thumbnail : `https://i.ytimg.com/vi/${vid.videoId}/maxresdefault.jpg`,
|
thumbnail : {
|
||||||
|
width : vid.thumbnail.thumbnails[vid.thumbnail.thumbnails.length - 1].width,
|
||||||
|
height : vid.thumbnail.thumbnails[vid.thumbnail.thumbnails.length - 1].height,
|
||||||
|
url : `https://i.ytimg.com/vi/${vid.videoId}/maxresdefault.jpg`
|
||||||
|
},
|
||||||
channel : {
|
channel : {
|
||||||
name : vid.author,
|
name : vid.author,
|
||||||
id : vid.channelId,
|
id : vid.channelId,
|
||||||
@ -30,19 +48,18 @@ export async function yt_initial_data(url : string){
|
|||||||
averageRating : vid.averageRating,
|
averageRating : vid.averageRating,
|
||||||
live : vid.isLiveContent,
|
live : vid.isLiveContent,
|
||||||
private : vid.isPrivate
|
private : vid.isPrivate
|
||||||
}
|
})
|
||||||
let final = {
|
return {
|
||||||
player_response,
|
player_response,
|
||||||
response,
|
response,
|
||||||
html5player,
|
html5player,
|
||||||
format,
|
format,
|
||||||
video_details
|
video_details
|
||||||
}
|
}
|
||||||
return final
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function yt_deciphered_data(url : string) {
|
export async function video_info(url : string) {
|
||||||
let data = await yt_initial_data(url)
|
let data = await video_basic_info(url)
|
||||||
if(data.format[0].signatureCipher || data.format[0].cipher){
|
if(data.format[0].signatureCipher || data.format[0].cipher){
|
||||||
data.format = await format_decipher(data.format, data.html5player)
|
data.format = await format_decipher(data.format, data.html5player)
|
||||||
return data
|
return data
|
||||||
@ -51,3 +68,116 @@ export async function yt_deciphered_data(url : string) {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function playlist_info(url : string , options? : PlaylistOptions) {
|
||||||
|
if (!options) options = { limit: 100, requestOptions: {} };
|
||||||
|
if(!options.limit) options.limit = 100
|
||||||
|
if (!url || typeof url !== "string") throw new Error(`Expected playlist url, received ${typeof url}!`);
|
||||||
|
if(url.search('(\\?|\\&)list\\=') === -1) throw new Error('This is not a PlayList URL')
|
||||||
|
|
||||||
|
let Playlist_id = url.split('list=')[1].split('&')[0]
|
||||||
|
let new_url = `https://www.youtube.com/playlist?list=${Playlist_id}`
|
||||||
|
|
||||||
|
let body = await url_get(new_url)
|
||||||
|
let response = JSON.parse(body.split("var ytInitialData = ")[1].split(";</script>")[0])
|
||||||
|
if(response.alerts && response.alerts[0].alertRenderer.type === 'ERROR') throw new Error(`While parsing playlist url\n ${response.alerts[0].alertRenderer.text.runs[0].text}`)
|
||||||
|
|
||||||
|
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;
|
||||||
|
let videos = getPlaylistVideos(parsed, options.limit);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@ -1 +1 @@
|
|||||||
export { yt_initial_data, yt_deciphered_data } from './extractor'
|
export { video_basic_info, video_info, playlist_info } from './extractor'
|
||||||
@ -2,6 +2,7 @@ import { Video } from "../classes/Video";
|
|||||||
import { PlayList } from "../classes/Playlist";
|
import { PlayList } from "../classes/Playlist";
|
||||||
import { Channel } from "../classes/Channel";
|
import { Channel } from "../classes/Channel";
|
||||||
import { RequestInit } from "node-fetch";
|
import { RequestInit } from "node-fetch";
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
export interface ParseSearchInterface {
|
export interface ParseSearchInterface {
|
||||||
type?: "video" | "playlist" | "channel" | "all";
|
type?: "video" | "playlist" | "channel" | "all";
|
||||||
@ -31,7 +32,7 @@ export function ParseSearchResult(html :string, options? : ParseSearchInterface)
|
|||||||
details = JSON.parse(html.split('{"itemSectionRenderer":{"contents":')[html.split('{"itemSectionRenderer":{"contents":').length - 1].split(',"continuations":[{')[0]);
|
details = JSON.parse(html.split('{"itemSectionRenderer":{"contents":')[html.split('{"itemSectionRenderer":{"contents":').length - 1].split(',"continuations":[{')[0]);
|
||||||
fetched = true;
|
fetched = true;
|
||||||
} catch {
|
} catch {
|
||||||
/* do nothing */
|
/* Do nothing*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fetched) {
|
if (!fetched) {
|
||||||
@ -76,40 +77,7 @@ export function ParseSearchResult(html :string, options? : ParseSearchInterface)
|
|||||||
return results as (Video | Channel | PlayList)[];
|
return results as (Video | Channel | PlayList)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPlaylistVideos(data:any, limit : number = Infinity) : Video[] {
|
function parseDuration(duration: string): number {
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseDuration(duration: string): number {
|
|
||||||
duration ??= "0:00";
|
duration ??= "0:00";
|
||||||
const args = duration.split(":");
|
const args = duration.split(":");
|
||||||
let dur = 0;
|
let dur = 0;
|
||||||
@ -128,11 +96,6 @@ export function parseDuration(duration: string): number {
|
|||||||
return dur;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseChannel(data?: any): Channel | void {
|
export function parseChannel(data?: any): Channel | void {
|
||||||
if (!data || !data.channelRenderer) return;
|
if (!data || !data.channelRenderer) return;
|
||||||
const badge = data.channelRenderer.ownerBadges && data.channelRenderer.ownerBadges[0];
|
const badge = data.channelRenderer.ownerBadges && data.channelRenderer.ownerBadges[0];
|
||||||
@ -140,10 +103,14 @@ export function parseChannel(data?: any): Channel | void {
|
|||||||
let res = new Channel({
|
let res = new Channel({
|
||||||
id: data.channelRenderer.channelId,
|
id: data.channelRenderer.channelId,
|
||||||
name: data.channelRenderer.title.simpleText,
|
name: data.channelRenderer.title.simpleText,
|
||||||
icon: data.channelRenderer.thumbnail.thumbnails[data.channelRenderer.thumbnail.thumbnails.length - 1],
|
icon: {
|
||||||
|
url : data.channelRenderer.thumbnail.thumbnails[data.channelRenderer.thumbnail.thumbnails.length - 1].url.replace('//', 'https://'),
|
||||||
|
width : data.channelRenderer.thumbnail.thumbnails[data.channelRenderer.thumbnail.thumbnails.length - 1].width,
|
||||||
|
height: data.channelRenderer.thumbnail.thumbnails[data.channelRenderer.thumbnail.thumbnails.length - 1].height
|
||||||
|
},
|
||||||
url: url,
|
url: url,
|
||||||
verified: Boolean(badge?.metadataBadgeRenderer?.style?.toLowerCase().includes("verified")),
|
verified: Boolean(badge?.metadataBadgeRenderer?.style?.toLowerCase().includes("verified")),
|
||||||
subscribers: data.channelRenderer.subscriberCountText.simpleText
|
subscribers: (data.channelRenderer.subscriberCountText?.simpleText) ? data.channelRenderer.subscriberCountText.simpleText : '0 subscribers'
|
||||||
});
|
});
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { search } from "./YouTube/";
|
import { playlist_info } from "./YouTube";
|
||||||
|
|
||||||
let main = async() => {
|
let main = async() => {
|
||||||
let time_start = Date.now()
|
let time_start = Date.now()
|
||||||
await search('https://www.youtube.com/results?search_query=Hello+Neghibour')
|
let playlist = await playlist_info('https://www.youtube.com/watch?v=bM7SZ5SBzyY&list=PLzkuLC6Yvumv_Rd5apfPRWEcjf9b1JRnq')
|
||||||
let time_end = Date.now()
|
let time_end = Date.now()
|
||||||
console.log(`Time Taken : ${(time_end - time_start)/1000} seconds`)
|
console.log(`Time Taken : ${(time_end - time_start)/1000} seconds`)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user