UNPKG

discord-player-ytdlp

Version:

Discord Player extractor that utilizing yt-dlp

995 lines (993 loc) 39.3 kB
"use strict"; 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