diff --git a/play-dl/SoundCloud/index.ts b/play-dl/SoundCloud/index.ts index 4b284c5..239ddd6 100644 --- a/play-dl/SoundCloud/index.ts +++ b/play-dl/SoundCloud/index.ts @@ -13,9 +13,20 @@ interface SoundDataOptions { const pattern = /^(?:(https?):\/\/)?(?:(?:www|m)\.)?(api\.soundcloud\.com|soundcloud\.com|snd\.sc)\/(.*)$/; /** - * Function to get info from a soundcloud url + * Gets info from a soundcloud url. + * + * ```ts + * let sound = await play.soundcloud('soundcloud url') + * + * // sound.type === "track" | "playlist" | "user" + * + * if (sound.type === "track") { + * spot = spot as play.SoundCloudTrack + * // Code with SoundCloud track class. + * } + * ``` * @param url soundcloud url - * @returns SoundCloud Track or SoundCloud Playlist + * @returns A {@link SoundCloudTrack} or {@link SoundCloudPlaylist} */ export async function soundcloud(url: string): Promise { if (!soundData) throw new Error('SoundCloud Data is missing\nDid you forgot to do authorization ?'); @@ -85,7 +96,17 @@ export async function stream(url: string, quality?: number): Promise play.setToken({ + * soundcloud : { + * client_id : clientID + * } + * })) + * ``` * @returns client ID */ export async function getFreeClientID(): Promise { @@ -133,12 +154,16 @@ export async function check_id(id: string): Promise { else return true; } /** - * Function to validate for a soundcloud url + * Validates a soundcloud url * @param url soundcloud url - * @returns "false" | 'track' | 'playlist' + * @returns + * ```ts + * false | 'track' | 'playlist' + * ``` */ export async function so_validate(url: string): Promise { - if (!url.match(pattern)) return 'search'; + if (!url.startsWith('https')) return 'search'; + if (!url.match(pattern)) return false; const data = await request( `https://api-v2.soundcloud.com/resolve?url=${url}&client_id=${soundData.client_id}` ).catch((err: Error) => err); @@ -153,7 +178,7 @@ export async function so_validate(url: string): Promise { if (!spotifyData) throw new Error('Spotify Data is missing\nDid you forgot to do authorization ?'); @@ -72,12 +83,16 @@ export async function spotify(url: string): Promise { } else throw new Error('URL is out of scope for play-dl.'); } /** - * Function to validate Spotify url - * @param url url for validation - * @returns type of url or false. + * Validate Spotify url + * @param url Spotify URL + * @returns + * ```ts + * 'track' | 'playlist' | 'album' | 'search' | false + * ``` */ export function sp_validate(url: string): 'track' | 'playlist' | 'album' | 'search' | false { - if (!url.match(pattern)) return 'search'; + if (!url.startsWith('https')) return 'search'; + if (!url.match(pattern)) return false; if (url.indexOf('track/') !== -1) { return 'track'; } else if (url.indexOf('album/') !== -1) { @@ -128,7 +143,14 @@ export async function SpotifyAuthorize(data: SpotifyDataOptions, file: boolean): return true; } /** - * Function to check if authorization token is expired or not. + * Checks if spotify token is expired or not. + * + * Update token if returned false. + * ```ts + * if (!play.is_expired()) { + * await play.refreshToken() + * } + * ``` * @returns boolean */ export function is_expired(): boolean { @@ -183,8 +205,14 @@ export async function sp_search( return results; } /** - * Function to refresh Token - * @returns boolean to check whether token is refreshed or not + * Refreshes Token + * + * ```ts + * if (!play.is_expired()) { + * await play.refreshToken() + * } + * ``` + * @returns boolean */ export async function refreshToken(): Promise { const response = await request(`https://accounts.spotify.com/api/token`, { diff --git a/play-dl/YouTube/classes/Channel.ts b/play-dl/YouTube/classes/Channel.ts index 2b4530c..51196f9 100644 --- a/play-dl/YouTube/classes/Channel.ts +++ b/play-dl/YouTube/classes/Channel.ts @@ -41,9 +41,9 @@ export class YouTubeChannel { */ url?: string; /** - * YouTube Channel Icon data. + * YouTube Channel Icons data. */ - icon?: ChannelIconInterface; + icons?: ChannelIconInterface[]; /** * YouTube Channel subscribers count. */ @@ -60,7 +60,7 @@ export class YouTubeChannel { this.artist = !!data.artist || false; this.id = data.id || null; this.url = data.url || null; - this.icon = data.icon || { url: null, width: 0, height: 0 }; + this.icons = data.icon || [{ url: null, width: 0, height: 0 }]; this.subscribers = data.subscribers || null; } @@ -71,9 +71,9 @@ export class YouTubeChannel { */ iconURL(options = { size: 0 }): string | undefined { if (typeof options.size !== 'number' || options.size < 0) throw new Error('invalid icon size'); - if (!this.icon?.url) return undefined; - const def = this.icon.url.split('=s')[1].split('-c')[0]; - return this.icon.url.replace(`=s${def}-c`, `=s${options.size}-c`); + if (!this.icons?.[0]?.url) return undefined; + const def = this.icons?.[0]?.url.split('=s')[1].split('-c')[0]; + return this.icons?.[0]?.url.replace(`=s${def}-c`, `=s${options.size}-c`); } /** * Converts Channel Class to channel name. @@ -93,7 +93,7 @@ export class YouTubeChannel { artist: this.artist, id: this.id, url: this.url, - icon: this.icon, + icons: this.icons, type: this.type, subscribers: this.subscribers }; @@ -128,7 +128,7 @@ interface ChannelJSON{ /** * YouTube Channel Icon data. */ - icon?: ChannelIconInterface; + icons?: ChannelIconInterface[]; /** * YouTube Channel subscribers count. */ diff --git a/play-dl/YouTube/utils/cookie.ts b/play-dl/YouTube/utils/cookie.ts index 45e3447..c391f7c 100644 --- a/play-dl/YouTube/utils/cookie.ts +++ b/play-dl/YouTube/utils/cookie.ts @@ -46,12 +46,23 @@ export function setCookieToken(options: { cookie: string }) { youtubeData = { cookie }; youtubeData.file = false; } - -export function cookieHeaders(headCookie: string[]) { +/** + * Updates cookies locally either in file or in memory. + * + * Example + * ```ts + * const response = ... // Any https package get function. + * + * play.cookieHeaders(response.headers['set-cookie']) + * ``` + * @param headCookie response headers['set-cookie'] array + * @returns Nothing + */ +export function cookieHeaders(headCookie: string[]): void { if (!youtubeData?.cookie) return; headCookie.forEach((x: string) => { - x.split(';').forEach((x) => { - const arr = x.split('='); + x.split(';').forEach((z) => { + const arr = z.split('='); if (arr.length <= 1) return; const key = arr.shift()?.trim() as string; const value = arr.join('=').trim(); diff --git a/play-dl/YouTube/utils/extractor.ts b/play-dl/YouTube/utils/extractor.ts index 80cfc3c..6a2798f 100644 --- a/play-dl/YouTube/utils/extractor.ts +++ b/play-dl/YouTube/utils/extractor.ts @@ -18,13 +18,25 @@ const video_id_pattern = /^[a-zA-Z\d_-]{11,12}$/; const playlist_id_pattern = /^(PL|UU|LL|RD|OL)[a-zA-Z\d_-]{16,41}$/; const DEFAULT_API_KEY = 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'; const video_pattern = - /^((?:https?:)?\/\/)?(?:(?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/; + /^((?:https?:)?\/\/)?(?:(?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|shorts\/|embed\/|v\/)?)([\w\-]+)(\S+)?$/; const playlist_pattern = /^((?:https?:)?\/\/)?(?:(?:www|m)\.)?(youtube\.com)\/(?:(playlist|watch))(.*)?((\?|\&)list=)(PL|UU|LL|RD|OL)[a-zA-Z\d_-]{16,41}(.*)?$/; /** - * Command to validate a YouTube url - * @param url Url for validation - * @returns type of url or false. + * Validate YouTube URL or ID. + * + * **CAUTION :** If your search word is 11-12 long, you might get it validated as video ID. + * + * To avoid above, add one more condition to yt_validate + * ```ts + * if (url.startsWith('https') && yt_validate(url) === 'video') { + * // YouTube Video Url. + * } + * ``` + * @param url YouTube URL OR ID + * @returns + * ``` + * 'playlist' | 'video' | 'search' | false + * ``` */ export function yt_validate(url: string): 'playlist' | 'video' | 'search' | false { if (url.indexOf('list=') === -1) { @@ -51,7 +63,7 @@ export function yt_validate(url: string): 'playlist' | 'video' | 'search' | fals } } /** - * Function to extract ID of YouTube url. + * Extract ID of YouTube url. * @param url ID or url of YouTube * @returns ID of video or playlist. */ @@ -75,9 +87,32 @@ export function extractID(url: string): string { } /** * 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. + * + * Example + * ```ts + * const video = await play.video_basic_info('youtube video url') + * + * const res = ... // Any https package get function. + * const video = await play.video_basic_info(res.body, { htmldata : true }) + * + * const video = await play.video_basic_info('youtube video url', { proxy : [{ + host : "IP or hostname", + port : 8080, + authentication: { + username: 'username'; + password: 'very secret'; + } + }] }) // Authentication is optional. + + // OR + + const video = await play.video_basic_info('youtube video url', { proxy : ['url'] }) + * ``` + * @param url YouTube url or ID or html body data + * @param options Video Info Options + * - `Proxy[]` proxy : sends data through a proxy + * - `boolean` htmldata : given data is html data or not + * @returns Video Basic Info {@link InfoData}. */ export async function video_basic_info(url: string, options: InfoOptions = {}) : Promise { let body: string; @@ -112,11 +147,11 @@ export async function video_basic_info(url: string, options: InfoOptions = {}) : player_response.playabilityStatus.errorScreen.playerKavRenderer?.reason.simpleText }` ); + const ownerInfo = initial_response.contents.twoColumnWatchNextResults.results?.results?.contents[1]?.videoSecondaryInfoRenderer + ?.owner?.videoOwnerRenderer const 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]; + ownerInfo?.badges && + ownerInfo?.badges[0]; const html5player = `https://www.youtube.com${body.split('"jsUrl":"')[1].split('"')[0]}`; const related: string[] = []; initial_response.contents.twoColumnWatchNextResults.secondaryResults.secondaryResults.results.forEach( @@ -142,7 +177,8 @@ export async function video_basic_info(url: string, options: InfoOptions = {}) : id: vid.channelId, url: `https://www.youtube.com/channel/${vid.channelId}`, verified: Boolean(badge?.metadataBadgeRenderer?.style?.toLowerCase().includes('verified')), - artist: Boolean(badge?.metadataBadgeRenderer?.style?.toLowerCase().includes('artist')) + artist: Boolean(badge?.metadataBadgeRenderer?.style?.toLowerCase().includes('artist')), + icons : ownerInfo?.thumbnail?.thumbnails || undefined }, views: vid.viewCount, tags: vid.keywords, @@ -182,14 +218,40 @@ function parseSeconds(seconds: number): string { 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. + * Gets data from YouTube url or ID or html body data and deciphers it. + * ``` + * video_basic_info + decipher_info = video_info + * ``` + * + * Example + * ```ts + * const video = await play.video_info('youtube video url') + * + * const res = ... // Any https package get function. + * const video = await play.video_info(res.body, { htmldata : true }) + * + * const video = await play.video_info('youtube video url', { proxy : [{ + host : "IP or hostname", + port : 8080, + authentication: { + username: 'username'; + password: 'very secret'; + } + }] }) // Authentication is optional. + + // OR + + const video = await play.video_info('youtube video url', { proxy : ['url'] }) + * ``` + * @param url YouTube url or ID or html body data + * @param options Video Info Options + * - `Proxy[]` proxy : sends data through a proxy + * - `boolean` htmldata : given data is html data or not + * @returns Deciphered Video Info {@link InfoData}. */ export async function video_info(url: string, options: InfoOptions = {}): Promise { const data = await video_basic_info(url, options); - if (data.LiveStreamData.isLive === true && data.LiveStreamData.hlsManifestUrl !== null) { + if (data.LiveStreamData.isLive === true && data.LiveStreamData.dashManifestUrl !== null) { return data; } else if (data.format[0].signatureCipher || data.format[0].cipher) { data.format = await format_decipher(data.format, data.html5player); @@ -200,11 +262,11 @@ export async function video_info(url: string, options: InfoOptions = {}): Promis } /** * Function uses data from video_basic_info and deciphers it if it contains signatures. - * @param data basic_video_info data - * @returns Data containing video_details, LiveStreamData and formats of video url. + * @param data Data - {@link InfoData} + * @returns Deciphered Video Info {@link InfoData} */ export async function decipher_info(data: InfoData) { - if (data.LiveStreamData.isLive === true && data.LiveStreamData.hlsManifestUrl !== null) { + if (data.LiveStreamData.isLive === true && data.LiveStreamData.dashManifestUrl !== null) { return data; } else if (data.format[0].signatureCipher || data.format[0].cipher) { data.format = await format_decipher(data.format, data.html5player); @@ -214,9 +276,32 @@ export async function decipher_info(data: InfoData) { } } /** - * Function to get YouTube playlist info from a playlist url. + * Gets YouTube playlist info from a playlist url. + * + * Example + * ```ts + * const playlist = await play.playlist_info('youtube playlist url') + * + * const playlist = await play.playlist_info('youtube playlist url', { incomplete : true }) + * + * const playlist = await play.playlist_info('youtube playlist url', { proxy : [{ + host : "IP or hostname", + port : 8080, + authentication: { + username: 'username'; + password: 'very secret'; + } + }] }) // Authentication is optional. + + // OR + + const playlist = await play.playlist_info('youtube playlist url', { proxy : ['url'] }) + * ``` * @param url Playlist URL - * @param options incomplete and proxy to add. + * @param options Playlist Info Options + * - `boolean` incomplete : If set to true, parses playlist with hidden videos. + * - `Proxy[]` proxy : sends data through a proxy + * * @returns YouTube Playlist */ export async function playlist_info(url: string, options: PlaylistOptions = {}): Promise { diff --git a/play-dl/YouTube/utils/parser.ts b/play-dl/YouTube/utils/parser.ts index 7e85a38..0062bb6 100644 --- a/play-dl/YouTube/utils/parser.ts +++ b/play-dl/YouTube/utils/parser.ts @@ -134,14 +134,8 @@ export function parseVideo(data?: any): YouTubeVideo { data.videoRenderer.ownerText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl || data.videoRenderer.ownerText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url }`, - icon: { - url: data.videoRenderer.channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer.thumbnail - .thumbnails[0].url, - width: data.videoRenderer.channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer.thumbnail - .thumbnails[0].width, - height: data.videoRenderer.channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer.thumbnail - .thumbnails[0].height - }, + icons : data.videoRenderer.channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer.thumbnail + .thumbnails, verified: Boolean(badge?.metadataBadgeRenderer?.style?.toLowerCase().includes('verified')), artist: Boolean(badge?.metadataBadgeRenderer?.style?.toLowerCase().includes('artist')) }, diff --git a/play-dl/index.ts b/play-dl/index.ts index 15ecf62..3ca0ff4 100644 --- a/play-dl/index.ts +++ b/play-dl/index.ts @@ -61,13 +61,13 @@ import { YouTubePlayList } from './YouTube/classes/Playlist'; import { YouTubeChannel } from './YouTube/classes/Channel'; import { SpotifyAlbum, SpotifyPlaylist, SpotifyTrack } from './Spotify/classes'; import { DeezerAlbum, DeezerPlaylist, DeezerTrack } from './Deezer/classes'; + /** * Main stream Command for streaming through various sources * @param url The video / track url to make stream of * @param options contains quality, cookie and proxy to set for stream * @returns YouTube / SoundCloud Stream to play */ - export async function stream(url: string, options: StreamOptions = {}): Promise { if (url.length === 0) throw new Error('Stream URL has a length of 0. Check your url again.'); if (url.indexOf('spotify') !== -1) { @@ -91,7 +91,6 @@ export async function stream(url: string, options: StreamOptions = {}): Promise< * @returns Array of YouTube or Spotify or SoundCloud or Deezer deezer?: 'track' | 'playlist' | 'album'; */ - export async function search( query: string, options: { source : { deezer : "album" } } & SearchOptions) : Promise; export async function search( query: string, options: { source : { deezer : "playlist" } } & SearchOptions) : Promise; export async function search( query: string, options: { source : { deezer : "track" } } & SearchOptions) : Promise; @@ -104,6 +103,8 @@ export async function search( query: string, options: { source : { spotify : "tr export async function search( query: string, options: { source : { youtube : "channel" } } & SearchOptions) : Promise; export async function search( query: string, options: { source : { youtube : "playlist" } } & SearchOptions) : Promise; export async function search( query: string, options: { source : { youtube : "video" } } & SearchOptions) : Promise; +export async function search( query: string, options: { limit : number } & SearchOptions ) : Promise; +export async function search( query: string, options? : SearchOptions) : Promise; export async function search( query: string, options: SearchOptions = {} diff --git a/play-dl/token.ts b/play-dl/token.ts index 17237b9..89600b8 100644 --- a/play-dl/token.ts +++ b/play-dl/token.ts @@ -16,7 +16,18 @@ interface tokenOptions { cookie: string; }; } - +/** + * Sets + * + * i> YouTube :- cookies. + * + * ii> SoundCloud :- client ID. + * + * iii> Spotify :- client ID, client secret, refresh token, market. + * + * locally in memory. + * @param options {@link tokenOptions} + */ export function setToken(options: tokenOptions) { if (options.spotify) setSpotifyToken(options.spotify); if (options.soundcloud) setSoundCloudToken(options.soundcloud);