← back to browse
goat command

video

search & download video (-v) or audio (-a) from youtube

0
views
0
likes
0
installs

Aliases: play, yt, gaan

raw source
const fs = require("fs-extra");
const path = require("path");
const { spawn } = require("child_process");
const ytSearch = require("yt-search");
const axios = require("axios");

const ytdlpPath = path.join(__dirname, "yt-dlp.exe");

module.exports = {
  config: {
    name: "video",
    aliases: ["play", "yt", "gaan"],
    version: "16.0.0",
    author: "protik shah",
    countDown: 15,
    role: 0,
    shortDescription: "search & download video (-v) or audio (-a) from youtube",
    category: "media",
    guide: {
      en: "{p}video -v <song name>\n{p}video -a <song name>\nThen reply with a number (1-5) to pick."
    }
  },

  onStart: async function ({ api, event, args }) {
    const { threadID, messageID, senderID } = event;

    if (args.length === 0) {
      return api.sendMessage(
        "❌ Please provide a song name.\n\n💡 Usage:\n-v <song name> (video)\n-a <song name> (audio only)",
        threadID, messageID
      );
    }

    let isAudioOnly = false;
    let query = args.join(" ");

    if (args[0] === "-a" || args[0] === "--audio") {
      isAudioOnly = true;
      query = args.slice(1).join(" ");
    } else if (args[0] === "-v" || args[0] === "--video") {
      isAudioOnly = false;
      query = args.slice(1).join(" ");
    }

    if (!query) return api.sendMessage("❌ Please provide a song name.", threadID, messageID);

    api.setMessageReaction("⏳", messageID, () => {}, true);

    try {
      const searchResults = await ytSearch(query);
      if (!searchResults || !searchResults.videos || searchResults.videos.length === 0) {
        api.setMessageReaction("❌", messageID, () => {}, true);
        return api.sendMessage("❌ No results found.", threadID, messageID);
      }

      const topResults = searchResults.videos.slice(0, 5);

      // Download thumbnails to temp cache so we can attach them
      const cacheDir = path.join(__dirname, "cache");
      await fs.ensureDir(cacheDir);

      const thumbPaths = [];
      for (let i = 0; i < topResults.length; i++) {
        try {
          const thumbUrl = topResults[i].thumbnail;
          const thumbPath = path.join(cacheDir, `thumb_${Date.now()}_${i}.jpg`);
          const response = await axios.get(thumbUrl, { responseType: "arraybuffer" });
          await fs.writeFile(thumbPath, response.data);
          thumbPaths.push(thumbPath);
        } catch (e) {
          console.error("Thumbnail download error:", e.message);
        }
      }

      let listText = `🔍 Results for: ${query}\n\n`;
      topResults.forEach((v, i) => {
        listText += `${i + 1}. ${v.title}\n⏱️ ${v.timestamp} | 👤 ${v.author.name}\n\n`;
      });
      listText += `👉 Reply with a number (1-${topResults.length}) to download as ${isAudioOnly ? "AUDIO" : "VIDEO"}.`;

      const attachments = thumbPaths.map(p => fs.createReadStream(p));

      api.sendMessage(
        {
          body: listText,
          attachment: attachments.length > 0 ? attachments : undefined
        },
        threadID,
        (err, info) => {
          // Clean up thumbnail files after sending
          thumbPaths.forEach(p => { if (fs.existsSync(p)) fs.unlinkSync(p); });

          if (err) {
            console.error("Send list error:", err);
            return;
          }

          // Register the reply listener
          global.GoatBot.onReply.set(info.messageID, {
            commandName: this.config.name,
            messageID: info.messageID,
            author: senderID,
            results: topResults,
            isAudioOnly: isAudioOnly
          });
        },
        messageID
      );

      api.setMessageReaction("✅", messageID, () => {}, true);

    } catch (err) {
      console.error("Video Search Error:", err);
      api.setMessageReaction("❌", messageID, () => {}, true);
      return api.sendMessage(`❌ Error: ${err.message || "Something went wrong"}`, threadID, messageID);
    }
  },

  onReply: async function ({ api, event, Reply }) {
    const { threadID, messageID, senderID, body } = event;

    // Only the person who searched can pick
    if (senderID !== Reply.author) return;

    const choice = parseInt(body.trim());
    if (isNaN(choice) || choice < 1 || choice > Reply.results.length) {
      return api.sendMessage(`❌ Please reply with a number between 1 and ${Reply.results.length}.`, threadID, messageID);
    }

    const selectedVideo = Reply.results[choice - 1];
    const videoUrl = selectedVideo.url;
    const title = selectedVideo.title;
    const isAudioOnly = Reply.isAudioOnly;

    api.setMessageReaction("⏳", messageID, () => {}, true);
    api.sendMessage(`⬇️ Downloading ${isAudioOnly ? "audio" : "video"}: ${title}`, threadID);

    const cacheDir = path.join(__dirname, "cache");
    await fs.ensureDir(cacheDir);

    const outputTemplate = path.join(cacheDir, `yt_${Date.now()}.%(ext)s`);

    const ytArgs = [
      videoUrl,
      "--no-playlist",
      "--ffmpeg-location", __dirname,
      "-o", outputTemplate,
      "--print", "after_move:filepath"
    ];

    if (isAudioOnly) {
      ytArgs.push("-x", "--audio-format", "mp3");
    } else {
      ytArgs.push(
        "-f", "bestvideo[height<=480]+bestaudio/best[height<=480]",
        "--merge-output-format", "mp4"
      );
    }

    let finalPath = null;
    let errorOutput = "";

    const proc = spawn(ytdlpPath, ytArgs);

    proc.stdout.on("data", (data) => {
      const text = data.toString().trim();
      if (text) finalPath = text;
    });

    proc.stderr.on("data", (data) => {
      errorOutput += data.toString();
    });

    proc.on("close", async (code) => {
      try {
        if (code !== 0 || !finalPath || !fs.existsSync(finalPath)) {
          console.error("yt-dlp error output:", errorOutput);
          api.setMessageReaction("❌", messageID, () => {}, true);
          return api.sendMessage("❌ Error: ডাউনলোড করতে ব্যর্থ হয়েছে।", threadID);
        }

        const fileSize = (await fs.stat(finalPath)).size;
        if (fileSize > 25 * 1024 * 1024) {
          await fs.unlink(finalPath);
          api.setMessageReaction("📏", messageID, () => {}, true);
          return api.sendMessage("❌ File too large (>25MB). Try a shorter video.", threadID);
        }

        const attachment = fs.createReadStream(finalPath);
        const msg = isAudioOnly
          ? `🎻✓ your Searching music ✔\n🎷☞«❮ ${title}`
          : `🎻✓ your Searching video ✔\n🎷☞«❮ ${title}`;

        await api.sendMessage({
          body: msg,
          attachment: attachment
        }, threadID, (err) => {
          if (err) console.error("Send error:", err);
          if (fs.existsSync(finalPath)) fs.unlinkSync(finalPath);
        });

        api.setMessageReaction("✅", messageID, () => {}, true);
      } catch (err) {
        console.error("Reply Download Error:", err);
        api.setMessageReaction("❌", messageID, () => {}, true);
        return api.sendMessage(`❌ Error: ${err.message || "Something went wrong"}`, threadID);
      }
    });
  }
};

View raw file