// code from goatstore // api: https://store.agi.bd const fs = require("fs-extra"); const path = require("path"); const axios = require("axios"); const moment = require("moment-timezone"); const ytdl = require("ytdl-core"); const ffmpeg = require("fluent-ffmpeg"); const ffmpegPath = require("@ffmpeg-installer/ffmpeg").path; ffmpeg.setFfmpegPath(ffmpegPath); // ═══════════════════════════════════════════════ // 🎬 YOUTUBE DOWNLOADER — GOAT BOT V2 // ═══════════════════════════════════════════════ const mediaSavePath = path.join(__dirname, "cache", "media_x213"); module.exports = { config: { name: "youtube", version: "2.0.0", author: "DungUwU → Goat Bot V2", countDown: 5, role: 0, shortDescription: { en: "Download YouTube video or audio" }, longDescription: { en: "Search YouTube and download video/audio from YouTube URL or keyword" }, category: "utility", guide: { en: "{pn} \n" + "{pn} \n\n" + "Example:\n" + "{pn} https://youtu.be/xxxxx\n" + "{pn} Arijit Singh song" }, envConfig: { YOUTUBE_API: "" } }, // ═══════════════════════════════════════════════ // 📂 LOAD // ═══════════════════════════════════════════════ onLoad: function () { try { fs.ensureDirSync(mediaSavePath); } catch (error) { console.error( "[ YOUTUBE ] Cache folder error:", error ); } }, // ═══════════════════════════════════════════════ // ⚡ MAIN COMMAND // ═══════════════════════════════════════════════ onStart: async function ({ api, event, args }) { const { threadID, messageID, senderID } = event; if (!args || args.length === 0) { return api.sendMessage( "❌ YouTube search বা URL দিতে হবে!\n\n" + "Example:\n" + "youtube Despacito\n" + "youtube https://youtu.be/xxxxx", threadID, messageID ); } const input = args.join(" ").trim(); const apiKey = process.env.YOUTUBE_API || this.config.envConfig.YOUTUBE_API; if (!apiKey) { return api.sendMessage( "❌ YouTube API key সেট করা হয়নি!\n\n" + "YOUTUBE_API environment variable সেট করুন।", threadID, messageID ); } // ═══════════════════════════════════════════════ // 🔗 YOUTUBE URL CHECK // ═══════════════════════════════════════════════ const urlPattern = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+/i; const isValidUrl = urlPattern.test(input); try { // ═══════════════════════════════════════════════ // 🔗 DIRECT YOUTUBE URL // ═══════════════════════════════════════════════ if (isValidUrl) { const videoID = extractVideoID(input); if (!videoID) { return api.sendMessage( "❌ YouTube video ID পাওয়া যায়নি!", threadID, messageID ); } const infoMessage = "🎬 ════「 𝗬𝗢𝗨𝗧𝗨𝗕𝗘 」════ 🎬\n\n" + "📺 → 𝟭. ভিডিও ডাউনলোড\n" + "🎵 → 𝟮. অডিও ডাউনলোড\n\n" + "↳ এই মেসেজে Reply করে 1 অথবা 2 লিখুন।"; return api.sendMessage( infoMessage, threadID, (error, info) => { if (error) { console.error( "[ YOUTUBE ] Send error:", error ); return; } if (!global.GoatBot) { console.error( "[ YOUTUBE ] GoatBot object not found." ); return; } if (!global.GoatBot.onReply) { global.GoatBot.onReply = []; } global.GoatBot.onReply.push({ commandName: this.config.name, messageID: info.messageID, author: senderID, type: "download", videoID }); }, messageID ); } // ═══════════════════════════════════════════════ // 🔎 SEARCH YOUTUBE // ═══════════════════════════════════════════════ const results = await searchYouTube( input, apiKey ); if (!results || results.length === 0) { return api.sendMessage( "❌ কোনো YouTube result পাওয়া যায়নি!", threadID, messageID ); } let IDs = []; let msg = ""; for (let i = 0; i < results.length; i++) { const item = results[i]; if ( !item.id || !item.id.videoId ) { continue; } const videoID = item.id.videoId; IDs.push(videoID); let duration = "00:00:00"; try { const videoInfo = await getVideoInfo( videoID, apiKey ); duration = prettyTime( videoInfo.contentDetails.duration ); } catch (error) { console.log( "[ YOUTUBE ] Duration error:", error.message ); } msg += `\n\n${IDs.length}. ${item.snippet.title}` + `\n⏱️ → সময়: ${duration}` + `\n────────────────────`; } if (IDs.length === 0) { return api.sendMessage( "❌ কোনো valid YouTube video পাওয়া যায়নি!", threadID, messageID ); } const searchMessage = "🔎 ═══「 𝗬𝗢𝗨𝗧𝗨𝗕𝗘 𝗦𝗘𝗔𝗥𝗖 」═══\n" + msg + "\n\n" + "━━━━━━━━━━━━━━━━━━\n" + "👉 উপরের result থেকে একটি number লিখে এই মেসেজে Reply করুন।"; return api.sendMessage( searchMessage, threadID, (error, info) => { if (error) { console.error( "[ YOUTUBE ] Search send error:", error ); return; } if (!global.GoatBot.onReply) { global.GoatBot.onReply = []; } global.GoatBot.onReply.push({ commandName: this.config.name, messageID: info.messageID, author: senderID, type: "list", IDs }); }, messageID ); } catch (error) { console.error( "[ YOUTUBE ] Main error:", error ); return api.sendMessage( "❌ YouTube থেকে তথ্য আনতে সমস্যা হয়েছে।\n" + "কিছুক্ষণ পর আবার চেষ্টা করুন।", threadID, messageID ); } }, // ═══════════════════════════════════════════════ // 💬 REPLY HANDLER // ═══════════════════════════════════════════════ onReply: async function ({ api, event, Reply }) { const { threadID, messageID, body, senderID } = event; // অন্য user-এর reply গ্রহণ করবে না if ( String(senderID) !== String(Reply.author) ) { return; } const apiKey = process.env.YOUTUBE_API || this.config.envConfig.YOUTUBE_API; if (!apiKey) { return api.sendMessage( "❌ YouTube API key সেট করা হয়নি!", threadID, messageID ); } // ═══════════════════════════════════════════════ // 📥 DOWNLOAD // ═══════════════════════════════════════════════ if (Reply.type === "download") { const choice = String(body).trim(); if (choice !== "1" && choice !== "2") { return api.sendMessage( "❌ শুধু 1 অথবা 2 লিখুন!\n\n" + "1️⃣ Video\n" + "2️⃣ Audio", threadID, messageID ); } const type = choice === "1" ? "video" : "audio"; const loadingMessage = type === "video" ? "📥 ভিডিও Download হচ্ছে...\n⏳ একটু অপেক্ষা করুন..." : "🎵 অডিও Download হচ্ছে...\n⏳ একটু অপেক্ষা করুন..."; const loadingInfo = await api.sendMessage( loadingMessage, threadID, messageID ); let filePath = null; try { const media = await downloadMedia( Reply.videoID, type, senderID ); filePath = media.filePath; if (media.error) { throw new Error( "Download failed" ); } if (!fs.existsSync(filePath)) { throw new Error( "Downloaded file not found" ); } const fileSize = fs.statSync(filePath).size; // Messenger limit const maxSize = type === "video" ? 50 * 1024 * 1024 : 25 * 1024 * 1024; if (fileSize > maxSize) { await api.sendMessage( "❌ ফাইলের size অনেক বড়!\n\n" + `📦 Size: ${formatBytes(fileSize)}\n` + `📌 Limit: ${formatBytes(maxSize)}`, threadID, messageID ); return; } // ═══════════════════════════════════════════ // 📌 VIDEO INFORMATION // ═══════════════════════════════════════════ let title = "YouTube Video"; let duration = "00:00:00"; try { const videoInfo = await getVideoInfo( Reply.videoID, apiKey ); title = videoInfo.snippet.title; duration = prettyTime( videoInfo.contentDetails.duration ); } catch (error) { console.log( "[ YOUTUBE ] Info error:", error.message ); } const currentTime = moment .tz("Asia/Dhaka") .format("HH:mm:ss"); const caption = `🎬 ═══「 𝗬𝗢𝗨𝗧𝗨𝗕𝗘 」═══ 🎬\n\n` + `📌 Title: ${title}\n` + `⏱️ Duration: ${duration}\n` + `📦 Type: ${type === "video" ? "Video" : "Audio"}\n` + `🕐 Time: ${currentTime}\n\n` + `━━━━━━━━━━━━━━━━━━`; // পুরনো reply message remove try { await api.unsendMessage( Reply.messageID ); } catch (error) {} // Loading message remove if ( loadingInfo && loadingInfo.messageID ) { try { await api.unsendMessage( loadingInfo.messageID ); } catch (error) {} } return api.sendMessage( { body: caption, attachment: fs.createReadStream( filePath ) }, threadID, (error) => { if (error) { console.error( "[ YOUTUBE ] Send media error:", error ); } // Delete file try { if ( filePath && fs.existsSync(filePath) ) { fs.unlinkSync(filePath); } } catch (deleteError) { console.error( "[ YOUTUBE ] File delete error:", deleteError ); } }, messageID ); } catch (error) { console.error( "[ YOUTUBE ] Download error:", error ); if ( filePath && fs.existsSync(filePath) ) { try { fs.unlinkSync(filePath); } catch (e) {} } return api.sendMessage( "❌ YouTube media download করতে সমস্যা হয়েছে!\n\n" + "সম্ভবত ভিডিওটি unavailable, private অথবা download করা যাচ্ছে না।", threadID, messageID ); } } // ═══════════════════════════════════════════════ // 🔎 SEARCH RESULT SELECT // ═══════════════════════════════════════════════ if (Reply.type === "list") { const selected = parseInt( String(body).trim() ); if ( isNaN(selected) || selected < 1 || selected > Reply.IDs.length ) { return api.sendMessage( `❌ সঠিক number দিন!\n\n` + `Available: 1 - ${Reply.IDs.length}`, threadID, messageID ); } const chosenID = Reply.IDs[selected - 1]; try { await api.unsendMessage( Reply.messageID ); } catch (error) {} const downloadMessage = "🎬 ═══「 𝗬𝗢𝗨𝗧𝗨𝗕𝗘 」═══ 🎬\n\n" + "📺 → 𝟭. ভিডিও ডাউনলোড\n" + "🎵 → 𝟮. অডিও ডাউনলোড\n\n" + "👉 এই মেসেজে Reply করে 1 অথবা 2 লিখুন।"; return api.sendMessage( downloadMessage, threadID, (error, info) => { if (error) { console.error( "[ YOUTUBE ] Reply message error:", error ); return; } if (!global.GoatBot.onReply) { global.GoatBot.onReply = []; } global.GoatBot.onReply.push({ commandName: this.config.name, messageID: info.messageID, author: senderID, type: "download", videoID: chosenID }); }, messageID ); } } }; // ═══════════════════════════════════════════════ // 🔎 YOUTUBE SEARCH // ═══════════════════════════════════════════════ async function searchYouTube( keyword, apiKey ) { const response = await axios.get( "https://www.googleapis.com/youtube/v3/search", { params: { part: "snippet", maxResults: 6, q: keyword, type: "video", key: apiKey } } ); return response.data.items || []; } // ═══════════════════════════════════════════════ // 📌 GET VIDEO INFO // ═══════════════════════════════════════════════ async function getVideoInfo( videoID, apiKey ) { const response = await axios.get( "https://www.googleapis.com/youtube/v3/videos", { params: { part: "snippet,contentDetails", id: videoID, key: apiKey } } ); if ( !response.data.items || !response.data.items.length ) { throw new Error( "Video information not found" ); } return response.data.items[0]; } // ═══════════════════════════════════════════════ // 📥 DOWNLOAD MEDIA // ═══════════════════════════════════════════════ async function downloadMedia( videoID, type, senderID ) { await fs.ensureDir( mediaSavePath ); const extension = type === "video" ? "mp4" : "m4a"; const filePath = path.join( mediaSavePath, `${Date.now()}_${senderID}.${extension}` ); try { const url = `https://www.youtube.com/watch?v=${videoID}`; if (type === "video") { // 360p MP4 const stream = ytdl(url, { quality: "18" }); await new Promise( (resolve, reject) => { const writeStream = fs.createWriteStream( filePath ); stream .pipe(writeStream) .on("finish", resolve) .on("error", reject); stream.on( "error", reject ); } ); } else { // Audio const stream = ytdl(url, { filter: "audioonly", quality: "highestaudio" }); await new Promise( (resolve, reject) => { ffmpeg(stream) .audioCodec("aac") .format("ipod") .save(filePath) .on( "end", resolve ) .on( "error", reject ); } ); } return { filePath, error: 0 }; } catch (error) { console.error( "[ YOUTUBE ] Media download error:", error ); if ( fs.existsSync(filePath) ) { try { fs.unlinkSync( filePath ); } catch (e) {} } return { filePath, error: 1 }; } } // ═══════════════════════════════════════════════ // 🔗 EXTRACT VIDEO ID // ═══════════════════════════════════════════════ function extractVideoID(url) { try { const patterns = [ /youtu\.be\/([^?&/]+)/i, /youtube\.com\/watch\?v=([^?&/]+)/i, /youtube\.com\/shorts\/([^?&/]+)/i, /youtube\.com\/embed\/([^?&/]+)/i, /youtube\.com\/v\/([^?&/]+)/i ]; for ( const pattern of patterns ) { const match = url.match(pattern); if (match) { return match[1]; } } return null; } catch (error) { return null; } } // ═══════════════════════════════════════════════ // ⏱️ ISO DURATION → HH:MM:SS // ═══════════════════════════════════════════════ function prettyTime(time) { if (!time) { return "00:00:00"; } let hours = 0; let minutes = 0; let seconds = 0; const hourMatch = time.match(/(\d+)H/); const minuteMatch = time.match(/(\d+)M/); const secondMatch = time.match(/(\d+)S/); if (hourMatch) { hours = parseInt( hourMatch[1] ); } if (minuteMatch) { minutes = parseInt( minuteMatch[1] ); } if (secondMatch) { seconds = parseInt( secondMatch[1] ); } return [ String(hours).padStart(2, "0"), String(minutes).padStart(2, "0"), String(seconds).padStart(2, "0") ].join(":"); } // ═══════════════════════════════════════════════ // 📦 FILE SIZE // ═══════════════════════════════════════════════ function formatBytes(bytes) { if (!bytes) { return "0 B"; } const units = [ "B", "KB", "MB", "GB" ]; const index = Math.floor( Math.log(bytes) / Math.log(1024) ); return ( (bytes / Math.pow( 1024, index )).toFixed(2) + " " + units[index] ); }