goat
command
yt
Search & download YouTube videos interactively
0
views
0
likes
0
installs
Aliases: youtube, yts
raw source
const axios = require("axios");
const fs = require("fs-extra");
const path = require("path");
const https = require("https");
let pendingDownloads = {}; // Track results per thread
module.exports = {
config: {
name: "yt",
aliases: ["youtube", "yts"],
version: "6.6",
author: "Aminul Sardar",
role: 0,
category: "media",
shortDescription: "Search & download YouTube videos interactively",
longDescription: "Search YouTube, show top results, then reply with a number to download the video.",
guide: {
en: "{pn} [search term] - Search YouTube\nReply 1-5 to download the video",
bn: "{pn} [সার্চ শব্দ] - ইউটিউব সার্চ\n1-5 নম্বর রিপ্লাই করলে ভিডিও ডাউনলোড হবে"
}
},
onStart: async function({ api, event, args }) {
const threadID = event.threadID;
const messageID = event.messageID;
const query = args.join(" ").trim();
if (!query) return api.sendMessage("❌ Please provide a search term!", threadID, messageID);
try {
// Fixed: Using correct search API endpoint
const res = await axios.get(`https://aminul-youtube-api.vercel.app/search?query=${encodeURIComponent(query)}`);
const data = res.data;
if (!data || data.length === 0) {
return api.sendMessage("😔 No videos found! Try another keyword.", threadID, messageID);
}
const videos = data.slice(0, 5); // Top 5 results
pendingDownloads[threadID] = videos;
// Build message with thumbnails
let attachments = [];
let msg = `🎬 **YouTube Search Results** 🎬\n🔎 **Query:** ${query}\n\n`;
msg += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
for (let i = 0; i < videos.length; i++) {
const v = videos[i];
msg += `✨ **${i + 1}. ${v.title}**\n`;
msg += `👤 Channel: ${v.author?.name || "Unknown"}\n`;
msg += `⏱ Duration: ${v.duration?.timestamp || "N/A"}\n`;
msg += `👁 Views: ${v.views?.toLocaleString() || "N/A"}\n`;
msg += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
// Download thumbnail if available
if (v.thumbnail) {
try {
const thumbPath = await downloadImage(v.thumbnail, `thumb_${threadID}_${i}.jpg`);
attachments.push(fs.createReadStream(thumbPath));
} catch (thumbErr) {
console.error("Thumbnail download error:", thumbErr);
}
}
}
msg += `\n📥 Reply with **1-${videos.length}** to download your chosen video!\n⚡ Powered by Aminul API`;
return api.sendMessage({
body: msg,
attachment: attachments.length > 0 ? attachments : null
}, threadID, messageID);
} catch (err) {
console.error("Search error:", err);
api.sendMessage("⚠️ Failed to fetch YouTube videos. Try again later.", threadID, messageID);
}
},
onChat: async function({ api, event }) {
const threadID = event.threadID;
const messageID = event.messageID;
const message = event.body?.trim();
if (!message || !pendingDownloads[threadID] || !/^[1-5]$/.test(message)) return;
const index = parseInt(message) - 1;
const video = pendingDownloads[threadID][index];
if (!video) {
return api.sendMessage("❌ Invalid selection!", threadID, messageID);
}
const title = video.title;
const url = video.url;
// Send initial loading message
const loadingMsg = await api.sendMessage(`⏳ Downloading **${title}**... Please wait!`, threadID);
try {
// Fixed: Using correct downloader API endpoint
const downloadInfo = await axios.get(`https://aminul-rest-api-three.vercel.app/downloader/alldownloader?url=${encodeURIComponent(url)}`);
// Fixed: Correct data path from API response
const videoData = downloadInfo.data?.data?.data;
if (!videoData) {
throw new Error("Invalid API response structure");
}
const videoUrl = videoData.high || videoData.low;
if (!videoUrl) {
return api.sendMessage("❌ Cannot download this video. It may be restricted.", threadID, messageID);
}
// Create cache directory if it doesn't exist
const cacheDir = path.join(__dirname, "cache");
await fs.ensureDir(cacheDir);
const filePath = path.join(cacheDir, `yt_${threadID}_${Date.now()}.mp4`);
await downloadFile(videoUrl, filePath);
await api.sendMessage({
body: `✅ **Download Successful!**\n\n🎬 **Title:** ${title}\n📥 Your video is ready!`,
attachment: fs.createReadStream(filePath)
}, threadID, () => {
// Clean up file after sending
fs.unlinkSync(filePath);
});
// Clean up thumbnails
for (let i = 0; i < 5; i++) {
const thumbPath = path.join(__dirname, `thumb_${threadID}_${i}.jpg`);
if (fs.existsSync(thumbPath)) {
fs.unlinkSync(thumbPath);
}
}
} catch (err) {
console.error("Download error:", err);
api.sendMessage("❌ Error occurred while downloading the video. Try again later.", threadID, messageID);
}
// Clear pending downloads
delete pendingDownloads[threadID];
}
};
// Helper: download thumbnail
async function downloadImage(url, filename) {
try {
const response = await axios({
method: 'GET',
url: url,
responseType: 'arraybuffer'
});
const filePath = path.join(__dirname, filename);
await fs.writeFile(filePath, response.data);
return filePath;
} catch (error) {
throw new Error(`Failed to download image: ${error.message}`);
}
}
// Helper: download video file
function downloadFile(url, filepath) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(filepath);
https.get(url, (response) => {
// Check for redirects
if (response.statusCode === 301 || response.statusCode === 302) {
const redirectUrl = response.headers.location;
return downloadFile(redirectUrl, filepath).then(resolve).catch(reject);
}
response.pipe(file);
file.on('finish', () => {
file.close();
resolve();
});
}).on('error', (err) => {
// Clean up failed download
if (fs.existsSync(filepath)) {
fs.unlinkSync(filepath);
}
reject(err);
});
});
}