discord-player-ytdlp
Version:
Discord Player extractor that utilizing yt-dlp
995 lines (993 loc) • 39.3 kB
JavaScript
;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
// utils.js
var require_utils = __commonJS({
"utils.js"(exports2, module2) {
var { exec } = require("child_process");
var { promisify } = require("util");
var fs = require("fs");
var path = require("path");
var { Innertube } = require("youtubei.js");
var execAsync = promisify(exec);
var innertube = null;
var lastCookies = null;
var initializeYouTube = async (options = {}) => {
const currentCookies = options.cookies;
const needsReinit = !innertube || currentCookies !== lastCookies;
if (needsReinit) {
try {
const initOptions = {};
if (options.cookies) {
if (typeof options.cookies === "string") {
initOptions.cookie = options.cookies;
} else {
initOptions.cookie = options.cookies;
}
}
if (options.client) {
initOptions.client_name = options.client;
}
initOptions.enable_session_cache = true;
if (innertube) {
try {
await innertube.session.signOut();
} catch (e) {
}
}
innertube = await Innertube.create(initOptions);
lastCookies = currentCookies;
} catch (error) {
console.error("\u274C Failed to initialize YouTube service for extractor:", error);
innertube = null;
}
}
return innertube;
};
var isValidUrl2 = (string) => {
try {
new URL(string);
return true;
} catch (_) {
return false;
}
};
var isYouTubeUrl2 = (url) => {
const youtubeRegex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be|m\.youtube\.com)/i;
return youtubeRegex.test(url);
};
var isYouTubePlaylistUrl2 = (url) => {
const playlistRegex = /[?&]list=([a-zA-Z0-9_-]+)/;
return isYouTubeUrl2(url) && playlistRegex.test(url);
};
var extractYouTubePlaylistId2 = (url) => {
const regex = /[?&]list=([a-zA-Z0-9_-]+)/;
const match = url.match(regex);
return match ? match[1] : null;
};
var extractYouTubeId2 = (url) => {
const regex = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/;
const match = url.match(regex);
return match ? match[1] : null;
};
var searchYouTube2 = async (query, limit = 1, options = {}) => {
try {
const yt = await initializeYouTube(options);
if (!yt) {
throw new Error("YouTube service not available");
}
const searchPromise = yt.search(query, { type: "video" });
const timeoutPromise = new Promise(
(_, reject) => setTimeout(() => reject(new Error("YouTube search timeout")), 1e4)
);
const searchResults = await Promise.race([searchPromise, timeoutPromise]);
if (!searchResults.videos || searchResults.videos.length === 0) {
return [];
}
const results = searchResults.videos.slice(0, limit).map((video) => {
var _a, _b, _c, _d, _e, _f;
return {
id: video.id,
title: ((_a = video.title) == null ? void 0 : _a.text) || "Unknown Title",
duration: ((_b = video.duration) == null ? void 0 : _b.text) || "Unknown",
thumbnail: ((_d = (_c = video.thumbnails) == null ? void 0 : _c[0]) == null ? void 0 : _d.url) || null,
url: `https://www.youtube.com/watch?v=${video.id}`,
author: ((_e = video.author) == null ? void 0 : _e.name) || "Unknown Artist",
views: ((_f = video.view_count) == null ? void 0 : _f.text) || "0"
};
});
return results;
} catch (error) {
console.error("YouTube search error:", error);
return [];
}
};
var getYouTubePlaylist2 = async (playlistId, options = {}) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J;
try {
if (playlistId.startsWith("RD")) {
let seedVideoId = null;
if (playlistId.length > 2) {
seedVideoId = playlistId.substring(2);
}
if (!seedVideoId || seedVideoId.length !== 11) {
throw new Error("Invalid YouTube Mix playlist ID format");
}
const yt2 = await initializeYouTube(options);
if (!yt2) {
throw new Error("YouTube service not available");
}
try {
const seedVideo = await yt2.getInfo(seedVideoId);
if (!seedVideo) {
throw new Error("Seed video not found");
}
const tracks2 = [{
id: seedVideoId,
title: ((_a = seedVideo.basic_info) == null ? void 0 : _a.title) || "Unknown Title",
duration: ((_c = (_b = seedVideo.basic_info) == null ? void 0 : _b.duration) == null ? void 0 : _c.text) || "Unknown",
thumbnail: ((_f = (_e = (_d = seedVideo.basic_info) == null ? void 0 : _d.thumbnail) == null ? void 0 : _e[0]) == null ? void 0 : _f.url) || null,
url: `https://www.youtube.com/watch?v=${seedVideoId}`,
author: ((_g = seedVideo.basic_info) == null ? void 0 : _g.author) || "Unknown Artist",
views: ((_h = seedVideo.basic_info) == null ? void 0 : _h.view_count) || "0"
}];
if (seedVideo.watch_next_feed) {
const relatedVideos = seedVideo.watch_next_feed.filter((item) => item.type === "CompactVideo" && item.id && item.title).slice(0, 19).map((video) => {
var _a2, _b2, _c2, _d2, _e2, _f2;
return {
id: video.id,
title: ((_a2 = video.title) == null ? void 0 : _a2.text) || "Unknown Title",
duration: ((_b2 = video.duration) == null ? void 0 : _b2.text) || "Unknown",
thumbnail: ((_d2 = (_c2 = video.thumbnails) == null ? void 0 : _c2[0]) == null ? void 0 : _d2.url) || null,
url: `https://www.youtube.com/watch?v=${video.id}`,
author: ((_e2 = video.author) == null ? void 0 : _e2.name) || "Unknown Artist",
views: ((_f2 = video.view_count) == null ? void 0 : _f2.text) || "0"
};
});
tracks2.push(...relatedVideos);
}
return {
id: playlistId,
title: `Mix - ${((_i = seedVideo.basic_info) == null ? void 0 : _i.title) || "Unknown"}`,
description: "YouTube Mix playlist (auto-generated)",
thumbnail: ((_l = (_k = (_j = seedVideo.basic_info) == null ? void 0 : _j.thumbnail) == null ? void 0 : _k[0]) == null ? void 0 : _l.url) || null,
author: "YouTube",
url: `https://www.youtube.com/playlist?list=${playlistId}`,
tracks: tracks2
};
} catch (mixError) {
console.error("Mix playlist generation error:", mixError);
throw new Error("Unable to access Mix playlist. This may be a private or unavailable Mix.");
}
}
const yt = await initializeYouTube(options);
if (!yt) {
throw new Error("YouTube service not available");
}
const playlistPromise = yt.getPlaylist(playlistId);
const timeoutPromise = new Promise(
(_, reject) => setTimeout(() => reject(new Error("Playlist request timeout")), 15e3)
);
const playlist = await Promise.race([playlistPromise, timeoutPromise]);
if (!playlist) {
throw new Error("Playlist not found or inaccessible");
}
if (!playlist.videos && !playlist.items) {
throw new Error("Playlist is private or unavailable. Please check your authentication.");
}
const tracks = [];
const videos = playlist.videos || playlist.items || [];
if (videos.length === 0) {
return {
id: playlistId,
title: ((_m = playlist.info) == null ? void 0 : _m.title) || ((_n = playlist.title) == null ? void 0 : _n.text) || "Unknown Playlist",
description: ((_o = playlist.info) == null ? void 0 : _o.description) || ((_p = playlist.description) == null ? void 0 : _p.text) || "",
thumbnail: ((_s = (_r = (_q = playlist.info) == null ? void 0 : _q.thumbnails) == null ? void 0 : _r[0]) == null ? void 0 : _s.url) || ((_u = (_t = playlist.thumbnails) == null ? void 0 : _t[0]) == null ? void 0 : _u.url) || null,
author: ((_w = (_v = playlist.info) == null ? void 0 : _v.author) == null ? void 0 : _w.name) || ((_x = playlist.author) == null ? void 0 : _x.name) || "Unknown",
url: `https://www.youtube.com/playlist?list=${playlistId}`,
tracks: []
};
}
const batchSize = 10;
for (let i = 0; i < videos.length; i += batchSize) {
const batch = videos.slice(i, i + batchSize);
const batchPromises = batch.map(async (video) => {
var _a2, _b2, _c2, _d2, _e2, _f2, _g2, _h2;
try {
const videoId = video.id || video.video_id;
const title = ((_a2 = video.title) == null ? void 0 : _a2.text) || video.title || "Unknown Title";
const author = ((_b2 = video.author) == null ? void 0 : _b2.name) || ((_c2 = video.channel) == null ? void 0 : _c2.name) || "Unknown Artist";
const duration = ((_d2 = video.duration) == null ? void 0 : _d2.text) || video.duration || "Unknown";
const thumbnail = ((_f2 = (_e2 = video.thumbnails) == null ? void 0 : _e2[0]) == null ? void 0 : _f2.url) || ((_g2 = video.thumbnail) == null ? void 0 : _g2.url) || null;
const views = ((_h2 = video.view_count) == null ? void 0 : _h2.text) || video.views || "0";
if (!videoId) {
return null;
}
return {
id: videoId,
title,
duration,
thumbnail,
url: `https://www.youtube.com/watch?v=${videoId}`,
author,
views
};
} catch (error) {
return null;
}
});
const batchResults = await Promise.allSettled(batchPromises);
const validResults = batchResults.filter((result) => result.status === "fulfilled" && result.value !== null).map((result) => result.value);
tracks.push(...validResults);
}
return {
id: playlistId,
title: ((_y = playlist.info) == null ? void 0 : _y.title) || ((_z = playlist.title) == null ? void 0 : _z.text) || "Unknown Playlist",
description: ((_A = playlist.info) == null ? void 0 : _A.description) || ((_B = playlist.description) == null ? void 0 : _B.text) || "",
thumbnail: ((_E = (_D = (_C = playlist.info) == null ? void 0 : _C.thumbnails) == null ? void 0 : _D[0]) == null ? void 0 : _E.url) || ((_G = (_F = playlist.thumbnails) == null ? void 0 : _F[0]) == null ? void 0 : _G.url) || null,
author: ((_I = (_H = playlist.info) == null ? void 0 : _H.author) == null ? void 0 : _I.name) || ((_J = playlist.author) == null ? void 0 : _J.name) || "Unknown",
url: `https://www.youtube.com/playlist?list=${playlistId}`,
tracks
};
} catch (error) {
console.error("YouTube playlist error:", error);
if (error.message.includes("unviewable") || error.message.includes("private")) {
throw new Error("This playlist is private or requires authentication. Please check your YouTube cookies.");
} else if (error.message.includes("timeout")) {
throw new Error("Playlist request timed out. Please try again.");
} else if (error.message.includes("not found")) {
throw new Error("Playlist not found. Please check the playlist ID.");
}
throw error;
}
};
var getYouTubeMetadata2 = async (videoId, options = {}) => {
var _a, _b, _c, _d, _e, _f, _g, _h;
try {
const yt = await initializeYouTube(options);
if (!yt) {
throw new Error("YouTube service not available");
}
const info = await yt.getInfo(videoId);
if (!info) {
throw new Error("Video not found");
}
let duration = "Unknown";
if ((_a = info.basic_info) == null ? void 0 : _a.duration) {
if (typeof info.basic_info.duration === "object") {
if (info.basic_info.duration.seconds) {
duration = formatDuration(info.basic_info.duration.seconds);
} else if (info.basic_info.duration.text) {
duration = info.basic_info.duration.text;
}
} else if (typeof info.basic_info.duration === "number") {
duration = formatDuration(info.basic_info.duration);
} else if (typeof info.basic_info.duration === "string") {
duration = info.basic_info.duration;
}
}
let thumbnail = null;
if ((_b = info.basic_info) == null ? void 0 : _b.thumbnail) {
if (Array.isArray(info.basic_info.thumbnail) && info.basic_info.thumbnail.length > 0) {
const thumbnails = info.basic_info.thumbnail;
thumbnail = ((_c = thumbnails[thumbnails.length - 1]) == null ? void 0 : _c.url) || ((_d = thumbnails[0]) == null ? void 0 : _d.url);
} else if (typeof info.basic_info.thumbnail === "string") {
thumbnail = info.basic_info.thumbnail;
}
}
return {
id: videoId,
title: ((_e = info.basic_info) == null ? void 0 : _e.title) || "Unknown Title",
duration,
thumbnail,
url: `https://www.youtube.com/watch?v=${videoId}`,
author: ((_f = info.basic_info) == null ? void 0 : _f.author) || "Unknown Artist",
views: ((_g = info.basic_info) == null ? void 0 : _g.view_count) || 0,
description: ((_h = info.basic_info) == null ? void 0 : _h.short_description) || ""
};
} catch (error) {
console.error("YouTube metadata error:", error);
return null;
}
};
var getRelatedTracks2 = async (videoId, options = {}, limit = 10) => {
try {
const yt = await initializeYouTube(options);
if (!yt) {
throw new Error("YouTube service not available");
}
const info = await yt.getInfo(videoId);
if (!info || !info.watch_next_feed) {
return [];
}
const relatedVideos = info.watch_next_feed.filter(
(item) => item.type === "CompactVideo" && item.id && item.title
).slice(0, limit);
return relatedVideos.map((video) => {
var _a, _b, _c, _d, _e, _f;
return {
id: video.id,
title: ((_a = video.title) == null ? void 0 : _a.text) || "Unknown Title",
duration: ((_b = video.duration) == null ? void 0 : _b.text) || "Unknown",
thumbnail: ((_d = (_c = video.thumbnails) == null ? void 0 : _c[0]) == null ? void 0 : _d.url) || null,
url: `https://www.youtube.com/watch?v=${video.id}`,
author: ((_e = video.author) == null ? void 0 : _e.name) || "Unknown Artist",
views: ((_f = video.view_count) == null ? void 0 : _f.text) || "0"
};
});
} catch (error) {
console.error("YouTube related tracks error:", error);
return [];
}
};
var getStreamingUrl2 = async (url, ytdlpPath, quality = "bestaudio[ext=m4a]/bestaudio[ext=webm]/bestaudio", cookies = null) => {
try {
if (!fs.existsSync(ytdlpPath)) {
throw new Error(`yt-dlp binary not found at: ${ytdlpPath}`);
}
const optimizedArgs = [
"-f",
quality,
"--get-url",
"--no-playlist",
"--no-warnings",
"--no-check-certificates",
"--prefer-insecure",
"--skip-download",
"--no-call-home",
"--no-cache-dir",
"--socket-timeout",
"10",
"--retries",
"3",
"--fragment-retries",
"3"
];
if (cookies && typeof cookies === "string" && cookies.trim()) {
const tempCookiesFile = path.join(__dirname, "temp_cookies.txt");
try {
const netscapeCookies = convertToNetscapeFormat(cookies);
fs.writeFileSync(tempCookiesFile, netscapeCookies);
optimizedArgs.push("--cookies", tempCookiesFile);
} catch (cookieError) {
}
}
const command = `"${ytdlpPath}" ${optimizedArgs.join(" ")} "${url}"`;
const { stdout, stderr } = await execAsync(command, {
timeout: 15e3,
// Reduced timeout for faster response
maxBuffer: 1024 * 1024,
// 1MB buffer should be enough for URL
encoding: "utf8",
windowsHide: true
// Hide console window on Windows
});
if (cookies) {
const tempCookiesFile = path.join(__dirname, "temp_cookies.txt");
try {
if (fs.existsSync(tempCookiesFile)) {
fs.unlinkSync(tempCookiesFile);
}
} catch (cleanupError) {
}
}
if (stderr && !stdout) {
throw new Error(`yt-dlp error: ${stderr}`);
}
const streamUrl = stdout.trim();
if (!streamUrl || !streamUrl.startsWith("http")) {
throw new Error("Invalid streaming URL returned");
}
return streamUrl;
} catch (error) {
console.error("yt-dlp streaming error:", error.message);
throw error;
}
};
var getYouTubeMetadataWithYtDlp2 = async (videoId, ytdlpPath, cookies = null) => {
let tempCookiesFile = null;
try {
if (!fs.existsSync(ytdlpPath)) {
throw new Error(`yt-dlp binary not found at: ${ytdlpPath}`);
}
const url = `https://www.youtube.com/watch?v=${videoId}`;
const args = [
"-J",
"--no-playlist",
"--no-warnings",
"--no-check-certificates",
"--skip-download",
"--no-call-home",
"--no-cache-dir",
"--socket-timeout",
"15",
"--retries",
"1",
"--fragment-retries",
"1",
"--extractor-retries",
"1"
];
if (cookies) {
tempCookiesFile = path.join(__dirname, `temp_cookies_metadata_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.txt`);
try {
const netscapeCookies = convertToNetscapeFormat(cookies);
fs.writeFileSync(tempCookiesFile, netscapeCookies);
args.push("--cookies", tempCookiesFile);
} catch (cookieError) {
console.warn("Failed to write cookies for metadata, continuing without:", cookieError.message);
tempCookiesFile = null;
}
}
const command = `"${ytdlpPath}" ${args.join(" ")} "${url}"`;
const { stdout, stderr } = await execAsync(command, {
timeout: 3e4,
// Increased timeout to 30 seconds
maxBuffer: 2 * 1024 * 1024,
// Increased buffer to 2MB
windowsHide: true,
// Hide console window on Windows
killSignal: "SIGKILL"
// Use SIGKILL instead of SIGTERM for more reliable termination
});
if (stderr && !stdout) {
throw new Error(`yt-dlp metadata error: ${stderr}`);
}
if (!stdout || stdout.trim() === "") {
throw new Error("yt-dlp returned empty response");
}
let info;
try {
info = JSON.parse(stdout);
} catch (parseError) {
throw new Error(`Failed to parse yt-dlp JSON response: ${parseError.message}`);
}
if (!info.id && !info.display_id) {
throw new Error("Invalid video data: missing video ID");
}
return {
id: videoId,
title: info.title || info.fulltitle || "Unknown Title",
duration: info.duration ? formatDuration(info.duration) : "Unknown",
thumbnail: info.thumbnail || (info.thumbnails && info.thumbnails[0] ? info.thumbnails[0].url : null),
url: `https://www.youtube.com/watch?v=${videoId}`,
author: info.uploader || info.channel || info.uploader_id || "Unknown Artist",
views: info.view_count || 0,
description: info.description || ""
};
} catch (error) {
console.error("yt-dlp YouTube metadata error:", error);
throw error;
} finally {
if (tempCookiesFile) {
try {
if (fs.existsSync(tempCookiesFile)) {
fs.unlinkSync(tempCookiesFile);
}
} catch (cleanupError) {
}
}
}
};
var getBasicInfo2 = async (url, ytdlpPath) => {
try {
if (!fs.existsSync(ytdlpPath)) {
throw new Error(`yt-dlp binary not found at: ${ytdlpPath}`);
}
const command = `"${ytdlpPath}" -J --flat-playlist --no-warnings "${url}"`;
const { stdout, stderr } = await execAsync(command, {
timeout: 15e3,
// Reduced timeout
maxBuffer: 1024 * 1024,
// 1MB buffer
windowsHide: true
// Hide console window on Windows
});
if (stderr && !stdout) {
throw new Error(`yt-dlp info error: ${stderr}`);
}
const info = JSON.parse(stdout);
return {
id: info.id || "unknown",
title: info.title || info.fulltitle || "Unknown Title",
duration: info.duration ? formatDuration(info.duration) : "Unknown",
thumbnail: info.thumbnail || null,
url: info.webpage_url || url,
author: info.uploader || info.channel || "Unknown Artist",
description: info.description || ""
};
} catch (error) {
console.error("yt-dlp info error:", error);
throw error;
}
};
var formatDuration = (seconds) => {
if (!seconds || isNaN(seconds)) return "Unknown";
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor(seconds % 3600 / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
} else {
return `${minutes}:${secs.toString().padStart(2, "0")}`;
}
};
var canExtract2 = async (url, ytdlpPath) => {
try {
if (!fs.existsSync(ytdlpPath)) {
return false;
}
const command = `"${ytdlpPath}" --simulate --quiet "${url}"`;
await execAsync(command, {
timeout: 1e4,
windowsHide: true
// Hide console window on Windows
});
return true;
} catch (error) {
return false;
}
};
var validateUrl2 = (url) => {
if (!url || typeof url !== "string") {
return false;
}
if (!isValidUrl2(url)) {
return false;
}
const unsupportedProtocols = ["file:", "ftp:", "mailto:"];
const protocol = new URL(url).protocol;
if (unsupportedProtocols.includes(protocol)) {
return false;
}
return true;
};
var createTrackObject = (info, source = "yt-dlp") => {
return {
title: info.title || "Unknown Title",
author: info.author || "Unknown Artist",
duration: info.duration || "Unknown",
url: info.url,
thumbnail: info.thumbnail || null,
source,
raw: info
};
};
var convertToNetscapeFormat = (browserCookies) => {
try {
let netscapeCookies = "# Netscape HTTP Cookie File\n";
netscapeCookies += "# This is a generated file! Do not edit.\n\n";
const cookies = browserCookies.split(";").map((cookie) => cookie.trim());
for (const cookie of cookies) {
if (!cookie || !cookie.includes("=")) continue;
const [name, ...valueParts] = cookie.split("=");
const value = valueParts.join("=");
if (!name || !value) continue;
const domain = ".youtube.com";
const domainSpecified = "TRUE";
const path2 = "/";
const secure = name.includes("Secure") ? "TRUE" : "FALSE";
const expires = "0";
netscapeCookies += `${domain} ${domainSpecified} ${path2} ${secure} ${expires} ${name.trim()} ${value.trim()}
`;
}
return netscapeCookies;
} catch (error) {
return browserCookies;
}
};
module2.exports = {
initializeYouTube,
isValidUrl: isValidUrl2,
isYouTubeUrl: isYouTubeUrl2,
isYouTubePlaylistUrl: isYouTubePlaylistUrl2,
extractYouTubeId: extractYouTubeId2,
extractYouTubePlaylistId: extractYouTubePlaylistId2,
searchYouTube: searchYouTube2,
getYouTubePlaylist: getYouTubePlaylist2,
getYouTubeMetadata: getYouTubeMetadata2,
getYouTubeMetadataWithYtDlp: getYouTubeMetadataWithYtDlp2,
getRelatedTracks: getRelatedTracks2,
getStreamingUrl: getStreamingUrl2,
getBasicInfo: getBasicInfo2,
formatDuration,
canExtract: canExtract2,
validateUrl: validateUrl2,
createTrackObject,
convertToNetscapeFormat
};
}
});
// index.js
var { BaseExtractor, Track, Playlist } = require("discord-player");
var {
isValidUrl,
isYouTubeUrl,
isYouTubePlaylistUrl,
extractYouTubeId,
extractYouTubePlaylistId,
searchYouTube,
getYouTubePlaylist,
getYouTubeMetadata,
getYouTubeMetadataWithYtDlp,
getRelatedTracks,
getStreamingUrl,
getBasicInfo,
canExtract,
validateUrl
} = require_utils();
var YtDlpExtractor = class extends BaseExtractor {
static identifier = "ytdlp-extractor";
constructor(context, options) {
var _a, _b;
super(context, options);
this.ytdlpPath = options.ytdlpPath;
this.priority = options.priority || 100;
this.enableYouTubeSearch = options.enableYouTubeSearch !== false;
this.enableDirectUrls = options.enableDirectUrls !== false;
this.streamQuality = options.streamQuality || "bestaudio[ext=m4a]/bestaudio[ext=webm]/bestaudio";
this.preferYtdlpMetadata = options.preferYtdlpMetadata !== false;
this.youtubeiOptions = {
cookies: ((_a = options.youtubeiOptions) == null ? void 0 : _a.cookies) || null,
client: ((_b = options.youtubeiOptions) == null ? void 0 : _b.client) || null
};
this.protocols = ["http:", "https:"];
this.debug("YtDlp Extractor initialized");
}
/**
* Activate the extractor
*/
async activate() {
this.debug("Activating YtDlp Extractor");
const fs = require("fs");
if (!fs.existsSync(this.ytdlpPath)) {
throw new Error(`yt-dlp binary not found at: ${this.ytdlpPath}`);
}
this.debug("YtDlp Extractor activated successfully");
}
/**
* Deactivate the extractor
*/
async deactivate() {
this.debug("YtDlp Extractor deactivated");
}
/**
* Validate if this extractor can handle the query
*/
async validate(query, type) {
try {
this.debug(`Validating query: ${query}, type: ${type}`);
if (isValidUrl(query)) {
if (!this.enableDirectUrls) {
this.debug("Direct URLs disabled");
return false;
}
if (isYouTubeUrl(query) && this.enableYouTubeSearch) {
this.debug("YouTube URL detected and enabled");
return true;
}
this.debug("Checking if yt-dlp can extract URL");
const canHandle = await canExtract(query, this.ytdlpPath);
return canHandle;
}
if (this.enableYouTubeSearch) {
this.debug("Search query accepted");
return true;
}
this.debug("Query not supported");
return false;
} catch (error) {
this.debug(`Validation error: ${error.message}`);
return false;
}
}
/**
* Handle the query and return track information
*/
async handle(query, context) {
try {
this.debug(`Handling query: ${query}`);
if (isValidUrl(query)) {
return await this.handleDirectUrl(query, context);
} else {
return await this.handleSearchQuery(query, context);
}
} catch (error) {
this.debug(`Handle error: ${error.message}`);
return this.createResponse(null, []);
}
}
/**
* Handle direct URL queries
*/
async handleDirectUrl(url, context) {
var _a, _b;
try {
if (!validateUrl(url)) {
throw new Error("Invalid URL format");
}
if (isYouTubeUrl(url)) {
if (isYouTubePlaylistUrl(url)) {
return await this.handleYouTubePlaylist(url, context);
}
const videoId = extractYouTubeId(url);
if (!videoId) {
throw new Error("Could not extract YouTube video ID");
}
let trackInfo;
let metadataSource;
if (this.preferYtdlpMetadata) {
metadataSource = "yt-dlp";
try {
const cookies = ((_a = this.youtubeiOptions) == null ? void 0 : _a.cookies) || null;
this.debug(`Attempting to get metadata using yt-dlp for video: ${videoId}`);
trackInfo = await getYouTubeMetadataWithYtDlp(videoId, this.ytdlpPath, cookies);
this.debug(`Successfully got metadata using yt-dlp`);
} catch (ytdlpError) {
this.debug(`yt-dlp metadata failed: ${ytdlpError.message}`);
this.debug(`Falling back to youtubei.js for metadata`);
metadataSource = "youtubei.js";
try {
trackInfo = await getYouTubeMetadata(videoId, this.youtubeiOptions);
if (trackInfo) {
this.debug(`Successfully got metadata using youtubei.js fallback`);
}
} catch (youtubeiError) {
this.debug(`youtubei.js metadata also failed: ${youtubeiError.message}`);
trackInfo = null;
}
}
} else {
metadataSource = "youtubei.js";
try {
this.debug(`Attempting to get metadata using youtubei.js for video: ${videoId}`);
trackInfo = await getYouTubeMetadata(videoId, this.youtubeiOptions);
if (trackInfo) {
this.debug(`Successfully got metadata using youtubei.js`);
} else {
throw new Error("youtubei.js returned null");
}
} catch (youtubeiError) {
this.debug(`youtubei.js metadata failed: ${youtubeiError.message}`);
this.debug(`Falling back to yt-dlp for metadata`);
metadataSource = "yt-dlp";
try {
const cookies = ((_b = this.youtubeiOptions) == null ? void 0 : _b.cookies) || null;
trackInfo = await getYouTubeMetadataWithYtDlp(videoId, this.ytdlpPath, cookies);
if (trackInfo) {
this.debug(`Successfully got metadata using yt-dlp fallback`);
}
} catch (ytdlpError) {
this.debug(`yt-dlp metadata also failed: ${ytdlpError.message}`);
trackInfo = null;
}
}
}
if (!trackInfo) {
throw new Error("Could not get YouTube metadata from either yt-dlp or youtubei.js");
}
trackInfo.metadataSource = metadataSource;
const track = new Track(this, {
title: trackInfo.title,
author: trackInfo.author,
duration: trackInfo.duration,
url: trackInfo.url,
thumbnail: trackInfo.thumbnail,
source: "ytdlp-extractor",
raw: trackInfo,
requestedBy: context.requestedBy,
queryType: "arbitrary"
});
return this.createResponse(null, [track]);
} else {
const trackInfo = await getBasicInfo(url, this.ytdlpPath);
const track = new Track(this, {
title: trackInfo.title,
author: trackInfo.author,
duration: trackInfo.duration,
url: trackInfo.url,
thumbnail: trackInfo.thumbnail,
source: "ytdlp-extractor",
raw: trackInfo,
requestedBy: context.requestedBy,
queryType: "arbitrary"
});
return this.createResponse(null, [track]);
}
} catch (error) {
this.debug(`Direct URL error: ${error.message}`);
throw error;
}
}
/**
* Handle YouTube playlist URLs
*/
async handleYouTubePlaylist(url, context) {
try {
const playlistId = extractYouTubePlaylistId(url);
if (!playlistId) {
throw new Error("Could not extract playlist ID");
}
const playlistInfo = await getYouTubePlaylist(playlistId, this.youtubeiOptions);
if (!playlistInfo || !playlistInfo.tracks || playlistInfo.tracks.length === 0) {
throw new Error("Could not get playlist information or playlist is empty");
}
const playlist = new Playlist(this, {
title: playlistInfo.title,
description: playlistInfo.description,
thumbnail: playlistInfo.thumbnail,
type: "playlist",
source: "ytdlp-extractor",
author: {
name: playlistInfo.author,
url: null
},
tracks: [],
id: playlistId,
url: playlistInfo.url,
rawPlaylist: playlistInfo
});
const tracks = playlistInfo.tracks.map((trackData) => {
const track = new Track(this, {
title: trackData.title,
author: trackData.author,
duration: trackData.duration,
url: trackData.url,
thumbnail: trackData.thumbnail,
source: "ytdlp-extractor",
raw: trackData,
requestedBy: context.requestedBy,
queryType: "arbitrary",
playlist
});
return track;
});
playlist.tracks = tracks;
return this.createResponse(playlist, tracks);
} catch (error) {
this.debug(`YouTube playlist error: ${error.message}`);
throw error;
}
}
/**
* Handle search queries (YouTube only)
*/
async handleSearchQuery(query, context) {
try {
if (!this.enableYouTubeSearch) {
throw new Error("YouTube search is disabled");
}
const searchResults = await searchYouTube(query, 1, this.youtubeiOptions);
if (!searchResults || searchResults.length === 0) {
return this.createResponse(null, []);
}
const result = searchResults[0];
const track = new Track(this, {
title: result.title,
author: result.author,
duration: result.duration,
url: result.url,
// This YouTube URL will be used by yt-dlp for streaming
thumbnail: result.thumbnail,
source: "ytdlp-extractor",
raw: {
...result,
originalQuery: query,
searchMethod: "youtubei"
},
requestedBy: context.requestedBy,
queryType: "youtubeSearch"
});
return this.createResponse(null, [track]);
} catch (error) {
this.debug(`Search query error: ${error.message}`);
return this.createResponse(null, []);
}
}
/**
* Get streaming URL for a track
*/
async stream(info) {
var _a, _b, _c;
try {
this.debug(`Getting stream for: ${info.title || ((_a = info.raw) == null ? void 0 : _a.title) || "Unknown"}`);
const url = info.url || ((_b = info.raw) == null ? void 0 : _b.url);
if (!url) {
throw new Error("No URL found in track info");
}
const cookies = ((_c = this.youtubeiOptions) == null ? void 0 : _c.cookies) || null;
const streamUrl = await getStreamingUrl(url, this.ytdlpPath, this.streamQuality, cookies);
if (!streamUrl || !streamUrl.startsWith("http")) {
throw new Error("Invalid streaming URL returned");
}
this.debug(`Stream URL obtained successfully`);
return streamUrl;
} catch (error) {
this.debug(`Stream error: ${error.message}`);
throw error;
}
}
/**
* Get related tracks for autoplay functionality
*/
async getRelatedTracks(track, history) {
try {
this.debug(`Getting related tracks for: ${track.title}`);
if (!isYouTubeUrl(track.url)) {
this.debug("Non-YouTube track, no related tracks available");
return this.createResponse(null, []);
}
const videoId = extractYouTubeId(track.url);
if (!videoId) {
this.debug("Could not extract video ID");
return this.createResponse(null, []);
}
let relatedTracks = await getRelatedTracks(videoId, this.youtubeiOptions, 10);
if (!relatedTracks || relatedTracks.length === 0) {
this.debug("No related tracks from YouTube API, trying search-based approach");
if (track.author && track.author !== "Unknown Artist") {
const searchQuery = `${track.author} music`;
relatedTracks = await searchYouTube(searchQuery, 5, this.youtubeiOptions);
}
}
const historyUrls = new Set(
history.tracks.toArray().map((t) => t.url)
);
const filteredTracks = relatedTracks.filter(
(trackData) => !historyUrls.has(trackData.url) && trackData.url !== track.url
);
if (filteredTracks.length === 0) {
this.debug("No new related tracks found after filtering");
return this.createResponse(null, []);
}
const tracks = filteredTracks.slice(0, 5).map((trackData) => {
const relatedTrack = new Track(this, {
title: trackData.title,
author: trackData.author,
duration: trackData.duration,
url: trackData.url,
thumbnail: trackData.thumbnail,
source: "ytdlp-extractor",
raw: {
...trackData,
relatedTo: track.url,
autoplay: true
},
requestedBy: track.requestedBy,
queryType: "autoplay"
});
return relatedTrack;
});
this.debug(`Found ${tracks.length} related tracks`);
return this.createResponse(null, tracks);
} catch (error) {
this.debug(`Related tracks error: ${error.message}`);
return this.createResponse(null, []);
}
}
/**
* Bridge functionality for other extractors
*/
async bridge(track, sourceExtractor) {
try {
if ((sourceExtractor == null ? void 0 : sourceExtractor.identifier) !== this.identifier) {
const streamUrl = await this.stream(track);
return { stream: streamUrl, type: "arbitrary" };
}
return null;
} catch (error) {
this.debug(`Bridge error: ${error.message}`);
return null;
}
}
/**
* Create bridge query for track search
*/
createBridgeQuery(track) {
return `${track.author} - ${track.title}`;
}
};
module.exports = { YtDlpExtractor };
//# sourceMappingURL=index.js.map