import { IncomingMessage } from 'node:http'; import { RequestOptions, request as httpsRequest } from 'node:https'; import { URL } from 'node:url'; import { BrotliDecompress, Deflate, Gunzip, createGunzip, createBrotliDecompress, createDeflate } from 'node:zlib'; import { cookieHeaders, getCookies } from '../YouTube/utils/cookie'; import { getRandomUserAgent } from './useragent'; interface RequestOpts extends RequestOptions { body?: string; method?: 'GET' | 'POST' | 'HEAD'; cookies?: boolean; } /** * Main module which play-dl uses to make a request to stream url. * @param url URL to make https request to * @param options Request options for https request * @returns IncomingMessage from the request */ export function request_stream(req_url: string, options: RequestOpts = { method: 'GET' }): Promise { return new Promise(async (resolve, reject) => { let res = await https_getter(req_url, options).catch((err: Error) => err); if (res instanceof Error) { reject(res); return; } if (Number(res.statusCode) >= 300 && Number(res.statusCode) < 400) { res = await request_stream(res.headers.location as string, options); } resolve(res); }); } /** * Makes a request and follows redirects if necessary * @param req_url URL to make https request to * @param cookies_added Whether cookies were added or not * @param options Request options for https request * @returns A promise with the final response object */ function internalRequest( req_url: string, cookies_added: boolean, options: RequestOpts = { method: 'GET' } ): Promise { return new Promise(async (resolve, reject) => { let res = await https_getter(req_url, options).catch((err: Error) => err); if (res instanceof Error) { reject(res); return; } if (res.headers && res.headers['set-cookie'] && cookies_added) { cookieHeaders(res.headers['set-cookie']); } if (Number(res.statusCode) >= 300 && Number(res.statusCode) < 400) { res = await internalRequest(res.headers.location as string, cookies_added, options); } else if (Number(res.statusCode) > 400) { reject(new Error(`Got ${res.statusCode} from the request`)); return; } resolve(res); }); } /** * Main module which play-dl uses to make a request * @param url URL to make https request to * @param options Request options for https request * @returns body of that request */ export function request(req_url: string, options: RequestOpts = { method: 'GET' }): Promise { return new Promise(async (resolve, reject) => { let cookies_added = false; if (options.cookies) { let cook = getCookies(); if (typeof cook === 'string' && options.headers) { Object.assign(options.headers, { cookie: cook }); cookies_added = true; } } if (options.headers) { options.headers = { ...options.headers, 'accept-encoding': 'gzip, deflate, br', 'user-agent': getRandomUserAgent() }; } const res = await internalRequest(req_url, cookies_added, options).catch((err: Error) => err); if (res instanceof Error) { reject(res); return; } const data: string[] = []; let decoder: BrotliDecompress | Gunzip | Deflate | undefined = undefined; const encoding = res.headers['content-encoding']; if (encoding === 'gzip') decoder = createGunzip(); else if (encoding === 'br') decoder = createBrotliDecompress(); else if (encoding === 'deflate') decoder = createDeflate(); if (decoder) { res.pipe(decoder); decoder.setEncoding('utf-8'); decoder.on('data', (c) => data.push(c)); decoder.on('end', () => resolve(data.join(''))); } else { res.setEncoding('utf-8'); res.on('data', (c) => data.push(c)); res.on('end', () => resolve(data.join(''))); } }); } export function request_resolve_redirect(url: string): Promise { return new Promise(async (resolve, reject) => { let res = await https_getter(url, { method: 'HEAD' }).catch((err: Error) => err); if (res instanceof Error) { reject(res); return; } const statusCode = Number(res.statusCode); if (statusCode < 300) { resolve(url); } else if (statusCode < 400) { const resolved = await request_resolve_redirect(res.headers.location as string).catch((err) => err); if (res instanceof Error) { reject(res); return; } resolve(resolved); } else { reject(new Error(`${res.statusCode}: ${res.statusMessage}, ${url}`)); } }); } /** * Main module that play-dl uses for making a https request * @param req_url URL to make https request to * @param options Request options for https request * @returns Incoming Message from the https request */ function https_getter(req_url: string, options: RequestOpts = {}): Promise { return new Promise((resolve, reject) => { const s = new URL(req_url); options.method ??= 'GET'; const req_options: RequestOptions = { host: s.hostname, path: s.pathname + s.search, headers: options.headers ?? {}, method: options.method }; const req = httpsRequest(req_options, resolve); req.on('error', (err) => { reject(err); }); if (options.method === 'POST') req.write(options.body); req.end(); }); }