Files
NekoMelody/src/utils/Request.ts

135 lines
3.9 KiB
TypeScript
Raw Normal View History

import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
2024-08-18 10:26:28 +07:00
import playwright, { Browser } from "playwright";
2024-08-17 17:17:18 +07:00
let browser: Browser | null = null;
2024-08-18 11:15:53 +07:00
let globalHeaders: Record<string, string> = {};
let globalCookies: string = "";
export async function makeStreamRequest(
url: string,
options: AxiosRequestConfig = {},
body?: any,
): Promise<AxiosResponse> {
const { headers = {}, method = "GET" } = options;
let config: AxiosRequestConfig = {
url,
method,
2024-08-18 11:15:53 +07:00
headers: {
...globalHeaders,
...headers,
Cookie: globalCookies,
},
data: body,
responseType: "stream",
};
// Override / Add config
config = Object.assign(config, options);
try {
const response = await axios(config);
return response;
} catch (err) {
throw err;
}
}
export async function getStream(
url: string,
options: AxiosRequestConfig = { method: "GET" },
): Promise<AxiosResponse<any, any>> {
try {
let response = await makeStreamRequest(url, options);
const visitedUrls = new Set<string>();
// Handle redirection and detect redirection loop
while (
response.status >= 300 &&
response.status < 400 &&
response.headers.location
) {
const redirectUrl = response.headers.location;
if (visitedUrls.has(redirectUrl)) {
throw new Error("Redirection loop detected");
}
visitedUrls.add(redirectUrl);
response = await makeStreamRequest(redirectUrl, options);
}
return response;
} catch (error) {
throw error;
}
}
export async function getYouTubeFormats(id: string) {
2024-08-18 10:10:53 +07:00
if (!browser) {
2024-08-18 10:26:28 +07:00
browser = await playwright["chromium"].launch({
2024-08-18 10:10:53 +07:00
headless: true,
args: [
"--disable-gpu",
"--disable-dev-shm-usage",
"--disable-setuid-sandbox",
"--no-first-run",
"--no-sandbox",
"--no-zygote",
"--deterministic-fetch",
"--disable-features=IsolateOrigins",
"--disable-site-isolation-trials",
"--single-process",
],
});
browser.once("disconnected", () => {
browser = null;
});
}
2024-08-17 17:17:18 +07:00
const page = await browser.newPage();
2024-08-18 11:15:53 +07:00
page.once("request", (request) => {
globalHeaders = request.headers();
});
2024-08-17 17:17:18 +07:00
await page.goto(`https://www.youtube.com/watch?v=${id}&has_verified=1`, {
2024-08-18 10:26:28 +07:00
waitUntil: "domcontentloaded",
});
2024-08-17 17:17:18 +07:00
const body = await page.evaluate(() => document.body.innerHTML);
2024-08-18 11:15:53 +07:00
const cookies = await page.context().cookies();
globalCookies = cookies
.map((cookie) => `${cookie.name}=${cookie.value}`)
.join("; ");
2024-08-18 10:26:28 +07:00
await page.close();
await browser.close();
const match = body.match(
/var ytInitialPlayerResponse = (.*?)(?=;\s*<\/script>)/,
);
const data = match ? match[1] : null;
if (!data) throw new Error("Failed to get YouTube formats");
try {
let formats = JSON.parse(data).streamingData.adaptiveFormats;
if (!formats) throw new Error("Failed to parse YouTube formats");
// Filters only audio formats that are webm
formats = formats.filter((format: any) =>
format.mimeType.startsWith("audio/webm;"),
);
// Sort the quality of the formats
formats = formats.sort((a: any, b: any) => {
const aQuality = a.audioQuality === "AUDIO_QUALITY_MEDIUM" ? 0 : 1;
const bQuality = b.audioQuality === "AUDIO_QUALITY_MEDIUM" ? 0 : 1;
return aQuality - bQuality;
});
return formats;
} catch (err) {
2024-08-17 20:10:50 +07:00
console.error(err);
throw new Error("Failed to parse YouTube formats");
}
}