const ytSearch = require("yt-search"); const axios = require("axios"); const fs = require("fs"); const path = require("path"); const API_BASE = process.env.API_BASE || "https://azadx69x-ytb-api.vercel.app"; async function fetchStream(url) { const res = await axios({ url, responseType: "stream" }); return res.data; } module.exports = { config: { name: "youtube", aliases: ["ytb"], version: "1.1.1", author: "Azadx69x", countDown: 5, role: 0, description: { en: "Search and download YouTube video/audio" }, category: "media", guide: { en: "{pn} -v \n{pn} -a " } }, onStart: async function ({ api, args, event, commandName }) { try { const typeFlag = args[0]; if (!["-v", "-a"].includes(typeFlag)) { return api.sendMessage( "āŒ Usage:\n)ytb -v \n)ytb -a ", event.threadID, event.messageID ); } const query = args.slice(1).join(" "); if (!query) { return api.sendMessage( "āŒ Provide a search query or YouTube URL.", event.threadID, event.messageID ); } if (query.startsWith("http")) { return await downloadMedia( query, typeFlag === "-v" ? "mp4" : "mp3", api, event ); } const searchRes = await ytSearch(query); const results = searchRes.videos.slice(0, 6); if (!results.length) { return api.sendMessage( "āŒ No results found.", event.threadID, event.messageID ); } let msg = "šŸŽµ YouTube Results\n\n"; results.forEach((v, i) => { msg += `${i + 1}. ${v.title}\nā± ${v.timestamp}\n\n`; }); msg += "Reply with number (1-6) to download."; const attachments = await Promise.all( results.map(v => fetchStream(v.thumbnail)) ); api.sendMessage( { body: msg, attachment: attachments }, event.threadID, (err, sent) => { if (err) return console.error(err); global.GoatBot.onReply.set(sent.messageID, { commandName, results, type: typeFlag, messageID: sent.messageID }); }, event.messageID ); } catch (err) { console.error("[YT] onStart error:", err); api.sendMessage( "āŒ Failed to execute YouTube command.", event.threadID, event.messageID ); } }, onReply: async function ({ event, api, Reply }) { try { const { results, type, messageID } = Reply; const choice = parseInt(event.body); if (isNaN(choice) || choice < 1 || choice > results.length) { return api.sendMessage( "āŒ Invalid choice. Reply 1-6.", event.threadID, event.messageID ); } await api.unsendMessage(messageID); const selected = results[choice - 1]; await downloadMedia( selected.url, type === "-v" ? "mp4" : "mp3", api, event ); } catch (err) { console.error("[YT] onReply error:", err); api.sendMessage( "āŒ Error processing selection.", event.threadID, event.messageID ); } } }; async function downloadMedia(url, type, api, event) { try { const res = await axios.get( `${API_BASE}/download?url=${encodeURIComponent(url)}&type=${type}`, { responseType: "stream" } ); const fileName = `yt_${Date.now()}.${type}`; const filePath = path.join(__dirname, fileName); const writer = fs.createWriteStream(filePath); res.data.pipe(writer); await new Promise((resolve, reject) => { writer.on("finish", resolve); writer.on("error", reject); }); await api.sendMessage( { attachment: fs.createReadStream(filePath) }, event.threadID, () => fs.unlinkSync(filePath), event.messageID ); } catch (err) { console.error("[YT] download error:", err); api.sendMessage( `āŒ Failed to download ${type}.`, event.threadID, event.messageID ); } }