← back to browse
goat command

sing

Send a song as audio

0
views
0
likes
0
installs
raw source
const axios = require('axios');
const fs = require('fs-extra');
const path = require('path');

const API_BASE_URL = process.env.SING_API_BASE_URL || "https://noobdevs-apis.onrender.com";

module.exports.config = {
  name: "sing",
  version: "1.0.0",
  author: "Badhon-00",
  countDown: 5,
  role: 0,
  shortDescription: "Send a song as audio",
  longDescription: "Searches by song title and sends the matched track as an audio attachment.",
  category: "media",
  guide: {
    en: "{p}sing <song title>"
  }
};

module.exports.onStart = async function ({ api, event, args, message }) {
  const songName = args.join(" ");

  if (!songName) {
    return message.reply(
      "──────────────────\n" +
      "⚠️ MISSING SONG QUERY\n" +
      "──────────────────\n" +
      "Please provide a song title.\n\n" +
      "💡 Example: sing tera mera rishta mashup\n" +
      "🎬 For video instead: sing-v <title>\n" +
      "──────────────────"
    );
  }

  const searchingMsg = await message.reply(`🔍 Searching: "${songName}"\n⏳ Please wait while fetching track metadata...`);
  const cacheFolder = path.join(__dirname, 'cache');
  let audioFilePath = null;

  const cleanupSearchingMsg = () => {
    if (searchingMsg && searchingMsg.messageID) api.unsendMessage(searchingMsg.messageID).catch(() => {});
  };

  try {
    const searchUrl = `${API_BASE_URL}/Melissa/api/v2/NoobDevs/song_search?q=${encodeURIComponent(songName)}`;
    const searchRes = await axios.get(searchUrl, { timeout: 20000, validateStatus: () => true });

    if (searchRes.status === 404) {
      cleanupSearchingMsg();
      return message.reply(`❌ Track Search Failed\nNo track found for "${songName}".`);
    }
    if (searchRes.status !== 200 || !searchRes.data || !searchRes.data.status) {
      cleanupSearchingMsg();
      const detail = (searchRes.data && (searchRes.data.message || searchRes.data.error)) || `HTTP ${searchRes.status}`;
      return message.reply(`❌ Track Search Failed\n${detail}`);
    }

    const song = searchRes.data;
    const streamEndpoint = `${API_BASE_URL}/Melissa/api/v2/NoobDevs/searchaudio?sing=${encodeURIComponent(song.url)}`;

    await fs.ensureDir(cacheFolder);
    audioFilePath = path.join(cacheFolder, `${Date.now()}_song.mp3`);

    const audioResponse = await axios({
      method: 'GET',
      url: streamEndpoint,
      responseType: 'stream',
      timeout: 60000,
      validateStatus: () => true
    });

    if (audioResponse.status !== 200) {
      let detail = `HTTP ${audioResponse.status}`;
      try {
        const chunks = [];
        for await (const chunk of audioResponse.data) chunks.push(chunk);
        const body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
        detail = body.error || body.message || detail;
        if (body.hint) detail += `\n💡 ${body.hint}`;
      } catch (_) { /* not JSON */ }

      cleanupSearchingMsg();
      return message.reply(`❌ Download Failed\n${detail}`);
    }

    const fileWriter = fs.createWriteStream(audioFilePath);
    audioResponse.data.pipe(fileWriter);

    await new Promise((resolve, reject) => {
      fileWriter.on('finish', resolve);
      fileWriter.on('error', reject);
      audioResponse.data.on('error', reject);
    });

    const stats = await fs.stat(audioFilePath);
    if (stats.size === 0) {
      throw new Error('Downloaded file is empty — the source stream failed silently.');
    }

    const replyBody =
      "🎵 MELISSA MUSIC PLAYER 🎵\n" +
      "────────────────────────\n" +
      `🎧 Title: ${song.title}\n` +
      `👤 Artist/Channel: ${song.author}\n` +
      `⏱️ Duration: ${song.duration}\n` +
      `👁️ Views: ${song.views ? String(song.views) : 'N/A'}\n` +
      "────────────────────────\n" +
      "✨ Audio attached below. Enjoy listening!";

    await message.reply({
      body: replyBody,
      attachment: fs.createReadStream(audioFilePath)
    });

    cleanupSearchingMsg();

  } catch (error) {
    console.error(error);
    cleanupSearchingMsg();
    const isTimeout = error.code === 'ECONNABORTED';
    return message.reply(
      isTimeout
        ? `❌ Timed Out\nThe song server took too long to respond. Try again in a moment.`
        : `❌ Error Processing Song\n${error.message}`
    );
  } finally {
    if (audioFilePath && await fs.pathExists(audioFilePath)) {
      await fs.remove(audioFilePath).catch(() => {});
    }
  }
};

View raw file