161 lines
5.8 KiB
TypeScript

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<IncomingMessage> {
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<IncomingMessage> {
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<string> {
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<string> {
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<IncomingMessage> {
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();
});
}