diff --git a/play-dl/SoundCloud/classes.ts b/play-dl/SoundCloud/classes.ts index 4296021..9674af3 100644 --- a/play-dl/SoundCloud/classes.ts +++ b/play-dl/SoundCloud/classes.ts @@ -32,7 +32,9 @@ export interface SoundCloudTrackFormat { }; quality: string; } - +/** + * SoundCloud Track + */ export class SoundCloudTrack { name: string; id: number; @@ -100,7 +102,9 @@ export class SoundCloudTrack { }; } } - +/** + * SoundCloud Playlist + */ export class SoundCloudPlaylist { name: string; id: number; @@ -193,7 +197,9 @@ export class SoundCloudPlaylist { }; } } - +/** + * SoundCloud Stream class + */ export class Stream { stream: PassThrough; type: StreamType; diff --git a/play-dl/SoundCloud/index.ts b/play-dl/SoundCloud/index.ts index 50262b3..1d9c923 100644 --- a/play-dl/SoundCloud/index.ts +++ b/play-dl/SoundCloud/index.ts @@ -13,8 +13,12 @@ interface SoundDataOptions { } const pattern = /^(?:(https?):\/\/)?(?:(?:www|m)\.)?(soundcloud\.com|snd\.sc)\/(.*)$/; - -export async function soundcloud(url: string): Promise { +/** + * Function to get info from a soundcloud url + * @param url soundcloud url + * @returns SoundCloud Track or SoundCloud Playlist + */ +export async function soundcloud(url: string): Promise { if (!soundData) throw new Error('SoundCloud Data is missing\nDid you forgot to do authorization ?'); if (!url.match(pattern)) throw new Error('This is not a SoundCloud URL'); @@ -32,8 +36,17 @@ export async function soundcloud(url: string): Promise { const data = await soundcloud(url); @@ -67,7 +85,16 @@ export async function stream(url: string, quality?: number): Promise { : StreamType.Arbitrary; return new Stream(s_data.url, type); } +/** + * Type for SoundCloud Stream + */ export type SoundCloudStream = Stream; +/** + * Function for creating a Stream of soundcloud using a SoundCloud Track Class + * @param data SoundCloud Track Class + * @param quality Quality to select from + * @returns SoundCloud Stream + */ export async function stream_from_info(data: SoundCloudTrack, quality?: number): Promise { const HLSformats = parseHlsFormats(data.formats); if (typeof quality !== 'number') quality = HLSformats.length - 1; @@ -80,7 +107,11 @@ export async function stream_from_info(data: SoundCloudTrack, quality?: number): : StreamType.Arbitrary; return new Stream(s_data.url, type); } - +/** + * Function to check client ID + * @param id Client ID + * @returns boolean + */ export async function check_id(id: string): Promise { const response = await request(`https://api-v2.soundcloud.com/search?client_id=${id}&q=Rick+Roll&limit=0`).catch( (err: Error) => { @@ -90,7 +121,11 @@ export async function check_id(id: string): Promise { if (response instanceof Error) return false; else return true; } - +/** + * Function to validate for a soundcloud url + * @param url soundcloud url + * @returns "false" | 'track' | 'playlist' + */ export async function so_validate(url: string): Promise { const data = await request( `https://api-v2.soundcloud.com/resolve?url=${url}&client_id=${soundData.client_id}` @@ -103,7 +138,11 @@ export async function so_validate(url: string): Promise { diff --git a/play-dl/Spotify/classes.ts b/play-dl/Spotify/classes.ts index 52540a8..807497a 100644 --- a/play-dl/Spotify/classes.ts +++ b/play-dl/Spotify/classes.ts @@ -26,8 +26,10 @@ interface SpotifyCopyright { text: string; type: string; } - -export class SpotifyVideo { +/** + * Class for Spotify Track + */ +export class SpotifyTrack { name: string; type: 'track' | 'playlist' | 'album'; id: string; @@ -36,8 +38,8 @@ export class SpotifyVideo { durationInSec: number; durationInMs: number; artists: SpotifyArtists[]; - album: SpotifyTrackAlbum; - thumbnail: SpotifyThumbnail; + album: SpotifyTrackAlbum | undefined; + thumbnail: SpotifyThumbnail | undefined; constructor(data: any) { this.name = data.name; this.id = data.id; @@ -55,15 +57,19 @@ export class SpotifyVideo { }); }); this.artists = artists; - this.album = { - name: data.album.name, - url: data.external_urls.spotify, - id: data.album.id, - release_date: data.album.release_date, - release_date_precision: data.album.release_date_precision, - total_tracks: data.album.total_tracks - }; - this.thumbnail = data.album.images[0]; + if (!data.album?.name) this.album = undefined; + else { + this.album = { + name: data.album.name, + url: data.external_urls.spotify, + id: data.album.id, + release_date: data.album.release_date, + release_date_precision: data.album.release_date_precision, + total_tracks: data.album.total_tracks + }; + } + if (!data.album?.images?.[0]) this.thumbnail = undefined; + else this.thumbnail = data.album.images[0]; } toJSON() { @@ -81,7 +87,9 @@ export class SpotifyVideo { }; } } - +/** + * Class for Spotify Playlist + */ export class SpotifyPlaylist { name: string; type: 'track' | 'playlist' | 'album'; @@ -93,7 +101,7 @@ export class SpotifyPlaylist { owner: SpotifyArtists; tracksCount: number; private spotifyData: SpotifyDataOptions; - private fetched_tracks: Map; + private fetched_tracks: Map; constructor(data: any, spotifyData: SpotifyDataOptions) { this.name = data.name; this.type = 'playlist'; @@ -108,9 +116,9 @@ export class SpotifyPlaylist { id: data.owner.id }; this.tracksCount = Number(data.tracks.total); - const videos: SpotifyVideo[] = []; + const videos: SpotifyTrack[] = []; data.tracks.items.forEach((v: any) => { - videos.push(new SpotifyVideo(v.track)); + videos.push(new SpotifyTrack(v.track)); }); this.fetched_tracks = new Map(); this.fetched_tracks.set('1', videos); @@ -136,11 +144,11 @@ export class SpotifyPlaylist { } } ).catch((err) => reject(`Response Error : \n${err}`)); - const videos: SpotifyVideo[] = []; + const videos: SpotifyTrack[] = []; if (typeof response !== 'string') return; const json_data = JSON.parse(response); json_data.items.forEach((v: any) => { - videos.push(new SpotifyVideo(v.track)); + videos.push(new SpotifyTrack(v.track)); }); this.fetched_tracks.set(`${i}`, videos); resolve('Success'); @@ -163,7 +171,7 @@ export class SpotifyPlaylist { get total_tracks() { const page_number: number = this.total_pages; - return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyVideo[]).length; + return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length; } toJSON() { @@ -179,7 +187,9 @@ export class SpotifyPlaylist { }; } } - +/** + * Class for Spotify Album + */ export class SpotifyAlbum { name: string; type: 'track' | 'playlist' | 'album'; @@ -192,7 +202,7 @@ export class SpotifyAlbum { release_date_precision: string; trackCount: number; private spotifyData: SpotifyDataOptions; - private fetched_tracks: Map; + private fetched_tracks: Map; constructor(data: any, spotifyData: SpotifyDataOptions) { this.name = data.name; this.type = 'album'; @@ -212,9 +222,9 @@ export class SpotifyAlbum { this.release_date = data.release_date; this.release_date_precision = data.release_date_precision; this.trackCount = data.total_tracks; - const videos: SpotifyTracks[] = []; + const videos: SpotifyTrack[] = []; data.tracks.items.forEach((v: any) => { - videos.push(new SpotifyTracks(v)); + videos.push(new SpotifyTrack(v)); }); this.fetched_tracks = new Map(); this.fetched_tracks.set('1', videos); @@ -240,11 +250,11 @@ export class SpotifyAlbum { } } ).catch((err) => reject(`Response Error : \n${err}`)); - const videos: SpotifyTracks[] = []; + const videos: SpotifyTrack[] = []; if (typeof response !== 'string') return; const json_data = JSON.parse(response); json_data.items.forEach((v: any) => { - videos.push(new SpotifyTracks(v)); + videos.push(new SpotifyTrack(v)); }); this.fetched_tracks.set(`${i}`, videos); resolve('Success'); @@ -267,7 +277,7 @@ export class SpotifyAlbum { get total_tracks() { const page_number: number = this.total_pages; - return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyVideo[]).length; + return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length; } toJSON() { @@ -284,45 +294,3 @@ export class SpotifyAlbum { }; } } - -class SpotifyTracks { - name: string; - type: 'track' | 'playlist' | 'album'; - id: string; - url: string; - explicit: boolean; - durationInSec: number; - durationInMs: number; - artists: SpotifyArtists[]; - constructor(data: any) { - this.name = data.name; - this.id = data.id; - this.type = 'track'; - this.url = data.external_urls.spotify; - this.explicit = data.explicit; - this.durationInMs = data.duration_ms; - this.durationInSec = Math.round(this.durationInMs / 1000); - const artists: SpotifyArtists[] = []; - data.artists.forEach((v: any) => { - artists.push({ - name: v.name, - id: v.id, - url: v.external_urls.spotify - }); - }); - this.artists = artists; - } - - toJSON() { - return { - name: this.name, - id: this.id, - type: this.type, - url: this.url, - explicit: this.explicit, - durationInMs: this.durationInMs, - durationInSec: this.durationInSec, - artists: this.artists - }; - } -} diff --git a/play-dl/Spotify/index.ts b/play-dl/Spotify/index.ts index fee1362..cd2f7d5 100644 --- a/play-dl/Spotify/index.ts +++ b/play-dl/Spotify/index.ts @@ -1,12 +1,14 @@ import { request } from '../YouTube/utils/request'; -import { SpotifyAlbum, SpotifyPlaylist, SpotifyVideo } from './classes'; +import { SpotifyAlbum, SpotifyPlaylist, SpotifyTrack } from './classes'; import fs from 'fs'; let spotifyData: SpotifyDataOptions; if (fs.existsSync('.data/spotify.data')) { spotifyData = JSON.parse(fs.readFileSync('.data/spotify.data').toString()); } - +/** + * Spotify Data options that are stored in spotify.data file. + */ export interface SpotifyDataOptions { client_id: string; client_secret: string; @@ -21,8 +23,12 @@ export interface SpotifyDataOptions { } const pattern = /^((https:)?\/\/)?open.spotify.com\/(track|album|playlist)\//; - -export async function spotify(url: string): Promise { +/** + * Function to get Playlist | Album | Track + * @param url url of spotify from which you want info + * @returns Spotify type. + */ +export async function spotify(url: string): Promise { if (!spotifyData) throw new Error('Spotify Data is missing\nDid you forgot to do authorization ?'); if (!url.match(pattern)) throw new Error('This is not a Spotify URL'); if (url.indexOf('track/') !== -1) { @@ -35,7 +41,7 @@ export async function spotify(url: string): Promise { const response = await request(`https://accounts.spotify.com/api/token`, { headers: { @@ -104,20 +118,31 @@ export async function SpotifyAuthorize(data: SpotifyDataOptions): Promise= (spotifyData.expiry as number)) return true; else return false; } - -export type Spotify = SpotifyAlbum | SpotifyPlaylist | SpotifyVideo; - +/** + * type for Spotify Class + */ +export type Spotify = SpotifyAlbum | SpotifyPlaylist | SpotifyTrack; +/** + * Function for searching songs on Spotify + * @param query searching query + * @param type "album" | "playlist" | "track" + * @param limit max no of results + * @returns Spotify type. + */ export async function sp_search( query: string, type: 'album' | 'playlist' | 'track', limit: number = 10 ): Promise { - const results: (SpotifyAlbum | SpotifyPlaylist | SpotifyVideo)[] = []; + const results: Spotify[] = []; if (!spotifyData) throw new Error('Spotify Data is missing\nDid you forgot to do authorization ?'); if (query.length === 0) throw new Error('Pass some query to search.'); if (limit > 50 || limit < 0) throw new Error(`You crossed limit range of Spotify [ 0 - 50 ]`); @@ -137,7 +162,7 @@ export async function sp_search( const json_data = JSON.parse(response); if (type === 'track') { json_data.tracks.items.forEach((track: any) => { - results.push(new SpotifyVideo(track)); + results.push(new SpotifyTrack(track)); }); } else if (type === 'album') { json_data.albums.items.forEach((album: any) => { @@ -150,7 +175,10 @@ export async function sp_search( } return results; } - +/** + * Function to refresh Token + * @returns boolean to check whether token is refreshed or not + */ export async function refreshToken(): Promise { const response = await request(`https://accounts.spotify.com/api/token`, { headers: { diff --git a/play-dl/YouTube/classes/Channel.ts b/play-dl/YouTube/classes/Channel.ts index 2f9520a..5891a69 100644 --- a/play-dl/YouTube/classes/Channel.ts +++ b/play-dl/YouTube/classes/Channel.ts @@ -3,7 +3,9 @@ export interface ChannelIconInterface { width: number; height: number; } - +/** + * Class for YouTube Channel url + */ export class YouTubeChannel { name?: string; verified?: boolean; diff --git a/play-dl/YouTube/classes/LiveStream.ts b/play-dl/YouTube/classes/LiveStream.ts index 534182e..202295a 100644 --- a/play-dl/YouTube/classes/LiveStream.ts +++ b/play-dl/YouTube/classes/LiveStream.ts @@ -1,7 +1,7 @@ import { PassThrough } from 'stream'; import { IncomingMessage } from 'http'; -import { parseAudioFormats, StreamType } from '../stream'; -import { request, request_stream } from '../utils/request'; +import { parseAudioFormats, StreamOptions, StreamType } from '../stream'; +import { Proxy, request, request_stream } from '../utils/request'; import { video_info } from '..'; export interface FormatInterface { @@ -119,7 +119,9 @@ export class LiveStreaming { }, this.interval); } } - +/** + * Class for YouTube Stream + */ export class Stream { stream: PassThrough; type: StreamType; @@ -133,6 +135,7 @@ export class Stream { private data_ended: boolean; private playing_count: number; private quality: number; + private proxy: Proxy[]; private request: IncomingMessage | null; constructor( url: string, @@ -141,11 +144,12 @@ export class Stream { contentLength: number, video_url: string, cookie: string, - quality: number + options: StreamOptions ) { this.stream = new PassThrough({ highWaterMark: 10 * 1000 * 1000 }); this.url = url; - this.quality = quality; + this.quality = options.quality as number; + this.proxy = options.proxy || []; this.type = type; this.bytes_count = 0; this.video_url = video_url; @@ -177,7 +181,7 @@ export class Stream { } private async retry() { - const info = await video_info(this.video_url, { cookie: this.cookie }); + const info = await video_info(this.video_url, { cookie: this.cookie, proxy: this.proxy }); const audioFormat = parseAudioFormats(info.format); this.url = audioFormat[this.quality].url; } diff --git a/play-dl/YouTube/classes/Playlist.ts b/play-dl/YouTube/classes/Playlist.ts index b51c710..ee07455 100644 --- a/play-dl/YouTube/classes/Playlist.ts +++ b/play-dl/YouTube/classes/Playlist.ts @@ -3,7 +3,9 @@ import { request } from '../utils/request'; import { YouTubeChannel } from './Channel'; import { YouTubeVideo } from './Video'; const BASE_API = 'https://www.youtube.com/youtubei/v1/browse?key='; - +/** + * Class for YouTube Playlist url + */ export class YouTubePlayList { id?: string; title?: string; diff --git a/play-dl/YouTube/classes/Video.ts b/play-dl/YouTube/classes/Video.ts index 59f30a5..d52b9f5 100644 --- a/play-dl/YouTube/classes/Video.ts +++ b/play-dl/YouTube/classes/Video.ts @@ -29,7 +29,9 @@ interface VideoOptions { private: boolean; tags: string[]; } - +/** + * Class for YouTube Video url + */ export class YouTubeVideo { id?: string; url: string; diff --git a/play-dl/YouTube/search.ts b/play-dl/YouTube/search.ts index adc9128..7446ba9 100644 --- a/play-dl/YouTube/search.ts +++ b/play-dl/YouTube/search.ts @@ -10,8 +10,16 @@ enum SearchType { Channel = 'EgIQAg%253D%253D' } +/** + * Type for YouTube returns + */ export type YouTube = YouTubeVideo | YouTubeChannel | YouTubePlayList; - +/** + * Command to search from YouTube + * @param search The query to search + * @param options limit & type of YouTube search you want. + * @returns YouTube type. + */ export async function yt_search(search: string, options: ParseSearchInterface = {}): Promise { let url = 'https://www.youtube.com/results?search_query=' + search.replaceAll(' ', '+'); options.type ??= 'video'; diff --git a/play-dl/YouTube/stream.ts b/play-dl/YouTube/stream.ts index 6953d26..c87f683 100644 --- a/play-dl/YouTube/stream.ts +++ b/play-dl/YouTube/stream.ts @@ -26,7 +26,11 @@ export interface InfoData { format: any[]; video_details: any; } - +/** + * Command to find audio formats from given format array + * @param formats Formats to search from + * @returns Audio Formats array + */ export function parseAudioFormats(formats: any[]) { const result: any[] = []; formats.forEach((format) => { @@ -39,9 +43,16 @@ export function parseAudioFormats(formats: any[]) { }); return result; } - +/** + * Type for YouTube Stream + */ export type YouTubeStream = Stream | LiveStreaming; - +/** + * Stream command for YouTube + * @param url YouTube URL + * @param options lets you add quality, cookie, proxy support for stream + * @returns Stream class with type and stream for playing. + */ export async function stream(url: string, options: StreamOptions = {}): Promise { const info = await video_info(url, { cookie: options.cookie, proxy: options.proxy }); const final: any[] = []; @@ -73,10 +84,15 @@ export async function stream(url: string, options: StreamOptions = {}): Promise< Number(final[0].contentLength), info.video_details.url, options.cookie as string, - options.quality + options ); } - +/** + * Stream command for YouTube using info from video_info function. + * @param info video_info data + * @param options lets you add quality, cookie, proxy support for stream + * @returns Stream class with type and stream for playing. + */ export async function stream_from_info(info: InfoData, options: StreamOptions = {}): Promise { const final: any[] = []; if ( @@ -107,6 +123,6 @@ export async function stream_from_info(info: InfoData, options: StreamOptions = Number(final[0].contentLength), info.video_details.url, options.cookie as string, - options.quality + options ); } diff --git a/play-dl/YouTube/utils/cipher.ts b/play-dl/YouTube/utils/cipher.ts index b9f63cb..dcb6880 100644 --- a/play-dl/YouTube/utils/cipher.ts +++ b/play-dl/YouTube/utils/cipher.ts @@ -9,6 +9,7 @@ interface formatOptions { cipher?: string; s?: string; } +// RegExp for various js functions const var_js = '[a-zA-Z_\\$][a-zA-Z_0-9]*'; const singlequote_js = `'[^'\\\\]*(:?\\\\[\\s\\S][^'\\\\]*)*'`; const duoblequote_js = `"[^"\\\\]*(:?\\\\[\\s\\S][^"\\\\]*)*"`; @@ -37,7 +38,11 @@ const reverse_regexp = new RegExp(`(?:^|,)(${key_js})${reverse_function}`, 'm'); const slice_regexp = new RegExp(`(?:^|,)(${key_js})${slice_function}`, 'm'); const splice_regexp = new RegExp(`(?:^|,)(${key_js})${splice_function}`, 'm'); const swap_regexp = new RegExp(`(?:^|,)(${key_js})${swap_function}`, 'm'); - +/** + * Function to get tokens from html5player body data. + * @param body body data of html5player. + * @returns Array of tokens. + */ export function js_tokens(body: string) { const function_action = function_regexp.exec(body); const object_action = obj_regexp.exec(body); @@ -82,7 +87,12 @@ export function js_tokens(body: string) { } return tokens; } - +/** + * Function to decipher signature + * @param tokens Tokens from js_tokens function + * @param signature Signatured format url + * @returns deciphered signature + */ function deciper_signature(tokens: string[], signature: string) { let sig = signature.split(''); const len = tokens.length; @@ -109,14 +119,24 @@ function deciper_signature(tokens: string[], signature: string) { } return sig.join(''); } - +/** + * Function to swap positions in a array + * @param array array + * @param position position to switch with first element + * @returns new array with swapped positions. + */ function swappositions(array: string[], position: number) { const first = array[0]; array[0] = array[position]; array[position] = first; return array; } - +/** + * Sets Download url with some extra parameter + * @param format video fomat + * @param sig deciphered signature + * @returns void + */ function download_url(format: formatOptions, sig: string) { let decoded_url; if (!format.url) return; @@ -132,8 +152,13 @@ function download_url(format: formatOptions, sig: string) { } format.url = parsed_url.toString(); } - -export async function format_decipher(formats: formatOptions[], html5player: string) { +/** + * Main function which handles all queries related to video format deciphering + * @param formats video formats + * @param html5player url of html5player + * @returns array of format. + */ +export async function format_decipher(formats: formatOptions[], html5player: string): Promise { const body = await request(html5player); const tokens = js_tokens(body); formats.forEach((format) => { diff --git a/play-dl/YouTube/utils/extractor.ts b/play-dl/YouTube/utils/extractor.ts index ed72797..e8eb971 100644 --- a/play-dl/YouTube/utils/extractor.ts +++ b/play-dl/YouTube/utils/extractor.ts @@ -17,7 +17,11 @@ const DEFAULT_API_KEY = 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'; const video_pattern = /^((?:https?:)?\/\/)?(?:(?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/; const playlist_pattern = /^((?:https?:)?\/\/)?(?:(?:www|m)\.)?(youtube\.com)\/(?:(playlist|watch))(.*)?((\?|\&)list=)/; - +/** + * Command to validate a YouTube url + * @param url Url for validation + * @returns type of url or false. + */ export function yt_validate(url: string): 'playlist' | 'video' | false { if (url.indexOf('list=') === -1) { if (!url.match(video_pattern)) return false; @@ -31,7 +35,11 @@ export function yt_validate(url: string): 'playlist' | 'video' | false { return 'playlist'; } } - +/** + * Function to extract ID of YouTube url. + * @param url ID or url of YouTube + * @returns ID of video or playlist. + */ export function extractID(url: string): string { if (url.startsWith('https')) { if (url.indexOf('list=') === -1) { @@ -45,7 +53,12 @@ export function extractID(url: string): string { } } else return url; } - +/** + * Basic function to get data from a YouTube url or ID. + * @param url YouTube url or ID + * @param options cookie and proxy parameters to add + * @returns Data containing video_details, LiveStreamData and formats of video url. + */ export async function video_basic_info(url: string, options: InfoOptions = {}) { let video_id: string; if (url.startsWith('https')) { @@ -123,7 +136,11 @@ export async function video_basic_info(url: string, options: InfoOptions = {}) { related_videos: related }; } - +/** + * Function to convert seconds to [hour : minutes : seconds] format + * @param seconds seconds to convert + * @returns [hour : minutes : seconds] format + */ function parseSeconds(seconds: number): string { const d = Number(seconds); const h = Math.floor(d / 3600); @@ -135,7 +152,12 @@ function parseSeconds(seconds: number): string { const sDisplay = s > 0 ? (s < 10 ? `0${s}` : s) : '00'; return hDisplay + mDisplay + sDisplay; } - +/** + * Function which gets data from video_basic_info and deciphers it if it contains signatures. + * @param url YouTube Video URL + * @param options cookie and proxy parameters to add + * @returns Data containing video_details, LiveStreamData and formats of video url. + */ export async function video_info(url: string, options: InfoOptions = {}) { const data = await video_basic_info(url, options); if (data.LiveStreamData.isLive === true && data.LiveStreamData.hlsManifestUrl !== null) { @@ -147,8 +169,13 @@ export async function video_info(url: string, options: InfoOptions = {}) { return data; } } - -export async function playlist_info(url: string, options: PlaylistOptions = {}) { +/** + * Function to get YouTube playlist info from a playlist url. + * @param url Playlist URL + * @param options incomplete and proxy to add. + * @returns YouTube Playlist + */ +export async function playlist_info(url: string, options: PlaylistOptions = {}): Promise { if (!url || typeof url !== 'string') throw new Error(`Expected playlist url, received ${typeof url}!`); let Playlist_id: string; if (url.startsWith('https')) { @@ -184,7 +211,7 @@ export async function playlist_info(url: string, options: PlaylistOptions = {}) const videos = getPlaylistVideos(parsed, 100); const data = playlistDetails[0].playlistSidebarPrimaryInfoRenderer; - if (!data.title.runs || !data.title.runs.length) return undefined; + if (!data.title.runs || !data.title.runs.length) throw new Error('Failed to Parse Playlist info.'); const author = playlistDetails[1]?.playlistSidebarSecondaryInfoRenderer.videoOwner; const views = data.stats.length === 3 ? data.stats[1].simpleText.replace(/[^0-9]/g, '') : 0; @@ -234,7 +261,12 @@ export async function playlist_info(url: string, options: PlaylistOptions = {}) }); return res; } - +/** + * Function to parse Playlist from YouTube search + * @param data html data of that request + * @param limit No. of videos to parse + * @returns Array of YouTubeVideo. + */ export function getPlaylistVideos(data: any, limit = Infinity): YouTubeVideo[] { const videos = []; @@ -270,7 +302,11 @@ export function getPlaylistVideos(data: any, limit = Infinity): YouTubeVideo[] { } return videos; } - +/** + * Function to convert [hour : minutes : seconds] format to seconds + * @param duration hour : minutes : seconds format + * @returns seconds + */ function parseDuration(duration: string): number { duration ??= '0:00'; const args = duration.split(':'); @@ -289,7 +325,11 @@ function parseDuration(duration: string): number { return dur; } - +/** + * Function to get Continuation Token + * @param data html data of playlist url + * @returns token + */ export function getContinuationToken(data: any): string { const continuationToken = data.find((x: any) => Object.keys(x)[0] === 'continuationItemRenderer') ?.continuationItemRenderer.continuationEndpoint?.continuationCommand?.token; diff --git a/play-dl/YouTube/utils/parser.ts b/play-dl/YouTube/utils/parser.ts index e02797f..b1499f5 100644 --- a/play-dl/YouTube/utils/parser.ts +++ b/play-dl/YouTube/utils/parser.ts @@ -1,6 +1,7 @@ import { YouTubeVideo } from '../classes/Video'; import { YouTubePlayList } from '../classes/Playlist'; import { YouTubeChannel } from '../classes/Channel'; +import { YouTube } from '..'; export interface ParseSearchInterface { type?: 'video' | 'playlist' | 'channel'; @@ -12,11 +13,13 @@ export interface thumbnail { height: string; url: string; } - -export function ParseSearchResult( - html: string, - options?: ParseSearchInterface -): (YouTubeVideo | YouTubePlayList | YouTubeChannel)[] { +/** + * Main command which converts html body data and returns the type of data requested. + * @param html body of that request + * @param options limit & type of YouTube search you want. + * @returns Array of one of YouTube type. + */ +export function ParseSearchResult(html: string, options?: ParseSearchInterface): YouTube[] { if (!html) throw new Error("Can't parse Search result without data"); if (!options) options = { type: 'video', limit: 0 }; if (!options.type) options.type = 'video'; @@ -45,7 +48,11 @@ export function ParseSearchResult( } return results; } - +/** + * Function to convert [hour : minutes : seconds] format to seconds + * @param duration hour : minutes : seconds format + * @returns seconds + */ function parseDuration(duration: string): number { duration ??= '0:00'; const args = duration.split(':'); @@ -64,7 +71,11 @@ function parseDuration(duration: string): number { return dur; } - +/** + * Function to parse Channel searches + * @param data body of that channel request. + * @returns YouTubeChannel class + */ export function parseChannel(data?: any): YouTubeChannel { if (!data || !data.channelRenderer) throw new Error('Failed to Parse YouTube Channel'); const badge = data.channelRenderer.ownerBadges && data.channelRenderer.ownerBadges[0]; @@ -93,7 +104,11 @@ export function parseChannel(data?: any): YouTubeChannel { return res; } - +/** + * Function to parse Video searches + * @param data body of that video request. + * @returns YouTubeVideo class + */ export function parseVideo(data?: any): YouTubeVideo { if (!data || !data.videoRenderer) throw new Error('Failed to Parse YouTube Video'); @@ -133,7 +148,11 @@ export function parseVideo(data?: any): YouTubeVideo { return res; } - +/** + * Function to parse Playlist searches + * @param data body of that playlist request. + * @returns YouTubePlaylist class + */ export function parsePlaylist(data?: any): YouTubePlayList { if (!data.playlistRenderer) throw new Error('Failed to Parse YouTube Playlist'); diff --git a/play-dl/YouTube/utils/request.ts b/play-dl/YouTube/utils/request.ts index 8a17852..f2735f2 100644 --- a/play-dl/YouTube/utils/request.ts +++ b/play-dl/YouTube/utils/request.ts @@ -2,7 +2,9 @@ import https, { RequestOptions } from 'https'; import tls from 'tls'; import http, { ClientRequest, IncomingMessage } from 'http'; import { URL } from 'url'; - +/** + * Types for Proxy + */ export type Proxy = ProxyOpts | string; interface ProxyOpts { @@ -25,7 +27,12 @@ interface RequestOpts extends RequestOptions { method?: 'GET' | 'POST'; proxies?: Proxy[]; } - +/** + * Main module that play-dl uses for making a https request + * @param req_url URL to make https request to + * @param options Request options for https request + * @returns Incoming Message from the https request + */ function https_getter(req_url: string, options: RequestOpts = {}): Promise { return new Promise((resolve, reject) => { const s = new URL(req_url); @@ -45,13 +52,23 @@ function https_getter(req_url: string, options: RequestOpts = {}): Promise { return new Promise((resolve, reject) => { const proxy: string | ProxyOpts = req_proxy[randomIntFromInterval(0, req_proxy.length)]; @@ -127,7 +144,12 @@ async function proxy_getter(req_url: string, req_proxy: Proxy[]): Promise { return new Promise(async (resolve, reject) => { if (!options?.proxies) { @@ -160,7 +182,12 @@ export async function request(url: string, options?: RequestOpts): Promise { return new Promise(async (resolve, reject) => { let res = await https_getter(url, options).catch((err: Error) => err); diff --git a/play-dl/index.ts b/play-dl/index.ts index 32f7fa5..16ff171 100644 --- a/play-dl/index.ts +++ b/play-dl/index.ts @@ -32,7 +32,7 @@ import { yt_search } from './YouTube/search'; /** * Main stream Command for streaming through various sources * @param url The video / track url to make stream of - * @param options contains quality and cookie to set for stream + * @param options contains quality, cookie and proxy to set for stream * @returns YouTube / SoundCloud Stream to play */ @@ -46,7 +46,7 @@ export async function stream(url: string, options: StreamOptions = {}): Promise< * Main Search Command for searching through various sources * @param query string to search. * @param options contains limit and source to choose. - * @returns + * @returns Array of YouTube or Spotify or SoundCloud */ export async function search( query: string, @@ -61,10 +61,11 @@ export async function search( } /** - * Command to be used - * @param info - * @param options - * @returns + * stream Command for streaming through various sources using data from video_info or soundcloud + * SoundCloud Track is only supported + * @param info video_info data or SoundCloud Track data. + * @param options contains quality, cookie and proxy to set for stream + * @returns YouTube / SoundCloud Stream to play */ export async function stream_from_info( info: InfoData | SoundCloudTrack, @@ -73,7 +74,11 @@ export async function stream_from_info( if (info instanceof SoundCloudTrack) return await so_stream_info(info, options.quality); else return await yt_stream_info(info, options); } - +/** + * Command to validate the provided url. It checks whether it supports play-dl or not. + * @param url url to validate + * @returns On failure, returns false else type of url. + */ export async function validate( url: string ): Promise<'so_playlist' | 'so_track' | 'sp_track' | 'sp_album' | 'sp_playlist' | 'yt_video' | 'yt_playlist' | false> { @@ -89,7 +94,9 @@ export async function validate( return check !== false ? (('yt_' + check) as 'yt_video' | 'yt_playlist') : false; } } - +/** + * Authorization interface for Spotify and SoundCloud. + */ export function authorization(): void { const ask = readline.createInterface({ input: process.stdin,