1 line
332 KiB
Plaintext
1 line
332 KiB
Plaintext
{"version":3,"sources":["../play-dl/Request/index.ts","../play-dl/YouTube/utils/cookie.ts","../play-dl/Request/useragents.json","../play-dl/Request/useragent.ts","../play-dl/YouTube/classes/LiveStream.ts","../play-dl/YouTube/utils/cipher.ts","../play-dl/YouTube/classes/Channel.ts","../play-dl/YouTube/classes/Thumbnail.ts","../play-dl/YouTube/classes/Video.ts","../play-dl/YouTube/classes/Playlist.ts","../play-dl/YouTube/utils/extractor.ts","../play-dl/YouTube/classes/WebmSeeker.ts","../play-dl/YouTube/classes/SeekStream.ts","../play-dl/YouTube/stream.ts","../play-dl/YouTube/utils/parser.ts","../play-dl/YouTube/search.ts","../play-dl/Spotify/classes.ts","../play-dl/Spotify/index.ts","../play-dl/SoundCloud/index.ts","../play-dl/SoundCloud/classes.ts","../play-dl/Deezer/index.ts","../play-dl/Deezer/classes.ts","../play-dl/token.ts","../play-dl/index.ts"],"sourcesContent":["import { IncomingMessage } from 'node:http';\r\nimport { RequestOptions, request as httpsRequest } from 'node:https';\r\nimport { URL } from 'node:url';\r\nimport { BrotliDecompress, Deflate, Gunzip, createGunzip, createBrotliDecompress, createDeflate } from 'node:zlib';\r\nimport { cookieHeaders, getCookies } from '../YouTube/utils/cookie';\r\nimport { getRandomUserAgent } from './useragent';\r\n\r\ninterface RequestOpts extends RequestOptions {\r\n body?: string;\r\n method?: 'GET' | 'POST' | 'HEAD';\r\n cookies?: boolean;\r\n cookieJar?: { [key: string]: string };\r\n}\r\n\r\n/**\r\n * Main module which play-dl uses to make a request to stream url.\r\n * @param url URL to make https request to\r\n * @param options Request options for https request\r\n * @returns IncomingMessage from the request\r\n */\r\nexport function request_stream(req_url: string, options: RequestOpts = { method: 'GET' }): Promise<IncomingMessage> {\r\n return new Promise(async (resolve, reject) => {\r\n let res = await https_getter(req_url, options).catch((err: Error) => err);\r\n if (res instanceof Error) {\r\n reject(res);\r\n return;\r\n }\r\n if (Number(res.statusCode) >= 300 && Number(res.statusCode) < 400) {\r\n res = await request_stream(res.headers.location as string, options);\r\n }\r\n resolve(res);\r\n });\r\n}\r\n/**\r\n * Makes a request and follows redirects if necessary\r\n * @param req_url URL to make https request to\r\n * @param options Request options for https request\r\n * @returns A promise with the final response object\r\n */\r\nfunction internalRequest(req_url: string, options: RequestOpts = { method: 'GET' }): Promise<IncomingMessage> {\r\n return new Promise(async (resolve, reject) => {\r\n let res = await https_getter(req_url, options).catch((err: Error) => err);\r\n if (res instanceof Error) {\r\n reject(res);\r\n return;\r\n }\r\n if (Number(res.statusCode) >= 300 && Number(res.statusCode) < 400) {\r\n res = await internalRequest(res.headers.location as string, options);\r\n } else if (Number(res.statusCode) > 400) {\r\n reject(new Error(`Got ${res.statusCode} from the request`));\r\n return;\r\n }\r\n resolve(res);\r\n });\r\n}\r\n/**\r\n * Main module which play-dl uses to make a request\r\n * @param url URL to make https request to\r\n * @param options Request options for https request\r\n * @returns body of that request\r\n */\r\nexport function request(req_url: string, options: RequestOpts = { method: 'GET' }): Promise<string> {\r\n return new Promise(async (resolve, reject) => {\r\n let cookies_added = false;\r\n if (options.cookies) {\r\n let cook = getCookies();\r\n if (typeof cook === 'string' && options.headers) {\r\n Object.assign(options.headers, { cookie: cook });\r\n cookies_added = true;\r\n }\r\n }\r\n if (options.cookieJar) {\r\n const cookies = [];\r\n for (const cookie of Object.entries(options.cookieJar)) {\r\n cookies.push(cookie.join('='));\r\n }\r\n\r\n if (cookies.length !== 0) {\r\n if (!options.headers) options.headers = {};\r\n const existingCookies = cookies_added ? `; ${options.headers.cookie}` : '';\r\n Object.assign(options.headers, { cookie: `${cookies.join('; ')}${existingCookies}` });\r\n }\r\n }\r\n if (options.headers) {\r\n options.headers = {\r\n ...options.headers,\r\n 'accept-encoding': 'gzip, deflate, br',\r\n 'user-agent': getRandomUserAgent()\r\n };\r\n }\r\n const res = await internalRequest(req_url, options).catch((err: Error) => err);\r\n if (res instanceof Error) {\r\n reject(res);\r\n return;\r\n }\r\n if (res.headers && res.headers['set-cookie']) {\r\n if (options.cookieJar) {\r\n for (const cookie of res.headers['set-cookie']) {\r\n const parts = cookie.split(';')[0].trim().split('=');\r\n options.cookieJar[parts.shift() as string] = parts.join('=');\r\n }\r\n }\r\n if (cookies_added) {\r\n cookieHeaders(res.headers['set-cookie']);\r\n }\r\n }\r\n const data: string[] = [];\r\n let decoder: BrotliDecompress | Gunzip | Deflate | undefined = undefined;\r\n const encoding = res.headers['content-encoding'];\r\n if (encoding === 'gzip') decoder = createGunzip();\r\n else if (encoding === 'br') decoder = createBrotliDecompress();\r\n else if (encoding === 'deflate') decoder = createDeflate();\r\n\r\n if (decoder) {\r\n res.pipe(decoder);\r\n decoder.setEncoding('utf-8');\r\n decoder.on('data', (c) => data.push(c));\r\n decoder.on('end', () => resolve(data.join('')));\r\n } else {\r\n res.setEncoding('utf-8');\r\n res.on('data', (c) => data.push(c));\r\n res.on('end', () => resolve(data.join('')));\r\n }\r\n });\r\n}\r\n\r\nexport function request_resolve_redirect(url: string): Promise<string> {\r\n return new Promise(async (resolve, reject) => {\r\n let res = await https_getter(url, { method: 'HEAD' }).catch((err: Error) => err);\r\n if (res instanceof Error) {\r\n reject(res);\r\n return;\r\n }\r\n const statusCode = Number(res.statusCode);\r\n if (statusCode < 300) {\r\n resolve(url);\r\n } else if (statusCode < 400) {\r\n const resolved = await request_resolve_redirect(res.headers.location as string).catch((err) => err);\r\n if (resolved instanceof Error) {\r\n reject(resolved);\r\n return;\r\n }\r\n\r\n resolve(resolved);\r\n } else {\r\n reject(new Error(`${res.statusCode}: ${res.statusMessage}, ${url}`));\r\n }\r\n });\r\n}\r\n\r\nexport function request_content_length(url: string): Promise<number> {\r\n return new Promise(async (resolve, reject) => {\r\n let res = await https_getter(url, { method: 'HEAD' }).catch((err: Error) => err);\r\n if (res instanceof Error) {\r\n reject(res);\r\n return;\r\n }\r\n const statusCode = Number(res.statusCode);\r\n if (statusCode < 300) {\r\n resolve(Number(res.headers['content-length']));\r\n } else if (statusCode < 400) {\r\n const newURL = await request_resolve_redirect(res.headers.location as string).catch((err) => err);\r\n if (newURL instanceof Error) {\r\n reject(newURL);\r\n return;\r\n }\r\n\r\n const res2 = await request_content_length(newURL).catch((err) => err);\r\n if (res2 instanceof Error) {\r\n reject(res2);\r\n return;\r\n }\r\n\r\n resolve(res2);\r\n } else {\r\n reject(\r\n new Error(`Failed to get content length with error: ${res.statusCode}, ${res.statusMessage}, ${url}`)\r\n );\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Main module that play-dl uses for making a https request\r\n * @param req_url URL to make https request to\r\n * @param options Request options for https request\r\n * @returns Incoming Message from the https request\r\n */\r\nfunction https_getter(req_url: string, options: RequestOpts = {}): Promise<IncomingMessage> {\r\n return new Promise((resolve, reject) => {\r\n const s = new URL(req_url);\r\n options.method ??= 'GET';\r\n const req_options: RequestOptions = {\r\n host: s.hostname,\r\n path: s.pathname + s.search,\r\n headers: options.headers ?? {},\r\n method: options.method\r\n };\r\n\r\n const req = httpsRequest(req_options, resolve);\r\n req.on('error', (err) => {\r\n reject(err);\r\n });\r\n if (options.method === 'POST') req.write(options.body);\r\n req.end();\r\n });\r\n}\r\n","import { existsSync, readFileSync, writeFileSync } from 'node:fs';\r\n\r\nlet youtubeData: youtubeDataOptions;\r\nif (existsSync('.data/youtube.data')) {\r\n youtubeData = JSON.parse(readFileSync('.data/youtube.data', 'utf-8'));\r\n youtubeData.file = true;\r\n}\r\n\r\ninterface youtubeDataOptions {\r\n cookie?: Object;\r\n file?: boolean;\r\n}\r\n\r\nexport function getCookies(): undefined | string {\r\n let result = '';\r\n if (!youtubeData?.cookie) return undefined;\r\n for (const [key, value] of Object.entries(youtubeData.cookie)) {\r\n result += `${key}=${value};`;\r\n }\r\n return result;\r\n}\r\n\r\nexport function setCookie(key: string, value: string): boolean {\r\n if (!youtubeData?.cookie) return false;\r\n key = key.trim();\r\n value = value.trim();\r\n Object.assign(youtubeData.cookie, { [key]: value });\r\n return true;\r\n}\r\n\r\nexport function uploadCookie() {\r\n if (youtubeData.cookie && youtubeData.file)\r\n writeFileSync('.data/youtube.data', JSON.stringify(youtubeData, undefined, 4));\r\n}\r\n\r\nexport function setCookieToken(options: { cookie: string }) {\r\n let cook = options.cookie;\r\n let cookie: Object = {};\r\n cook.split(';').forEach((x) => {\r\n const arr = x.split('=');\r\n if (arr.length <= 1) return;\r\n const key = arr.shift()?.trim() as string;\r\n const value = arr.join('=').trim();\r\n Object.assign(cookie, { [key]: value });\r\n });\r\n youtubeData = { cookie };\r\n youtubeData.file = false;\r\n}\r\n\r\n/**\r\n * Updates cookies locally either in file or in memory.\r\n *\r\n * Example\r\n * ```ts\r\n * const response = ... // Any https package get function.\r\n *\r\n * play.cookieHeaders(response.headers['set-cookie'])\r\n * ```\r\n * @param headCookie response headers['set-cookie'] array\r\n * @returns Nothing\r\n */\r\nexport function cookieHeaders(headCookie: string[]): void {\r\n if (!youtubeData?.cookie) return;\r\n headCookie.forEach((x: string) => {\r\n x.split(';').forEach((z) => {\r\n const arr = z.split('=');\r\n if (arr.length <= 1) return;\r\n const key = arr.shift()?.trim() as string;\r\n const value = arr.join('=').trim();\r\n setCookie(key, value);\r\n });\r\n });\r\n uploadCookie();\r\n}\r\n","[\r\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36\",\r\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53\",\r\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.30\",\r\n \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36\",\r\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36\",\r\n \"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 YaBrowser/19.10.3.281 Yowser/2.5 Safari/537.36\",\r\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36\",\r\n \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\",\r\n \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\",\r\n \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36\"\r\n]","import useragents from './useragents.json';\r\n\r\nexport function setUserAgent(array: string[]): void {\r\n useragents.push(...array);\r\n}\r\n\r\nfunction getRandomInt(min: number, max: number): number {\r\n min = Math.ceil(min);\r\n max = Math.floor(max);\r\n return Math.floor(Math.random() * (max - min + 1)) + min;\r\n}\r\n\r\nexport function getRandomUserAgent() {\r\n const random = getRandomInt(0, useragents.length - 1);\r\n return useragents[random];\r\n}\r\n","import { Readable } from 'node:stream';\r\nimport { IncomingMessage } from 'node:http';\r\nimport { parseAudioFormats, StreamOptions, StreamType } from '../stream';\r\nimport { request, request_stream } from '../../Request';\r\nimport { video_stream_info } from '../utils/extractor';\r\nimport { URL } from 'node:url';\r\n\r\n/**\r\n * YouTube Live Stream class for playing audio from Live Stream videos.\r\n */\r\nexport class LiveStream {\r\n /**\r\n * Readable Stream through which data passes\r\n */\r\n stream: Readable;\r\n /**\r\n * Type of audio data that we recieved from live stream youtube url.\r\n */\r\n type: StreamType;\r\n /**\r\n * Incoming message that we recieve.\r\n *\r\n * Storing this is essential.\r\n * This helps to destroy the TCP connection completely if you stopped player in between the stream\r\n */\r\n private request?: IncomingMessage;\r\n /**\r\n * Timer that creates loop from interval time provided.\r\n */\r\n private normal_timer?: Timer;\r\n /**\r\n * Timer used to update dash url so as to avoid 404 errors after long hours of streaming.\r\n *\r\n * It updates dash_url every 30 minutes.\r\n */\r\n private dash_timer: Timer;\r\n /**\r\n * Given Dash URL.\r\n */\r\n private dash_url: string;\r\n /**\r\n * Base URL in dash manifest file.\r\n */\r\n private base_url: string;\r\n /**\r\n * Interval to fetch data again to dash url.\r\n */\r\n private interval: number;\r\n /**\r\n * Timer used to update dash url so as to avoid 404 errors after long hours of streaming.\r\n *\r\n * It updates dash_url every 30 minutes.\r\n */\r\n private video_url: string;\r\n /**\r\n * No of segments of data to add in stream before starting to loop\r\n */\r\n private precache: number;\r\n /**\r\n * Segment sequence number\r\n */\r\n private sequence: number;\r\n /**\r\n * Live Stream Class Constructor\r\n * @param dash_url dash manifest URL\r\n * @param target_interval interval time for fetching dash data again\r\n * @param video_url Live Stream video url.\r\n */\r\n constructor(dash_url: string, interval: number, video_url: string, precache?: number) {\r\n this.stream = new Readable({ highWaterMark: 5 * 1000 * 1000, read() {} });\r\n this.type = StreamType.Arbitrary;\r\n this.sequence = 0;\r\n this.dash_url = dash_url;\r\n this.base_url = '';\r\n this.interval = interval;\r\n this.video_url = video_url;\r\n this.precache = precache || 3;\r\n this.dash_timer = new Timer(() => {\r\n this.dash_updater();\r\n this.dash_timer.reuse();\r\n }, 1800);\r\n this.stream.on('close', () => {\r\n this.cleanup();\r\n });\r\n this.initialize_dash();\r\n }\r\n /**\r\n * This cleans every used variable in class.\r\n *\r\n * This is used to prevent re-use of this class and helping garbage collector to collect it.\r\n */\r\n private cleanup() {\r\n this.normal_timer?.destroy();\r\n this.dash_timer.destroy();\r\n this.request?.destroy();\r\n this.video_url = '';\r\n this.request = undefined;\r\n this.dash_url = '';\r\n this.base_url = '';\r\n this.interval = 0;\r\n }\r\n /**\r\n * Updates dash url.\r\n *\r\n * Used by dash_timer for updating dash_url every 30 minutes.\r\n */\r\n private async dash_updater() {\r\n const info = await video_stream_info(this.video_url);\r\n if (info.LiveStreamData.dashManifestUrl) this.dash_url = info.LiveStreamData.dashManifestUrl;\r\n return this.initialize_dash();\r\n }\r\n /**\r\n * Initializes dash after getting dash url.\r\n *\r\n * Start if it is first time of initialishing dash function.\r\n */\r\n private async initialize_dash() {\r\n const response = await request(this.dash_url);\r\n const audioFormat = response\r\n .split('<AdaptationSet id=\"0\"')[1]\r\n .split('</AdaptationSet>')[0]\r\n .split('</Representation>');\r\n if (audioFormat[audioFormat.length - 1] === '') audioFormat.pop();\r\n this.base_url = audioFormat[audioFormat.length - 1].split('<BaseURL>')[1].split('</BaseURL>')[0];\r\n await request_stream(`https://${new URL(this.base_url).host}/generate_204`);\r\n if (this.sequence === 0) {\r\n const list = audioFormat[audioFormat.length - 1]\r\n .split('<SegmentList>')[1]\r\n .split('</SegmentList>')[0]\r\n .replaceAll('<SegmentURL media=\"', '')\r\n .split('\"/>');\r\n if (list[list.length - 1] === '') list.pop();\r\n if (list.length > this.precache) list.splice(0, list.length - this.precache);\r\n this.sequence = Number(list[0].split('sq/')[1].split('/')[0]);\r\n this.first_data(list.length);\r\n }\r\n }\r\n /**\r\n * Used only after initializing dash function first time.\r\n * @param len Length of data that you want to\r\n */\r\n private async first_data(len: number) {\r\n for (let i = 1; i <= len; i++) {\r\n await new Promise(async (resolve) => {\r\n const stream = await request_stream(this.base_url + 'sq/' + this.sequence).catch((err: Error) => err);\r\n if (stream instanceof Error) {\r\n this.stream.emit('error', stream);\r\n return;\r\n }\r\n this.request = stream;\r\n stream.on('data', (c) => {\r\n this.stream.push(c);\r\n });\r\n stream.on('end', () => {\r\n this.sequence++;\r\n resolve('');\r\n });\r\n stream.once('error', (err) => {\r\n this.stream.emit('error', err);\r\n });\r\n });\r\n }\r\n this.normal_timer = new Timer(() => {\r\n this.loop();\r\n this.normal_timer?.reuse();\r\n }, this.interval);\r\n }\r\n /**\r\n * This loops function in Live Stream Class.\r\n *\r\n * Gets next segment and push it.\r\n */\r\n private loop() {\r\n return new Promise(async (resolve) => {\r\n const stream = await request_stream(this.base_url + 'sq/' + this.sequence).catch((err: Error) => err);\r\n if (stream instanceof Error) {\r\n this.stream.emit('error', stream);\r\n return;\r\n }\r\n this.request = stream;\r\n stream.on('data', (c) => {\r\n this.stream.push(c);\r\n });\r\n stream.on('end', () => {\r\n this.sequence++;\r\n resolve('');\r\n });\r\n stream.once('error', (err) => {\r\n this.stream.emit('error', err);\r\n });\r\n });\r\n }\r\n /**\r\n * Deprecated Functions\r\n */\r\n pause() {}\r\n /**\r\n * Deprecated Functions\r\n */\r\n resume() {}\r\n}\r\n/**\r\n * YouTube Stream Class for playing audio from normal videos.\r\n */\r\nexport class Stream {\r\n /**\r\n * Readable Stream through which data passes\r\n */\r\n stream: Readable;\r\n /**\r\n * Type of audio data that we recieved from normal youtube url.\r\n */\r\n type: StreamType;\r\n /**\r\n * Audio Endpoint Format Url to get data from.\r\n */\r\n private url: string;\r\n /**\r\n * Used to calculate no of bytes data that we have recieved\r\n */\r\n private bytes_count: number;\r\n /**\r\n * Calculate per second bytes by using contentLength (Total bytes) / Duration (in seconds)\r\n */\r\n private per_sec_bytes: number;\r\n /**\r\n * Total length of audio file in bytes\r\n */\r\n private content_length: number;\r\n /**\r\n * YouTube video url. [ Used only for retrying purposes only. ]\r\n */\r\n private video_url: string;\r\n /**\r\n * Timer for looping data every 265 seconds.\r\n */\r\n private timer: Timer;\r\n /**\r\n * Quality given by user. [ Used only for retrying purposes only. ]\r\n */\r\n private quality: number;\r\n /**\r\n * Incoming message that we recieve.\r\n *\r\n * Storing this is essential.\r\n * This helps to destroy the TCP connection completely if you stopped player in between the stream\r\n */\r\n private request: IncomingMessage | null;\r\n /**\r\n * YouTube Stream Class constructor\r\n * @param url Audio Endpoint url.\r\n * @param type Type of Stream\r\n * @param duration Duration of audio playback [ in seconds ]\r\n * @param contentLength Total length of Audio file in bytes.\r\n * @param video_url YouTube video url.\r\n * @param options Options provided to stream function.\r\n */\r\n constructor(\r\n url: string,\r\n type: StreamType,\r\n duration: number,\r\n contentLength: number,\r\n video_url: string,\r\n options: StreamOptions\r\n ) {\r\n this.stream = new Readable({ highWaterMark: 5 * 1000 * 1000, read() {} });\r\n this.url = url;\r\n this.quality = options.quality as number;\r\n this.type = type;\r\n this.bytes_count = 0;\r\n this.video_url = video_url;\r\n this.per_sec_bytes = Math.ceil(contentLength / duration);\r\n this.content_length = contentLength;\r\n this.request = null;\r\n this.timer = new Timer(() => {\r\n this.timer.reuse();\r\n this.loop();\r\n }, 265);\r\n this.stream.on('close', () => {\r\n this.timer.destroy();\r\n this.cleanup();\r\n });\r\n this.loop();\r\n }\r\n /**\r\n * Retry if we get 404 or 403 Errors.\r\n */\r\n private async retry() {\r\n const info = await video_stream_info(this.video_url);\r\n const audioFormat = parseAudioFormats(info.format);\r\n this.url = audioFormat[this.quality].url;\r\n }\r\n /**\r\n * This cleans every used variable in class.\r\n *\r\n * This is used to prevent re-use of this class and helping garbage collector to collect it.\r\n */\r\n private cleanup() {\r\n this.request?.destroy();\r\n this.request = null;\r\n this.url = '';\r\n }\r\n /**\r\n * Getting data from audio endpoint url and passing it to stream.\r\n *\r\n * If 404 or 403 occurs, it will retry again.\r\n */\r\n private async loop() {\r\n if (this.stream.destroyed) {\r\n this.timer.destroy();\r\n this.cleanup();\r\n return;\r\n }\r\n const end: number = this.bytes_count + this.per_sec_bytes * 300;\r\n const stream = await request_stream(this.url, {\r\n headers: {\r\n range: `bytes=${this.bytes_count}-${end >= this.content_length ? '' : end}`\r\n }\r\n }).catch((err: Error) => err);\r\n if (stream instanceof Error) {\r\n this.stream.emit('error', stream);\r\n this.bytes_count = 0;\r\n this.per_sec_bytes = 0;\r\n this.cleanup();\r\n return;\r\n }\r\n if (Number(stream.statusCode) >= 400) {\r\n this.cleanup();\r\n await this.retry();\r\n this.timer.reuse();\r\n this.loop();\r\n return;\r\n }\r\n this.request = stream;\r\n stream.on('data', (c) => {\r\n this.stream.push(c);\r\n });\r\n\r\n stream.once('error', async () => {\r\n this.cleanup();\r\n await this.retry();\r\n this.timer.reuse();\r\n this.loop();\r\n });\r\n\r\n stream.on('data', (chunk: any) => {\r\n this.bytes_count += chunk.length;\r\n });\r\n\r\n stream.on('end', () => {\r\n if (end >= this.content_length) {\r\n this.timer.destroy();\r\n this.stream.push(null);\r\n this.cleanup();\r\n }\r\n });\r\n }\r\n /**\r\n * Pauses timer.\r\n * Stops running of loop.\r\n *\r\n * Useful if you don't want to get excess data to be stored in stream.\r\n */\r\n pause() {\r\n this.timer.pause();\r\n }\r\n /**\r\n * Resumes timer.\r\n * Starts running of loop.\r\n */\r\n resume() {\r\n this.timer.resume();\r\n }\r\n}\r\n/**\r\n * Timer Class.\r\n *\r\n * setTimeout + extra features ( re-starting, pausing, resuming ).\r\n */\r\nexport class Timer {\r\n /**\r\n * Boolean for checking if Timer is destroyed or not.\r\n */\r\n private destroyed: boolean;\r\n /**\r\n * Boolean for checking if Timer is paused or not.\r\n */\r\n private paused: boolean;\r\n /**\r\n * setTimeout function\r\n */\r\n private timer: NodeJS.Timer;\r\n /**\r\n * Callback to be executed once timer finishes.\r\n */\r\n private callback: () => void;\r\n /**\r\n * Seconds time when it is started.\r\n */\r\n private time_start: number;\r\n /**\r\n * Total time left.\r\n */\r\n private time_left: number;\r\n /**\r\n * Total time given by user [ Used only for re-using timer. ]\r\n */\r\n private time_total: number;\r\n /**\r\n * Constructor for Timer Class\r\n * @param callback Function to execute when timer is up.\r\n * @param time Total time to wait before execution.\r\n */\r\n constructor(callback: () => void, time: number) {\r\n this.callback = callback;\r\n this.time_total = time;\r\n this.time_left = time;\r\n this.paused = false;\r\n this.destroyed = false;\r\n this.time_start = process.hrtime()[0];\r\n this.timer = setTimeout(this.callback, this.time_total * 1000);\r\n }\r\n /**\r\n * Pauses Timer\r\n * @returns Boolean to tell that if it is paused or not.\r\n */\r\n pause() {\r\n if (!this.paused && !this.destroyed) {\r\n this.paused = true;\r\n clearTimeout(this.timer);\r\n this.time_left = this.time_left - (process.hrtime()[0] - this.time_start);\r\n return true;\r\n } else return false;\r\n }\r\n /**\r\n * Resumes Timer\r\n * @returns Boolean to tell that if it is resumed or not.\r\n */\r\n resume() {\r\n if (this.paused && !this.destroyed) {\r\n this.paused = false;\r\n this.time_start = process.hrtime()[0];\r\n this.timer = setTimeout(this.callback, this.time_left * 1000);\r\n return true;\r\n } else return false;\r\n }\r\n /**\r\n * Reusing of timer\r\n * @returns Boolean to tell if it is re-used or not.\r\n */\r\n reuse() {\r\n if (!this.destroyed) {\r\n clearTimeout(this.timer);\r\n this.time_left = this.time_total;\r\n this.paused = false;\r\n this.time_start = process.hrtime()[0];\r\n this.timer = setTimeout(this.callback, this.time_total * 1000);\r\n return true;\r\n } else return false;\r\n }\r\n /**\r\n * Destroy timer.\r\n *\r\n * It can't be used again.\r\n */\r\n destroy() {\r\n clearTimeout(this.timer);\r\n this.destroyed = true;\r\n this.callback = () => {};\r\n this.time_total = 0;\r\n this.time_left = 0;\r\n this.paused = false;\r\n this.time_start = 0;\r\n }\r\n}\r\n","import { URL, URLSearchParams } from 'node:url';\r\nimport { request } from './../../Request';\r\n\r\ninterface formatOptions {\r\n url?: string;\r\n sp?: string;\r\n signatureCipher?: string;\r\n cipher?: string;\r\n s?: string;\r\n}\r\n// RegExp for various js functions\r\nconst var_js = '[a-zA-Z_\\\\$]\\\\w*';\r\nconst singlequote_js = `'[^'\\\\\\\\]*(:?\\\\\\\\[\\\\s\\\\S][^'\\\\\\\\]*)*'`;\r\nconst duoblequote_js = `\"[^\"\\\\\\\\]*(:?\\\\\\\\[\\\\s\\\\S][^\"\\\\\\\\]*)*\"`;\r\nconst quote_js = `(?:${singlequote_js}|${duoblequote_js})`;\r\nconst key_js = `(?:${var_js}|${quote_js})`;\r\nconst prop_js = `(?:\\\\.${var_js}|\\\\[${quote_js}\\\\])`;\r\nconst empty_js = `(?:''|\"\")`;\r\nconst reverse_function = ':function\\\\(a\\\\)\\\\{' + '(?:return )?a\\\\.reverse\\\\(\\\\)' + '\\\\}';\r\nconst slice_function = ':function\\\\(a,b\\\\)\\\\{' + 'return a\\\\.slice\\\\(b\\\\)' + '\\\\}';\r\nconst splice_function = ':function\\\\(a,b\\\\)\\\\{' + 'a\\\\.splice\\\\(0,b\\\\)' + '\\\\}';\r\nconst swap_function =\r\n ':function\\\\(a,b\\\\)\\\\{' +\r\n 'var c=a\\\\[0\\\\];a\\\\[0\\\\]=a\\\\[b(?:%a\\\\.length)?\\\\];a\\\\[b(?:%a\\\\.length)?\\\\]=c(?:;return a)?' +\r\n '\\\\}';\r\nconst obj_regexp = new RegExp(\r\n `var (${var_js})=\\\\{((?:(?:${key_js}${reverse_function}|${key_js}${slice_function}|${key_js}${splice_function}|${key_js}${swap_function}),?\\\\r?\\\\n?)+)\\\\};`\r\n);\r\nconst function_regexp = new RegExp(\r\n `${\r\n `function(?: ${var_js})?\\\\(a\\\\)\\\\{` + `a=a\\\\.split\\\\(${empty_js}\\\\);\\\\s*` + `((?:(?:a=)?${var_js}`\r\n }${prop_js}\\\\(a,\\\\d+\\\\);)+)` +\r\n `return a\\\\.join\\\\(${empty_js}\\\\)` +\r\n `\\\\}`\r\n);\r\nconst reverse_regexp = new RegExp(`(?:^|,)(${key_js})${reverse_function}`, 'm');\r\nconst slice_regexp = new RegExp(`(?:^|,)(${key_js})${slice_function}`, 'm');\r\nconst splice_regexp = new RegExp(`(?:^|,)(${key_js})${splice_function}`, 'm');\r\nconst swap_regexp = new RegExp(`(?:^|,)(${key_js})${swap_function}`, 'm');\r\n/**\r\n * Function to get tokens from html5player body data.\r\n * @param body body data of html5player.\r\n * @returns Array of tokens.\r\n */\r\nfunction js_tokens(body: string) {\r\n const function_action = function_regexp.exec(body);\r\n const object_action = obj_regexp.exec(body);\r\n if (!function_action || !object_action) return null;\r\n\r\n const object = object_action[1].replace(/\\$/g, '\\\\$');\r\n const object_body = object_action[2].replace(/\\$/g, '\\\\$');\r\n const function_body = function_action[1].replace(/\\$/g, '\\\\$');\r\n\r\n let result = reverse_regexp.exec(object_body);\r\n const reverseKey = result && result[1].replace(/\\$/g, '\\\\$').replace(/\\$|^'|^\"|'$|\"$/g, '');\r\n\r\n result = slice_regexp.exec(object_body);\r\n const sliceKey = result && result[1].replace(/\\$/g, '\\\\$').replace(/\\$|^'|^\"|'$|\"$/g, '');\r\n\r\n result = splice_regexp.exec(object_body);\r\n const spliceKey = result && result[1].replace(/\\$/g, '\\\\$').replace(/\\$|^'|^\"|'$|\"$/g, '');\r\n\r\n result = swap_regexp.exec(object_body);\r\n const swapKey = result && result[1].replace(/\\$/g, '\\\\$').replace(/\\$|^'|^\"|'$|\"$/g, '');\r\n\r\n const keys = `(${[reverseKey, sliceKey, spliceKey, swapKey].join('|')})`;\r\n const myreg = `(?:a=)?${object}(?:\\\\.${keys}|\\\\['${keys}'\\\\]|\\\\[\"${keys}\"\\\\])` + `\\\\(a,(\\\\d+)\\\\)`;\r\n const tokenizeRegexp = new RegExp(myreg, 'g');\r\n const tokens = [];\r\n while ((result = tokenizeRegexp.exec(function_body)) !== null) {\r\n const key = result[1] || result[2] || result[3];\r\n switch (key) {\r\n case swapKey:\r\n tokens.push(`sw${result[4]}`);\r\n break;\r\n case reverseKey:\r\n tokens.push('rv');\r\n break;\r\n case sliceKey:\r\n tokens.push(`sl${result[4]}`);\r\n break;\r\n case spliceKey:\r\n tokens.push(`sp${result[4]}`);\r\n break;\r\n }\r\n }\r\n return tokens;\r\n}\r\n/**\r\n * Function to decipher signature\r\n * @param tokens Tokens from js_tokens function\r\n * @param signature Signatured format url\r\n * @returns deciphered signature\r\n */\r\nfunction deciper_signature(tokens: string[], signature: string) {\r\n let sig = signature.split('');\r\n const len = tokens.length;\r\n for (let i = 0; i < len; i++) {\r\n let token = tokens[i],\r\n pos;\r\n switch (token.slice(0, 2)) {\r\n case 'sw':\r\n pos = parseInt(token.slice(2));\r\n swappositions(sig, pos);\r\n break;\r\n case 'rv':\r\n sig.reverse();\r\n break;\r\n case 'sl':\r\n pos = parseInt(token.slice(2));\r\n sig = sig.slice(pos);\r\n break;\r\n case 'sp':\r\n pos = parseInt(token.slice(2));\r\n sig.splice(0, pos);\r\n break;\r\n }\r\n }\r\n return sig.join('');\r\n}\r\n/**\r\n * Function to swap positions in a array\r\n * @param array array\r\n * @param position position to switch with first element\r\n */\r\nfunction swappositions(array: string[], position: number) {\r\n const first = array[0];\r\n array[0] = array[position];\r\n array[position] = first;\r\n}\r\n/**\r\n * Sets Download url with some extra parameter\r\n * @param format video fomat\r\n * @param sig deciphered signature\r\n * @returns void\r\n */\r\nfunction download_url(format: formatOptions, sig: string) {\r\n if (!format.url) return;\r\n\r\n const decoded_url = decodeURIComponent(format.url);\r\n\r\n const parsed_url = new URL(decoded_url);\r\n parsed_url.searchParams.set('ratebypass', 'yes');\r\n\r\n if (sig) {\r\n parsed_url.searchParams.set(format.sp || 'signature', sig);\r\n }\r\n format.url = parsed_url.toString();\r\n}\r\n/**\r\n * Main function which handles all queries related to video format deciphering\r\n * @param formats video formats\r\n * @param html5player url of html5player\r\n * @returns array of format.\r\n */\r\nexport async function format_decipher(formats: formatOptions[], html5player: string): Promise<formatOptions[]> {\r\n const body = await request(html5player);\r\n const tokens = js_tokens(body);\r\n formats.forEach((format) => {\r\n const cipher = format.signatureCipher || format.cipher;\r\n if (cipher) {\r\n const params = Object.fromEntries(new URLSearchParams(cipher));\r\n Object.assign(format, params);\r\n delete format.signatureCipher;\r\n delete format.cipher;\r\n }\r\n if (tokens && format.s) {\r\n const sig = deciper_signature(tokens, format.s);\r\n download_url(format, sig);\r\n delete format.s;\r\n delete format.sp;\r\n }\r\n });\r\n return formats;\r\n}\r\n","export interface ChannelIconInterface {\r\n /**\r\n * YouTube Channel Icon URL\r\n */\r\n url: string;\r\n /**\r\n * YouTube Channel Icon Width\r\n */\r\n width: number;\r\n /**\r\n * YouTube Channel Icon Height\r\n */\r\n height: number;\r\n}\r\n/**\r\n * YouTube Channel Class\r\n */\r\nexport class YouTubeChannel {\r\n /**\r\n * YouTube Channel Title\r\n */\r\n name?: string;\r\n /**\r\n * YouTube Channel Verified status.\r\n */\r\n verified?: boolean;\r\n /**\r\n * YouTube Channel artist if any.\r\n */\r\n artist?: boolean;\r\n /**\r\n * YouTube Channel ID.\r\n */\r\n id?: string;\r\n /**\r\n * YouTube Class type. == \"channel\"\r\n */\r\n type: 'video' | 'playlist' | 'channel';\r\n /**\r\n * YouTube Channel Url\r\n */\r\n url?: string;\r\n /**\r\n * YouTube Channel Icons data.\r\n */\r\n icons?: ChannelIconInterface[];\r\n /**\r\n * YouTube Channel subscribers count.\r\n */\r\n subscribers?: string;\r\n /**\r\n * YouTube Channel Constructor\r\n * @param data YouTube Channel data that we recieve from basic info or from search\r\n */\r\n constructor(data: any = {}) {\r\n if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);\r\n this.type = 'channel';\r\n this.name = data.name || null;\r\n this.verified = !!data.verified || false;\r\n this.artist = !!data.artist || false;\r\n this.id = data.id || null;\r\n this.url = data.url || null;\r\n this.icons = data.icons || [{ url: null, width: 0, height: 0 }];\r\n this.subscribers = data.subscribers || null;\r\n }\r\n\r\n /**\r\n * Returns channel icon url\r\n * @param {object} options Icon options\r\n * @param {number} [options.size=0] Icon size. **Default is 0**\r\n */\r\n iconURL(options = { size: 0 }): string | undefined {\r\n if (typeof options.size !== 'number' || options.size < 0) throw new Error('invalid icon size');\r\n if (!this.icons?.[0]?.url) return undefined;\r\n const def = this.icons?.[0]?.url.split('=s')[1].split('-c')[0];\r\n return this.icons?.[0]?.url.replace(`=s${def}-c`, `=s${options.size}-c`);\r\n }\r\n /**\r\n * Converts Channel Class to channel name.\r\n * @returns name of channel\r\n */\r\n toString(): string {\r\n return this.name || '';\r\n }\r\n /**\r\n * Converts Channel Class to JSON format\r\n * @returns json data of the channel\r\n */\r\n toJSON(): ChannelJSON {\r\n return {\r\n name: this.name,\r\n verified: this.verified,\r\n artist: this.artist,\r\n id: this.id,\r\n url: this.url,\r\n icons: this.icons,\r\n type: this.type,\r\n subscribers: this.subscribers\r\n };\r\n }\r\n}\r\n\r\ninterface ChannelJSON {\r\n /**\r\n * YouTube Channel Title\r\n */\r\n name?: string;\r\n /**\r\n * YouTube Channel Verified status.\r\n */\r\n verified?: boolean;\r\n /**\r\n * YouTube Channel artist if any.\r\n */\r\n artist?: boolean;\r\n /**\r\n * YouTube Channel ID.\r\n */\r\n id?: string;\r\n /**\r\n * Type of Class [ Channel ]\r\n */\r\n type: 'video' | 'playlist' | 'channel';\r\n /**\r\n * YouTube Channel Url\r\n */\r\n url?: string;\r\n /**\r\n * YouTube Channel Icon data.\r\n */\r\n icons?: ChannelIconInterface[];\r\n /**\r\n * YouTube Channel subscribers count.\r\n */\r\n subscribers?: string;\r\n}\r\n","export class YouTubeThumbnail {\r\n url: string;\r\n width: number;\r\n height: number;\r\n\r\n constructor(data: any) {\r\n this.url = data.url;\r\n this.width = data.width;\r\n this.height = data.height;\r\n }\r\n\r\n toJSON() {\r\n return {\r\n url: this.url,\r\n width: this.width,\r\n height: this.height\r\n };\r\n }\r\n}\r\n","import { YouTubeChannel } from './Channel';\r\nimport { YouTubeThumbnail } from './Thumbnail';\r\n\r\n/**\r\n * Licensed music in the video\r\n * \r\n * The property names change depending on your region's language.\r\n */\r\ninterface VideoMusic {\r\n song?: string;\r\n url?: string | null;\r\n artist?: string;\r\n album?: string;\r\n writers?: string;\r\n licenses?: string;\r\n}\r\n\r\ninterface VideoOptions {\r\n /**\r\n * YouTube Video ID\r\n */\r\n id?: string;\r\n /**\r\n * YouTube video url\r\n */\r\n url: string;\r\n /**\r\n * YouTube Video title\r\n */\r\n title?: string;\r\n /**\r\n * YouTube Video description.\r\n */\r\n description?: string;\r\n /**\r\n * YouTube Video Duration Formatted\r\n */\r\n durationRaw: string;\r\n /**\r\n * YouTube Video Duration in seconds\r\n */\r\n durationInSec: number;\r\n /**\r\n * YouTube Video Uploaded Date\r\n */\r\n uploadedAt?: string;\r\n /**\r\n * If the video is upcoming or a premiere that isn't currently live, this will contain the premiere date, for watch page playlists this will be true, it defaults to undefined\r\n */\r\n upcoming?: Date | true;\r\n /**\r\n * YouTube Views\r\n */\r\n views: number;\r\n /**\r\n * YouTube Thumbnail Data\r\n */\r\n thumbnail?: {\r\n width: number | undefined;\r\n height: number | undefined;\r\n url: string | undefined;\r\n };\r\n /**\r\n * YouTube Video's uploader Channel Data\r\n */\r\n channel?: YouTubeChannel;\r\n /**\r\n * YouTube Video's likes\r\n */\r\n likes: number;\r\n /**\r\n * YouTube Video live status\r\n */\r\n live: boolean;\r\n /**\r\n * YouTube Video private status\r\n */\r\n private: boolean;\r\n /**\r\n * YouTube Video tags\r\n */\r\n tags: string[];\r\n /**\r\n * `true` if the video has been identified by the YouTube community as inappropriate or offensive to some audiences and viewer discretion is advised\r\n */\r\n discretionAdvised?: boolean;\r\n /**\r\n * Gives info about music content in that video.\r\n * \r\n * The property names of VideoMusic change depending on your region's language.\r\n */\r\n music?: VideoMusic[];\r\n /**\r\n * The chapters for this video\r\n *\r\n * If the video doesn't have any chapters or if the video object wasn't created by {@link video_basic_info} or {@link video_info} this will be an empty array.\r\n */\r\n chapters: VideoChapter[];\r\n}\r\n\r\nexport interface VideoChapter {\r\n /**\r\n * The title of the chapter\r\n */\r\n title: string;\r\n /**\r\n * The timestamp of the start of the chapter\r\n */\r\n timestamp: string;\r\n /**\r\n * The start of the chapter in seconds\r\n */\r\n seconds: number;\r\n /**\r\n * Thumbnails of the frame at the start of this chapter\r\n */\r\n thumbnails: YouTubeThumbnail[];\r\n}\r\n\r\n/**\r\n * Class for YouTube Video url\r\n */\r\nexport class YouTubeVideo {\r\n /**\r\n * YouTube Video ID\r\n */\r\n id?: string;\r\n /**\r\n * YouTube video url\r\n */\r\n url: string;\r\n /**\r\n * YouTube Class type. == \"video\"\r\n */\r\n type: 'video' | 'playlist' | 'channel';\r\n /**\r\n * YouTube Video title\r\n */\r\n title?: string;\r\n /**\r\n * YouTube Video description.\r\n */\r\n description?: string;\r\n /**\r\n * YouTube Video Duration Formatted\r\n */\r\n durationRaw: string;\r\n /**\r\n * YouTube Video Duration in seconds\r\n */\r\n durationInSec: number;\r\n /**\r\n * YouTube Video Uploaded Date\r\n */\r\n uploadedAt?: string;\r\n /**\r\n * YouTube Live Date\r\n */\r\n liveAt?: string;\r\n /**\r\n * If the video is upcoming or a premiere that isn't currently live, this will contain the premiere date, for watch page playlists this will be true, it defaults to undefined\r\n */\r\n upcoming?: Date | true;\r\n /**\r\n * YouTube Views\r\n */\r\n views: number;\r\n /**\r\n * YouTube Thumbnail Data\r\n */\r\n thumbnails: YouTubeThumbnail[];\r\n /**\r\n * YouTube Video's uploader Channel Data\r\n */\r\n channel?: YouTubeChannel;\r\n /**\r\n * YouTube Video's likes\r\n */\r\n likes: number;\r\n /**\r\n * YouTube Video live status\r\n */\r\n live: boolean;\r\n /**\r\n * YouTube Video private status\r\n */\r\n private: boolean;\r\n /**\r\n * YouTube Video tags\r\n */\r\n tags: string[];\r\n /**\r\n * `true` if the video has been identified by the YouTube community as inappropriate or offensive to some audiences and viewer discretion is advised\r\n */\r\n discretionAdvised?: boolean;\r\n /**\r\n * Gives info about music content in that video.\r\n */\r\n music?: VideoMusic[];\r\n /**\r\n * The chapters for this video\r\n *\r\n * If the video doesn't have any chapters or if the video object wasn't created by {@link video_basic_info} or {@link video_info} this will be an empty array.\r\n */\r\n chapters: VideoChapter[];\r\n /**\r\n * Constructor for YouTube Video Class\r\n * @param data JSON parsed data.\r\n */\r\n constructor(data: any) {\r\n if (!data) throw new Error(`Can not initiate ${this.constructor.name} without data`);\r\n\r\n this.id = data.id || undefined;\r\n this.url = `https://www.youtube.com/watch?v=${this.id}`;\r\n this.type = 'video';\r\n this.title = data.title || undefined;\r\n this.description = data.description || undefined;\r\n this.durationRaw = data.duration_raw || '0:00';\r\n this.durationInSec = (data.duration < 0 ? 0 : data.duration) || 0;\r\n this.uploadedAt = data.uploadedAt || undefined;\r\n this.liveAt = data.liveAt || undefined;\r\n this.upcoming = data.upcoming;\r\n this.views = parseInt(data.views) || 0;\r\n const thumbnails = [];\r\n for (const thumb of data.thumbnails) {\r\n thumbnails.push(new YouTubeThumbnail(thumb));\r\n }\r\n this.thumbnails = thumbnails || [];\r\n this.channel = new YouTubeChannel(data.channel) || {};\r\n this.likes = data.likes || 0;\r\n this.live = !!data.live;\r\n this.private = !!data.private;\r\n this.tags = data.tags || [];\r\n this.discretionAdvised = data.discretionAdvised ?? undefined;\r\n this.music = data.music || [];\r\n this.chapters = data.chapters || [];\r\n }\r\n /**\r\n * Converts class to title name of video.\r\n * @returns Title name\r\n */\r\n toString(): string {\r\n return this.url || '';\r\n }\r\n /**\r\n * Converts class to JSON data\r\n * @returns JSON data.\r\n */\r\n toJSON(): VideoOptions {\r\n return {\r\n id: this.id,\r\n url: this.url,\r\n title: this.title,\r\n description: this.description,\r\n durationInSec: this.durationInSec,\r\n durationRaw: this.durationRaw,\r\n uploadedAt: this.uploadedAt,\r\n thumbnail: this.thumbnails[this.thumbnails.length - 1].toJSON() || this.thumbnails,\r\n channel: this.channel,\r\n views: this.views,\r\n tags: this.tags,\r\n likes: this.likes,\r\n live: this.live,\r\n private: this.private,\r\n discretionAdvised: this.discretionAdvised,\r\n music: this.music,\r\n chapters: this.chapters\r\n };\r\n }\r\n}\r\n","import { getPlaylistVideos, getContinuationToken } from '../utils/extractor';\r\nimport { request } from '../../Request';\r\nimport { YouTubeChannel } from './Channel';\r\nimport { YouTubeVideo } from './Video';\r\nimport { YouTubeThumbnail } from './Thumbnail';\r\nconst BASE_API = 'https://www.youtube.com/youtubei/v1/browse?key=';\r\n/**\r\n * YouTube Playlist Class containing vital informations about playlist.\r\n */\r\nexport class YouTubePlayList {\r\n /**\r\n * YouTube Playlist ID\r\n */\r\n id?: string;\r\n /**\r\n * YouTube Playlist Name\r\n */\r\n title?: string;\r\n /**\r\n * YouTube Class type. == \"playlist\"\r\n */\r\n type: 'video' | 'playlist' | 'channel';\r\n /**\r\n * Total no of videos in that playlist\r\n */\r\n videoCount?: number;\r\n /**\r\n * Time when playlist was last updated\r\n */\r\n lastUpdate?: string;\r\n /**\r\n * Total views of that playlist\r\n */\r\n views?: number;\r\n /**\r\n * YouTube Playlist url\r\n */\r\n url?: string;\r\n /**\r\n * YouTube Playlist url with starting video url.\r\n */\r\n link?: string;\r\n /**\r\n * YouTube Playlist channel data\r\n */\r\n channel?: YouTubeChannel;\r\n /**\r\n * YouTube Playlist thumbnail Data\r\n */\r\n thumbnail?: YouTubeThumbnail;\r\n /**\r\n * Videos array containing data of first 100 videos\r\n */\r\n private videos?: YouTubeVideo[];\r\n /**\r\n * Map contaning data of all fetched videos\r\n */\r\n private fetched_videos: Map<string, YouTubeVideo[]>;\r\n /**\r\n * Token containing API key, Token, ClientVersion.\r\n */\r\n private _continuation: {\r\n api?: string;\r\n token?: string;\r\n clientVersion?: string;\r\n } = {};\r\n /**\r\n * Total no of pages count.\r\n */\r\n private __count: number;\r\n /**\r\n * Constructor for YouTube Playlist Class\r\n * @param data Json Parsed YouTube Playlist data\r\n * @param searchResult If the data is from search or not\r\n */\r\n constructor(data: any, searchResult = false) {\r\n if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);\r\n this.__count = 0;\r\n this.fetched_videos = new Map();\r\n this.type = 'playlist';\r\n if (searchResult) this.__patchSearch(data);\r\n else this.__patch(data);\r\n }\r\n /**\r\n * Updates variable according to a normal data.\r\n * @param data Json Parsed YouTube Playlist data\r\n */\r\n private __patch(data: any) {\r\n this.id = data.id || undefined;\r\n this.url = data.url || undefined;\r\n this.title = data.title || undefined;\r\n this.videoCount = data.videoCount || 0;\r\n this.lastUpdate = data.lastUpdate || undefined;\r\n this.views = data.views || 0;\r\n this.link = data.link || undefined;\r\n this.channel = new YouTubeChannel(data.channel) || undefined;\r\n this.thumbnail = data.thumbnail ? new YouTubeThumbnail(data.thumbnail) : undefined;\r\n this.videos = data.videos || [];\r\n this.__count++;\r\n this.fetched_videos.set(`${this.__count}`, this.videos as YouTubeVideo[]);\r\n this._continuation.api = data.continuation?.api ?? undefined;\r\n this._continuation.token = data.continuation?.token ?? undefined;\r\n this._continuation.clientVersion = data.continuation?.clientVersion ?? '<important data>';\r\n }\r\n /**\r\n * Updates variable according to a searched data.\r\n * @param data Json Parsed YouTube Playlist data\r\n */\r\n private __patchSearch(data: any) {\r\n this.id = data.id || undefined;\r\n this.url = this.id ? `https://www.youtube.com/playlist?list=${this.id}` : undefined;\r\n this.title = data.title || undefined;\r\n this.thumbnail = new YouTubeThumbnail(data.thumbnail) || undefined;\r\n this.channel = data.channel || undefined;\r\n this.videos = [];\r\n this.videoCount = data.videos || 0;\r\n this.link = undefined;\r\n this.lastUpdate = undefined;\r\n this.views = 0;\r\n }\r\n /**\r\n * Parses next segment of videos from playlist and returns parsed data.\r\n * @param limit Total no of videos to parse.\r\n *\r\n * Default = Infinity\r\n * @returns Array of YouTube Video Class\r\n */\r\n async next(limit = Infinity): Promise<YouTubeVideo[]> {\r\n if (!this._continuation || !this._continuation.token) return [];\r\n\r\n const nextPage = await request(`${BASE_API}${this._continuation.api}&prettyPrint=false`, {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n continuation: this._continuation.token,\r\n context: {\r\n client: {\r\n utcOffsetMinutes: 0,\r\n gl: 'US',\r\n hl: 'en',\r\n clientName: 'WEB',\r\n clientVersion: this._continuation.clientVersion\r\n },\r\n user: {},\r\n request: {}\r\n }\r\n })\r\n });\r\n\r\n const contents =\r\n JSON.parse(nextPage)?.onResponseReceivedActions[0]?.appendContinuationItemsAction?.continuationItems;\r\n if (!contents) return [];\r\n\r\n const playlist_videos = getPlaylistVideos(contents, limit);\r\n this.fetched_videos.set(`${this.__count}`, playlist_videos);\r\n this._continuation.token = getContinuationToken(contents);\r\n return playlist_videos;\r\n }\r\n /**\r\n * Fetches remaining data from playlist\r\n *\r\n * For fetching and getting all songs data, see `total_pages` property.\r\n * @param max Max no of videos to fetch\r\n *\r\n * Default = Infinity\r\n * @returns\r\n */\r\n async fetch(max = Infinity): Promise<YouTubePlayList> {\r\n const continuation = this._continuation.token;\r\n if (!continuation) return this;\r\n if (max < 1) max = Infinity;\r\n\r\n while (typeof this._continuation.token === 'string' && this._continuation.token.length) {\r\n this.__count++;\r\n const res = await this.next();\r\n max -= res.length;\r\n if (max <= 0) break;\r\n if (!res.length) break;\r\n }\r\n\r\n return this;\r\n }\r\n /**\r\n * YouTube Playlists are divided into pages.\r\n *\r\n * For example, if you want to get 101 - 200 songs\r\n *\r\n * ```ts\r\n * const playlist = await play.playlist_info('playlist url')\r\n *\r\n * await playlist.fetch()\r\n *\r\n * const result = playlist.page(2)\r\n * ```\r\n * @param number Page number\r\n * @returns Array of YouTube Video Class\r\n * @see {@link YouTubePlayList.all_videos}\r\n */\r\n page(number: number): YouTubeVideo[] {\r\n if (!number) throw new Error('Page number is not provided');\r\n if (!this.fetched_videos.has(`${number}`)) throw new Error('Given Page number is invalid');\r\n return this.fetched_videos.get(`${number}`) as YouTubeVideo[];\r\n }\r\n /**\r\n * Gets total number of pages in that playlist class.\r\n * @see {@link YouTubePlayList.all_videos}\r\n */\r\n get total_pages() {\r\n return this.fetched_videos.size;\r\n }\r\n /**\r\n * This tells total number of videos that have been fetched so far.\r\n *\r\n * This can be equal to videosCount if all videos in playlist have been fetched and they are not hidden.\r\n */\r\n get total_videos() {\r\n const page_number: number = this.total_pages;\r\n return (page_number - 1) * 100 + (this.fetched_videos.get(`${page_number}`) as YouTubeVideo[]).length;\r\n }\r\n /**\r\n * Fetches all the videos in the playlist and returns them\r\n *\r\n * ```ts\r\n * const playlist = await play.playlist_info('playlist url')\r\n *\r\n * const videos = await playlist.all_videos()\r\n * ```\r\n * @returns An array of {@link YouTubeVideo} objects\r\n * @see {@link YouTubePlayList.fetch}\r\n */\r\n async all_videos(): Promise<YouTubeVideo[]> {\r\n await this.fetch();\r\n\r\n const videos = [];\r\n\r\n for (const page of this.fetched_videos.values()) videos.push(...page);\r\n\r\n return videos;\r\n }\r\n /**\r\n * Converts Playlist Class to a json parsed data.\r\n * @returns\r\n */\r\n toJSON(): PlaylistJSON {\r\n return {\r\n id: this.id,\r\n title: this.title,\r\n thumbnail: this.thumbnail?.toJSON() || this.thumbnail,\r\n channel: this.channel,\r\n url: this.url,\r\n videos: this.videos\r\n };\r\n }\r\n}\r\n\r\ninterface PlaylistJSON {\r\n /**\r\n * YouTube Playlist ID\r\n */\r\n id?: string;\r\n /**\r\n * YouTube Playlist Name\r\n */\r\n title?: string;\r\n /**\r\n * Total no of videos in that playlist\r\n */\r\n videoCount?: number;\r\n /**\r\n * Time when playlist was last updated\r\n */\r\n lastUpdate?: string;\r\n /**\r\n * Total views of that playlist\r\n */\r\n views?: number;\r\n /**\r\n * YouTube Playlist url\r\n */\r\n url?: string;\r\n /**\r\n * YouTube Playlist url with starting video url.\r\n */\r\n link?: string;\r\n /**\r\n * YouTube Playlist channel data\r\n */\r\n channel?: YouTubeChannel;\r\n /**\r\n * YouTube Playlist thumbnail Data\r\n */\r\n thumbnail?: {\r\n width: number | undefined;\r\n height: number | undefined;\r\n url: string | undefined;\r\n };\r\n /**\r\n * first 100 videos in that playlist\r\n */\r\n videos?: YouTubeVideo[];\r\n}\r\n","import { request } from './../../Request/index';\r\nimport { format_decipher } from './cipher';\r\nimport { VideoChapter, YouTubeVideo } from '../classes/Video';\r\nimport { YouTubePlayList } from '../classes/Playlist';\r\nimport { InfoData, StreamInfoData } from './constants';\r\nimport { URL, URLSearchParams } from 'node:url';\r\nimport { parseAudioFormats } from '../stream';\r\n\r\ninterface InfoOptions {\r\n htmldata?: boolean;\r\n language?: string;\r\n}\r\n\r\ninterface PlaylistOptions {\r\n incomplete?: boolean;\r\n language?: string;\r\n}\r\n\r\nconst video_id_pattern = /^[a-zA-Z\\d_-]{11,12}$/;\r\nconst playlist_id_pattern = /^(PL|UU|LL|RD|OL)[a-zA-Z\\d_-]{10,}$/;\r\nconst DEFAULT_API_KEY = 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8';\r\nconst video_pattern =\r\n /^((?:https?:)?\\/\\/)?(?:(?:www|m|music)\\.)?((?:youtube\\.com|youtu.be))(\\/(?:[\\w\\-]+\\?v=|shorts\\/|embed\\/|live\\/|v\\/)?)([\\w\\-]+)(\\S+)?$/;\r\nconst playlist_pattern =\r\n /^((?:https?:)?\\/\\/)?(?:(?:www|m|music)\\.)?((?:youtube\\.com|youtu.be))\\/(?:(playlist|watch))?(.*)?((\\?|\\&)list=)(PL|UU|LL|RD|OL)[a-zA-Z\\d_-]{10,}(&.*)?$/;\r\n/**\r\n * Validate YouTube URL or ID.\r\n *\r\n * **CAUTION :** If your search word is 11 or 12 characters long, you might get it validated as video ID.\r\n *\r\n * To avoid above, add one more condition to yt_validate\r\n * ```ts\r\n * if (url.startsWith('https') && yt_validate(url) === 'video') {\r\n * // YouTube Video Url.\r\n * }\r\n * ```\r\n * @param url YouTube URL OR ID\r\n * @returns\r\n * ```\r\n * 'playlist' | 'video' | 'search' | false\r\n * ```\r\n */\r\nexport function yt_validate(url: string): 'playlist' | 'video' | 'search' | false {\r\n const url_ = url.trim();\r\n if (url_.indexOf('list=') === -1) {\r\n if (url_.startsWith('https')) {\r\n if (url_.match(video_pattern)) {\r\n let id: string;\r\n if (url_.includes('youtu.be/')) id = url_.split('youtu.be/')[1].split(/(\\?|\\/|&)/)[0];\r\n else if (url_.includes('youtube.com/embed/'))\r\n id = url_.split('youtube.com/embed/')[1].split(/(\\?|\\/|&)/)[0];\r\n else if (url_.includes('youtube.com/shorts/'))\r\n id = url_.split('youtube.com/shorts/')[1].split(/(\\?|\\/|&)/)[0];\r\n else id = url_.split('watch?v=')[1]?.split(/(\\?|\\/|&)/)[0];\r\n if (id?.match(video_id_pattern)) return 'video';\r\n else return false;\r\n } else return false;\r\n } else {\r\n if (url_.match(video_id_pattern)) return 'video';\r\n else if (url_.match(playlist_id_pattern)) return 'playlist';\r\n else return 'search';\r\n }\r\n } else {\r\n if (!url_.match(playlist_pattern)) return yt_validate(url_.replace(/(\\?|\\&)list=[^&]*/, ''));\r\n else return 'playlist';\r\n }\r\n}\r\n/**\r\n * Extracts the video ID from a YouTube URL.\r\n *\r\n * Will return the value of `urlOrId` if it looks like a video ID.\r\n * @param urlOrId A YouTube URL or video ID\r\n * @returns the video ID or `false` if it can't find a video ID.\r\n */\r\nfunction extractVideoId(urlOrId: string): string | false {\r\n if (urlOrId.startsWith('https://') && urlOrId.match(video_pattern)) {\r\n let id: string;\r\n if (urlOrId.includes('youtu.be/')) {\r\n id = urlOrId.split('youtu.be/')[1].split(/(\\?|\\/|&)/)[0];\r\n } else if (urlOrId.includes('youtube.com/embed/')) {\r\n id = urlOrId.split('youtube.com/embed/')[1].split(/(\\?|\\/|&)/)[0];\r\n } else if (urlOrId.includes('youtube.com/shorts/')) {\r\n id = urlOrId.split('youtube.com/shorts/')[1].split(/(\\?|\\/|&)/)[0];\r\n } else if (urlOrId.includes('youtube.com/live/')) {\r\n id = urlOrId.split('youtube.com/live/')[1].split(/(\\?|\\/|&)/)[0];\r\n } else {\r\n id = (urlOrId.split('watch?v=')[1] ?? urlOrId.split('&v=')[1]).split(/(\\?|\\/|&)/)[0];\r\n }\r\n\r\n if (id.match(video_id_pattern)) return id;\r\n } else if (urlOrId.match(video_id_pattern)) {\r\n return urlOrId;\r\n }\r\n\r\n return false;\r\n}\r\n/**\r\n * Extract ID of YouTube url.\r\n * @param url ID or url of YouTube\r\n * @returns ID of video or playlist.\r\n */\r\nexport function extractID(url: string): string {\r\n const check = yt_validate(url);\r\n if (!check || check === 'search') throw new Error('This is not a YouTube url or videoId or PlaylistID');\r\n const url_ = url.trim();\r\n if (url_.startsWith('https')) {\r\n if (url_.indexOf('list=') === -1) {\r\n const video_id = extractVideoId(url_);\r\n if (!video_id) throw new Error('This is not a YouTube url or videoId or PlaylistID');\r\n return video_id;\r\n } else {\r\n return url_.split('list=')[1].split('&')[0];\r\n }\r\n } else return url_;\r\n}\r\n/**\r\n * Basic function to get data from a YouTube url or ID.\r\n *\r\n * Example\r\n * ```ts\r\n * const video = await play.video_basic_info('youtube video url')\r\n *\r\n * const res = ... // Any https package get function.\r\n *\r\n * const video = await play.video_basic_info(res.body, { htmldata : true })\r\n * ```\r\n * @param url YouTube url or ID or html body data\r\n * @param options Video Info Options\r\n * - `boolean` htmldata : given data is html data or not\r\n * @returns Video Basic Info {@link InfoData}.\r\n */\r\nexport async function video_basic_info(url: string, options: InfoOptions = {}): Promise<InfoData> {\r\n if (typeof url !== 'string') throw new Error('url parameter is not a URL string or a string of HTML');\r\n const url_ = url.trim();\r\n let body: string;\r\n const cookieJar = {};\r\n if (options.htmldata) {\r\n body = url_;\r\n } else {\r\n const video_id = extractVideoId(url_);\r\n if (!video_id) throw new Error('This is not a YouTube Watch URL');\r\n const new_url = `https://www.youtube.com/watch?v=${video_id}&has_verified=1`;\r\n body = await request(new_url, {\r\n headers: {\r\n 'accept-language': options.language || 'en-US;q=0.9'\r\n },\r\n cookies: true,\r\n cookieJar\r\n });\r\n }\r\n if (body.indexOf('Our systems have detected unusual traffic from your computer network.') !== -1)\r\n throw new Error('Captcha page: YouTube has detected that you are a bot!');\r\n const player_data = body\r\n .split('var ytInitialPlayerResponse = ')?.[1]\r\n ?.split(';</script>')[0]\r\n .split(/(?<=}}});\\s*(var|const|let)\\s/)[0];\r\n if (!player_data) throw new Error('Initial Player Response Data is undefined.');\r\n const initial_data = body\r\n .split('var ytInitialData = ')?.[1]\r\n ?.split(';</script>')[0]\r\n .split(/;\\s*(var|const|let)\\s/)[0];\r\n if (!initial_data) throw new Error('Initial Response Data is undefined.');\r\n const player_response = JSON.parse(player_data);\r\n const initial_response = JSON.parse(initial_data);\r\n const vid = player_response.videoDetails;\r\n\r\n let discretionAdvised = false;\r\n let upcoming = false;\r\n if (player_response.playabilityStatus.status !== 'OK') {\r\n if (player_response.playabilityStatus.status === 'CONTENT_CHECK_REQUIRED') {\r\n if (options.htmldata)\r\n throw new Error(\r\n `Accepting the viewer discretion is not supported when using htmldata, video: ${vid.videoId}`\r\n );\r\n discretionAdvised = true;\r\n const cookies =\r\n initial_response.topbar.desktopTopbarRenderer.interstitial?.consentBumpV2Renderer.agreeButton\r\n .buttonRenderer.command.saveConsentAction;\r\n if (cookies) {\r\n Object.assign(cookieJar, {\r\n VISITOR_INFO1_LIVE: cookies.visitorCookie,\r\n CONSENT: cookies.consentCookie\r\n });\r\n }\r\n\r\n const updatedValues = await acceptViewerDiscretion(vid.videoId, cookieJar, body, true);\r\n player_response.streamingData = updatedValues.streamingData;\r\n initial_response.contents.twoColumnWatchNextResults.secondaryResults = updatedValues.relatedVideos;\r\n } else if (player_response.playabilityStatus.status === 'LIVE_STREAM_OFFLINE') upcoming = true;\r\n else\r\n throw new Error(\r\n `While getting info from url\\n${\r\n player_response.playabilityStatus.errorScreen.playerErrorMessageRenderer?.reason.simpleText ??\r\n player_response.playabilityStatus.errorScreen.playerKavRenderer?.reason.simpleText ??\r\n player_response.playabilityStatus.reason\r\n }`\r\n );\r\n }\r\n const ownerInfo =\r\n initial_response.contents.twoColumnWatchNextResults.results?.results?.contents[1]?.videoSecondaryInfoRenderer\r\n ?.owner?.videoOwnerRenderer;\r\n const badge = ownerInfo?.badges?.[0]?.metadataBadgeRenderer?.style?.toLowerCase();\r\n const html5player = `https://www.youtube.com${body.split('\"jsUrl\":\"')[1].split('\"')[0]}`;\r\n const related: string[] = [];\r\n initial_response.contents.twoColumnWatchNextResults.secondaryResults.secondaryResults.results.forEach(\r\n (res: any) => {\r\n if (res.compactVideoRenderer)\r\n related.push(`https://www.youtube.com/watch?v=${res.compactVideoRenderer.videoId}`);\r\n if (res.itemSectionRenderer?.contents)\r\n res.itemSectionRenderer.contents.forEach((x: any) => {\r\n if (x.compactVideoRenderer)\r\n related.push(`https://www.youtube.com/watch?v=${x.compactVideoRenderer.videoId}`);\r\n });\r\n }\r\n );\r\n const microformat = player_response.microformat.playerMicroformatRenderer;\r\n const musicInfo = initial_response.engagementPanels.find((item: any) => item?.engagementPanelSectionListRenderer?.panelIdentifier == 'engagement-panel-structured-description')?.engagementPanelSectionListRenderer.content.structuredDescriptionContentRenderer.items\r\n .find((el: any) => el.videoDescriptionMusicSectionRenderer)?.videoDescriptionMusicSectionRenderer.carouselLockups;\r\n\r\n const music: any[] = [];\r\n if (musicInfo) {\r\n musicInfo.forEach((x: any) => {\r\n if (!x.carouselLockupRenderer) return;\r\n const row = x.carouselLockupRenderer;\r\n\r\n const song = row.videoLockup?.compactVideoRenderer.title.simpleText ?? row.videoLockup?.compactVideoRenderer.title.runs?.find((x:any) => x.text)?.text;\r\n const metadata = row.infoRows?.map((info: any) => [info.infoRowRenderer.title.simpleText.toLowerCase(), ((info.infoRowRenderer.expandedMetadata ?? info.infoRowRenderer.defaultMetadata)?.runs?.map((i:any) => i.text).join(\"\")) ?? info.infoRowRenderer.defaultMetadata?.simpleText ?? info.infoRowRenderer.expandedMetadata?.simpleText ?? \"\"]);\r\n const contents = Object.fromEntries(metadata ?? {});\r\n const id = row.videoLockup?.compactVideoRenderer.navigationEndpoint?.watchEndpoint.videoId\r\n ?? row.infoRows?.find((x: any) => x.infoRowRenderer.title.simpleText.toLowerCase() == \"song\")?.infoRowRenderer.defaultMetadata.runs?.find((x: any) => x.navigationEndpoint)?.navigationEndpoint.watchEndpoint?.videoId;\r\n\r\n music.push({song, url: id ? `https://www.youtube.com/watch?v=${id}` : null, ...contents})\r\n });\r\n }\r\n const rawChapters =\r\n initial_response.playerOverlays.playerOverlayRenderer.decoratedPlayerBarRenderer?.decoratedPlayerBarRenderer.playerBar?.multiMarkersPlayerBarRenderer.markersMap?.find(\r\n (m: any) => m.key === 'DESCRIPTION_CHAPTERS'\r\n )?.value?.chapters;\r\n const chapters: VideoChapter[] = [];\r\n if (rawChapters) {\r\n for (const { chapterRenderer } of rawChapters) {\r\n chapters.push({\r\n title: chapterRenderer.title.simpleText,\r\n timestamp: parseSeconds(chapterRenderer.timeRangeStartMillis / 1000),\r\n seconds: chapterRenderer.timeRangeStartMillis / 1000,\r\n thumbnails: chapterRenderer.thumbnail.thumbnails\r\n });\r\n }\r\n }\r\n let upcomingDate;\r\n if (upcoming) {\r\n if (microformat.liveBroadcastDetails.startTimestamp)\r\n upcomingDate = new Date(microformat.liveBroadcastDetails.startTimestamp);\r\n else {\r\n const timestamp =\r\n player_response.playabilityStatus.liveStreamability.liveStreamabilityRenderer.offlineSlate\r\n .liveStreamOfflineSlateRenderer.scheduledStartTime;\r\n upcomingDate = new Date(parseInt(timestamp) * 1000);\r\n }\r\n }\r\n\r\n const likeRenderer = initial_response.contents.twoColumnWatchNextResults.results.results.contents\r\n .find((content: any) => content.videoPrimaryInfoRenderer)\r\n ?.videoPrimaryInfoRenderer.videoActions.menuRenderer.topLevelButtons?.find(\r\n (button: any) => button.toggleButtonRenderer?.defaultIcon.iconType === 'LIKE' || button.segmentedLikeDislikeButtonRenderer?.likeButton.toggleButtonRenderer?.defaultIcon.iconType === 'LIKE'\r\n )\r\n\r\n const video_details = new YouTubeVideo({\r\n id: vid.videoId,\r\n title: vid.title,\r\n description: vid.shortDescription,\r\n duration: Number(vid.lengthSeconds),\r\n duration_raw: parseSeconds(vid.lengthSeconds),\r\n uploadedAt: microformat.publishDate,\r\n liveAt: microformat.liveBroadcastDetails?.startTimestamp,\r\n upcoming: upcomingDate,\r\n thumbnails: vid.thumbnail.thumbnails,\r\n channel: {\r\n name: vid.author,\r\n id: vid.channelId,\r\n url: `https://www.youtube.com/channel/${vid.channelId}`,\r\n verified: Boolean(badge?.includes('verified')),\r\n artist: Boolean(badge?.includes('artist')),\r\n icons: ownerInfo?.thumbnail?.thumbnails || undefined\r\n },\r\n views: vid.viewCount,\r\n tags: vid.keywords,\r\n likes: parseInt(\r\n likeRenderer?.toggleButtonRenderer?.defaultText.accessibility?.accessibilityData.label.replace(/\\D+/g, '') ?? \r\n likeRenderer?.segmentedLikeDislikeButtonRenderer?.likeButton.toggleButtonRenderer?.defaultText.accessibility?.accessibilityData.label.replace(/\\D+/g, '') ?? 0\r\n ),\r\n live: vid.isLiveContent,\r\n private: vid.isPrivate,\r\n discretionAdvised,\r\n music,\r\n chapters\r\n });\r\n let format = [];\r\n if (!upcoming) {\r\n // TODO: Properly handle the formats, for now ignore and use iOS formats\r\n //format.push(...(player_response.streamingData.formats ?? []));\r\n //format.push(...(player_response.streamingData.adaptiveFormats ?? []));\r\n\r\n // get the formats for the android player for legacy videos\r\n // fixes the stream being closed because not enough data\r\n // arrived in time for ffmpeg to be able to extract audio data\r\n //if (parseAudioFormats(format).length === 0 && !options.htmldata) {\r\n // format = await getAndroidFormats(vid.videoId, cookieJar, body);\r\n //}\r\n format = await getIosFormats(vid.videoId, cookieJar, body);\r\n }\r\n const LiveStreamData = {\r\n isLive: video_details.live,\r\n dashManifestUrl: player_response.streamingData?.dashManifestUrl ?? null,\r\n hlsManifestUrl: player_response.streamingData?.hlsManifestUrl ?? null\r\n };\r\n return {\r\n LiveStreamData,\r\n html5player,\r\n format,\r\n video_details,\r\n related_videos: related\r\n };\r\n}\r\n/**\r\n * Gets the data required for streaming from YouTube url, ID or html body data and deciphers it.\r\n *\r\n * Internal function used by {@link stream} instead of {@link video_info}\r\n * because it only extracts the information required for streaming.\r\n *\r\n * @param url YouTube url or ID or html body data\r\n * @param options Video Info Options\r\n * - `boolean` htmldata : given data is html data or not\r\n * @returns Deciphered Video Info {@link StreamInfoData}.\r\n */\r\nexport async function video_stream_info(url: string, options: InfoOptions = {}): Promise<StreamInfoData> {\r\n if (typeof url !== 'string') throw new Error('url parameter is not a URL string or a string of HTML');\r\n let body: string;\r\n const cookieJar = {};\r\n if (options.htmldata) {\r\n body = url;\r\n } else {\r\n const video_id = extractVideoId(url);\r\n if (!video_id) throw new Error('This is not a YouTube Watch URL');\r\n const new_url = `https://www.youtube.com/watch?v=${video_id}&has_verified=1`;\r\n body = await request(new_url, {\r\n headers: { 'accept-language': 'en-US,en;q=0.9' },\r\n cookies: true,\r\n cookieJar\r\n });\r\n }\r\n if (body.indexOf('Our systems have detected unusual traffic from your computer network.') !== -1)\r\n throw new Error('Captcha page: YouTube has detected that you are a bot!');\r\n const player_data = body\r\n .split('var ytInitialPlayerResponse = ')?.[1]\r\n ?.split(';</script>')[0]\r\n .split(/(?<=}}});\\s*(var|const|let)\\s/)[0];\r\n if (!player_data) throw new Error('Initial Player Response Data is undefined.');\r\n const player_response = JSON.parse(player_data);\r\n let upcoming = false;\r\n if (player_response.playabilityStatus.status !== 'OK') {\r\n if (player_response.playabilityStatus.status === 'CONTENT_CHECK_REQUIRED') {\r\n if (options.htmldata)\r\n throw new Error(\r\n `Accepting the viewer discretion is not supported when using htmldata, video: ${player_response.videoDetails.videoId}`\r\n );\r\n\r\n const initial_data = body\r\n .split('var ytInitialData = ')?.[1]\r\n ?.split(';</script>')[0]\r\n .split(/;\\s*(var|const|let)\\s/)[0];\r\n if (!initial_data) throw new Error('Initial Response Data is undefined.');\r\n\r\n const cookies =\r\n JSON.parse(initial_data).topbar.desktopTopbarRenderer.interstitial?.consentBumpV2Renderer.agreeButton\r\n .buttonRenderer.command.saveConsentAction;\r\n if (cookies) {\r\n Object.assign(cookieJar, {\r\n VISITOR_INFO1_LIVE: cookies.visitorCookie,\r\n CONSENT: cookies.consentCookie\r\n });\r\n }\r\n\r\n const updatedValues = await acceptViewerDiscretion(\r\n player_response.videoDetails.videoId,\r\n cookieJar,\r\n body,\r\n false\r\n );\r\n player_response.streamingData = updatedValues.streamingData;\r\n } else if (player_response.playabilityStatus.status === 'LIVE_STREAM_OFFLINE') upcoming = true;\r\n else\r\n throw new Error(\r\n `While getting info from url\\n${\r\n player_response.playabilityStatus.errorScreen.playerErrorMessageRenderer?.reason.simpleText ??\r\n player_response.playabilityStatus.errorScreen.playerKavRenderer?.reason.simpleText ??\r\n player_response.playabilityStatus.reason\r\n }`\r\n );\r\n }\r\n const html5player = `https://www.youtube.com${body.split('\"jsUrl\":\"')[1].split('\"')[0]}`;\r\n const duration = Number(player_response.videoDetails.lengthSeconds);\r\n const video_details = {\r\n url: `https://www.youtube.com/watch?v=${player_response.videoDetails.videoId}`,\r\n durationInSec: (duration < 0 ? 0 : duration) || 0\r\n };\r\n let format = [];\r\n if (!upcoming) {\r\n // TODO: Properly handle the formats, for now ignore and use iOS formats\r\n //format.push(...(player_response.streamingData.formats ?? []));\r\n //format.push(...(player_response.streamingData.adaptiveFormats ?? []));\r\n\r\n // get the formats for the android player for legacy videos\r\n // fixes the stream being closed because not enough data\r\n // arrived in time for ffmpeg to be able to extract audio data\r\n //if (parseAudioFormats(format).length === 0 && !options.htmldata) {\r\n // format = await getAndroidFormats(player_response.videoDetails.videoId, cookieJar, body);\r\n //}\r\n format = await getIosFormats(player_response.videoDetails.videoId, cookieJar, body);\r\n }\r\n\r\n const LiveStreamData = {\r\n isLive: player_response.videoDetails.isLiveContent,\r\n dashManifestUrl: player_response.streamingData?.dashManifestUrl ?? null,\r\n hlsManifestUrl: player_response.streamingData?.hlsManifestUrl ?? null\r\n };\r\n return await decipher_info(\r\n {\r\n LiveStreamData,\r\n html5player,\r\n format,\r\n video_details\r\n },\r\n true\r\n );\r\n}\r\n/**\r\n * Function to convert seconds to [hour : minutes : seconds] format\r\n * @param seconds seconds to convert\r\n * @returns [hour : minutes : seconds] format\r\n */\r\nfunction parseSeconds(seconds: number): string {\r\n const d = Number(seconds);\r\n const h = Math.floor(d / 3600);\r\n const m = Math.floor((d % 3600) / 60);\r\n const s = Math.floor((d % 3600) % 60);\r\n\r\n const hDisplay = h > 0 ? (h < 10 ? `0${h}` : h) + ':' : '';\r\n const mDisplay = m > 0 ? (m < 10 ? `0${m}` : m) + ':' : '00:';\r\n const sDisplay = s > 0 ? (s < 10 ? `0${s}` : s) : '00';\r\n return hDisplay + mDisplay + sDisplay;\r\n}\r\n/**\r\n * Gets data from YouTube url or ID or html body data and deciphers it.\r\n * ```\r\n * video_basic_info + decipher_info = video_info\r\n * ```\r\n *\r\n * Example\r\n * ```ts\r\n * const video = await play.video_info('youtube video url')\r\n *\r\n * const res = ... // Any https package get function.\r\n *\r\n * const video = await play.video_info(res.body, { htmldata : true })\r\n * ```\r\n * @param url YouTube url or ID or html body data\r\n * @param options Video Info Options\r\n * - `boolean` htmldata : given data is html data or not\r\n * @returns Deciphered Video Info {@link InfoData}.\r\n */\r\nexport async function video_info(url: string, options: InfoOptions = {}): Promise<InfoData> {\r\n const data = await video_basic_info(url.trim(), options);\r\n return await decipher_info(data);\r\n}\r\n/**\r\n * Function uses data from video_basic_info and deciphers it if it contains signatures.\r\n * @param data Data - {@link InfoData}\r\n * @param audio_only `boolean` - To decipher only audio formats only.\r\n * @returns Deciphered Video Info {@link InfoData}\r\n */\r\nexport async function decipher_info<T extends InfoData | StreamInfoData>(\r\n data: T,\r\n audio_only: boolean = false\r\n): Promise<T> {\r\n if (\r\n data.LiveStreamData.isLive === true &&\r\n data.LiveStreamData.dashManifestUrl !== null &&\r\n data.video_details.durationInSec === 0\r\n ) {\r\n return data;\r\n } else if (data.format.length > 0 && (data.format[0].signatureCipher || data.format[0].cipher)) {\r\n if (audio_only) data.format = parseAudioFormats(data.format);\r\n data.format = await format_decipher(data.format, data.html5player);\r\n return data;\r\n } else return data;\r\n}\r\n/**\r\n * Gets YouTube playlist info from a playlist url.\r\n *\r\n * Example\r\n * ```ts\r\n * const playlist = await play.playlist_info('youtube playlist url')\r\n *\r\n * const playlist = await play.playlist_info('youtube playlist url', { incomplete : true })\r\n * ```\r\n * @param url Playlist URL\r\n * @param options Playlist Info Options\r\n * - `boolean` incomplete : When this is set to `false` (default) this function will throw an error\r\n * if the playlist contains hidden videos.\r\n * If it is set to `true`, it parses the playlist skipping the hidden videos,\r\n * only visible videos are included in the resulting {@link YouTubePlaylist}.\r\n *\r\n * @returns YouTube Playlist\r\n */\r\nexport async function playlist_info(url: string, options: PlaylistOptions = {}): Promise<YouTubePlayList> {\r\n if (!url || typeof url !== 'string') throw new Error(`Expected playlist url, received ${typeof url}!`);\r\n let url_ = url.trim();\r\n if (!url_.startsWith('https')) url_ = `https://www.youtube.com/playlist?list=${url_}`;\r\n if (url_.indexOf('list=') === -1) throw new Error('This is not a Playlist URL');\r\n\r\n if (url_.includes('music.youtube.com')) {\r\n const urlObj = new URL(url_);\r\n urlObj.hostname = 'www.youtube.com';\r\n url_ = urlObj.toString();\r\n }\r\n\r\n const body = await request(url_, {\r\n headers: {\r\n 'accept-language': options.language || 'en-US;q=0.9'\r\n }\r\n });\r\n if (body.indexOf('Our systems have detected unusual traffic from your computer network.') !== -1)\r\n throw new Error('Captcha page: YouTube has detected that you are a bot!');\r\n const response = JSON.parse(\r\n body\r\n .split('var ytInitialData = ')[1]\r\n .split(';</script>')[0]\r\n .split(/;\\s*(var|const|let)\\s/)[0]\r\n );\r\n if (response.alerts) {\r\n if (response.alerts[0].alertWithButtonRenderer?.type === 'INFO') {\r\n if (!options.incomplete)\r\n throw new Error(\r\n `While parsing playlist url\\n${response.alerts[0].alertWithButtonRenderer.text.simpleText}`\r\n );\r\n } else if (response.alerts[0].alertRenderer?.type === 'ERROR')\r\n throw new Error(`While parsing playlist url\\n${response.alerts[0].alertRenderer.text.runs[0].text}`);\r\n else throw new Error('While parsing playlist url\\nUnknown Playlist Error');\r\n }\r\n if (response.currentVideoEndpoint) {\r\n return getWatchPlaylist(response, body, url_);\r\n } else return getNormalPlaylist(response, body);\r\n}\r\n/**\r\n * Function to parse Playlist from YouTube search\r\n * @param data html data of that request\r\n * @param limit No. of videos to parse\r\n * @returns Array of YouTubeVideo.\r\n */\r\nexport function getPlaylistVideos(data: any, limit = Infinity): YouTubeVideo[] {\r\n const videos = [];\r\n\r\n for (let i = 0; i < data.length; i++) {\r\n if (limit === videos.length) break;\r\n const info = data[i].playlistVideoRenderer;\r\n if (!info || !info.shortBylineText) continue;\r\n\r\n videos.push(\r\n new YouTubeVideo({\r\n id: info.videoId,\r\n duration: parseInt(info.lengthSeconds) || 0,\r\n duration_raw: info.lengthText?.simpleText ?? '0:00',\r\n thumbnails: info.thumbnail.thumbnails,\r\n title: info.title.runs[0].text,\r\n upcoming: info.upcomingEventData?.startTime\r\n ? new Date(parseInt(info.upcomingEventData.startTime) * 1000)\r\n : undefined,\r\n channel: {\r\n id: info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId || undefined,\r\n name: info.shortBylineText.runs[0].text || undefined,\r\n url: `https://www.youtube.com${\r\n info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl ||\r\n info.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url\r\n }`,\r\n icon: undefined\r\n }\r\n })\r\n );\r\n }\r\n return videos;\r\n}\r\n/**\r\n * Function to get Continuation Token\r\n * @param data html data of playlist url\r\n * @returns token\r\n */\r\nexport function getContinuationToken(data: any): string {\r\n return data.find((x: any) => Object.keys(x)[0] === 'continuationItemRenderer')?.continuationItemRenderer\r\n .continuationEndpoint?.continuationCommand?.token;\r\n}\r\n\r\nasync function acceptViewerDiscretion(\r\n videoId: string,\r\n cookieJar: { [key: string]: string },\r\n body: string,\r\n extractRelated: boolean\r\n): Promise<{ streamingData: any; relatedVideos?: any }> {\r\n const apiKey =\r\n body.split('INNERTUBE_API_KEY\":\"')[1]?.split('\"')[0] ??\r\n body.split('innertubeApiKey\":\"')[1]?.split('\"')[0] ??\r\n DEFAULT_API_KEY;\r\n const sessionToken =\r\n body.split('\"XSRF_TOKEN\":\"')[1]?.split('\"')[0].replaceAll('\\\\u003d', '=') ??\r\n body.split('\"xsrf_token\":\"')[1]?.split('\"')[0].replaceAll('\\\\u003d', '=');\r\n if (!sessionToken)\r\n throw new Error(`Unable to extract XSRF_TOKEN to accept the viewer discretion popup for video: ${videoId}.`);\r\n\r\n const verificationResponse = await request(`https://www.youtube.com/youtubei/v1/verify_age?key=${apiKey}&prettyPrint=false`, {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n context: {\r\n client: {\r\n utcOffsetMinutes: 0,\r\n gl: 'US',\r\n hl: 'en',\r\n clientName: 'WEB',\r\n clientVersion:\r\n body.split('\"INNERTUBE_CONTEXT_CLIENT_VERSION\":\"')[1]?.split('\"')[0] ??\r\n body.split('\"innertube_context_client_version\":\"')[1]?.split('\"')[0] ??\r\n '<some version>'\r\n },\r\n user: {},\r\n request: {}\r\n },\r\n nextEndpoint: {\r\n urlEndpoint: {\r\n url: `/watch?v=${videoId}&has_verified=1`\r\n }\r\n },\r\n setControvercy: true\r\n }),\r\n cookies: true,\r\n cookieJar\r\n });\r\n\r\n const endpoint = JSON.parse(verificationResponse).actions[0].navigateAction.endpoint;\r\n\r\n const videoPage = await request(`https://www.youtube.com/${endpoint.urlEndpoint.url}&pbj=1`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded'\r\n },\r\n body: new URLSearchParams([\r\n ['command', JSON.stringify(endpoint)],\r\n ['session_token', sessionToken]\r\n ]).toString(),\r\n cookies: true,\r\n cookieJar\r\n });\r\n\r\n if (videoPage.includes('<h1>Something went wrong</h1>'))\r\n throw new Error(`Unable to accept the viewer discretion popup for video: ${videoId}`);\r\n\r\n const videoPageData = JSON.parse(videoPage);\r\n\r\n if (videoPageData[2].playerResponse.playabilityStatus.status !== 'OK')\r\n throw new Error(\r\n `While getting info from url after trying to accept the discretion popup for video ${videoId}\\n${\r\n videoPageData[2].playerResponse.playabilityStatus.errorScreen.playerErrorMessageRenderer?.reason\r\n .simpleText ??\r\n videoPageData[2].playerResponse.playabilityStatus.errorScreen.playerKavRenderer?.reason.simpleText\r\n }`\r\n );\r\n\r\n const streamingData = videoPageData[2].playerResponse.streamingData;\r\n\r\n if (extractRelated)\r\n return {\r\n streamingData,\r\n relatedVideos: videoPageData[3].response.contents.twoColumnWatchNextResults.secondaryResults\r\n };\r\n\r\n return { streamingData };\r\n}\r\n\r\nasync function getIosFormats(videoId: string, cookieJar: { [key: string]: string }, body: string): Promise<any[]> {\r\n const apiKey =\r\n body.split('INNERTUBE_API_KEY\":\"')[1]?.split('\"')[0] ??\r\n body.split('innertubeApiKey\":\"')[1]?.split('\"')[0] ??\r\n DEFAULT_API_KEY;\r\n\r\n const response = await request(`https://www.youtube.com/youtubei/v1/player?key=${apiKey}&prettyPrint=false`, {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n context: {\r\n client: {\r\n clientName: 'IOS',\r\n clientVersion: '19.09.3',\r\n deviceModel: 'iPhone16,1',\r\n userAgent: 'com.google.ios.youtube/19.09.3 (iPhone; CPU iPhone OS 17_5 like Mac OS X)',\r\n hl: 'en',\r\n timeZone: 'UTC',\r\n utcOffsetMinutes: 0\r\n }\r\n },\r\n videoId: videoId,\r\n playbackContext: { contentPlaybackContext: { html5Preference: 'HTML5_PREF_WANTS' } },\r\n contentCheckOk: true,\r\n racyCheckOk: true\r\n }),\r\n cookies: true,\r\n cookieJar\r\n });\r\n\r\n return JSON.parse(response).streamingData.adaptiveFormats;\r\n //return JSON.parse(response).streamingData.formats;\r\n}\r\n\r\nfunction getWatchPlaylist(response: any, body: any, url: string): YouTubePlayList {\r\n const playlist_details = response.contents.twoColumnWatchNextResults.playlist?.playlist;\r\n if (!playlist_details)\r\n throw new Error(\"Watch playlist unavailable due to YouTube layout changes.\")\r\n\r\n const videos = getWatchPlaylistVideos(playlist_details.contents);\r\n const API_KEY =\r\n body.split('INNERTUBE_API_KEY\":\"')[1]?.split('\"')[0] ??\r\n body.split('innertubeApiKey\":\"')[1]?.split('\"')[0] ??\r\n DEFAULT_API_KEY;\r\n\r\n const videoCount = playlist_details.totalVideos;\r\n const channel = playlist_details.shortBylineText?.runs?.[0];\r\n const badge = playlist_details.badges?.[0]?.metadataBadgeRenderer?.style.toLowerCase();\r\n\r\n return new YouTubePlayList({\r\n continuation: {\r\n api: API_KEY,\r\n token: getContinuationToken(playlist_details.contents),\r\n clientVersion:\r\n body.split('\"INNERTUBE_CONTEXT_CLIENT_VERSION\":\"')[1]?.split('\"')[0] ??\r\n body.split('\"innertube_context_client_version\":\"')[1]?.split('\"')[0] ??\r\n '<some version>'\r\n },\r\n id: playlist_details.playlistId || '',\r\n title: playlist_details.title || '',\r\n videoCount: parseInt(videoCount) || 0,\r\n videos: videos,\r\n url: url,\r\n channel: {\r\n id: channel?.navigationEndpoint?.browseEndpoint?.browseId || null,\r\n name: channel?.text || null,\r\n url: `https://www.youtube.com${\r\n channel?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl ||\r\n channel?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url\r\n }`,\r\n verified: Boolean(badge?.includes('verified')),\r\n artist: Boolean(badge?.includes('artist'))\r\n }\r\n });\r\n}\r\n\r\nfunction getNormalPlaylist(response: any, body: any): YouTubePlayList {\r\n const json_data =\r\n response.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents[0]\r\n .itemSectionRenderer.contents[0].playlistVideoListRenderer.contents;\r\n const playlist_details = response.sidebar.playlistSidebarRenderer.items;\r\n\r\n const API_KEY =\r\n body.split('INNERTUBE_API_KEY\":\"')[1]?.split('\"')[0] ??\r\n body.split('innertubeApiKey\":\"')[1]?.split('\"')[0] ??\r\n DEFAULT_API_KEY;\r\n const videos = getPlaylistVideos(json_data, 100);\r\n\r\n const data = playlist_details[0].playlistSidebarPrimaryInfoRenderer;\r\n if (!data.title.runs || !data.title.runs.length) throw new Error('Failed to Parse Playlist info.');\r\n\r\n const author = playlist_details[1]?.playlistSidebarSecondaryInfoRenderer.videoOwner;\r\n const views = data.stats.length === 3 ? data.stats[1].simpleText.replace(/\\D/g, '') : 0;\r\n const lastUpdate =\r\n data.stats\r\n .find((x: any) => 'runs' in x && x['runs'].find((y: any) => y.text.toLowerCase().includes('last update')))\r\n ?.runs.pop()?.text ?? null;\r\n const videosCount = data.stats[0].runs[0].text.replace(/\\D/g, '') || 0;\r\n\r\n const res = new YouTubePlayList({\r\n continuation: {\r\n api: API_KEY,\r\n token: getContinuationToken(json_data),\r\n clientVersion:\r\n body.split('\"INNERTUBE_CONTEXT_CLIENT_VERSION\":\"')[1]?.split('\"')[0] ??\r\n body.split('\"innertube_context_client_version\":\"')[1]?.split('\"')[0] ??\r\n '<some version>'\r\n },\r\n id: data.title.runs[0].navigationEndpoint.watchEndpoint.playlistId,\r\n title: data.title.runs[0].text,\r\n videoCount: parseInt(videosCount) || 0,\r\n lastUpdate: lastUpdate,\r\n views: parseInt(views) || 0,\r\n videos: videos,\r\n url: `https://www.youtube.com/playlist?list=${data.title.runs[0].navigationEndpoint.watchEndpoint.playlistId}`,\r\n link: `https://www.youtube.com${data.title.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`,\r\n channel: author\r\n ? {\r\n name: author.videoOwnerRenderer.title.runs[0].text,\r\n id: author.videoOwnerRenderer.title.runs[0].navigationEndpoint.browseEndpoint.browseId,\r\n url: `https://www.youtube.com${\r\n author.videoOwnerRenderer.navigationEndpoint.commandMetadata.webCommandMetadata.url ||\r\n author.videoOwnerRenderer.navigationEndpoint.browseEndpoint.canonicalBaseUrl\r\n }`,\r\n icons: author.videoOwnerRenderer.thumbnail.thumbnails ?? []\r\n }\r\n : {},\r\n thumbnail: data.thumbnailRenderer.playlistVideoThumbnailRenderer?.thumbnail.thumbnails.length\r\n ? data.thumbnailRenderer.playlistVideoThumbnailRenderer.thumbnail.thumbnails[\r\n data.thumbnailRenderer.playlistVideoThumbnailRenderer.thumbnail.thumbnails.length - 1\r\n ]\r\n : null\r\n });\r\n return res;\r\n}\r\n\r\nfunction getWatchPlaylistVideos(data: any, limit = Infinity): YouTubeVideo[] {\r\n const videos: YouTubeVideo[] = [];\r\n\r\n for (let i = 0; i < data.length; i++) {\r\n if (limit === videos.length) break;\r\n const info = data[i].playlistPanelVideoRenderer;\r\n if (!info || !info.shortBylineText) continue;\r\n const channel_info = info.shortBylineText.runs[0];\r\n\r\n videos.push(\r\n new YouTubeVideo({\r\n id: info.videoId,\r\n duration: parseDuration(info.lengthText?.simpleText) || 0,\r\n duration_raw: info.lengthText?.simpleText ?? '0:00',\r\n thumbnails: info.thumbnail.thumbnails,\r\n title: info.title.simpleText,\r\n upcoming:\r\n info.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer?.style === 'UPCOMING' || undefined,\r\n channel: {\r\n id: channel_info.navigationEndpoint.browseEndpoint.browseId || undefined,\r\n name: channel_info.text || undefined,\r\n url: `https://www.youtube.com${\r\n channel_info.navigationEndpoint.browseEndpoint.canonicalBaseUrl ||\r\n channel_info.navigationEndpoint.commandMetadata.webCommandMetadata.url\r\n }`,\r\n icon: undefined\r\n }\r\n })\r\n );\r\n }\r\n\r\n return videos;\r\n}\r\n\r\nfunction parseDuration(text: string): number {\r\n if (!text) return 0;\r\n const split = text.split(':');\r\n\r\n switch (split.length) {\r\n case 2:\r\n return parseInt(split[0]) * 60 + parseInt(split[1]);\r\n\r\n case 3:\r\n return parseInt(split[0]) * 60 * 60 + parseInt(split[1]) * 60 + parseInt(split[2]);\r\n\r\n default:\r\n return 0;\r\n }\r\n}","import { WebmElements, WebmHeader } from 'play-audio';\r\nimport { Duplex, DuplexOptions } from 'node:stream';\r\n\r\nenum DataType {\r\n master,\r\n string,\r\n uint,\r\n binary,\r\n float\r\n}\r\n\r\nexport enum WebmSeekerState {\r\n READING_HEAD = 'READING_HEAD',\r\n READING_DATA = 'READING_DATA'\r\n}\r\n\r\ninterface WebmSeekerOptions extends DuplexOptions {\r\n mode?: 'precise' | 'granular';\r\n}\r\n\r\nconst WEB_ELEMENT_KEYS = Object.keys(WebmElements);\r\n\r\nexport class WebmSeeker extends Duplex {\r\n remaining?: Buffer;\r\n state: WebmSeekerState;\r\n chunk?: Buffer;\r\n cursor: number;\r\n header: WebmHeader;\r\n headfound: boolean;\r\n headerparsed: boolean;\r\n seekfound: boolean;\r\n private data_size: number;\r\n private offset: number;\r\n private data_length: number;\r\n private sec: number;\r\n private time: number;\r\n\r\n constructor(sec: number, options: WebmSeekerOptions) {\r\n super(options);\r\n this.state = WebmSeekerState.READING_HEAD;\r\n this.cursor = 0;\r\n this.header = new WebmHeader();\r\n this.headfound = false;\r\n this.headerparsed = false;\r\n this.seekfound = false;\r\n this.data_length = 0;\r\n this.data_size = 0;\r\n this.offset = 0;\r\n this.sec = sec;\r\n this.time = Math.floor(sec / 10) * 10;\r\n }\r\n\r\n private get vint_length(): number {\r\n let i = 0;\r\n for (; i < 8; i++) {\r\n if ((1 << (7 - i)) & this.chunk![this.cursor]) break;\r\n }\r\n return ++i;\r\n }\r\n\r\n private vint_value(): boolean {\r\n if (!this.chunk) return false;\r\n const length = this.vint_length;\r\n if (this.chunk.length < this.cursor + length) return false;\r\n let value = this.chunk[this.cursor] & ((1 << (8 - length)) - 1);\r\n for (let i = this.cursor + 1; i < this.cursor + length; i++) value = (value << 8) + this.chunk[i];\r\n this.data_size = length;\r\n this.data_length = value;\r\n return true;\r\n }\r\n\r\n cleanup() {\r\n this.cursor = 0;\r\n this.chunk = undefined;\r\n this.remaining = undefined;\r\n }\r\n\r\n _read() {}\r\n\r\n seek(content_length: number): Error | number {\r\n let clusterlength = 0,\r\n position = 0;\r\n let time_left = (this.sec - this.time) * 1000 || 0;\r\n time_left = Math.round(time_left / 20) * 20;\r\n if (!this.header.segment.cues) return new Error('Failed to Parse Cues');\r\n\r\n for (let i = 0; i < this.header.segment.cues.length; i++) {\r\n const data = this.header.segment.cues[i];\r\n if (Math.floor((data.time as number) / 1000) === this.time) {\r\n position = data.position as number;\r\n clusterlength = (this.header.segment.cues[i + 1]?.position || content_length) - position - 1;\r\n break;\r\n } else continue;\r\n }\r\n if (clusterlength === 0) return position;\r\n return this.offset + Math.round(position + (time_left / 20) * (clusterlength / 500));\r\n }\r\n\r\n _write(chunk: Buffer, _: BufferEncoding, callback: (error?: Error | null) => void): void {\r\n if (this.remaining) {\r\n this.chunk = Buffer.concat([this.remaining, chunk]);\r\n this.remaining = undefined;\r\n } else this.chunk = chunk;\r\n\r\n let err: Error | undefined;\r\n\r\n if (this.state === WebmSeekerState.READING_HEAD) err = this.readHead();\r\n else if (!this.seekfound) err = this.getClosestBlock();\r\n else err = this.readTag();\r\n\r\n if (err) callback(err);\r\n else callback();\r\n }\r\n\r\n private readHead(): Error | undefined {\r\n if (!this.chunk) return new Error('Chunk is missing');\r\n\r\n while (this.chunk.length > this.cursor) {\r\n const oldCursor = this.cursor;\r\n const id = this.vint_length;\r\n if (this.chunk.length < this.cursor + id) break;\r\n\r\n const ebmlID = this.parseEbmlID(this.chunk.slice(this.cursor, this.cursor + id).toString('hex'));\r\n this.cursor += id;\r\n\r\n if (!this.vint_value()) {\r\n this.cursor = oldCursor;\r\n break;\r\n }\r\n if (!ebmlID) {\r\n this.cursor += this.data_size + this.data_length;\r\n continue;\r\n }\r\n\r\n if (!this.headfound) {\r\n if (ebmlID.name === 'ebml') this.headfound = true;\r\n else return new Error('Failed to find EBML ID at start of stream.');\r\n }\r\n const data = this.chunk.slice(\r\n this.cursor + this.data_size,\r\n this.cursor + this.data_size + this.data_length\r\n );\r\n const parse = this.header.parse(ebmlID, data);\r\n if (parse instanceof Error) return parse;\r\n\r\n // stop parsing the header once we have found the correct cue\r\n\r\n if (ebmlID.name === 'seekHead') this.offset = oldCursor;\r\n\r\n if (\r\n ebmlID.name === 'cueClusterPosition' &&\r\n this.header.segment.cues!.length > 2 &&\r\n this.time === (this.header.segment.cues!.at(-2)!.time as number) / 1000\r\n )\r\n this.emit('headComplete');\r\n\r\n if (ebmlID.type === DataType.master) {\r\n this.cursor += this.data_size;\r\n continue;\r\n }\r\n\r\n if (this.chunk.length < this.cursor + this.data_size + this.data_length) {\r\n this.cursor = oldCursor;\r\n break;\r\n } else this.cursor += this.data_size + this.data_length;\r\n }\r\n this.remaining = this.chunk.slice(this.cursor);\r\n this.cursor = 0;\r\n }\r\n\r\n private readTag(): Error | undefined {\r\n if (!this.chunk) return new Error('Chunk is missing');\r\n\r\n while (this.chunk.length > this.cursor) {\r\n const oldCursor = this.cursor;\r\n const id = this.vint_length;\r\n if (this.chunk.length < this.cursor + id) break;\r\n\r\n const ebmlID = this.parseEbmlID(this.chunk.slice(this.cursor, this.cursor + id).toString('hex'));\r\n this.cursor += id;\r\n\r\n if (!this.vint_value()) {\r\n this.cursor = oldCursor;\r\n break;\r\n }\r\n if (!ebmlID) {\r\n this.cursor += this.data_size + this.data_length;\r\n continue;\r\n }\r\n\r\n const data = this.chunk.slice(\r\n this.cursor + this.data_size,\r\n this.cursor + this.data_size + this.data_length\r\n );\r\n const parse = this.header.parse(ebmlID, data);\r\n if (parse instanceof Error) return parse;\r\n\r\n if (ebmlID.type === DataType.master) {\r\n this.cursor += this.data_size;\r\n continue;\r\n }\r\n\r\n if (this.chunk.length < this.cursor + this.data_size + this.data_length) {\r\n this.cursor = oldCursor;\r\n break;\r\n } else this.cursor += this.data_size + this.data_length;\r\n\r\n if (ebmlID.name === 'simpleBlock') {\r\n const track = this.header.segment.tracks![this.header.audioTrack];\r\n if (!track || track.trackType !== 2) return new Error('No audio Track in this webm file.');\r\n if ((data[0] & 0xf) === track.trackNumber) this.push(data.slice(4));\r\n }\r\n }\r\n this.remaining = this.chunk.slice(this.cursor);\r\n this.cursor = 0;\r\n }\r\n\r\n private getClosestBlock(): Error | undefined {\r\n if (this.sec === 0) {\r\n this.seekfound = true;\r\n return this.readTag();\r\n }\r\n if (!this.chunk) return new Error('Chunk is missing');\r\n this.cursor = 0;\r\n let positionFound = false;\r\n while (!positionFound && this.cursor < this.chunk.length) {\r\n this.cursor = this.chunk.indexOf('a3', this.cursor, 'hex');\r\n if (this.cursor === -1) return new Error('Failed to find nearest Block.');\r\n this.cursor++;\r\n if (!this.vint_value()) return new Error('Failed to find correct simpleBlock in first chunk');\r\n if (this.cursor + this.data_length + this.data_length > this.chunk.length) continue;\r\n const data = this.chunk.slice(\r\n this.cursor + this.data_size,\r\n this.cursor + this.data_size + this.data_length\r\n );\r\n const track = this.header.segment.tracks![this.header.audioTrack];\r\n if (!track || track.trackType !== 2) return new Error('No audio Track in this webm file.');\r\n if ((data[0] & 0xf) === track.trackNumber) {\r\n this.cursor += this.data_size + this.data_length;\r\n this.push(data.slice(4));\r\n positionFound = true;\r\n } else continue;\r\n }\r\n if (!positionFound) return new Error('Failed to find nearest correct simple Block.');\r\n this.seekfound = true;\r\n return this.readTag();\r\n }\r\n\r\n private parseEbmlID(ebmlID: string) {\r\n if (WEB_ELEMENT_KEYS.includes(ebmlID)) return WebmElements[ebmlID];\r\n else return false;\r\n }\r\n\r\n _destroy(error: Error | null, callback: (error: Error | null) => void): void {\r\n this.cleanup();\r\n callback(error);\r\n }\r\n\r\n _final(callback: (error?: Error | null) => void): void {\r\n this.cleanup();\r\n callback();\r\n }\r\n}\r\n","import { IncomingMessage } from 'node:http';\r\nimport { request_stream } from '../../Request';\r\nimport { parseAudioFormats, StreamOptions, StreamType } from '../stream';\r\nimport { video_stream_info } from '../utils/extractor';\r\nimport { Timer } from './LiveStream';\r\nimport { WebmSeeker, WebmSeekerState } from './WebmSeeker';\r\n\r\n/**\r\n * YouTube Stream Class for seeking audio to a timeStamp.\r\n */\r\nexport class SeekStream {\r\n /**\r\n * WebmSeeker Stream through which data passes\r\n */\r\n stream: WebmSeeker;\r\n /**\r\n * Type of audio data that we recieved from normal youtube url.\r\n */\r\n type: StreamType;\r\n /**\r\n * Audio Endpoint Format Url to get data from.\r\n */\r\n private url: string;\r\n /**\r\n * Used to calculate no of bytes data that we have recieved\r\n */\r\n private bytes_count: number;\r\n /**\r\n * Calculate per second bytes by using contentLength (Total bytes) / Duration (in seconds)\r\n */\r\n private per_sec_bytes: number;\r\n /**\r\n * Length of the header in bytes\r\n */\r\n private header_length: number;\r\n /**\r\n * Total length of audio file in bytes\r\n */\r\n private content_length: number;\r\n /**\r\n * YouTube video url. [ Used only for retrying purposes only. ]\r\n */\r\n private video_url: string;\r\n /**\r\n * Timer for looping data every 265 seconds.\r\n */\r\n private timer: Timer;\r\n /**\r\n * Quality given by user. [ Used only for retrying purposes only. ]\r\n */\r\n private quality: number;\r\n /**\r\n * Incoming message that we recieve.\r\n *\r\n * Storing this is essential.\r\n * This helps to destroy the TCP connection completely if you stopped player in between the stream\r\n */\r\n private request: IncomingMessage | null;\r\n /**\r\n * YouTube Stream Class constructor\r\n * @param url Audio Endpoint url.\r\n * @param type Type of Stream\r\n * @param duration Duration of audio playback [ in seconds ]\r\n * @param headerLength Length of the header in bytes.\r\n * @param contentLength Total length of Audio file in bytes.\r\n * @param bitrate Bitrate provided by YouTube.\r\n * @param video_url YouTube video url.\r\n * @param options Options provided to stream function.\r\n */\r\n constructor(\r\n url: string,\r\n duration: number,\r\n headerLength: number,\r\n contentLength: number,\r\n bitrate: number,\r\n video_url: string,\r\n options: StreamOptions\r\n ) {\r\n this.stream = new WebmSeeker(options.seek!, {\r\n highWaterMark: 5 * 1000 * 1000,\r\n readableObjectMode: true\r\n });\r\n this.url = url;\r\n this.quality = options.quality as number;\r\n this.type = StreamType.Opus;\r\n this.bytes_count = 0;\r\n this.video_url = video_url;\r\n this.per_sec_bytes = bitrate ? Math.ceil(bitrate / 8) : Math.ceil(contentLength / duration);\r\n this.header_length = headerLength;\r\n this.content_length = contentLength;\r\n this.request = null;\r\n this.timer = new Timer(() => {\r\n this.timer.reuse();\r\n this.loop();\r\n }, 265);\r\n this.stream.on('close', () => {\r\n this.timer.destroy();\r\n this.cleanup();\r\n });\r\n this.seek();\r\n }\r\n /**\r\n * **INTERNAL Function**\r\n *\r\n * Uses stream functions to parse Webm Head and gets Offset byte to seek to.\r\n * @returns Nothing\r\n */\r\n private async seek(): Promise<void> {\r\n const parse = await new Promise(async (res, rej) => {\r\n if (!this.stream.headerparsed) {\r\n const stream = await request_stream(this.url, {\r\n headers: {\r\n range: `bytes=0-${this.header_length}`\r\n }\r\n }).catch((err: Error) => err);\r\n\r\n if (stream instanceof Error) {\r\n rej(stream);\r\n return;\r\n }\r\n if (Number(stream.statusCode) >= 400) {\r\n rej(400);\r\n return;\r\n }\r\n this.request = stream;\r\n stream.pipe(this.stream, { end: false });\r\n\r\n // headComplete should always be called, leaving this here just in case\r\n stream.once('end', () => {\r\n this.stream.state = WebmSeekerState.READING_DATA;\r\n res('');\r\n });\r\n\r\n this.stream.once('headComplete', () => {\r\n stream.unpipe(this.stream);\r\n stream.destroy();\r\n this.stream.state = WebmSeekerState.READING_DATA;\r\n res('');\r\n });\r\n } else res('');\r\n }).catch((err) => err);\r\n if (parse instanceof Error) {\r\n this.stream.emit('error', parse);\r\n this.bytes_count = 0;\r\n this.per_sec_bytes = 0;\r\n this.cleanup();\r\n return;\r\n } else if (parse === 400) {\r\n await this.retry();\r\n this.timer.reuse();\r\n return this.seek();\r\n }\r\n const bytes = this.stream.seek(this.content_length);\r\n if (bytes instanceof Error) {\r\n this.stream.emit('error', bytes);\r\n this.bytes_count = 0;\r\n this.per_sec_bytes = 0;\r\n this.cleanup();\r\n return;\r\n }\r\n\r\n this.stream.seekfound = false;\r\n this.bytes_count = bytes;\r\n this.timer.reuse();\r\n this.loop();\r\n }\r\n /**\r\n * Retry if we get 404 or 403 Errors.\r\n */\r\n private async retry() {\r\n const info = await video_stream_info(this.video_url);\r\n const audioFormat = parseAudioFormats(info.format);\r\n this.url = audioFormat[this.quality].url;\r\n }\r\n /**\r\n * This cleans every used variable in class.\r\n *\r\n * This is used to prevent re-use of this class and helping garbage collector to collect it.\r\n */\r\n private cleanup() {\r\n this.request?.destroy();\r\n this.request = null;\r\n this.url = '';\r\n }\r\n /**\r\n * Getting data from audio endpoint url and passing it to stream.\r\n *\r\n * If 404 or 403 occurs, it will retry again.\r\n */\r\n private async loop() {\r\n if (this.stream.destroyed) {\r\n this.timer.destroy();\r\n this.cleanup();\r\n return;\r\n }\r\n const end: number = this.bytes_count + this.per_sec_bytes * 300;\r\n const stream = await request_stream(this.url, {\r\n headers: {\r\n range: `bytes=${this.bytes_count}-${end >= this.content_length ? '' : end}`\r\n }\r\n }).catch((err: Error) => err);\r\n if (stream instanceof Error) {\r\n this.stream.emit('error', stream);\r\n this.bytes_count = 0;\r\n this.per_sec_bytes = 0;\r\n this.cleanup();\r\n return;\r\n }\r\n if (Number(stream.statusCode) >= 400) {\r\n this.cleanup();\r\n await this.retry();\r\n this.timer.reuse();\r\n this.loop();\r\n return;\r\n }\r\n this.request = stream;\r\n stream.pipe(this.stream, { end: false });\r\n\r\n stream.once('error', async () => {\r\n this.cleanup();\r\n await this.retry();\r\n this.timer.reuse();\r\n this.loop();\r\n });\r\n\r\n stream.on('data', (chunk: any) => {\r\n this.bytes_count += chunk.length;\r\n });\r\n\r\n stream.on('end', () => {\r\n if (end >= this.content_length) {\r\n this.timer.destroy();\r\n this.stream.end();\r\n this.cleanup();\r\n }\r\n });\r\n }\r\n /**\r\n * Pauses timer.\r\n * Stops running of loop.\r\n *\r\n * Useful if you don't want to get excess data to be stored in stream.\r\n */\r\n pause() {\r\n this.timer.pause();\r\n }\r\n /**\r\n * Resumes timer.\r\n * Starts running of loop.\r\n */\r\n resume() {\r\n this.timer.resume();\r\n }\r\n}\r\n","import { request_content_length, request_stream } from '../Request';\r\nimport { LiveStream, Stream } from './classes/LiveStream';\r\nimport { SeekStream } from './classes/SeekStream';\r\nimport { InfoData, StreamInfoData } from './utils/constants';\r\nimport { video_stream_info } from './utils/extractor';\r\nimport { URL } from 'node:url';\r\n\r\nexport enum StreamType {\r\n Arbitrary = 'arbitrary',\r\n Raw = 'raw',\r\n OggOpus = 'ogg/opus',\r\n WebmOpus = 'webm/opus',\r\n Opus = 'opus'\r\n}\r\n\r\nexport interface StreamOptions {\r\n seek?: number;\r\n quality?: number;\r\n language?: string;\r\n htmldata?: boolean;\r\n precache?: number;\r\n discordPlayerCompatibility?: boolean;\r\n}\r\n\r\n/**\r\n * Command to find audio formats from given format array\r\n * @param formats Formats to search from\r\n * @returns Audio Formats array\r\n */\r\nexport function parseAudioFormats(formats: any[]) {\r\n const result: any[] = [];\r\n formats.forEach((format) => {\r\n const type = format.mimeType as string;\r\n if (type.startsWith('audio')) {\r\n format.codec = type.split('codecs=\"')[1].split('\"')[0];\r\n format.container = type.split('audio/')[1].split(';')[0];\r\n result.push(format);\r\n }\r\n });\r\n return result;\r\n}\r\n/**\r\n * Type for YouTube Stream\r\n */\r\nexport type YouTubeStream = Stream | LiveStream | SeekStream;\r\n/**\r\n * Stream command for YouTube\r\n * @param url YouTube URL\r\n * @param options lets you add quality for stream\r\n * @returns Stream class with type and stream for playing.\r\n */\r\nexport async function stream(url: string, options: StreamOptions = {}): Promise<YouTubeStream> {\r\n const info = await video_stream_info(url, { htmldata: options.htmldata, language: options.language });\r\n return await stream_from_info(info, options);\r\n}\r\n/**\r\n * Stream command for YouTube using info from video_info or decipher_info function.\r\n * @param info video_info data\r\n * @param options lets you add quality for stream\r\n * @returns Stream class with type and stream for playing.\r\n */\r\nexport async function stream_from_info(\r\n info: InfoData | StreamInfoData,\r\n options: StreamOptions = {}\r\n): Promise<YouTubeStream> {\r\n if (info.format.length === 0)\r\n throw new Error('Upcoming and premiere videos that are not currently live cannot be streamed.');\r\n if (options.quality && !Number.isInteger(options.quality))\r\n throw new Error(\"Quality must be set to an integer.\")\r\n\r\n const final: any[] = [];\r\n if (\r\n info.LiveStreamData.isLive === true &&\r\n info.LiveStreamData.dashManifestUrl !== null &&\r\n info.video_details.durationInSec === 0\r\n ) {\r\n return new LiveStream(\r\n info.LiveStreamData.dashManifestUrl,\r\n info.format[info.format.length - 1].targetDurationSec as number,\r\n info.video_details.url,\r\n options.precache\r\n );\r\n }\r\n\r\n const audioFormat = parseAudioFormats(info.format);\r\n if (typeof options.quality !== 'number') options.quality = audioFormat.length - 1;\r\n else if (options.quality <= 0) options.quality = 0;\r\n else if (options.quality >= audioFormat.length) options.quality = audioFormat.length - 1;\r\n if (audioFormat.length !== 0) final.push(audioFormat[options.quality]);\r\n else final.push(info.format[info.format.length - 1]);\r\n let type: StreamType =\r\n final[0].codec === 'opus' && final[0].container === 'webm' ? StreamType.WebmOpus : StreamType.Arbitrary;\r\n await request_stream(`https://${new URL(final[0].url).host}/generate_204`);\r\n if (type === StreamType.WebmOpus) {\r\n if (!options.discordPlayerCompatibility) {\r\n options.seek ??= 0;\r\n if (options.seek >= info.video_details.durationInSec || options.seek < 0)\r\n throw new Error(`Seeking beyond limit. [ 0 - ${info.video_details.durationInSec - 1}]`);\r\n return new SeekStream(\r\n final[0].url,\r\n info.video_details.durationInSec,\r\n final[0].indexRange.end,\r\n Number(final[0].contentLength),\r\n Number(final[0].bitrate),\r\n info.video_details.url,\r\n options\r\n );\r\n } else if (options.seek) throw new Error('Can not seek with discordPlayerCompatibility set to true.');\r\n }\r\n\r\n let contentLength;\r\n if (final[0].contentLength) {\r\n contentLength = Number(final[0].contentLength);\r\n } else {\r\n contentLength = await request_content_length(final[0].url);\r\n }\r\n\r\n return new Stream(\r\n final[0].url,\r\n type,\r\n info.video_details.durationInSec,\r\n contentLength,\r\n info.video_details.url,\r\n options\r\n );\r\n}\r\n","import { YouTubeVideo } from '../classes/Video';\r\nimport { YouTubePlayList } from '../classes/Playlist';\r\nimport { YouTubeChannel } from '../classes/Channel';\r\nimport { YouTube } from '..';\r\nimport { YouTubeThumbnail } from '../classes/Thumbnail';\r\n\r\nconst BLURRED_THUMBNAILS = [\r\n '-oaymwEpCOADEI4CSFryq4qpAxsIARUAAAAAGAElAADIQj0AgKJDeAHtAZmZGUI=',\r\n '-oaymwEiCOADEI4CSFXyq4qpAxQIARUAAIhCGAFwAcABBu0BmZkZQg==',\r\n '-oaymwEiCOgCEMoBSFXyq4qpAxQIARUAAIhCGAFwAcABBu0BZmbmQQ==',\r\n '-oaymwEiCNAFEJQDSFXyq4qpAxQIARUAAIhCGAFwAcABBu0BZmZmQg==',\r\n '-oaymwEdCNAFEJQDSFryq4qpAw8IARUAAIhCGAHtAWZmZkI=',\r\n '-oaymwEdCNACELwBSFryq4qpAw8IARUAAIhCGAHtAT0K10E='\r\n];\r\n\r\nexport interface ParseSearchInterface {\r\n type?: 'video' | 'playlist' | 'channel';\r\n limit?: number;\r\n language?: string;\r\n unblurNSFWThumbnails?: boolean;\r\n}\r\n\r\nexport interface thumbnail {\r\n width: string;\r\n height: string;\r\n url: string;\r\n}\r\n/**\r\n * Main command which converts html body data and returns the type of data requested.\r\n * @param html body of that request\r\n * @param options limit & type of YouTube search you want.\r\n * @returns Array of one of YouTube type.\r\n */\r\nexport function ParseSearchResult(html: string, options?: ParseSearchInterface): YouTube[] {\r\n if (!html) throw new Error(\"Can't parse Search result without data\");\r\n if (!options) options = { type: 'video', limit: 0 };\r\n else if (!options.type) options.type = 'video';\r\n const hasLimit = typeof options.limit === 'number' && options.limit > 0;\r\n options.unblurNSFWThumbnails ??= false;\r\n\r\n const data = html\r\n .split('var ytInitialData = ')?.[1]\r\n ?.split(';</script>')[0]\r\n .split(/;\\s*(var|const|let)\\s/)[0];\r\n const json_data = JSON.parse(data);\r\n const results = [];\r\n const details =\r\n json_data.contents.twoColumnSearchResultsRenderer.primaryContents.sectionListRenderer.contents.flatMap(\r\n (s: any) => s.itemSectionRenderer?.contents\r\n );\r\n for (const detail of details) {\r\n if (hasLimit && results.length === options.limit) break;\r\n if (!detail || (!detail.videoRenderer && !detail.channelRenderer && !detail.playlistRenderer)) continue;\r\n switch (options.type) {\r\n case 'video': {\r\n const parsed = parseVideo(detail);\r\n if (parsed) {\r\n if (options.unblurNSFWThumbnails) parsed.thumbnails.forEach(unblurThumbnail);\r\n results.push(parsed);\r\n }\r\n break;\r\n }\r\n case 'channel': {\r\n const parsed = parseChannel(detail);\r\n if (parsed) results.push(parsed);\r\n break;\r\n }\r\n case 'playlist': {\r\n const parsed = parsePlaylist(detail);\r\n if (parsed) {\r\n if (options.unblurNSFWThumbnails && parsed.thumbnail) unblurThumbnail(parsed.thumbnail);\r\n results.push(parsed);\r\n }\r\n break;\r\n }\r\n default:\r\n throw new Error(`Unknown search type: ${options.type}`);\r\n }\r\n }\r\n return results;\r\n}\r\n/**\r\n * Function to convert [hour : minutes : seconds] format to seconds\r\n * @param duration hour : minutes : seconds format\r\n * @returns seconds\r\n */\r\nfunction parseDuration(duration: string): number {\r\n if (!duration) return 0;\r\n const args = duration.split(':');\r\n let dur = 0;\r\n\r\n switch (args.length) {\r\n case 3:\r\n dur = parseInt(args[0]) * 60 * 60 + parseInt(args[1]) * 60 + parseInt(args[2]);\r\n break;\r\n case 2:\r\n dur = parseInt(args[0]) * 60 + parseInt(args[1]);\r\n break;\r\n default:\r\n dur = parseInt(args[0]);\r\n }\r\n\r\n return dur;\r\n}\r\n/**\r\n * Function to parse Channel searches\r\n * @param data body of that channel request.\r\n * @returns YouTubeChannel class\r\n */\r\nexport function parseChannel(data?: any): YouTubeChannel {\r\n if (!data || !data.channelRenderer) throw new Error('Failed to Parse YouTube Channel');\r\n const badge = data.channelRenderer.ownerBadges?.[0]?.metadataBadgeRenderer?.style?.toLowerCase();\r\n const url = `https://www.youtube.com${\r\n data.channelRenderer.navigationEndpoint.browseEndpoint.canonicalBaseUrl ||\r\n data.channelRenderer.navigationEndpoint.commandMetadata.webCommandMetadata.url\r\n }`;\r\n const thumbnail = data.channelRenderer.thumbnail.thumbnails[data.channelRenderer.thumbnail.thumbnails.length - 1];\r\n const res = new YouTubeChannel({\r\n id: data.channelRenderer.channelId,\r\n name: data.channelRenderer.title.simpleText,\r\n icon: {\r\n url: thumbnail.url.replace('//', 'https://'),\r\n width: thumbnail.width,\r\n height: thumbnail.height\r\n },\r\n url: url,\r\n verified: Boolean(badge?.includes('verified')),\r\n artist: Boolean(badge?.includes('artist')),\r\n subscribers: data.channelRenderer.subscriberCountText?.simpleText ?? '0 subscribers'\r\n });\r\n\r\n return res;\r\n}\r\n/**\r\n * Function to parse Video searches\r\n * @param data body of that video request.\r\n * @returns YouTubeVideo class\r\n */\r\nexport function parseVideo(data?: any): YouTubeVideo {\r\n if (!data || !data.videoRenderer) throw new Error('Failed to Parse YouTube Video');\r\n\r\n const channel = data.videoRenderer.ownerText.runs[0];\r\n const badge = data.videoRenderer.ownerBadges?.[0]?.metadataBadgeRenderer?.style?.toLowerCase();\r\n const durationText = data.videoRenderer.lengthText;\r\n const res = new YouTubeVideo({\r\n id: data.videoRenderer.videoId,\r\n url: `https://www.youtube.com/watch?v=${data.videoRenderer.videoId}`,\r\n title: data.videoRenderer.title.runs[0].text,\r\n description: data.videoRenderer.detailedMetadataSnippets?.[0].snippetText.runs?.length\r\n ? data.videoRenderer.detailedMetadataSnippets[0].snippetText.runs.map((run: any) => run.text).join('')\r\n : '',\r\n duration: durationText ? parseDuration(durationText.simpleText) : 0,\r\n duration_raw: durationText ? durationText.simpleText : null,\r\n thumbnails: data.videoRenderer.thumbnail.thumbnails,\r\n channel: {\r\n id: channel.navigationEndpoint.browseEndpoint.browseId || null,\r\n name: channel.text || null,\r\n url: `https://www.youtube.com${\r\n channel.navigationEndpoint.browseEndpoint.canonicalBaseUrl ||\r\n channel.navigationEndpoint.commandMetadata.webCommandMetadata.url\r\n }`,\r\n icons: data.videoRenderer.channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer.thumbnail\r\n .thumbnails,\r\n verified: Boolean(badge?.includes('verified')),\r\n artist: Boolean(badge?.includes('artist'))\r\n },\r\n uploadedAt: data.videoRenderer.publishedTimeText?.simpleText ?? null,\r\n upcoming: data.videoRenderer.upcomingEventData?.startTime\r\n ? new Date(parseInt(data.videoRenderer.upcomingEventData.startTime) * 1000)\r\n : undefined,\r\n views: data.videoRenderer.viewCountText?.simpleText?.replace(/\\D/g, '') ?? 0,\r\n live: durationText ? false : true\r\n });\r\n\r\n return res;\r\n}\r\n/**\r\n * Function to parse Playlist searches\r\n * @param data body of that playlist request.\r\n * @returns YouTubePlaylist class\r\n */\r\nexport function parsePlaylist(data?: any): YouTubePlayList {\r\n if (!data || !data.playlistRenderer) throw new Error('Failed to Parse YouTube Playlist');\r\n\r\n const thumbnail =\r\n data.playlistRenderer.thumbnails[0].thumbnails[data.playlistRenderer.thumbnails[0].thumbnails.length - 1];\r\n const channel = data.playlistRenderer.shortBylineText.runs?.[0];\r\n\r\n const res = new YouTubePlayList(\r\n {\r\n id: data.playlistRenderer.playlistId,\r\n title: data.playlistRenderer.title.simpleText,\r\n thumbnail: {\r\n id: data.playlistRenderer.playlistId,\r\n url: thumbnail.url,\r\n height: thumbnail.height,\r\n width: thumbnail.width\r\n },\r\n channel: {\r\n id: channel?.navigationEndpoint.browseEndpoint.browseId,\r\n name: channel?.text,\r\n url: `https://www.youtube.com${channel?.navigationEndpoint.commandMetadata.webCommandMetadata.url}`\r\n },\r\n videos: parseInt(data.playlistRenderer.videoCount.replace(/\\D/g, ''))\r\n },\r\n true\r\n );\r\n\r\n return res;\r\n}\r\n\r\nfunction unblurThumbnail(thumbnail: YouTubeThumbnail) {\r\n if (BLURRED_THUMBNAILS.find((sqp) => thumbnail.url.includes(sqp))) {\r\n thumbnail.url = thumbnail.url.split('?')[0];\r\n\r\n // we need to update the size parameters as the sqp parameter also included a cropped size\r\n switch (thumbnail.url.split('/').at(-1)!.split('.')[0]) {\r\n case 'hq2':\r\n case 'hqdefault':\r\n thumbnail.width = 480;\r\n thumbnail.height = 360;\r\n break;\r\n case 'hq720':\r\n thumbnail.width = 1280;\r\n thumbnail.height = 720;\r\n break;\r\n case 'sddefault':\r\n thumbnail.width = 640;\r\n thumbnail.height = 480;\r\n break;\r\n case 'mqdefault':\r\n thumbnail.width = 320;\r\n thumbnail.height = 180;\r\n break;\r\n case 'default':\r\n thumbnail.width = 120;\r\n thumbnail.height = 90;\r\n break;\r\n default:\r\n thumbnail.width = thumbnail.height = NaN;\r\n }\r\n }\r\n}\r\n","import { request } from './../Request';\r\nimport { ParseSearchInterface, ParseSearchResult } from './utils/parser';\r\nimport { YouTubeVideo } from './classes/Video';\r\nimport { YouTubeChannel } from './classes/Channel';\r\nimport { YouTubePlayList } from './classes/Playlist';\r\n\r\nenum SearchType {\r\n Video = 'EgIQAQ%253D%253D',\r\n PlayList = 'EgIQAw%253D%253D',\r\n Channel = 'EgIQAg%253D%253D'\r\n}\r\n\r\n/**\r\n * Type for YouTube returns\r\n */\r\nexport type YouTube = YouTubeVideo | YouTubeChannel | YouTubePlayList;\r\n/**\r\n * Command to search from YouTube\r\n * @param search The query to search\r\n * @param options limit & type of YouTube search you want.\r\n * @returns YouTube type.\r\n */\r\nexport async function yt_search(search: string, options: ParseSearchInterface = {}): Promise<YouTube[]> {\r\n let url = 'https://www.youtube.com/results?search_query=' + search;\r\n options.type ??= 'video';\r\n if (url.indexOf('&sp=') === -1) {\r\n url += '&sp=';\r\n switch (options.type) {\r\n case 'channel':\r\n url += SearchType.Channel;\r\n break;\r\n case 'playlist':\r\n url += SearchType.PlayList;\r\n break;\r\n case 'video':\r\n url += SearchType.Video;\r\n break;\r\n default:\r\n throw new Error(`Unknown search type: ${options.type}`);\r\n }\r\n }\r\n const body = await request(url, {\r\n headers: {\r\n 'accept-language': options.language || 'en-US;q=0.9'\r\n }\r\n });\r\n if (body.indexOf('Our systems have detected unusual traffic from your computer network.') !== -1)\r\n throw new Error('Captcha page: YouTube has detected that you are a bot!');\r\n return ParseSearchResult(body, options);\r\n}\r\n","import { request } from '../Request';\r\nimport { SpotifyDataOptions } from '.';\r\nimport { AlbumJSON, PlaylistJSON, TrackJSON } from './constants';\r\n\r\nexport interface SpotifyTrackAlbum {\r\n /**\r\n * Spotify Track Album name\r\n */\r\n name: string;\r\n /**\r\n * Spotify Track Album url\r\n */\r\n url: string;\r\n /**\r\n * Spotify Track Album id\r\n */\r\n id: string;\r\n /**\r\n * Spotify Track Album release date\r\n */\r\n release_date: string;\r\n /**\r\n * Spotify Track Album release date **precise**\r\n */\r\n release_date_precision: string;\r\n /**\r\n * Spotify Track Album total tracks number\r\n */\r\n total_tracks: number;\r\n}\r\n\r\nexport interface SpotifyArtists {\r\n /**\r\n * Spotify Artist Name\r\n */\r\n name: string;\r\n /**\r\n * Spotify Artist Url\r\n */\r\n url: string;\r\n /**\r\n * Spotify Artist ID\r\n */\r\n id: string;\r\n}\r\n\r\nexport interface SpotifyThumbnail {\r\n /**\r\n * Spotify Thumbnail height\r\n */\r\n height: number;\r\n /**\r\n * Spotify Thumbnail width\r\n */\r\n width: number;\r\n /**\r\n * Spotify Thumbnail url\r\n */\r\n url: string;\r\n}\r\n\r\nexport interface SpotifyCopyright {\r\n /**\r\n * Spotify Copyright Text\r\n */\r\n text: string;\r\n /**\r\n * Spotify Copyright Type\r\n */\r\n type: string;\r\n}\r\n/**\r\n * Spotify Track Class\r\n */\r\nexport class SpotifyTrack {\r\n /**\r\n * Spotify Track Name\r\n */\r\n name: string;\r\n /**\r\n * Spotify Class type. == \"track\"\r\n */\r\n type: 'track' | 'playlist' | 'album';\r\n /**\r\n * Spotify Track ID\r\n */\r\n id: string;\r\n /**\r\n * Spotify Track ISRC\r\n */\r\n isrc: string;\r\n /**\r\n * Spotify Track url\r\n */\r\n url: string;\r\n /**\r\n * Spotify Track explicit info.\r\n */\r\n explicit: boolean;\r\n /**\r\n * Spotify Track playability info.\r\n */\r\n playable: boolean;\r\n /**\r\n * Spotify Track Duration in seconds\r\n */\r\n durationInSec: number;\r\n /**\r\n * Spotify Track Duration in milli seconds\r\n */\r\n durationInMs: number;\r\n /**\r\n * Spotify Track Artists data [ array ]\r\n */\r\n artists: SpotifyArtists[];\r\n /**\r\n * Spotify Track Album data\r\n */\r\n album: SpotifyTrackAlbum | undefined;\r\n /**\r\n * Spotify Track Thumbnail Data\r\n */\r\n thumbnail: SpotifyThumbnail | undefined;\r\n /**\r\n * Constructor for Spotify Track\r\n * @param data\r\n */\r\n constructor(data: any) {\r\n this.name = data.name;\r\n this.id = data.id;\r\n this.isrc = data.external_ids?.isrc || '';\r\n this.type = 'track';\r\n this.url = data.external_urls.spotify;\r\n this.explicit = data.explicit;\r\n this.playable = data.is_playable;\r\n this.durationInMs = data.duration_ms;\r\n this.durationInSec = Math.round(this.durationInMs / 1000);\r\n const artists: SpotifyArtists[] = [];\r\n data.artists.forEach((v: any) => {\r\n artists.push({\r\n name: v.name,\r\n id: v.id,\r\n url: v.external_urls.spotify\r\n });\r\n });\r\n this.artists = artists;\r\n if (!data.album?.name) this.album = undefined;\r\n else {\r\n this.album = {\r\n name: data.album.name,\r\n url: data.external_urls.spotify,\r\n id: data.album.id,\r\n release_date: data.album.release_date,\r\n release_date_precision: data.album.release_date_precision,\r\n total_tracks: data.album.total_tracks\r\n };\r\n }\r\n if (!data.album?.images?.[0]) this.thumbnail = undefined;\r\n else this.thumbnail = data.album.images[0];\r\n }\r\n\r\n toJSON(): TrackJSON {\r\n return {\r\n name: this.name,\r\n id: this.id,\r\n url: this.url,\r\n explicit: this.explicit,\r\n durationInMs: this.durationInMs,\r\n durationInSec: this.durationInSec,\r\n artists: this.artists,\r\n album: this.album,\r\n thumbnail: this.thumbnail\r\n };\r\n }\r\n}\r\n/**\r\n * Spotify Playlist Class\r\n */\r\nexport class SpotifyPlaylist {\r\n /**\r\n * Spotify Playlist Name\r\n */\r\n name: string;\r\n /**\r\n * Spotify Class type. == \"playlist\"\r\n */\r\n type: 'track' | 'playlist' | 'album';\r\n /**\r\n * Spotify Playlist collaborative boolean.\r\n */\r\n collaborative: boolean;\r\n /**\r\n * Spotify Playlist Description\r\n */\r\n description: string;\r\n /**\r\n * Spotify Playlist URL\r\n */\r\n url: string;\r\n /**\r\n * Spotify Playlist ID\r\n */\r\n id: string;\r\n /**\r\n * Spotify Playlist Thumbnail Data\r\n */\r\n thumbnail: SpotifyThumbnail;\r\n /**\r\n * Spotify Playlist Owner Artist data\r\n */\r\n owner: SpotifyArtists;\r\n /**\r\n * Spotify Playlist total tracks Count\r\n */\r\n tracksCount: number;\r\n /**\r\n * Spotify Playlist Spotify data\r\n *\r\n * @private\r\n */\r\n private spotifyData: SpotifyDataOptions;\r\n /**\r\n * Spotify Playlist fetched tracks Map\r\n *\r\n * @private\r\n */\r\n private fetched_tracks: Map<string, SpotifyTrack[]>;\r\n /**\r\n * Boolean to tell whether it is a searched result or not.\r\n */\r\n private readonly search: boolean;\r\n /**\r\n * Constructor for Spotify Playlist Class\r\n * @param data JSON parsed data of playlist\r\n * @param spotifyData Data about sporify token for furhter fetching.\r\n */\r\n constructor(data: any, spotifyData: SpotifyDataOptions, search: boolean) {\r\n this.name = data.name;\r\n this.type = 'playlist';\r\n this.search = search;\r\n this.collaborative = data.collaborative;\r\n this.description = data.description;\r\n this.url = data.external_urls.spotify;\r\n this.id = data.id;\r\n this.thumbnail = data.images[0];\r\n this.owner = {\r\n name: data.owner.display_name,\r\n url: data.owner.external_urls.spotify,\r\n id: data.owner.id\r\n };\r\n this.tracksCount = Number(data.tracks.total);\r\n const videos: SpotifyTrack[] = [];\r\n if (!this.search)\r\n data.tracks.items.forEach((v: any) => {\r\n if (v.track) videos.push(new SpotifyTrack(v.track));\r\n });\r\n this.fetched_tracks = new Map();\r\n this.fetched_tracks.set('1', videos);\r\n this.spotifyData = spotifyData;\r\n }\r\n /**\r\n * Fetches Spotify Playlist tracks more than 100 tracks.\r\n *\r\n * For getting all tracks in playlist, see `total_pages` property.\r\n * @returns Playlist Class.\r\n */\r\n async fetch() {\r\n if (this.search) return this;\r\n let fetching: number;\r\n if (this.tracksCount > 1000) fetching = 1000;\r\n else fetching = this.tracksCount;\r\n if (fetching <= 100) return this;\r\n const work = [];\r\n for (let i = 2; i <= Math.ceil(fetching / 100); i++) {\r\n work.push(\r\n new Promise(async (resolve, reject) => {\r\n const response = await request(\r\n `https://api.spotify.com/v1/playlists/${this.id}/tracks?offset=${\r\n (i - 1) * 100\r\n }&limit=100&market=${this.spotifyData.market}`,\r\n {\r\n headers: {\r\n Authorization: `${this.spotifyData.token_type} ${this.spotifyData.access_token}`\r\n }\r\n }\r\n ).catch((err) => reject(`Response Error : \\n${err}`));\r\n const videos: SpotifyTrack[] = [];\r\n if (typeof response !== 'string') return;\r\n const json_data = JSON.parse(response);\r\n json_data.items.forEach((v: any) => {\r\n if (v.track) videos.push(new SpotifyTrack(v.track));\r\n });\r\n this.fetched_tracks.set(`${i}`, videos);\r\n resolve('Success');\r\n })\r\n );\r\n }\r\n await Promise.allSettled(work);\r\n return this;\r\n }\r\n /**\r\n * Spotify Playlist tracks are divided in pages.\r\n *\r\n * For example getting data of 101 - 200 videos in a playlist,\r\n *\r\n * ```ts\r\n * const playlist = await play.spotify('playlist url')\r\n *\r\n * await playlist.fetch()\r\n *\r\n * const result = playlist.page(2)\r\n * ```\r\n * @param num Page Number\r\n * @returns\r\n */\r\n page(num: number) {\r\n if (!num) throw new Error('Page number is not provided');\r\n if (!this.fetched_tracks.has(`${num}`)) throw new Error('Given Page number is invalid');\r\n return this.fetched_tracks.get(`${num}`) as SpotifyTrack[];\r\n }\r\n /**\r\n * Gets total number of pages in that playlist class.\r\n * @see {@link SpotifyPlaylist.all_tracks}\r\n */\r\n get total_pages() {\r\n return this.fetched_tracks.size;\r\n }\r\n /**\r\n * Spotify Playlist total no of tracks that have been fetched so far.\r\n */\r\n get total_tracks() {\r\n if (this.search) return this.tracksCount;\r\n const page_number: number = this.total_pages;\r\n return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length;\r\n }\r\n /**\r\n * Fetches all the tracks in the playlist and returns them\r\n *\r\n * ```ts\r\n * const playlist = await play.spotify('playlist url')\r\n *\r\n * const tracks = await playlist.all_tracks()\r\n * ```\r\n * @returns An array of {@link SpotifyTrack}\r\n */\r\n async all_tracks(): Promise<SpotifyTrack[]> {\r\n await this.fetch();\r\n\r\n const tracks: SpotifyTrack[] = [];\r\n\r\n for (const page of this.fetched_tracks.values()) tracks.push(...page);\r\n\r\n return tracks;\r\n }\r\n /**\r\n * Converts Class to JSON\r\n * @returns JSON data\r\n */\r\n toJSON(): PlaylistJSON {\r\n return {\r\n name: this.name,\r\n collaborative: this.collaborative,\r\n description: this.description,\r\n url: this.url,\r\n id: this.id,\r\n thumbnail: this.thumbnail,\r\n owner: this.owner,\r\n tracksCount: this.tracksCount\r\n };\r\n }\r\n}\r\n/**\r\n * Spotify Album Class\r\n */\r\nexport class SpotifyAlbum {\r\n /**\r\n * Spotify Album Name\r\n */\r\n name: string;\r\n /**\r\n * Spotify Class type. == \"album\"\r\n */\r\n type: 'track' | 'playlist' | 'album';\r\n /**\r\n * Spotify Album url\r\n */\r\n url: string;\r\n /**\r\n * Spotify Album id\r\n */\r\n id: string;\r\n /**\r\n * Spotify Album Thumbnail data\r\n */\r\n thumbnail: SpotifyThumbnail;\r\n /**\r\n * Spotify Album artists [ array ]\r\n */\r\n artists: SpotifyArtists[];\r\n /**\r\n * Spotify Album copyright data [ array ]\r\n */\r\n copyrights: SpotifyCopyright[];\r\n /**\r\n * Spotify Album Release date\r\n */\r\n release_date: string;\r\n /**\r\n * Spotify Album Release Date **precise**\r\n */\r\n release_date_precision: string;\r\n /**\r\n * Spotify Album total no of tracks\r\n */\r\n tracksCount: number;\r\n /**\r\n * Spotify Album Spotify data\r\n *\r\n * @private\r\n */\r\n private spotifyData: SpotifyDataOptions;\r\n /**\r\n * Spotify Album fetched tracks Map\r\n *\r\n * @private\r\n */\r\n private fetched_tracks: Map<string, SpotifyTrack[]>;\r\n /**\r\n * Boolean to tell whether it is a searched result or not.\r\n */\r\n private readonly search: boolean;\r\n /**\r\n * Constructor for Spotify Album Class\r\n * @param data Json parsed album data\r\n * @param spotifyData Spotify credentials\r\n */\r\n constructor(data: any, spotifyData: SpotifyDataOptions, search: boolean) {\r\n this.name = data.name;\r\n this.type = 'album';\r\n this.id = data.id;\r\n this.search = search;\r\n this.url = data.external_urls.spotify;\r\n this.thumbnail = data.images[0];\r\n const artists: SpotifyArtists[] = [];\r\n data.artists.forEach((v: any) => {\r\n artists.push({\r\n name: v.name,\r\n id: v.id,\r\n url: v.external_urls.spotify\r\n });\r\n });\r\n this.artists = artists;\r\n this.copyrights = data.copyrights;\r\n this.release_date = data.release_date;\r\n this.release_date_precision = data.release_date_precision;\r\n this.tracksCount = data.total_tracks;\r\n const videos: SpotifyTrack[] = [];\r\n if (!this.search)\r\n data.tracks.items.forEach((v: any) => {\r\n videos.push(new SpotifyTrack(v));\r\n });\r\n this.fetched_tracks = new Map();\r\n this.fetched_tracks.set('1', videos);\r\n this.spotifyData = spotifyData;\r\n }\r\n /**\r\n * Fetches Spotify Album tracks more than 50 tracks.\r\n *\r\n * For getting all tracks in album, see `total_pages` property.\r\n * @returns Album Class.\r\n */\r\n async fetch() {\r\n if (this.search) return this;\r\n let fetching: number;\r\n if (this.tracksCount > 500) fetching = 500;\r\n else fetching = this.tracksCount;\r\n if (fetching <= 50) return this;\r\n const work = [];\r\n for (let i = 2; i <= Math.ceil(fetching / 50); i++) {\r\n work.push(\r\n new Promise(async (resolve, reject) => {\r\n const response = await request(\r\n `https://api.spotify.com/v1/albums/${this.id}/tracks?offset=${(i - 1) * 50}&limit=50&market=${\r\n this.spotifyData.market\r\n }`,\r\n {\r\n headers: {\r\n Authorization: `${this.spotifyData.token_type} ${this.spotifyData.access_token}`\r\n }\r\n }\r\n ).catch((err) => reject(`Response Error : \\n${err}`));\r\n const videos: SpotifyTrack[] = [];\r\n if (typeof response !== 'string') return;\r\n const json_data = JSON.parse(response);\r\n json_data.items.forEach((v: any) => {\r\n if (v) videos.push(new SpotifyTrack(v));\r\n });\r\n this.fetched_tracks.set(`${i}`, videos);\r\n resolve('Success');\r\n })\r\n );\r\n }\r\n await Promise.allSettled(work);\r\n return this;\r\n }\r\n /**\r\n * Spotify Album tracks are divided in pages.\r\n *\r\n * For example getting data of 51 - 100 videos in a album,\r\n *\r\n * ```ts\r\n * const album = await play.spotify('album url')\r\n *\r\n * await album.fetch()\r\n *\r\n * const result = album.page(2)\r\n * ```\r\n * @param num Page Number\r\n * @returns\r\n */\r\n page(num: number) {\r\n if (!num) throw new Error('Page number is not provided');\r\n if (!this.fetched_tracks.has(`${num}`)) throw new Error('Given Page number is invalid');\r\n return this.fetched_tracks.get(`${num}`);\r\n }\r\n /**\r\n * Gets total number of pages in that album class.\r\n * @see {@link SpotifyAlbum.all_tracks}\r\n */\r\n get total_pages() {\r\n return this.fetched_tracks.size;\r\n }\r\n /**\r\n * Spotify Album total no of tracks that have been fetched so far.\r\n */\r\n get total_tracks() {\r\n if (this.search) return this.tracksCount;\r\n const page_number: number = this.total_pages;\r\n return (page_number - 1) * 100 + (this.fetched_tracks.get(`${page_number}`) as SpotifyTrack[]).length;\r\n }\r\n /**\r\n * Fetches all the tracks in the album and returns them\r\n *\r\n * ```ts\r\n * const album = await play.spotify('album url')\r\n *\r\n * const tracks = await album.all_tracks()\r\n * ```\r\n * @returns An array of {@link SpotifyTrack}\r\n */\r\n async all_tracks(): Promise<SpotifyTrack[]> {\r\n await this.fetch();\r\n\r\n const tracks: SpotifyTrack[] = [];\r\n\r\n for (const page of this.fetched_tracks.values()) tracks.push(...page);\r\n\r\n return tracks;\r\n }\r\n /**\r\n * Converts Class to JSON\r\n * @returns JSON data\r\n */\r\n toJSON(): AlbumJSON {\r\n return {\r\n name: this.name,\r\n id: this.id,\r\n type: this.type,\r\n url: this.url,\r\n thumbnail: this.thumbnail,\r\n artists: this.artists,\r\n copyrights: this.copyrights,\r\n release_date: this.release_date,\r\n release_date_precision: this.release_date_precision,\r\n tracksCount: this.tracksCount\r\n };\r\n }\r\n}\r\n","import { request } from '../Request';\r\nimport { SpotifyAlbum, SpotifyPlaylist, SpotifyTrack } from './classes';\r\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\r\n\r\nlet spotifyData: SpotifyDataOptions;\r\nif (existsSync('.data/spotify.data')) {\r\n spotifyData = JSON.parse(readFileSync('.data/spotify.data', 'utf-8'));\r\n spotifyData.file = true;\r\n}\r\n/**\r\n * Spotify Data options that are stored in spotify.data file.\r\n */\r\nexport interface SpotifyDataOptions {\r\n client_id: string;\r\n client_secret: string;\r\n redirect_url?: string;\r\n authorization_code?: string;\r\n access_token?: string;\r\n refresh_token?: string;\r\n token_type?: string;\r\n expires_in?: number;\r\n expiry?: number;\r\n market?: string;\r\n file?: boolean;\r\n}\r\n\r\nconst pattern = /^((https:)?\\/\\/)?open\\.spotify\\.com\\/(?:intl\\-.{2}\\/)?(track|album|playlist)\\//;\r\n/**\r\n * Gets Spotify url details.\r\n *\r\n * ```ts\r\n * let spot = await play.spotify('spotify url')\r\n *\r\n * // spot.type === \"track\" | \"playlist\" | \"album\"\r\n *\r\n * if (spot.type === \"track\") {\r\n * spot = spot as play.SpotifyTrack\r\n * // Code with spotify track class.\r\n * }\r\n * ```\r\n * @param url Spotify Url\r\n * @returns A {@link SpotifyTrack} or {@link SpotifyPlaylist} or {@link SpotifyAlbum}\r\n */\r\nexport async function spotify(url: string): Promise<Spotify> {\r\n if (!spotifyData) throw new Error('Spotify Data is missing\\nDid you forgot to do authorization ?');\r\n const url_ = url.trim();\r\n if (!url_.match(pattern)) throw new Error('This is not a Spotify URL');\r\n if (url_.indexOf('track/') !== -1) {\r\n const trackID = url_.split('track/')[1].split('&')[0].split('?')[0];\r\n const response = await request(`https://api.spotify.com/v1/tracks/${trackID}?market=${spotifyData.market}`, {\r\n headers: {\r\n Authorization: `${spotifyData.token_type} ${spotifyData.access_token}`\r\n }\r\n }).catch((err: Error) => {\r\n return err;\r\n });\r\n if (response instanceof Error) throw response;\r\n const resObj = JSON.parse(response);\r\n if (resObj.error) throw new Error(`Got ${resObj.error.status} from the spotify request: ${resObj.error.message}`);\r\n return new SpotifyTrack(resObj);\r\n } else if (url_.indexOf('album/') !== -1) {\r\n const albumID = url.split('album/')[1].split('&')[0].split('?')[0];\r\n const response = await request(`https://api.spotify.com/v1/albums/${albumID}?market=${spotifyData.market}`, {\r\n headers: {\r\n Authorization: `${spotifyData.token_type} ${spotifyData.access_token}`\r\n }\r\n }).catch((err: Error) => {\r\n return err;\r\n });\r\n if (response instanceof Error) throw response;\r\n const resObj = JSON.parse(response);\r\n if (resObj.error) throw new Error(`Got ${resObj.error.status} from the spotify request: ${resObj.error.message}`);\r\n return new SpotifyAlbum(resObj, spotifyData, false);\r\n } else if (url_.indexOf('playlist/') !== -1) {\r\n const playlistID = url.split('playlist/')[1].split('&')[0].split('?')[0];\r\n const response = await request(\r\n `https://api.spotify.com/v1/playlists/${playlistID}?market=${spotifyData.market}`,\r\n {\r\n headers: {\r\n Authorization: `${spotifyData.token_type} ${spotifyData.access_token}`\r\n }\r\n }\r\n ).catch((err: Error) => {\r\n return err;\r\n });\r\n if (response instanceof Error) throw response;\r\n const resObj = JSON.parse(response);\r\n if (resObj.error) throw new Error(`Got ${resObj.error.status} from the spotify request: ${resObj.error.message}`);\r\n return new SpotifyPlaylist(resObj, spotifyData, false);\r\n } else throw new Error('URL is out of scope for play-dl.');\r\n}\r\n/**\r\n * Validate Spotify url\r\n * @param url Spotify URL\r\n * @returns\r\n * ```ts\r\n * 'track' | 'playlist' | 'album' | 'search' | false\r\n * ```\r\n */\r\nexport function sp_validate(url: string): 'track' | 'playlist' | 'album' | 'search' | false {\r\n const url_ = url.trim();\r\n if (!url_.startsWith('https')) return 'search';\r\n if (!url_.match(pattern)) return false;\r\n if (url_.indexOf('track/') !== -1) {\r\n return 'track';\r\n } else if (url_.indexOf('album/') !== -1) {\r\n return 'album';\r\n } else if (url_.indexOf('playlist/') !== -1) {\r\n return 'playlist';\r\n } else return false;\r\n}\r\n/**\r\n * Fuction for authorizing for spotify data.\r\n * @param data Sportify Data options to validate\r\n * @returns boolean.\r\n */\r\nexport async function SpotifyAuthorize(data: SpotifyDataOptions, file: boolean): Promise<boolean> {\r\n const response = await request(`https://accounts.spotify.com/api/token`, {\r\n headers: {\r\n 'Authorization': `Basic ${Buffer.from(`${data.client_id}:${data.client_secret}`).toString('base64')}`,\r\n 'Content-Type': 'application/x-www-form-urlencoded'\r\n },\r\n body: `grant_type=authorization_code&code=${data.authorization_code}&redirect_uri=${encodeURI(\r\n data.redirect_url as string\r\n )}`,\r\n method: 'POST'\r\n }).catch((err: Error) => {\r\n return err;\r\n });\r\n if (response instanceof Error) throw response;\r\n const resp_json = JSON.parse(response);\r\n spotifyData = {\r\n client_id: data.client_id,\r\n client_secret: data.client_secret,\r\n redirect_url: data.redirect_url,\r\n access_token: resp_json.access_token,\r\n refresh_token: resp_json.refresh_token,\r\n expires_in: Number(resp_json.expires_in),\r\n expiry: Date.now() + (resp_json.expires_in - 1) * 1000,\r\n token_type: resp_json.token_type,\r\n market: data.market\r\n };\r\n if (file) writeFileSync('.data/spotify.data', JSON.stringify(spotifyData, undefined, 4));\r\n else {\r\n console.log(`Client ID : ${spotifyData.client_id}`);\r\n console.log(`Client Secret : ${spotifyData.client_secret}`);\r\n console.log(`Refresh Token : ${spotifyData.refresh_token}`);\r\n console.log(`Market : ${spotifyData.market}`);\r\n console.log(`\\nPaste above info in setToken function.`);\r\n }\r\n return true;\r\n}\r\n/**\r\n * Checks if spotify token is expired or not.\r\n *\r\n * Update token if returned false.\r\n * ```ts\r\n * if (play.is_expired()) {\r\n * await play.refreshToken()\r\n * }\r\n * ```\r\n * @returns boolean\r\n */\r\nexport function is_expired(): boolean {\r\n if (Date.now() >= (spotifyData.expiry as number)) return true;\r\n else return false;\r\n}\r\n/**\r\n * type for Spotify Classes\r\n */\r\nexport type Spotify = SpotifyAlbum | SpotifyPlaylist | SpotifyTrack;\r\n/**\r\n * Function for searching songs on Spotify\r\n * @param query searching query\r\n * @param type \"album\" | \"playlist\" | \"track\"\r\n * @param limit max no of results\r\n * @returns Spotify type.\r\n */\r\nexport async function sp_search(\r\n query: string,\r\n type: 'album' | 'playlist' | 'track',\r\n limit: number = 10\r\n): Promise<Spotify[]> {\r\n const results: Spotify[] = [];\r\n if (!spotifyData) throw new Error('Spotify Data is missing\\nDid you forget to do authorization ?');\r\n if (query.length === 0) throw new Error('Pass some query to search.');\r\n if (limit > 50 || limit < 0) throw new Error(`You crossed limit range of Spotify [ 0 - 50 ]`);\r\n const response = await request(\r\n `https://api.spotify.com/v1/search?type=${type}&q=${query}&limit=${limit}&market=${spotifyData.market}`,\r\n {\r\n headers: {\r\n Authorization: `${spotifyData.token_type} ${spotifyData.access_token}`\r\n }\r\n }\r\n ).catch((err: Error) => {\r\n return err;\r\n });\r\n if (response instanceof Error) throw response;\r\n const json_data = JSON.parse(response);\r\n if (type === 'track') {\r\n json_data.tracks.items.forEach((track: any) => {\r\n results.push(new SpotifyTrack(track));\r\n });\r\n } else if (type === 'album') {\r\n json_data.albums.items.forEach((album: any) => {\r\n results.push(new SpotifyAlbum(album, spotifyData, true));\r\n });\r\n } else if (type === 'playlist') {\r\n json_data.playlists.items.forEach((playlist: any) => {\r\n results.push(new SpotifyPlaylist(playlist, spotifyData, true));\r\n });\r\n }\r\n return results;\r\n}\r\n/**\r\n * Refreshes Token\r\n *\r\n * ```ts\r\n * if (play.is_expired()) {\r\n * await play.refreshToken()\r\n * }\r\n * ```\r\n * @returns boolean\r\n */\r\nexport async function refreshToken(): Promise<boolean> {\r\n const response = await request(`https://accounts.spotify.com/api/token`, {\r\n headers: {\r\n 'Authorization': `Basic ${Buffer.from(`${spotifyData.client_id}:${spotifyData.client_secret}`).toString(\r\n 'base64'\r\n )}`,\r\n 'Content-Type': 'application/x-www-form-urlencoded'\r\n },\r\n body: `grant_type=refresh_token&refresh_token=${spotifyData.refresh_token}`,\r\n method: 'POST'\r\n }).catch((err: Error) => {\r\n return err;\r\n });\r\n if (response instanceof Error) return false;\r\n const resp_json = JSON.parse(response);\r\n spotifyData.access_token = resp_json.access_token;\r\n spotifyData.expires_in = Number(resp_json.expires_in);\r\n spotifyData.expiry = Date.now() + (resp_json.expires_in - 1) * 1000;\r\n spotifyData.token_type = resp_json.token_type;\r\n if (spotifyData.file) writeFileSync('.data/spotify.data', JSON.stringify(spotifyData, undefined, 4));\r\n return true;\r\n}\r\n\r\nexport async function setSpotifyToken(options: SpotifyDataOptions) {\r\n spotifyData = options;\r\n spotifyData.file = false;\r\n await refreshToken();\r\n}\r\n\r\nexport { SpotifyTrack, SpotifyAlbum, SpotifyPlaylist };\r\n","import { existsSync, readFileSync } from 'node:fs';\r\nimport { StreamType } from '../YouTube/stream';\r\nimport { request } from '../Request';\r\nimport { SoundCloudPlaylist, SoundCloudTrack, SoundCloudTrackFormat, SoundCloudStream } from './classes';\r\nlet soundData: SoundDataOptions;\r\nif (existsSync('.data/soundcloud.data')) {\r\n soundData = JSON.parse(readFileSync('.data/soundcloud.data', 'utf-8'));\r\n}\r\n\r\ninterface SoundDataOptions {\r\n client_id: string;\r\n}\r\n\r\nconst pattern = /^(?:(https?):\\/\\/)?(?:(?:www|m)\\.)?(api\\.soundcloud\\.com|soundcloud\\.com|snd\\.sc)\\/(.*)$/;\r\n/**\r\n * Gets info from a soundcloud url.\r\n *\r\n * ```ts\r\n * let sound = await play.soundcloud('soundcloud url')\r\n *\r\n * // sound.type === \"track\" | \"playlist\" | \"user\"\r\n *\r\n * if (sound.type === \"track\") {\r\n * spot = spot as play.SoundCloudTrack\r\n * // Code with SoundCloud track class.\r\n * }\r\n * ```\r\n * @param url soundcloud url\r\n * @returns A {@link SoundCloudTrack} or {@link SoundCloudPlaylist}\r\n */\r\nexport async function soundcloud(url: string): Promise<SoundCloud> {\r\n if (!soundData) throw new Error('SoundCloud Data is missing\\nDid you forget to do authorization ?');\r\n const url_ = url.trim();\r\n if (!url_.match(pattern)) throw new Error('This is not a SoundCloud URL');\r\n\r\n const data = await request(\r\n `https://api-v2.soundcloud.com/resolve?url=${url_}&client_id=${soundData.client_id}`\r\n ).catch((err: Error) => err);\r\n\r\n if (data instanceof Error) throw data;\r\n\r\n const json_data = JSON.parse(data);\r\n\r\n if (json_data.kind !== 'track' && json_data.kind !== 'playlist')\r\n throw new Error('This url is out of scope for play-dl.');\r\n\r\n if (json_data.kind === 'track') return new SoundCloudTrack(json_data);\r\n else return new SoundCloudPlaylist(json_data, soundData.client_id);\r\n}\r\n/**\r\n * Type of SoundCloud\r\n */\r\nexport type SoundCloud = SoundCloudTrack | SoundCloudPlaylist;\r\n/**\r\n * Function for searching in SoundCloud\r\n * @param query query to search\r\n * @param type 'tracks' | 'playlists' | 'albums'\r\n * @param limit max no. of results\r\n * @returns Array of SoundCloud type.\r\n */\r\nexport async function so_search(\r\n query: string,\r\n type: 'tracks' | 'playlists' | 'albums',\r\n limit: number = 10\r\n): Promise<SoundCloud[]> {\r\n const response = await request(\r\n `https://api-v2.soundcloud.com/search/${type}?q=${query}&client_id=${soundData.client_id}&limit=${limit}`\r\n );\r\n const results: (SoundCloudPlaylist | SoundCloudTrack)[] = [];\r\n const json_data = JSON.parse(response);\r\n json_data.collection.forEach((x: any) => {\r\n if (type === 'tracks') results.push(new SoundCloudTrack(x));\r\n else results.push(new SoundCloudPlaylist(x, soundData.client_id));\r\n });\r\n return results;\r\n}\r\n/**\r\n * Main Function for creating a Stream of soundcloud\r\n * @param url soundcloud url\r\n * @param quality Quality to select from\r\n * @returns SoundCloud Stream\r\n */\r\nexport async function stream(url: string, quality?: number): Promise<SoundCloudStream> {\r\n const data = await soundcloud(url);\r\n\r\n if (data instanceof SoundCloudPlaylist) throw new Error(\"Streams can't be created from playlist urls\");\r\n\r\n const HLSformats = parseHlsFormats(data.formats);\r\n if (typeof quality !== 'number') quality = HLSformats.length - 1;\r\n else if (quality <= 0) quality = 0;\r\n else if (quality >= HLSformats.length) quality = HLSformats.length - 1;\r\n const req_url = HLSformats[quality].url + '?client_id=' + soundData.client_id;\r\n const s_data = JSON.parse(await request(req_url));\r\n const type = HLSformats[quality].format.mime_type.startsWith('audio/ogg')\r\n ? StreamType.OggOpus\r\n : StreamType.Arbitrary;\r\n return new SoundCloudStream(s_data.url, type);\r\n}\r\n/**\r\n * Gets Free SoundCloud Client ID.\r\n *\r\n * Use this in beginning of your code to add SoundCloud support.\r\n *\r\n * ```ts\r\n * play.getFreeClientID().then((clientID) => play.setToken({\r\n * soundcloud : {\r\n * client_id : clientID\r\n * }\r\n * }))\r\n * ```\r\n * @returns client ID\r\n */\r\nexport async function getFreeClientID(): Promise<string> {\r\n const data: any = await request('https://soundcloud.com/', {headers: {}}).catch(err => err);\r\n\r\n if (data instanceof Error)\r\n throw new Error(\"Failed to get response from soundcloud.com: \" + data.message);\r\n\r\n const splitted = data.split('<script crossorigin src=\"');\r\n const urls: string[] = [];\r\n splitted.forEach((r: string) => {\r\n if (r.startsWith('https')) {\r\n urls.push(r.split('\"')[0]);\r\n }\r\n });\r\n const data2 = await request(urls[urls.length - 1]);\r\n return data2.split(',client_id:\"')[1].split('\"')[0];\r\n}\r\n/**\r\n * Function for creating a Stream of soundcloud using a SoundCloud Track Class\r\n * @param data SoundCloud Track Class\r\n * @param quality Quality to select from\r\n * @returns SoundCloud Stream\r\n */\r\nexport async function stream_from_info(data: SoundCloudTrack, quality?: number): Promise<SoundCloudStream> {\r\n const HLSformats = parseHlsFormats(data.formats);\r\n if (typeof quality !== 'number') quality = HLSformats.length - 1;\r\n else if (quality <= 0) quality = 0;\r\n else if (quality >= HLSformats.length) quality = HLSformats.length - 1;\r\n const req_url = HLSformats[quality].url + '?client_id=' + soundData.client_id;\r\n const s_data = JSON.parse(await request(req_url));\r\n const type = HLSformats[quality].format.mime_type.startsWith('audio/ogg')\r\n ? StreamType.OggOpus\r\n : StreamType.Arbitrary;\r\n return new SoundCloudStream(s_data.url, type);\r\n}\r\n/**\r\n * Function to check client ID\r\n * @param id Client ID\r\n * @returns boolean\r\n */\r\nexport async function check_id(id: string): Promise<boolean> {\r\n const response = await request(`https://api-v2.soundcloud.com/search?client_id=${id}&q=Rick+Roll&limit=0`).catch(\r\n (err: Error) => {\r\n return err;\r\n }\r\n );\r\n if (response instanceof Error) return false;\r\n else return true;\r\n}\r\n/**\r\n * Validates a soundcloud url\r\n * @param url soundcloud url\r\n * @returns\r\n * ```ts\r\n * false | 'track' | 'playlist'\r\n * ```\r\n */\r\nexport async function so_validate(url: string): Promise<false | 'track' | 'playlist' | 'search'> {\r\n const url_ = url.trim();\r\n if (!url_.startsWith('https')) return 'search';\r\n if (!url_.match(pattern)) return false;\r\n const data = await request(\r\n `https://api-v2.soundcloud.com/resolve?url=${url_}&client_id=${soundData.client_id}`\r\n ).catch((err: Error) => err);\r\n\r\n if (data instanceof Error) return false;\r\n\r\n const json_data = JSON.parse(data);\r\n if (json_data.kind === 'track') return 'track';\r\n else if (json_data.kind === 'playlist') return 'playlist';\r\n else return false;\r\n}\r\n/**\r\n * Function to select only hls streams from SoundCloud format array\r\n * @param data SoundCloud Track Format data\r\n * @returns HLS Formats Array\r\n */\r\nfunction parseHlsFormats(data: SoundCloudTrackFormat[]) {\r\n const result: SoundCloudTrackFormat[] = [];\r\n data.forEach((format) => {\r\n if (format.format.protocol === 'hls') result.push(format);\r\n });\r\n return result;\r\n}\r\n\r\nexport function setSoundCloudToken(options: SoundDataOptions) {\r\n soundData = options;\r\n}\r\n\r\nexport { SoundCloudTrack, SoundCloudPlaylist, SoundCloudStream };\r\n","import { request, request_stream } from '../Request';\r\nimport { Readable } from 'node:stream';\r\nimport { IncomingMessage } from 'node:http';\r\nimport { StreamType } from '../YouTube/stream';\r\nimport { Timer } from '../YouTube/classes/LiveStream';\r\nimport { PlaylistJSON, SoundTrackJSON } from './constants';\r\n\r\nexport interface SoundCloudUser {\r\n /**\r\n * SoundCloud User Name\r\n */\r\n name: string;\r\n /**\r\n * SoundCloud User ID\r\n */\r\n id: string;\r\n /**\r\n * SoundCloud User URL\r\n */\r\n url: string;\r\n /**\r\n * SoundCloud Class type. == \"user\"\r\n */\r\n type: 'track' | 'playlist' | 'user';\r\n /**\r\n * SoundCloud User Verified status\r\n */\r\n verified: boolean;\r\n /**\r\n * SoundCloud User Description\r\n */\r\n description: string;\r\n /**\r\n * SoundCloud User First Name\r\n */\r\n first_name: string;\r\n /**\r\n * SoundCloud User Full Name\r\n */\r\n full_name: string;\r\n /**\r\n * SoundCloud User Last Name\r\n */\r\n last_name: string;\r\n /**\r\n * SoundCloud User thumbnail URL\r\n */\r\n thumbnail: string;\r\n}\r\n\r\nexport interface SoundCloudTrackDeprecated {\r\n /**\r\n * SoundCloud Track fetched status\r\n */\r\n fetched: boolean;\r\n /**\r\n * SoundCloud Track ID\r\n */\r\n id: number;\r\n /**\r\n * SoundCloud Class type. == \"track\"\r\n */\r\n type: 'track';\r\n}\r\n\r\nexport interface SoundCloudTrackFormat {\r\n /**\r\n * SoundCloud Track Format Url\r\n */\r\n url: string;\r\n /**\r\n * SoundCloud Track Format preset\r\n */\r\n preset: string;\r\n /**\r\n * SoundCloud Track Format Duration\r\n */\r\n duration: number;\r\n /**\r\n * SoundCloud Track Format data containing protocol and mime_type\r\n */\r\n format: {\r\n protocol: string;\r\n mime_type: string;\r\n };\r\n /**\r\n * SoundCloud Track Format quality\r\n */\r\n quality: string;\r\n}\r\n/**\r\n * SoundCloud Track Class\r\n */\r\nexport class SoundCloudTrack {\r\n /**\r\n * SoundCloud Track Name\r\n */\r\n name: string;\r\n /**\r\n * SoundCloud Track ID\r\n */\r\n id: number;\r\n /**\r\n * SoundCloud Track url\r\n */\r\n url: string;\r\n /**\r\n * User friendly SoundCloud track URL\r\n */\r\n permalink: string;\r\n /**\r\n * SoundCloud Track fetched status\r\n */\r\n fetched: boolean;\r\n /**\r\n * SoundCloud Class type. === \"track\"\r\n */\r\n type: 'track' | 'playlist' | 'user';\r\n /**\r\n * SoundCloud Track Duration in seconds\r\n */\r\n durationInSec: number;\r\n /**\r\n * SoundCloud Track Duration in miili seconds\r\n */\r\n durationInMs: number;\r\n /**\r\n * SoundCloud Track formats data\r\n */\r\n formats: SoundCloudTrackFormat[];\r\n /**\r\n * SoundCloud Track Publisher Data\r\n */\r\n publisher: {\r\n name: string;\r\n id: number;\r\n artist: string;\r\n contains_music: boolean;\r\n writer_composer: string;\r\n } | null;\r\n /**\r\n * SoundCloud Track thumbnail\r\n */\r\n thumbnail: string;\r\n /**\r\n * SoundCloud Track user data\r\n */\r\n user: SoundCloudUser;\r\n /**\r\n * Constructor for SoundCloud Track Class\r\n * @param data JSON parsed track html data\r\n */\r\n constructor(data: any) {\r\n this.name = data.title;\r\n this.id = data.id;\r\n this.url = data.uri;\r\n this.permalink = data.permalink_url;\r\n this.fetched = true;\r\n this.type = 'track';\r\n this.durationInSec = Math.round(Number(data.duration) / 1000);\r\n this.durationInMs = Number(data.duration);\r\n if (data.publisher_metadata)\r\n this.publisher = {\r\n name: data.publisher_metadata.publisher,\r\n id: data.publisher_metadata.id,\r\n artist: data.publisher_metadata.artist,\r\n contains_music: Boolean(data.publisher_metadata.contains_music) || false,\r\n writer_composer: data.publisher_metadata.writer_composer\r\n };\r\n else this.publisher = null;\r\n this.formats = data.media.transcodings;\r\n this.user = {\r\n name: data.user.username,\r\n id: data.user.id,\r\n type: 'user',\r\n url: data.user.permalink_url,\r\n verified: Boolean(data.user.verified) || false,\r\n description: data.user.description,\r\n first_name: data.user.first_name,\r\n full_name: data.user.full_name,\r\n last_name: data.user.last_name,\r\n thumbnail: data.user.avatar_url\r\n };\r\n this.thumbnail = data.artwork_url;\r\n }\r\n /**\r\n * Converts class to JSON\r\n * @returns JSON parsed Data\r\n */\r\n toJSON(): SoundTrackJSON {\r\n return {\r\n name: this.name,\r\n id: this.id,\r\n url: this.url,\r\n permalink: this.permalink,\r\n fetched: this.fetched,\r\n durationInMs: this.durationInMs,\r\n durationInSec: this.durationInSec,\r\n publisher: this.publisher,\r\n formats: this.formats,\r\n thumbnail: this.thumbnail,\r\n user: this.user\r\n };\r\n }\r\n}\r\n/**\r\n * SoundCloud Playlist Class\r\n */\r\nexport class SoundCloudPlaylist {\r\n /**\r\n * SoundCloud Playlist Name\r\n */\r\n name: string;\r\n /**\r\n * SoundCloud Playlist ID\r\n */\r\n id: number;\r\n /**\r\n * SoundCloud Playlist URL\r\n */\r\n url: string;\r\n /**\r\n * SoundCloud Class type. == \"playlist\"\r\n */\r\n type: 'track' | 'playlist' | 'user';\r\n /**\r\n * SoundCloud Playlist Sub type. == \"album\" for soundcloud albums\r\n */\r\n sub_type: string;\r\n /**\r\n * SoundCloud Playlist Total Duration in seconds\r\n */\r\n durationInSec: number;\r\n /**\r\n * SoundCloud Playlist Total Duration in milli seconds\r\n */\r\n durationInMs: number;\r\n /**\r\n * SoundCloud Playlist user data\r\n */\r\n user: SoundCloudUser;\r\n /**\r\n * SoundCloud Playlist tracks [ It can be fetched or not fetched ]\r\n */\r\n tracks: SoundCloudTrack[] | SoundCloudTrackDeprecated[];\r\n /**\r\n * SoundCloud Playlist tracks number\r\n */\r\n tracksCount: number;\r\n /**\r\n * SoundCloud Client ID provided by user\r\n * @private\r\n */\r\n private client_id: string;\r\n /**\r\n * Constructor for SoundCloud Playlist\r\n * @param data JSON parsed SoundCloud playlist data\r\n * @param client_id Provided SoundCloud Client ID\r\n */\r\n constructor(data: any, client_id: string) {\r\n this.name = data.title;\r\n this.id = data.id;\r\n this.url = data.uri;\r\n this.client_id = client_id;\r\n this.type = 'playlist';\r\n this.sub_type = data.set_type;\r\n this.durationInSec = Math.round(Number(data.duration) / 1000);\r\n this.durationInMs = Number(data.duration);\r\n this.user = {\r\n name: data.user.username,\r\n id: data.user.id,\r\n type: 'user',\r\n url: data.user.permalink_url,\r\n verified: Boolean(data.user.verified) || false,\r\n description: data.user.description,\r\n first_name: data.user.first_name,\r\n full_name: data.user.full_name,\r\n last_name: data.user.last_name,\r\n thumbnail: data.user.avatar_url\r\n };\r\n this.tracksCount = data.track_count;\r\n const tracks: any[] = [];\r\n data.tracks.forEach((track: any) => {\r\n if (track.title) {\r\n tracks.push(new SoundCloudTrack(track));\r\n } else\r\n tracks.push({\r\n id: track.id,\r\n fetched: false,\r\n type: 'track'\r\n });\r\n });\r\n this.tracks = tracks;\r\n }\r\n /**\r\n * Fetches all unfetched songs in a playlist.\r\n *\r\n * For fetching songs and getting all songs, see `fetched_tracks` property.\r\n * @returns playlist class\r\n */\r\n async fetch(): Promise<SoundCloudPlaylist> {\r\n const work: any[] = [];\r\n for (let i = 0; i < this.tracks.length; i++) {\r\n if (!this.tracks[i].fetched) {\r\n work.push(\r\n new Promise(async (resolve) => {\r\n const num = i;\r\n const data = await request(\r\n `https://api-v2.soundcloud.com/tracks/${this.tracks[i].id}?client_id=${this.client_id}`\r\n );\r\n\r\n this.tracks[num] = new SoundCloudTrack(JSON.parse(data));\r\n resolve('');\r\n })\r\n );\r\n }\r\n }\r\n await Promise.allSettled(work);\r\n return this;\r\n }\r\n /**\r\n * Get total no. of fetched tracks\r\n * @see {@link SoundCloudPlaylist.all_tracks}\r\n */\r\n get total_tracks(): number {\r\n let count = 0;\r\n this.tracks.forEach((track) => {\r\n if (track instanceof SoundCloudTrack) count++;\r\n else return;\r\n });\r\n return count;\r\n }\r\n /**\r\n * Fetches all the tracks in the playlist and returns them\r\n *\r\n * ```ts\r\n * const playlist = await play.soundcloud('playlist url')\r\n *\r\n * const tracks = await playlist.all_tracks()\r\n * ```\r\n * @returns An array of {@link SoundCloudTrack}\r\n */\r\n async all_tracks(): Promise<SoundCloudTrack[]> {\r\n await this.fetch();\r\n\r\n return this.tracks as SoundCloudTrack[];\r\n }\r\n /**\r\n * Converts Class to JSON data\r\n * @returns JSON parsed data\r\n */\r\n toJSON(): PlaylistJSON {\r\n return {\r\n name: this.name,\r\n id: this.id,\r\n sub_type: this.sub_type,\r\n url: this.url,\r\n durationInMs: this.durationInMs,\r\n durationInSec: this.durationInSec,\r\n tracksCount: this.tracksCount,\r\n user: this.user,\r\n tracks: this.tracks\r\n };\r\n }\r\n}\r\n/**\r\n * SoundCloud Stream class\r\n */\r\nexport class SoundCloudStream {\r\n /**\r\n * Readable Stream through which data passes\r\n */\r\n stream: Readable;\r\n /**\r\n * Type of audio data that we recieved from normal youtube url.\r\n */\r\n type: StreamType;\r\n /**\r\n * Dash Url containing segment urls.\r\n * @private\r\n */\r\n private url: string;\r\n /**\r\n * Total time of downloaded segments data.\r\n * @private\r\n */\r\n private downloaded_time: number;\r\n /**\r\n * Timer for looping code every 5 minutes\r\n * @private\r\n */\r\n private timer: Timer;\r\n /**\r\n * Total segments Downloaded so far\r\n * @private\r\n */\r\n private downloaded_segments: number;\r\n /**\r\n * Incoming message that we recieve.\r\n *\r\n * Storing this is essential.\r\n * This helps to destroy the TCP connection completely if you stopped player in between the stream\r\n * @private\r\n */\r\n private request: IncomingMessage | null;\r\n /**\r\n * Array of segment time. Useful for calculating downloaded_time.\r\n */\r\n private time: number[];\r\n /**\r\n * Array of segment_urls in dash file.\r\n */\r\n private segment_urls: string[];\r\n /**\r\n * Constructor for SoundCloud Stream\r\n * @param url Dash url containing dash file.\r\n * @param type Stream Type\r\n */\r\n constructor(url: string, type: StreamType = StreamType.Arbitrary) {\r\n this.stream = new Readable({ highWaterMark: 5 * 1000 * 1000, read() {} });\r\n this.type = type;\r\n this.url = url;\r\n this.downloaded_time = 0;\r\n this.request = null;\r\n this.downloaded_segments = 0;\r\n this.time = [];\r\n this.timer = new Timer(() => {\r\n this.timer.reuse();\r\n this.start();\r\n }, 280);\r\n this.segment_urls = [];\r\n this.stream.on('close', () => {\r\n this.cleanup();\r\n });\r\n this.start();\r\n }\r\n /**\r\n * Parses SoundCloud dash file.\r\n * @private\r\n */\r\n private async parser() {\r\n const response = await request(this.url).catch((err: Error) => {\r\n return err;\r\n });\r\n if (response instanceof Error) throw response;\r\n const array = response.split('\\n');\r\n array.forEach((val) => {\r\n if (val.startsWith('#EXTINF:')) {\r\n this.time.push(parseFloat(val.replace('#EXTINF:', '')));\r\n } else if (val.startsWith('https')) {\r\n this.segment_urls.push(val);\r\n }\r\n });\r\n return;\r\n }\r\n /**\r\n * Starts looping of code for getting all segments urls data\r\n */\r\n private async start() {\r\n if (this.stream.destroyed) {\r\n this.cleanup();\r\n return;\r\n }\r\n this.time = [];\r\n this.segment_urls = [];\r\n this.downloaded_time = 0;\r\n await this.parser();\r\n this.segment_urls.splice(0, this.downloaded_segments);\r\n this.loop();\r\n }\r\n /**\r\n * Main Loop function for getting all segments urls data\r\n */\r\n private async loop() {\r\n if (this.stream.destroyed) {\r\n this.cleanup();\r\n return;\r\n }\r\n if (this.time.length === 0 || this.segment_urls.length === 0) {\r\n this.cleanup();\r\n this.stream.push(null);\r\n return;\r\n }\r\n this.downloaded_time += this.time.shift() as number;\r\n this.downloaded_segments++;\r\n const stream = await request_stream(this.segment_urls.shift() as string).catch((err: Error) => err);\r\n if (stream instanceof Error) {\r\n this.stream.emit('error', stream);\r\n this.cleanup();\r\n return;\r\n }\r\n\r\n this.request = stream;\r\n stream.on('data', (c) => {\r\n this.stream.push(c);\r\n });\r\n stream.on('end', () => {\r\n if (this.downloaded_time >= 300) return;\r\n else this.loop();\r\n });\r\n stream.once('error', (err) => {\r\n this.stream.emit('error', err);\r\n });\r\n }\r\n /**\r\n * This cleans every used variable in class.\r\n *\r\n * This is used to prevent re-use of this class and helping garbage collector to collect it.\r\n */\r\n private cleanup() {\r\n this.timer.destroy();\r\n this.request?.destroy();\r\n this.url = '';\r\n this.downloaded_time = 0;\r\n this.downloaded_segments = 0;\r\n this.request = null;\r\n this.time = [];\r\n this.segment_urls = [];\r\n }\r\n /**\r\n * Pauses timer.\r\n * Stops running of loop.\r\n *\r\n * Useful if you don't want to get excess data to be stored in stream.\r\n */\r\n pause() {\r\n this.timer.pause();\r\n }\r\n /**\r\n * Resumes timer.\r\n * Starts running of loop.\r\n */\r\n resume() {\r\n this.timer.resume();\r\n }\r\n}\r\n","import { URL } from 'node:url';\r\nimport { request, request_resolve_redirect } from '../Request';\r\nimport { DeezerAlbum, DeezerPlaylist, DeezerTrack } from './classes';\r\n\r\ninterface TypeData {\r\n type: 'track' | 'playlist' | 'album' | 'search' | false;\r\n id?: string;\r\n error?: string;\r\n}\r\n\r\ninterface DeezerSearchOptions {\r\n /**\r\n * The type to search for `'track'`, `'playlist'` or `'album'`. Defaults to `'track'`.\r\n */\r\n type?: 'track' | 'playlist' | 'album';\r\n /**\r\n * The maximum number of results to return, maximum `100`, defaults to `10`.\r\n */\r\n limit?: number;\r\n /**\r\n * Whether the search should be fuzzy or only return exact matches. Defaults to `true`.\r\n */\r\n fuzzy?: boolean;\r\n}\r\n\r\ninterface DeezerAdvancedSearchOptions {\r\n /**\r\n * The maximum number of results to return, maximum `100`, defaults to `10`.\r\n */\r\n limit?: number;\r\n /**\r\n * The name of the artist.\r\n */\r\n artist?: string;\r\n /**\r\n * The title of the album.\r\n */\r\n album?: string;\r\n /**\r\n * The title of the track.\r\n */\r\n title?: string;\r\n /**\r\n * The label that released the track.\r\n */\r\n label?: string;\r\n /**\r\n * The minimum duration in seconds.\r\n */\r\n minDurationInSec?: number;\r\n /**\r\n * The maximum duration in seconds.\r\n */\r\n maxDurationInSec?: number;\r\n /**\r\n * The minimum BPM.\r\n */\r\n minBPM?: number;\r\n /**\r\n * The minimum BPM.\r\n */\r\n maxBPM?: number;\r\n}\r\n\r\nasync function internalValidate(url: string): Promise<TypeData> {\r\n let urlObj;\r\n try {\r\n // will throw a TypeError if the input is not a valid URL so we need to catch it\r\n urlObj = new URL(url);\r\n } catch {\r\n return { type: 'search' };\r\n }\r\n\r\n if (urlObj.protocol !== 'https:' && urlObj.protocol !== 'http:') {\r\n return { type: 'search' };\r\n }\r\n\r\n let pathname = urlObj.pathname;\r\n if (pathname.endsWith('/')) {\r\n pathname = pathname.slice(0, -1);\r\n }\r\n const path = pathname.split('/');\r\n switch (urlObj.hostname) {\r\n case 'deezer.com':\r\n case 'www.deezer.com': {\r\n if (path.length === 4) {\r\n const lang = path.splice(1, 1)[0];\r\n if (!lang.match(/^[a-z]{2}$/)) {\r\n return { type: false };\r\n }\r\n } else if (path.length !== 3) {\r\n return { type: false };\r\n }\r\n\r\n if ((path[1] === 'track' || path[1] === 'album' || path[1] === 'playlist') && path[2].match(/^\\d+$/)) {\r\n return {\r\n type: path[1],\r\n id: path[2]\r\n };\r\n } else {\r\n return { type: false };\r\n }\r\n }\r\n case 'api.deezer.com': {\r\n if (\r\n path.length === 3 &&\r\n (path[1] === 'track' || path[1] === 'album' || path[1] === 'playlist') &&\r\n path[2].match(/^\\d+$/)\r\n ) {\r\n return {\r\n type: path[1],\r\n id: path[2]\r\n };\r\n } else {\r\n return { type: false };\r\n }\r\n }\r\n case 'deezer.page.link': {\r\n if (path.length === 2 && path[1].match(/^[A-Za-z0-9]+$/)) {\r\n const resolved = await request_resolve_redirect(url).catch((err) => err);\r\n\r\n if (resolved instanceof Error) {\r\n return { type: false, error: resolved.message };\r\n }\r\n\r\n return await internalValidate(resolved);\r\n } else {\r\n return { type: false };\r\n }\r\n }\r\n default:\r\n return { type: 'search' };\r\n }\r\n}\r\n\r\n/**\r\n * Shared type for Deezer tracks, playlists and albums\r\n */\r\nexport type Deezer = DeezerTrack | DeezerPlaylist | DeezerAlbum;\r\n\r\n/**\r\n * Fetches the information for a track, playlist or album on Deezer\r\n * @param url The track, playlist or album URL\r\n * @returns A {@link DeezerTrack}, {@link DeezerPlaylist} or {@link DeezerAlbum}\r\n * object depending on the provided URL.\r\n */\r\nexport async function deezer(url: string): Promise<Deezer> {\r\n const typeData = await internalValidate(url.trim());\r\n\r\n if (typeData.error) {\r\n throw new Error(`This is not a Deezer track, playlist or album URL:\\n${typeData.error}`);\r\n } else if (!typeData.type || typeData.type === 'search')\r\n throw new Error('This is not a Deezer track, playlist or album URL');\r\n\r\n const response = await request(`https://api.deezer.com/${typeData.type}/${typeData.id}`).catch((err: Error) => err);\r\n\r\n if (response instanceof Error) throw response;\r\n\r\n const jsonData = JSON.parse(response);\r\n\r\n if (jsonData.error) {\r\n throw new Error(`Deezer API Error: ${jsonData.error.type}: ${jsonData.error.message}`);\r\n }\r\n\r\n switch (typeData.type) {\r\n case 'track':\r\n return new DeezerTrack(jsonData, false);\r\n case 'playlist':\r\n return new DeezerPlaylist(jsonData, false);\r\n case 'album':\r\n return new DeezerAlbum(jsonData, false);\r\n }\r\n}\r\n\r\n/**\r\n * Validates a Deezer URL\r\n * @param url The URL to validate\r\n * @returns The type of the URL either `'track'`, `'playlist'`, `'album'`, `'search'` or `false`.\r\n * `false` means that the provided URL was a wrongly formatted or an unsupported Deezer URL.\r\n */\r\nexport async function dz_validate(url: string): Promise<'track' | 'playlist' | 'album' | 'search' | false> {\r\n const typeData = await internalValidate(url.trim());\r\n return typeData.type;\r\n}\r\n\r\n/**\r\n * Searches Deezer for tracks, playlists or albums\r\n * @param query The search query\r\n * @param options Extra options to configure the search:\r\n *\r\n * * type?: The type to search for `'track'`, `'playlist'` or `'album'`. Defaults to `'track'`.\r\n * * limit?: The maximum number of results to return, maximum `100`, defaults to `10`.\r\n * * fuzzy?: Whether the search should be fuzzy or only return exact matches. Defaults to `true`.\r\n * @returns An array of tracks, playlists or albums\r\n */\r\nexport async function dz_search(query: string, options: DeezerSearchOptions): Promise<Deezer[]> {\r\n let query_ = query.trim();\r\n\r\n const type = options.type ?? 'track';\r\n const limit = options.limit ?? 10;\r\n const fuzzy = options.fuzzy ?? true;\r\n\r\n if (query_.length === 0) throw new Error('A query is required to search.');\r\n if (limit > 100) throw new Error('The maximum search limit for Deezer is 100');\r\n if (limit < 1) throw new Error('The minimum search limit for Deezer is 1');\r\n if (type !== 'track' && type !== 'album' && type != 'playlist')\r\n throw new Error(`\"${type}\" is not a valid Deezer search type`);\r\n\r\n query_ = encodeURIComponent(query_);\r\n const response = await request(\r\n `https://api.deezer.com/search/${type}/?q=${query_}&limit=${limit}${fuzzy ? '' : 'strict=on'}`\r\n ).catch((err: Error) => err);\r\n\r\n if (response instanceof Error) throw response;\r\n\r\n const jsonData = JSON.parse(response);\r\n\r\n if (jsonData.error) {\r\n throw new Error(`Deezer API Error: ${jsonData.error.type}: ${jsonData.error.message}`);\r\n }\r\n\r\n let results: Deezer[] = [];\r\n switch (type) {\r\n case 'track':\r\n results = jsonData.data.map((track: any) => new DeezerTrack(track, true));\r\n break;\r\n case 'playlist':\r\n results = jsonData.data.map((playlist: any) => new DeezerPlaylist(playlist, true));\r\n break;\r\n case 'album':\r\n results = jsonData.data.map((album: any) => new DeezerAlbum(album, true));\r\n break;\r\n }\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * Searches Deezer for tracks using the specified metadata.\r\n * @param options The metadata and limit for the search\r\n *\r\n * * limit?: The maximum number of results to return, maximum `100`, defaults to `10`.\r\n * * artist?: The name of the artist\r\n * * album?: The title of the album\r\n * * title?: The title of the track\r\n * * label?: The label that released the track\r\n * * minDurationInSec?: The minimum duration in seconds\r\n * * maxDurationInSec?: The maximum duration in seconds\r\n * * minBpm?: The minimum BPM\r\n * * maxBpm?: The minimum BPM\r\n * @returns An array of tracks matching the metadata\r\n */\r\nexport async function dz_advanced_track_search(options: DeezerAdvancedSearchOptions): Promise<DeezerTrack[]> {\r\n const limit = options.limit ?? 10;\r\n\r\n if (limit > 100) throw new Error('The maximum search limit for Deezer is 100');\r\n if (limit < 1) throw new Error('The minimum search limit for Deezer is 1');\r\n\r\n const metadata: string[] = [];\r\n if (options.artist) metadata.push(`artist:\"${encodeURIComponent(options.artist.trim())}\"`);\r\n\r\n if (options.album) metadata.push(`album:\"${encodeURIComponent(options.album.trim())}\"`);\r\n\r\n if (options.title) metadata.push(`track:\"${encodeURIComponent(options.title.trim())}\"`);\r\n\r\n if (options.label) metadata.push(`label:\"${encodeURIComponent(options.label.trim())}\"`);\r\n\r\n if (!isNaN(Number(options.minDurationInSec))) metadata.push(`dur_min:${options.minDurationInSec}`);\r\n\r\n if (!isNaN(Number(options.maxDurationInSec))) metadata.push(`dur_max:${options.maxDurationInSec}`);\r\n\r\n if (!isNaN(Number(options.minBPM))) metadata.push(`bpm_min:${options.minBPM}`);\r\n\r\n if (!isNaN(Number(options.maxBPM))) metadata.push(`bpm_max:${options.maxBPM}`);\r\n\r\n if (metadata.length === 0) throw new Error('At least one type of metadata is required.');\r\n\r\n const response = await request(`https://api.deezer.com/search/track/?q=${metadata.join(' ')}&limit=${limit}`).catch(\r\n (err: Error) => err\r\n );\r\n\r\n if (response instanceof Error) throw response;\r\n\r\n const jsonData = JSON.parse(response);\r\n\r\n if (jsonData.error) {\r\n throw new Error(`Deezer API Error: ${jsonData.error.type}: ${jsonData.error.message}`);\r\n }\r\n\r\n const results = jsonData.data.map((track: any) => new DeezerTrack(track, true));\r\n\r\n return results;\r\n}\r\n\r\nexport { DeezerTrack, DeezerAlbum, DeezerPlaylist };\r\n","import { request } from '../Request';\r\n\r\n/**\r\n * Interface representing an image on Deezer\r\n * available in four sizes\r\n */\r\ninterface DeezerImage {\r\n /**\r\n * The largest version of the image\r\n */\r\n xl: string;\r\n /**\r\n * The second largest version of the image\r\n */\r\n big: string;\r\n /**\r\n * The second smallest version of the image\r\n */\r\n medium: string;\r\n /**\r\n * The smallest version of the image\r\n */\r\n small: string;\r\n}\r\n\r\n/**\r\n * Interface representing a Deezer genre\r\n */\r\ninterface DeezerGenre {\r\n /**\r\n * The name of the genre\r\n */\r\n name: string;\r\n /**\r\n * The thumbnail of the genre available in four sizes\r\n */\r\n picture: DeezerImage;\r\n}\r\n\r\n/**\r\n * Interface representing a Deezer user account\r\n */\r\ninterface DeezerUser {\r\n /**\r\n * The id of the user\r\n */\r\n id: number;\r\n /**\r\n * The name of the user\r\n */\r\n name: string;\r\n}\r\n\r\n/**\r\n * Class representing a Deezer track\r\n */\r\nexport class DeezerTrack {\r\n /**\r\n * The id of the track\r\n */\r\n id: number;\r\n /**\r\n * The title of the track\r\n */\r\n title: string;\r\n /**\r\n * A shorter version of the title\r\n */\r\n shortTitle: string;\r\n /**\r\n * The URL of the track on Deezer\r\n */\r\n url: string;\r\n /**\r\n * The duration of the track in seconds\r\n */\r\n durationInSec: number;\r\n /**\r\n * The rank of the track\r\n */\r\n rank: number;\r\n /**\r\n * `true` if the track contains any explicit lyrics\r\n */\r\n explicit: boolean;\r\n /**\r\n * URL to a file containing the first 30 seconds of the track\r\n */\r\n previewURL: string;\r\n /**\r\n * The artist of the track\r\n */\r\n artist: DeezerArtist;\r\n /**\r\n * The album that this track is in\r\n */\r\n album: DeezerTrackAlbum;\r\n /**\r\n * The type, always `'track'`, useful to determine what the deezer function returned\r\n */\r\n type: 'track' | 'playlist' | 'album';\r\n\r\n /**\r\n * Signifies that some properties are not populated\r\n *\r\n * Partial tracks can be populated by calling {@link DeezerTrack.fetch}.\r\n *\r\n * `true` for tracks in search results and `false` if the track was fetched directly or expanded.\r\n */\r\n partial: boolean;\r\n\r\n /**\r\n * The position of the track in the album\r\n *\r\n * `undefined` for partial tracks\r\n *\r\n * @see {@link DeezerTrack.partial}\r\n */\r\n trackPosition?: number;\r\n /**\r\n * The number of the disk the track is on\r\n *\r\n * `undefined` for partial tracks\r\n *\r\n * @see {@link DeezerTrack.partial}\r\n */\r\n diskNumber?: number;\r\n /**\r\n * The release date\r\n *\r\n * `undefined` for partial tracks\r\n *\r\n * @see {@link DeezerTrack.partial}\r\n */\r\n releaseDate?: Date;\r\n /**\r\n * The number of beats per minute\r\n *\r\n * `undefined` for partial tracks\r\n *\r\n * @see {@link DeezerTrack.partial}\r\n */\r\n bpm?: number;\r\n /**\r\n * The gain of the track\r\n *\r\n * `undefined` for partial tracks\r\n *\r\n * @see {@link DeezerTrack.partial}\r\n */\r\n gain?: number;\r\n /**\r\n * The artists that have contributed to the track\r\n *\r\n * `undefined` for partial tracks\r\n *\r\n * @see {@link DeezerTrack.partial}\r\n */\r\n contributors?: DeezerArtist[];\r\n\r\n /**\r\n * Creates a Deezer track from the data in an API response\r\n * @param data the data to use to create the track\r\n * @param partial Whether the track should be partial\r\n * @see {@link DeezerTrack.partial}\r\n */\r\n constructor(data: any, partial: boolean) {\r\n this.id = data.id;\r\n this.title = data.title;\r\n this.shortTitle = data.title_short;\r\n this.url = data.link;\r\n this.durationInSec = data.duration;\r\n this.rank = data.rank;\r\n this.explicit = data.explicit_lyrics;\r\n this.previewURL = data.preview;\r\n this.artist = new DeezerArtist(data.artist);\r\n this.album = new DeezerTrackAlbum(data.album);\r\n this.type = 'track';\r\n\r\n this.partial = partial;\r\n\r\n if (!partial) {\r\n this.trackPosition = data.track_position;\r\n this.diskNumber = data.disk_number;\r\n this.releaseDate = new Date(data.release_date);\r\n this.bpm = data.bpm;\r\n this.gain = data.gain;\r\n this.contributors = [];\r\n\r\n data.contributors.forEach((contributor: any) => {\r\n this.contributors?.push(new DeezerArtist(contributor));\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Fetches and populates the missing fields\r\n *\r\n * The property {@link partial} will be `false` if this method finishes successfully.\r\n *\r\n * @returns A promise with the same track this method was called on.\r\n */\r\n async fetch(): Promise<DeezerTrack> {\r\n if (!this.partial) return this;\r\n\r\n const response = await request(`https://api.deezer.com/track/${this.id}/`).catch((err: Error) => err);\r\n\r\n if (response instanceof Error) throw response;\r\n const jsonData = JSON.parse(response);\r\n\r\n this.partial = false;\r\n\r\n this.trackPosition = jsonData.track_position;\r\n this.diskNumber = jsonData.disk_number;\r\n this.releaseDate = new Date(jsonData.release_date);\r\n this.bpm = jsonData.bpm;\r\n this.gain = jsonData.gain;\r\n this.contributors = [];\r\n\r\n jsonData.contributors.forEach((contributor: any) => {\r\n this.contributors?.push(new DeezerArtist(contributor));\r\n });\r\n\r\n return this;\r\n }\r\n /**\r\n * Converts instances of this class to JSON data\r\n * @returns JSON data.\r\n */\r\n toJSON() {\r\n return {\r\n id: this.id,\r\n title: this.title,\r\n shortTitle: this.shortTitle,\r\n url: this.url,\r\n durationInSec: this.durationInSec,\r\n rank: this.rank,\r\n explicit: this.explicit,\r\n previewURL: this.previewURL,\r\n artist: this.artist,\r\n album: this.album,\r\n type: this.type,\r\n trackPosition: this.trackPosition,\r\n diskNumber: this.diskNumber,\r\n releaseDate: this.releaseDate,\r\n bpm: this.bpm,\r\n gain: this.gain,\r\n contributors: this.contributors\r\n };\r\n }\r\n}\r\n/**\r\n * Class for Deezer Albums\r\n */\r\nexport class DeezerAlbum {\r\n /**\r\n * The id of the album\r\n */\r\n id: number;\r\n /**\r\n * The title of the album\r\n */\r\n title: string;\r\n /**\r\n * The URL to the album on Deezer\r\n */\r\n url: string;\r\n /**\r\n * The record type of the album (e.g. EP, ALBUM, etc ...)\r\n */\r\n recordType: string;\r\n /**\r\n * `true` if the album contains any explicit lyrics\r\n */\r\n explicit: boolean;\r\n /**\r\n * The artist of the album\r\n */\r\n artist: DeezerArtist;\r\n /**\r\n * The album cover available in four sizes\r\n */\r\n cover: DeezerImage;\r\n /**\r\n * The type, always `'album'`, useful to determine what the deezer function returned\r\n */\r\n type: 'track' | 'playlist' | 'album';\r\n /**\r\n * The number of tracks in the album\r\n */\r\n tracksCount: number;\r\n\r\n /**\r\n * Signifies that some properties are not populated\r\n *\r\n * Partial albums can be populated by calling {@link DeezerAlbum.fetch}.\r\n *\r\n * `true` for albums in search results and `false` if the album was fetched directly or expanded.\r\n */\r\n partial: boolean;\r\n\r\n /**\r\n * The **u**niversal **p**roduct **c**ode of the album\r\n *\r\n * `undefined` for partial albums\r\n *\r\n * @see {@link DeezerAlbum.partial}\r\n */\r\n upc?: string;\r\n /**\r\n * The duration of the album in seconds\r\n *\r\n * `undefined` for partial albums\r\n *\r\n * @see {@link DeezerAlbum.partial}\r\n */\r\n durationInSec?: number;\r\n /**\r\n * The number of fans the album has\r\n *\r\n * `undefined` for partial albums\r\n *\r\n * @see {@link DeezerAlbum.partial}\r\n */\r\n numberOfFans?: number;\r\n /**\r\n * The release date of the album\r\n *\r\n * `undefined` for partial albums\r\n *\r\n * @see {@link DeezerAlbum.partial}\r\n */\r\n releaseDate?: Date;\r\n /**\r\n * Whether the album is available\r\n *\r\n * `undefined` for partial albums\r\n *\r\n * @see {@link DeezerAlbum.partial}\r\n */\r\n available?: boolean;\r\n /**\r\n * The list of genres present in this album\r\n *\r\n * `undefined` for partial albums\r\n *\r\n * @see {@link DeezerAlbum.partial}\r\n */\r\n genres?: DeezerGenre[];\r\n /**\r\n * The contributors to the album\r\n *\r\n * `undefined` for partial albums\r\n *\r\n * @see {@link DeezerAlbum.partial}\r\n */\r\n contributors?: DeezerArtist[];\r\n\r\n /**\r\n * The list of tracks in the album\r\n *\r\n * empty (length === 0) for partial albums\r\n *\r\n * Use {@link DeezerAlbum.fetch} to populate the tracks and other properties\r\n *\r\n * @see {@link DeezerAlbum.partial}\r\n */\r\n tracks: DeezerTrack[];\r\n\r\n /**\r\n * Creates a Deezer album from the data in an API response\r\n * @param data the data to use to create the album\r\n * @param partial Whether the album should be partial\r\n * @see {@link DeezerAlbum.partial}\r\n */\r\n constructor(data: any, partial: boolean) {\r\n this.id = data.id;\r\n this.title = data.title;\r\n this.url = data.link;\r\n this.recordType = data.record_type;\r\n this.explicit = data.explicit_lyrics;\r\n this.artist = new DeezerArtist(data.artist);\r\n this.type = 'album';\r\n this.tracksCount = data.nb_tracks;\r\n this.contributors = [];\r\n this.genres = [];\r\n this.tracks = [];\r\n this.cover = {\r\n xl: data.cover_xl,\r\n big: data.cover_big,\r\n medium: data.cover_medium,\r\n small: data.cover_small\r\n };\r\n\r\n this.partial = partial;\r\n\r\n if (!partial) {\r\n this.upc = data.upc;\r\n this.durationInSec = data.duration;\r\n this.numberOfFans = data.fans;\r\n this.releaseDate = new Date(data.release_date);\r\n this.available = data.available;\r\n\r\n data.contributors.forEach((contributor: any) => {\r\n this.contributors?.push(new DeezerArtist(contributor));\r\n });\r\n\r\n data.genres.data.forEach((genre: any) => {\r\n this.genres?.push({\r\n name: genre.name,\r\n picture: {\r\n xl: `${genre.picture}?size=xl`,\r\n big: `${genre.picture}?size=big`,\r\n medium: `${genre.picture}?size=medium`,\r\n small: `${genre.picture}?size=small`\r\n }\r\n });\r\n });\r\n\r\n const trackAlbum: any = {\r\n id: this.id,\r\n title: this.title,\r\n cover_xl: this.cover.xl,\r\n cover_big: this.cover.big,\r\n cover_medium: this.cover.medium,\r\n cover_small: this.cover.small,\r\n release_date: data.release_date\r\n };\r\n data.tracks.data.forEach((track: any) => {\r\n track.album = trackAlbum;\r\n this.tracks.push(new DeezerTrack(track, true));\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Fetches and populates the missing fields including all tracks.\r\n *\r\n * The property {@link DeezerAlbum.partial} will be `false` if this method finishes successfully.\r\n *\r\n * @returns A promise with the same album this method was called on.\r\n */\r\n async fetch(): Promise<DeezerAlbum> {\r\n if (!this.partial) return this;\r\n\r\n const response = await request(`https://api.deezer.com/album/${this.id}/`).catch((err: Error) => err);\r\n\r\n if (response instanceof Error) throw response;\r\n const jsonData = JSON.parse(response);\r\n\r\n this.partial = false;\r\n\r\n this.upc = jsonData.upc;\r\n this.durationInSec = jsonData.duration;\r\n this.numberOfFans = jsonData.fans;\r\n this.releaseDate = new Date(jsonData.release_date);\r\n this.available = jsonData.available;\r\n this.contributors = [];\r\n this.genres = [];\r\n this.tracks = [];\r\n\r\n jsonData.contributors.forEach((contributor: any) => {\r\n this.contributors?.push(new DeezerArtist(contributor));\r\n });\r\n\r\n jsonData.genres.data.forEach((genre: any) => {\r\n this.genres?.push({\r\n name: genre.name,\r\n picture: {\r\n xl: `${genre.picture}?size=xl`,\r\n big: `${genre.picture}?size=big`,\r\n medium: `${genre.picture}?size=medium`,\r\n small: `${genre.picture}?size=small`\r\n }\r\n });\r\n });\r\n\r\n const trackAlbum: any = {\r\n id: this.id,\r\n title: this.title,\r\n cover_xl: this.cover.xl,\r\n cover_big: this.cover.big,\r\n cover_medium: this.cover.medium,\r\n cover_small: this.cover.small,\r\n release_date: jsonData.release_date\r\n };\r\n jsonData.tracks.data.forEach((track: any) => {\r\n track.album = trackAlbum;\r\n this.tracks.push(new DeezerTrack(track, true));\r\n });\r\n\r\n return this;\r\n }\r\n /**\r\n * Fetches all the tracks in the album and returns them\r\n *\r\n * ```ts\r\n * const album = await play.deezer('album url')\r\n *\r\n * const tracks = await album.all_tracks()\r\n * ```\r\n * @returns An array of {@link DeezerTrack}\r\n */\r\n async all_tracks(): Promise<DeezerTrack[]> {\r\n await this.fetch();\r\n\r\n return this.tracks as DeezerTrack[];\r\n }\r\n /**\r\n * Converts instances of this class to JSON data\r\n * @returns JSON data.\r\n */\r\n toJSON() {\r\n return {\r\n id: this.id,\r\n title: this.title,\r\n url: this.url,\r\n recordType: this.recordType,\r\n explicit: this.explicit,\r\n artist: this.artist,\r\n cover: this.cover,\r\n type: this.type,\r\n upc: this.upc,\r\n tracksCount: this.tracksCount,\r\n durationInSec: this.durationInSec,\r\n numberOfFans: this.numberOfFans,\r\n releaseDate: this.releaseDate,\r\n available: this.available,\r\n genres: this.genres,\r\n contributors: this.contributors,\r\n tracks: this.tracks.map((track) => track.toJSON())\r\n };\r\n }\r\n}\r\n/**\r\n * Class for Deezer Playlists\r\n */\r\nexport class DeezerPlaylist {\r\n /**\r\n * The id of the playlist\r\n */\r\n id: number;\r\n /**\r\n * The title of the playlist\r\n */\r\n title: string;\r\n /**\r\n * Whether the playlist is public or private\r\n */\r\n public: boolean;\r\n /**\r\n * The URL of the playlist on Deezer\r\n */\r\n url: string;\r\n /**\r\n * Cover picture of the playlist available in four sizes\r\n */\r\n picture: DeezerImage;\r\n /**\r\n * The date of the playlist's creation\r\n */\r\n creationDate: Date;\r\n /**\r\n * The type, always `'playlist'`, useful to determine what the deezer function returned\r\n */\r\n type: 'track' | 'playlist' | 'album';\r\n /**\r\n * The Deezer user that created the playlist\r\n */\r\n creator: DeezerUser;\r\n /**\r\n * The number of tracks in the playlist\r\n */\r\n tracksCount: number;\r\n\r\n /**\r\n * Signifies that some properties are not populated\r\n *\r\n * Partial playlists can be populated by calling {@link DeezerPlaylist.fetch}.\r\n *\r\n * `true` for playlists in search results and `false` if the album was fetched directly or expanded.\r\n */\r\n partial: boolean;\r\n\r\n /**\r\n * Description of the playlist\r\n *\r\n * `undefined` for partial playlists\r\n *\r\n * @see {@link DeezerPlaylist.partial}\r\n */\r\n description?: string;\r\n /**\r\n * Duration of the playlist in seconds\r\n *\r\n * `undefined` for partial playlists\r\n *\r\n * @see {@link DeezerPlaylist.partial}\r\n */\r\n durationInSec?: number;\r\n /**\r\n * `true` if the playlist is the loved tracks playlist\r\n *\r\n * `undefined` for partial playlists\r\n *\r\n * @see {@link DeezerPlaylist.partial}\r\n */\r\n isLoved?: boolean;\r\n /**\r\n * Whether multiple users have worked on the playlist\r\n *\r\n * `undefined` for partial playlists\r\n *\r\n * @see {@link DeezerPlaylist.partial}\r\n */\r\n collaborative?: boolean;\r\n /**\r\n * The number of fans the playlist has\r\n *\r\n * `undefined` for partial playlists\r\n *\r\n * @see {@link DeezerPlaylist.partial}\r\n */\r\n fans?: number;\r\n\r\n /**\r\n * The list of tracks in the playlist\r\n *\r\n * empty (length === 0) for partial and non public playlists\r\n *\r\n * Use {@link DeezerPlaylist.fetch} to populate the tracks and other properties\r\n *\r\n * @see {@link DeezerPlaylist.partial}\r\n * @see {@link DeezerPlaylist.public}\r\n */\r\n tracks: DeezerTrack[];\r\n\r\n /**\r\n * Creates a Deezer playlist from the data in an API response\r\n * @param data the data to use to create the playlist\r\n * @param partial Whether the playlist should be partial\r\n * @see {@link DeezerPlaylist.partial}\r\n */\r\n constructor(data: any, partial: boolean) {\r\n this.id = data.id;\r\n this.title = data.title;\r\n this.public = data.public;\r\n this.url = data.link;\r\n this.creationDate = new Date(data.creation_date);\r\n this.type = 'playlist';\r\n this.tracksCount = data.nb_tracks;\r\n this.tracks = [];\r\n\r\n this.picture = {\r\n xl: data.picture_xl,\r\n big: data.picture_big,\r\n medium: data.picture_medium,\r\n small: data.picture_small\r\n };\r\n\r\n if (data.user) {\r\n this.creator = {\r\n id: data.user.id,\r\n name: data.user.name\r\n };\r\n } else {\r\n this.creator = {\r\n id: data.creator.id,\r\n name: data.creator.name\r\n };\r\n }\r\n\r\n this.partial = partial;\r\n\r\n if (!partial) {\r\n this.description = data.description;\r\n this.durationInSec = data.duration;\r\n this.isLoved = data.is_loved_track;\r\n this.collaborative = data.collaborative;\r\n this.fans = data.fans;\r\n\r\n if (this.public) {\r\n this.tracks = data.tracks.data.map((track: any) => {\r\n return new DeezerTrack(track, true);\r\n });\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Fetches and populates the missing fields, including all tracks.\r\n *\r\n * The property {@link DeezerPlaylist.partial} will be `false` if this method finishes successfully.\r\n *\r\n * @returns A promise with the same playlist this method was called on.\r\n */\r\n async fetch(): Promise<DeezerPlaylist> {\r\n if (!this.partial && (this.tracks.length === this.tracksCount || !this.public)) {\r\n return this;\r\n }\r\n\r\n if (this.partial) {\r\n const response = await request(`https://api.deezer.com/playlist/${this.id}/`).catch((err: Error) => err);\r\n\r\n if (response instanceof Error) throw response;\r\n const jsonData = JSON.parse(response);\r\n\r\n this.partial = false;\r\n\r\n this.description = jsonData.description;\r\n this.durationInSec = jsonData.duration;\r\n this.isLoved = jsonData.is_loved_track;\r\n this.collaborative = jsonData.collaborative;\r\n this.fans = jsonData.fans;\r\n\r\n if (this.public) {\r\n this.tracks = jsonData.tracks.data.map((track: any) => {\r\n return new DeezerTrack(track, true);\r\n });\r\n }\r\n }\r\n\r\n const currentTracksCount = this.tracks.length;\r\n if (this.public && currentTracksCount !== this.tracksCount) {\r\n let missing = this.tracksCount - currentTracksCount;\r\n\r\n if (missing > 1000) missing = 1000;\r\n\r\n const promises: Promise<DeezerTrack[]>[] = [];\r\n for (let i = 1; i <= Math.ceil(missing / 100); i++) {\r\n promises.push(\r\n new Promise(async (resolve, reject) => {\r\n const response = await request(\r\n `https://api.deezer.com/playlist/${this.id}/tracks?limit=100&index=${i * 100}`\r\n ).catch((err) => reject(err));\r\n\r\n if (typeof response !== 'string') return;\r\n const jsonData = JSON.parse(response);\r\n const tracks = jsonData.data.map((track: any) => {\r\n return new DeezerTrack(track, true);\r\n });\r\n\r\n resolve(tracks);\r\n })\r\n );\r\n }\r\n\r\n const results = await Promise.allSettled(promises);\r\n const newTracks: DeezerTrack[] = [];\r\n\r\n for (const result of results) {\r\n if (result.status === 'fulfilled') {\r\n newTracks.push(...result.value);\r\n } else {\r\n throw result.reason;\r\n }\r\n }\r\n\r\n this.tracks.push(...newTracks);\r\n }\r\n\r\n return this;\r\n }\r\n /**\r\n * Fetches all the tracks in the playlist and returns them\r\n *\r\n * ```ts\r\n * const playlist = await play.deezer('playlist url')\r\n *\r\n * const tracks = await playlist.all_tracks()\r\n * ```\r\n * @returns An array of {@link DeezerTrack}\r\n */\r\n async all_tracks(): Promise<DeezerTrack[]> {\r\n await this.fetch();\r\n\r\n return this.tracks as DeezerTrack[];\r\n }\r\n /**\r\n * Converts instances of this class to JSON data\r\n * @returns JSON data.\r\n */\r\n toJSON() {\r\n return {\r\n id: this.id,\r\n title: this.title,\r\n public: this.public,\r\n url: this.url,\r\n picture: this.picture,\r\n creationDate: this.creationDate,\r\n type: this.type,\r\n creator: this.creator,\r\n tracksCount: this.tracksCount,\r\n description: this.description,\r\n durationInSec: this.durationInSec,\r\n isLoved: this.isLoved,\r\n collaborative: this.collaborative,\r\n fans: this.fans,\r\n tracks: this.tracks.map((track) => track.toJSON())\r\n };\r\n }\r\n}\r\n\r\nclass DeezerTrackAlbum {\r\n id: number;\r\n title: string;\r\n url: string;\r\n cover: DeezerImage;\r\n releaseDate?: Date;\r\n\r\n constructor(data: any) {\r\n this.id = data.id;\r\n this.title = data.title;\r\n this.url = `https://www.deezer.com/album/${data.id}/`;\r\n this.cover = {\r\n xl: data.cover_xl,\r\n big: data.cover_big,\r\n medium: data.cover_medium,\r\n small: data.cover_small\r\n };\r\n\r\n if (data.release_date) this.releaseDate = new Date(data.release_date);\r\n }\r\n}\r\n/**\r\n * Class representing a Deezer artist\r\n */\r\nclass DeezerArtist {\r\n /**\r\n * The id of the artist\r\n */\r\n id: number;\r\n /**\r\n * The name of the artist\r\n */\r\n name: string;\r\n /**\r\n * The URL of the artist on Deezer\r\n */\r\n url: string;\r\n\r\n /**\r\n * The picture of the artist available in four sizes\r\n */\r\n picture?: DeezerImage;\r\n /**\r\n * The of the artist on the track\r\n */\r\n role?: string;\r\n\r\n constructor(data: any) {\r\n this.id = data.id;\r\n this.name = data.name;\r\n\r\n this.url = data.link ? data.link : `https://www.deezer.com/artist/${data.id}/`;\r\n\r\n if (data.picture_xl)\r\n this.picture = {\r\n xl: data.picture_xl,\r\n big: data.picture_big,\r\n medium: data.picture_medium,\r\n small: data.picture_small\r\n };\r\n\r\n if (data.role) this.role = data.role;\r\n }\r\n}\r\n","import { setUserAgent } from './Request/useragent';\r\nimport { setSoundCloudToken } from './SoundCloud';\r\nimport { setSpotifyToken } from './Spotify';\r\nimport { setCookieToken } from './YouTube/utils/cookie';\r\n\r\ninterface tokenOptions {\r\n spotify?: {\r\n client_id: string;\r\n client_secret: string;\r\n refresh_token: string;\r\n market: string;\r\n };\r\n soundcloud?: {\r\n client_id: string;\r\n };\r\n youtube?: {\r\n cookie: string;\r\n };\r\n useragent?: string[];\r\n}\r\n/**\r\n * Sets\r\n *\r\n * i> YouTube :- cookies.\r\n *\r\n * ii> SoundCloud :- client ID.\r\n *\r\n * iii> Spotify :- client ID, client secret, refresh token, market.\r\n *\r\n * iv> Useragents :- array of string.\r\n *\r\n * locally in memory.\r\n *\r\n * Example :\r\n * ```ts\r\n * play.setToken({\r\n * youtube : {\r\n * cookie : \"Your Cookies\"\r\n * }\r\n * }) // YouTube Cookies\r\n *\r\n * await play.setToken({\r\n * spotify : {\r\n * client_id: 'ID',\r\n client_secret: 'secret',\r\n refresh_token: 'token',\r\n market: 'US'\r\n * }\r\n * }) // Await this only when setting data for spotify\r\n * \r\n * play.setToken({\r\n * useragent: ['Your User-agent']\r\n * }) // Use this to avoid 429 errors.\r\n * ```\r\n * @param options {@link tokenOptions}\r\n */\r\nexport async function setToken(options: tokenOptions) {\r\n if (options.spotify) await setSpotifyToken(options.spotify);\r\n if (options.soundcloud) setSoundCloudToken(options.soundcloud);\r\n if (options.youtube) setCookieToken(options.youtube);\r\n if (options.useragent) setUserAgent(options.useragent);\r\n}\r\n","import {\r\n playlist_info,\r\n video_basic_info,\r\n video_info,\r\n decipher_info,\r\n yt_validate,\r\n extractID,\r\n YouTube,\r\n YouTubeStream,\r\n YouTubeChannel,\r\n YouTubePlayList,\r\n YouTubeVideo,\r\n InfoData\r\n} from './YouTube';\r\nimport {\r\n spotify,\r\n sp_validate,\r\n refreshToken,\r\n is_expired,\r\n SpotifyAlbum,\r\n SpotifyPlaylist,\r\n SpotifyTrack,\r\n Spotify,\r\n SpotifyAuthorize,\r\n sp_search\r\n} from './Spotify';\r\nimport {\r\n soundcloud,\r\n so_validate,\r\n SoundCloud,\r\n SoundCloudStream,\r\n getFreeClientID,\r\n SoundCloudPlaylist,\r\n SoundCloudTrack,\r\n check_id,\r\n so_search,\r\n stream as so_stream,\r\n stream_from_info as so_stream_info\r\n} from './SoundCloud';\r\nimport {\r\n deezer,\r\n dz_validate,\r\n dz_advanced_track_search,\r\n Deezer,\r\n DeezerTrack,\r\n DeezerPlaylist,\r\n DeezerAlbum,\r\n dz_search\r\n} from './Deezer';\r\nimport { setToken } from './token';\r\n\r\nenum AudioPlayerStatus {\r\n Idle = 'idle',\r\n Buffering = 'buffering',\r\n Paused = 'paused',\r\n Playing = 'playing',\r\n AutoPaused = 'autopaused'\r\n}\r\n\r\ninterface SearchOptions {\r\n limit?: number;\r\n source?: {\r\n youtube?: 'video' | 'playlist' | 'channel';\r\n spotify?: 'album' | 'playlist' | 'track';\r\n soundcloud?: 'tracks' | 'playlists' | 'albums';\r\n deezer?: 'track' | 'playlist' | 'album';\r\n };\r\n fuzzy?: boolean;\r\n language?: string;\r\n /**\r\n * !!! Before enabling this for public servers, please consider using Discord features like NSFW channels as not everyone in your server wants to see NSFW images. !!!\r\n * Unblurred images will likely have different dimensions than specified in the {@link YouTubeThumbnail} objects.\r\n */\r\n unblurNSFWThumbnails?: boolean;\r\n}\r\n\r\nimport { createInterface } from 'node:readline';\r\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\r\nimport { stream as yt_stream, StreamOptions, stream_from_info as yt_stream_info } from './YouTube/stream';\r\nimport { yt_search } from './YouTube/search';\r\nimport { EventEmitter } from 'stream';\r\n\r\nasync function stream(url: string, options: { seek?: number } & StreamOptions): Promise<YouTubeStream>;\r\nasync function stream(url: string, options?: StreamOptions): Promise<YouTubeStream | SoundCloudStream>;\r\n/**\r\n * Creates a Stream [ YouTube or SoundCloud ] class from a url for playing.\r\n *\r\n * Example\r\n * ```ts\r\n * const source = await play.stream('youtube video URL') // YouTube Video Stream\r\n *\r\n * const source = await play.stream('soundcloud track URL') // SoundCloud Track Stream\r\n *\r\n * const source = await play.stream('youtube video URL', { seek : 45 }) // Seeks 45 seconds (approx.) in YouTube Video Stream\r\n *\r\n * const resource = createAudioResource(source.stream, {\r\n * inputType : source.type\r\n * }) // Use discordjs voice createAudioResource function.\r\n * ```\r\n * @param url Video / Track URL\r\n * @param options\r\n *\r\n * - `number` seek : No of seconds to seek in stream.\r\n * - `string` language : Sets language of searched content [ YouTube search only. ], e.g. \"en-US\"\r\n * - `number` quality : Quality number. [ 0 = Lowest, 1 = Medium, 2 = Highest ]\r\n * - `boolean` htmldata : given data is html data or not\r\n * - `number` precache : No of segments of data to store before looping [YouTube Live Stream only]. [ Defaults to 3 ]\r\n * - `boolean` discordPlayerCompatibility : Conversion of Webm to Opus [ Defaults to false ]\r\n * @returns A {@link YouTubeStream} or {@link SoundCloudStream} Stream to play\r\n */\r\nasync function stream(url: string, options: StreamOptions = {}): Promise<YouTubeStream | SoundCloudStream> {\r\n const url_ = url.trim();\r\n if (url_.length === 0) throw new Error('Stream URL has a length of 0. Check your url again.');\r\n if (options.htmldata) return await yt_stream(url_, options);\r\n if (url_.indexOf('spotify') !== -1) {\r\n throw new Error(\r\n 'Streaming from Spotify is not supported. Please use search() to find a similar track on YouTube or SoundCloud instead.'\r\n );\r\n }\r\n if (url_.indexOf('deezer') !== -1) {\r\n throw new Error(\r\n 'Streaming from Deezer is not supported. Please use search() to find a similar track on YouTube or SoundCloud instead.'\r\n );\r\n }\r\n if (url_.indexOf('soundcloud') !== -1) return await so_stream(url_, options.quality);\r\n else return await yt_stream(url_, options);\r\n}\r\n\r\nasync function search(query: string, options: { source: { deezer: 'album' } } & SearchOptions): Promise<DeezerAlbum[]>;\r\nasync function search(\r\n query: string,\r\n options: { source: { deezer: 'playlist' } } & SearchOptions\r\n): Promise<DeezerPlaylist[]>;\r\nasync function search(query: string, options: { source: { deezer: 'track' } } & SearchOptions): Promise<DeezerTrack[]>;\r\nasync function search(\r\n query: string,\r\n options: { source: { soundcloud: 'albums' } } & SearchOptions\r\n): Promise<SoundCloudPlaylist[]>;\r\nasync function search(\r\n query: string,\r\n options: { source: { soundcloud: 'playlists' } } & SearchOptions\r\n): Promise<SoundCloudPlaylist[]>;\r\nasync function search(\r\n query: string,\r\n options: { source: { soundcloud: 'tracks' } } & SearchOptions\r\n): Promise<SoundCloudTrack[]>;\r\nasync function search(\r\n query: string,\r\n options: { source: { spotify: 'album' } } & SearchOptions\r\n): Promise<SpotifyAlbum[]>;\r\nasync function search(\r\n query: string,\r\n options: { source: { spotify: 'playlist' } } & SearchOptions\r\n): Promise<SpotifyPlaylist[]>;\r\nasync function search(\r\n query: string,\r\n options: { source: { spotify: 'track' } } & SearchOptions\r\n): Promise<SpotifyTrack[]>;\r\nasync function search(\r\n query: string,\r\n options: { source: { youtube: 'channel' } } & SearchOptions\r\n): Promise<YouTubeChannel[]>;\r\nasync function search(\r\n query: string,\r\n options: { source: { youtube: 'playlist' } } & SearchOptions\r\n): Promise<YouTubePlayList[]>;\r\nasync function search(\r\n query: string,\r\n options: { source: { youtube: 'video' } } & SearchOptions\r\n): Promise<YouTubeVideo[]>;\r\nasync function search(query: string, options: { limit: number } & SearchOptions): Promise<YouTubeVideo[]>;\r\nasync function search(query: string, options?: SearchOptions): Promise<YouTubeVideo[]>;\r\n/**\r\n * Searches through a particular source and gives respective info.\r\n * \r\n * Example\r\n * ```ts\r\n * const searched = await play.search('Rick Roll', { source : { youtube : \"video\" } }) // YouTube Video Search\r\n * \r\n * const searched = await play.search('Rick Roll', { limit : 1 }) // YouTube Video Search but returns only 1 video.\r\n * \r\n * const searched = await play.search('Rick Roll', { source : { spotify : \"track\" } }) // Spotify Track Search\r\n * \r\n * const searched = await play.search('Rick Roll', { source : { soundcloud : \"tracks\" } }) // SoundCloud Track Search\r\n * \r\n * const searched = await play.search('Rick Roll', { source : { deezer : \"track\" } }) // Deezer Track Search\r\n * ```\r\n * @param query string to search.\r\n * @param options\r\n * \r\n * - `number` limit : No of searches you want to have.\r\n * - `string` language : Sets language of searched content [ YouTube search only. ], e.g. \"en-US\"\r\n * - `boolean` unblurNSFWThumbnails : Unblurs NSFW thumbnails. Defaults to `false` [ YouTube search only. ]\r\n * !!! Before enabling this for public servers, please consider using Discord features like NSFW channels as not everyone in your server wants to see NSFW images. !!!\r\n * Unblurred images will likely have different dimensions than specified in the {@link YouTubeThumbnail} objects.\r\n * - `boolean` fuzzy : Whether the search should be fuzzy or only return exact matches. Defaults to `true`. [ for `Deezer` Only ]\r\n * - `Object` source : Contains type of source and type of result you want to have\r\n * ```ts\r\n * - youtube : 'video' | 'playlist' | 'channel';\r\n - spotify : 'album' | 'playlist' | 'track';\r\n - soundcloud : 'tracks' | 'playlists' | 'albums';\r\n - deezer : 'track' | 'playlist' | 'album';\r\n ```\r\n * @returns Array of {@link YouTube} or {@link Spotify} or {@link SoundCloud} or {@link Deezer} type\r\n */\r\nasync function search(\r\n query: string,\r\n options: SearchOptions = {}\r\n): Promise<YouTube[] | Spotify[] | SoundCloud[] | Deezer[]> {\r\n if (!options.source) options.source = { youtube: 'video' };\r\n const query_ = encodeURIComponent(query.trim());\r\n if (options.source.youtube)\r\n return await yt_search(query_, {\r\n limit: options.limit,\r\n type: options.source.youtube,\r\n language: options.language,\r\n unblurNSFWThumbnails: options.unblurNSFWThumbnails\r\n });\r\n else if (options.source.spotify) return await sp_search(query_, options.source.spotify, options.limit);\r\n else if (options.source.soundcloud) return await so_search(query_, options.source.soundcloud, options.limit);\r\n else if (options.source.deezer)\r\n return await dz_search(query_, { limit: options.limit, type: options.source.deezer, fuzzy: options.fuzzy });\r\n else throw new Error('Not possible to reach Here LOL. Easter Egg of play-dl if someone get this.');\r\n}\r\n\r\nasync function stream_from_info(info: SoundCloudTrack, options?: StreamOptions): Promise<SoundCloudStream>;\r\nasync function stream_from_info(info: InfoData, options?: StreamOptions): Promise<YouTubeStream>;\r\n/**\r\n * Creates a Stream [ YouTube or SoundCloud ] class from video or track info for playing.\r\n *\r\n * Example\r\n * ```ts\r\n * const info = await video_info('youtube URL')\r\n * const source = await play.stream_from_info(info) // YouTube Video Stream\r\n *\r\n * const soundInfo = await play.soundcloud('SoundCloud URL')\r\n * const source = await play.stream_from_info(soundInfo) // SoundCloud Track Stream\r\n *\r\n * const source = await play.stream_from_info(info, { seek : 45 }) // Seeks 45 seconds (approx.) in YouTube Video Stream\r\n *\r\n * const resource = createAudioResource(source.stream, {\r\n * inputType : source.type\r\n * }) // Use discordjs voice createAudioResource function.\r\n * ```\r\n * @param info YouTube video info OR SoundCloud track Class\r\n * @param options\r\n *\r\n * - `number` seek : No of seconds to seek in stream.\r\n * - `string` language : Sets language of searched content [ YouTube search only. ], e.g. \"en-US\"\r\n * - `number` quality : Quality number. [ 0 = Lowest, 1 = Medium, 2 = Highest ]\r\n * - `boolean` htmldata : given data is html data or not\r\n * - `number` precache : No of segments of data to store before looping [YouTube Live Stream only]. [ Defaults to 3 ]\r\n * - `boolean` discordPlayerCompatibility : Conversion of Webm to Opus[ Defaults to false ]\r\n * @returns A {@link YouTubeStream} or {@link SoundCloudStream} Stream to play\r\n */\r\nasync function stream_from_info(\r\n info: InfoData | SoundCloudTrack,\r\n options: StreamOptions = {}\r\n): Promise<YouTubeStream | SoundCloudStream> {\r\n if (info instanceof SoundCloudTrack) return await so_stream_info(info, options.quality);\r\n else return await yt_stream_info(info, options);\r\n}\r\n/**\r\n * Validates url that play-dl supports.\r\n *\r\n * - `so` - SoundCloud\r\n * - `sp` - Spotify\r\n * - `dz` - Deezer\r\n * - `yt` - YouTube\r\n * @param url URL\r\n * @returns\r\n * ```ts\r\n * 'so_playlist' / 'so_track' | 'sp_track' | 'sp_album' | 'sp_playlist' | 'dz_track' | 'dz_playlist' | 'dz_album' | 'yt_video' | 'yt_playlist' | 'search' | false\r\n * ```\r\n */\r\nasync function validate(\r\n url: string\r\n): Promise<\r\n | 'so_playlist'\r\n | 'so_track'\r\n | 'sp_track'\r\n | 'sp_album'\r\n | 'sp_playlist'\r\n | 'dz_track'\r\n | 'dz_playlist'\r\n | 'dz_album'\r\n | 'yt_video'\r\n | 'yt_playlist'\r\n | 'search'\r\n | false\r\n> {\r\n let check;\r\n const url_ = url.trim();\r\n if (!url_.startsWith('https')) return 'search';\r\n if (url_.indexOf('spotify') !== -1) {\r\n check = sp_validate(url_);\r\n return check !== false ? (('sp_' + check) as 'sp_track' | 'sp_album' | 'sp_playlist') : false;\r\n } else if (url_.indexOf('soundcloud') !== -1) {\r\n check = await so_validate(url_);\r\n return check !== false ? (('so_' + check) as 'so_playlist' | 'so_track') : false;\r\n } else if (url_.indexOf('deezer') !== -1) {\r\n check = await dz_validate(url_);\r\n return check !== false ? (('dz_' + check) as 'dz_track' | 'dz_playlist' | 'dz_album') : false;\r\n } else {\r\n check = yt_validate(url_);\r\n return check !== false ? (('yt_' + check) as 'yt_video' | 'yt_playlist') : false;\r\n }\r\n}\r\n/**\r\n * Authorization interface for Spotify, SoundCloud and YouTube.\r\n *\r\n * Either stores info in `.data` folder or shows relevant data to be used in `setToken` function.\r\n *\r\n * ```ts\r\n * const play = require('play-dl')\r\n *\r\n * play.authorization()\r\n * ```\r\n *\r\n * Just run the above command and you will get a interface asking some questions.\r\n */\r\nfunction authorization(): void {\r\n const ask = createInterface({\r\n input: process.stdin,\r\n output: process.stdout\r\n });\r\n ask.question('Do you want to save data in a file ? (Yes / No): ', (msg) => {\r\n let file: boolean;\r\n if (msg.toLowerCase() === 'yes') file = true;\r\n else if (msg.toLowerCase() === 'no') file = false;\r\n else {\r\n console.log(\"That option doesn't exist. Try again...\");\r\n ask.close();\r\n return;\r\n }\r\n ask.question('Choose your service - sc (for SoundCloud) / sp (for Spotify) / yo (for YouTube): ', (msg) => {\r\n if (msg.toLowerCase().startsWith('sp')) {\r\n let client_id: string, client_secret: string, redirect_url: string, market: string;\r\n ask.question('Start by entering your Client ID : ', (id) => {\r\n client_id = id;\r\n ask.question('Now enter your Client Secret : ', (secret) => {\r\n client_secret = secret;\r\n ask.question('Enter your Redirect URL now : ', (url) => {\r\n redirect_url = url;\r\n console.log(\r\n '\\nIf you would like to know your region code visit : \\nhttps://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements \\n'\r\n );\r\n ask.question('Enter your region code (2-letter country code) : ', (mar) => {\r\n if (mar.length === 2) market = mar;\r\n else {\r\n console.log(\r\n \"That doesn't look like a valid region code, IN will be selected as default.\"\r\n );\r\n market = 'IN';\r\n }\r\n console.log(\r\n '\\nNow open your browser and paste the below url, then authorize it and copy the redirected url. \\n'\r\n );\r\n console.log(\r\n `https://accounts.spotify.com/authorize?client_id=${client_id}&response_type=code&redirect_uri=${encodeURI(\r\n redirect_url\r\n )} \\n`\r\n );\r\n ask.question('Paste the url which you just copied : ', async (url) => {\r\n if (!existsSync('.data')) mkdirSync('.data');\r\n const spotifyData = {\r\n client_id,\r\n client_secret,\r\n redirect_url,\r\n authorization_code: url.split('code=')[1],\r\n market\r\n };\r\n const check = await SpotifyAuthorize(spotifyData, file);\r\n if (check === false) throw new Error('Failed to get access token.');\r\n ask.close();\r\n });\r\n });\r\n });\r\n });\r\n });\r\n } else if (msg.toLowerCase().startsWith('sc')) {\r\n if (!file) {\r\n console.log('You already had a client ID, just paste that in setToken function.');\r\n ask.close();\r\n return;\r\n }\r\n ask.question('Client ID : ', async (id) => {\r\n let client_id = id;\r\n if (!client_id) {\r\n console.log(\"You didn't provide a client ID. Try again...\");\r\n ask.close();\r\n return;\r\n }\r\n if (!existsSync('.data')) mkdirSync('.data');\r\n console.log('Validating your client ID, hold on...');\r\n if (await check_id(client_id)) {\r\n console.log('Client ID has been validated successfully.');\r\n writeFileSync('.data/soundcloud.data', JSON.stringify({ client_id }, undefined, 4));\r\n } else console.log(\"That doesn't look like a valid client ID. Retry with a correct client ID.\");\r\n ask.close();\r\n });\r\n } else if (msg.toLowerCase().startsWith('yo')) {\r\n if (!file) {\r\n console.log('You already had cookie, just paste that in setToken function.');\r\n ask.close();\r\n return;\r\n }\r\n ask.question('Cookies : ', (cook: string) => {\r\n if (!cook || cook.length === 0) {\r\n console.log(\"You didn't provide a cookie. Try again...\");\r\n ask.close();\r\n return;\r\n }\r\n if (!existsSync('.data')) mkdirSync('.data');\r\n console.log('Cookies has been added successfully.');\r\n let cookie: Object = {};\r\n cook.split(';').forEach((x) => {\r\n const arr = x.split('=');\r\n if (arr.length <= 1) return;\r\n const key = arr.shift()?.trim() as string;\r\n const value = arr.join('=').trim();\r\n Object.assign(cookie, { [key]: value });\r\n });\r\n writeFileSync('.data/youtube.data', JSON.stringify({ cookie }, undefined, 4));\r\n ask.close();\r\n });\r\n } else {\r\n console.log(\"That option doesn't exist. Try again...\");\r\n ask.close();\r\n }\r\n });\r\n });\r\n}\r\n/**\r\n * Attaches paused, playing, autoPaused Listeners to discordjs voice AudioPlayer.\r\n *\r\n * Useful if you don't want extra data to be downloaded by play-dl.\r\n * @param player discordjs voice AudioPlayer\r\n * @param resource A {@link YouTubeStream} or {@link SoundCloudStream}\r\n */\r\nfunction attachListeners(player: EventEmitter, resource: YouTubeStream | SoundCloudStream) {\r\n // cleanup existing listeners if they are still registered\r\n type listenerType = (...args: any[]) => void;\r\n\r\n const listeners = player.listeners(AudioPlayerStatus.Idle);\r\n for (const cleanup of listeners) {\r\n if ((cleanup as any).__playDlAttachedListener) {\r\n cleanup();\r\n player.removeListener(AudioPlayerStatus.Idle, cleanup as listenerType);\r\n }\r\n }\r\n\r\n const pauseListener = () => resource.pause();\r\n const resumeListener = () => resource.resume();\r\n const idleListener = () => {\r\n player.removeListener(AudioPlayerStatus.Paused, pauseListener);\r\n player.removeListener(AudioPlayerStatus.AutoPaused, pauseListener);\r\n player.removeListener(AudioPlayerStatus.Playing, resumeListener);\r\n };\r\n pauseListener.__playDlAttachedListener = true;\r\n resumeListener.__playDlAttachedListener = true;\r\n idleListener.__playDlAttachedListener = true;\r\n player.on(AudioPlayerStatus.Paused, pauseListener);\r\n player.on(AudioPlayerStatus.AutoPaused, pauseListener);\r\n player.on(AudioPlayerStatus.Playing, resumeListener);\r\n player.once(AudioPlayerStatus.Idle, idleListener);\r\n}\r\n\r\n// Export Main Commands\r\nexport {\r\n DeezerAlbum,\r\n DeezerPlaylist,\r\n DeezerTrack,\r\n SoundCloudPlaylist,\r\n SoundCloudStream,\r\n SoundCloudTrack,\r\n SpotifyAlbum,\r\n SpotifyPlaylist,\r\n SpotifyTrack,\r\n YouTubeChannel,\r\n YouTubePlayList,\r\n YouTubeVideo,\r\n attachListeners,\r\n authorization,\r\n decipher_info,\r\n deezer,\r\n dz_advanced_track_search,\r\n dz_validate,\r\n extractID,\r\n getFreeClientID,\r\n is_expired,\r\n playlist_info,\r\n refreshToken,\r\n search,\r\n setToken,\r\n so_validate,\r\n soundcloud,\r\n spotify,\r\n sp_validate,\r\n stream,\r\n stream_from_info,\r\n validate,\r\n video_basic_info,\r\n video_info,\r\n yt_validate,\r\n InfoData\r\n};\r\n\r\n// Export Types\r\nexport { Deezer, YouTube, SoundCloud, Spotify, YouTubeStream };\r\n\r\n// Export Default\r\nexport default {\r\n DeezerAlbum,\r\n DeezerPlaylist,\r\n DeezerTrack,\r\n SoundCloudPlaylist,\r\n SoundCloudStream,\r\n SoundCloudTrack,\r\n SpotifyAlbum,\r\n SpotifyPlaylist,\r\n SpotifyTrack,\r\n YouTubeChannel,\r\n YouTubePlayList,\r\n YouTubeVideo,\r\n attachListeners,\r\n authorization,\r\n decipher_info,\r\n deezer,\r\n dz_advanced_track_search,\r\n dz_validate,\r\n extractID,\r\n getFreeClientID,\r\n is_expired,\r\n playlist_info,\r\n refreshToken,\r\n search,\r\n setToken,\r\n so_validate,\r\n soundcloud,\r\n spotify,\r\n sp_validate,\r\n stream,\r\n stream_from_info,\r\n validate,\r\n video_basic_info,\r\n video_info,\r\n yt_validate\r\n};\r\n"],"mappings":"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","names":["httpsRequest","URL","createGunzip","createBrotliDecompress","createDeflate","existsSync","readFileSync","writeFileSync","youtubeData","existsSync","readFileSync","getCookies","result","key","value","__name","setCookie","uploadCookie","writeFileSync","setCookieToken","options","cook","cookie","x","arr","cookieHeaders","headCookie","z","useragents_default","setUserAgent","array","useragents_default","__name","getRandomInt","min","max","getRandomUserAgent","random","request_stream","req_url","options","resolve","reject","res","https_getter","err","__name","internalRequest","request","cookies_added","cook","getCookies","cookies","cookie","existingCookies","getRandomUserAgent","parts","cookieHeaders","data","decoder","encoding","createGunzip","createBrotliDecompress","createDeflate","c","request_resolve_redirect","url","statusCode","resolved","request_content_length","newURL","res2","URL","req_options","req","httpsRequest","Readable","URL","URLSearchParams","var_js","singlequote_js","duoblequote_js","quote_js","key_js","prop_js","empty_js","reverse_function","slice_function","splice_function","swap_function","obj_regexp","function_regexp","reverse_regexp","slice_regexp","splice_regexp","swap_regexp","js_tokens","body","function_action","object_action","object","object_body","function_body","result","reverseKey","sliceKey","spliceKey","swapKey","keys","myreg","tokenizeRegexp","tokens","__name","deciper_signature","signature","sig","len","i","token","pos","swappositions","array","position","first","download_url","format","decoded_url","parsed_url","URL","format_decipher","formats","html5player","request","cipher","params","URLSearchParams","_YouTubeChannel","data","options","def","__name","YouTubeChannel","_YouTubeThumbnail","data","__name","YouTubeThumbnail","_YouTubeVideo","data","thumbnails","thumb","YouTubeThumbnail","YouTubeChannel","__name","YouTubeVideo","BASE_API","_YouTubePlayList","data","searchResult","YouTubeChannel","YouTubeThumbnail","limit","nextPage","request","contents","playlist_videos","getPlaylistVideos","getContinuationToken","max","res","number","page_number","videos","page","__name","YouTubePlayList","URL","URLSearchParams","video_id_pattern","playlist_id_pattern","DEFAULT_API_KEY","video_pattern","playlist_pattern","yt_validate","url","url_","id","__name","extractVideoId","urlOrId","extractID","check","video_id","video_basic_info","options","body","cookieJar","new_url","request","player_data","initial_data","player_response","initial_response","vid","discretionAdvised","upcoming","cookies","updatedValues","acceptViewerDiscretion","ownerInfo","badge","html5player","related","res","x","microformat","musicInfo","item","el","music","row","song","metadata","info","i","contents","rawChapters","m","chapters","chapterRenderer","parseSeconds","upcomingDate","timestamp","likeRenderer","content","button","video_details","YouTubeVideo","format","getIosFormats","video_stream_info","duration","LiveStreamData","decipher_info","seconds","d","h","hDisplay","mDisplay","sDisplay","video_info","data","audio_only","parseAudioFormats","format_decipher","playlist_info","urlObj","URL","response","getWatchPlaylist","getNormalPlaylist","getPlaylistVideos","limit","videos","getContinuationToken","videoId","extractRelated","apiKey","sessionToken","verificationResponse","endpoint","videoPage","URLSearchParams","videoPageData","streamingData","playlist_details","getWatchPlaylistVideos","API_KEY","videoCount","channel","YouTubePlayList","json_data","author","views","lastUpdate","y","videosCount","channel_info","parseDuration","text","split","URL","_LiveStream","dash_url","interval","video_url","precache","Readable","Timer","info","video_stream_info","audioFormat","request","request_stream","URL","list","len","i","resolve","stream","err","c","__name","LiveStream","_Stream","url","type","duration","contentLength","options","parseAudioFormats","end","chunk","Stream","_Timer","callback","time","WebmElements","WebmHeader","Duplex","WEB_ELEMENT_KEYS","WebmElements","_WebmSeeker","Duplex","sec","options","WebmHeader","i","length","value","content_length","clusterlength","position","time_left","data","chunk","_","callback","err","oldCursor","id","ebmlID","parse","track","positionFound","error","__name","WebmSeeker","_SeekStream","url","duration","headerLength","contentLength","bitrate","video_url","options","WebmSeeker","Timer","parse","res","rej","stream","request_stream","err","bytes","info","video_stream_info","audioFormat","parseAudioFormats","end","chunk","__name","SeekStream","URL","parseAudioFormats","formats","result","format","type","__name","stream","url","options","info","video_stream_info","stream_from_info","final","LiveStream","audioFormat","request_stream","URL","SeekStream","contentLength","request_content_length","Stream","BLURRED_THUMBNAILS","ParseSearchResult","html","options","hasLimit","data","json_data","results","details","s","detail","parsed","parseVideo","unblurThumbnail","parseChannel","parsePlaylist","__name","parseDuration","duration","args","dur","badge","url","thumbnail","YouTubeChannel","channel","durationText","YouTubeVideo","run","YouTubePlayList","sqp","yt_search","search","options","url","body","request","ParseSearchResult","__name","_SpotifyTrack","data","artists","v","__name","SpotifyTrack","_SpotifyPlaylist","spotifyData","search","videos","fetching","work","resolve","reject","response","request","err","num","page_number","tracks","page","SpotifyPlaylist","_SpotifyAlbum","SpotifyAlbum","existsSync","readFileSync","writeFileSync","spotifyData","existsSync","readFileSync","pattern","spotify","url","url_","trackID","response","request","err","resObj","SpotifyTrack","albumID","SpotifyAlbum","playlistID","SpotifyPlaylist","__name","sp_validate","SpotifyAuthorize","data","file","resp_json","writeFileSync","is_expired","sp_search","query","type","limit","results","json_data","track","album","playlist","refreshToken","setSpotifyToken","options","existsSync","readFileSync","Readable","_SoundCloudTrack","data","__name","SoundCloudTrack","_SoundCloudPlaylist","client_id","tracks","track","work","i","resolve","num","request","count","SoundCloudPlaylist","_SoundCloudStream","url","type","Readable","Timer","response","err","val","stream","request_stream","c","SoundCloudStream","soundData","existsSync","readFileSync","pattern","soundcloud","url","url_","data","request","err","json_data","SoundCloudTrack","SoundCloudPlaylist","__name","so_search","query","type","limit","response","results","x","stream","quality","HLSformats","parseHlsFormats","req_url","s_data","SoundCloudStream","getFreeClientID","splitted","urls","r","stream_from_info","check_id","id","so_validate","result","format","setSoundCloudToken","options","URL","_DeezerTrack","data","partial","DeezerArtist","DeezerTrackAlbum","contributor","response","request","err","jsonData","__name","DeezerTrack","_DeezerAlbum","genre","trackAlbum","track","DeezerAlbum","_DeezerPlaylist","currentTracksCount","missing","promises","i","resolve","reject","tracks","results","newTracks","result","DeezerPlaylist","_DeezerTrackAlbum","_DeezerArtist","internalValidate","url","urlObj","URL","pathname","path","resolved","request_resolve_redirect","err","__name","deezer","typeData","response","request","jsonData","DeezerTrack","DeezerPlaylist","DeezerAlbum","dz_validate","dz_search","query","options","query_","type","limit","fuzzy","results","track","playlist","album","dz_advanced_track_search","metadata","setToken","options","setSpotifyToken","setSoundCloudToken","setCookieToken","setUserAgent","__name","createInterface","existsSync","mkdirSync","writeFileSync","stream","url","options","url_","__name","search","query","query_","yt_search","sp_search","so_search","dz_search","stream_from_info","info","SoundCloudTrack","validate","check","sp_validate","so_validate","dz_validate","yt_validate","authorization","ask","createInterface","msg","file","client_id","client_secret","redirect_url","market","id","secret","mar","existsSync","mkdirSync","spotifyData","SpotifyAuthorize","check_id","writeFileSync","cook","cookie","x","arr","key","value","attachListeners","player","resource","listeners","cleanup","pauseListener","resumeListener","idleListener","play_dl_default","DeezerAlbum","DeezerPlaylist","DeezerTrack","SoundCloudPlaylist","SoundCloudStream","SoundCloudTrack","SpotifyAlbum","SpotifyPlaylist","SpotifyTrack","YouTubeChannel","YouTubePlayList","YouTubeVideo","attachListeners","authorization","decipher_info","deezer","dz_advanced_track_search","dz_validate","extractID","getFreeClientID","is_expired","playlist_info","refreshToken","search","setToken","so_validate","soundcloud","spotify","sp_validate","stream","stream_from_info","validate","video_basic_info","video_info","yt_validate"]} |