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 " } }; 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 \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(() => {}); } } };