// code from goatstore // api: https://store.agi.bd const axios = require("axios"); const fs = require("fs"); const path = require("path"); const { createReadStream } = require("fs"); const BASE_URL = "https://vexoapi.site/api"; module.exports = { config: { name: "sing", version: "4.0", author: "Sakib", countDown: 5, role: 0, shortDescription: "Search or download MP3", longDescription: "Search songs and download MP3 from YouTube", category: "ANIME & MEDIA", guide: "{p}sing " }, onStart: async function ({ api, event, args }) { const { threadID, messageID, senderID, messageReply } = event; let query = args.join(" ").trim(); // ========================================= // REPLY করা YouTube link // ========================================= if (messageReply?.body) { const match = messageReply.body.match( /https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be)\/[^\s]+/i ); if (match?.[0]) { return downloadAudio( api, threadID, messageID, match[0] ); } } // ========================================= // সরাসরি YouTube link // ========================================= if ( /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\//i.test( query ) ) { return downloadAudio( api, threadID, messageID, query ); } // ========================================= // Query না থাকলে // ========================================= if (!query) { return api.sendMessage( "❌ Please provide a song name or YouTube link.\n\n" + "🎵 Example:\n" + "sing Ore Piya", threadID, messageID ); } // ========================================= // SEARCH YOUTUBE // ========================================= try { const response = await axios.get( `${BASE_URL}/search/youtube`, { params: { query: query }, timeout: 30000, headers: { "User-Agent": "Mozilla/5.0", "Accept": "application/json" } } ); console.log( "SING SEARCH RESPONSE:", JSON.stringify(response.data).slice(0, 3000) ); const results = normalizeSearchResults( response.data ).slice(0, 5); if (!results.length) { return api.sendMessage( "❌ No songs found.", threadID, messageID ); } let msg = "🎵 𝗠𝗨𝗦𝗜𝗖 𝗦𝗘𝗔𝗥𝗖 𝗥𝗘𝗦𝗨𝗟𝗧𝗦\n" + "━━━━━━━━━━━━━━━━━━\n"; for (let i = 0; i < results.length; i++) { const video = results[i]; msg += `${i + 1}. ${video.title}\n` + `👤 ${video.channel}\n` + `⏱️ ${video.duration}\n\n`; } msg += "━━━━━━━━━━━━━━━━━━\n" + "📥 Reply with 1-5 to download"; return api.sendMessage( msg, threadID, (err, info) => { if (err) { console.error( "SING SEARCH SEND ERROR:", err ); return; } if ( !global.GoatBot || !global.GoatBot.onReply ) { console.error( "global.GoatBot.onReply not available" ); return; } global.GoatBot.onReply.set( info.messageID, { commandName: this.config.name, messageID: info.messageID, author: senderID, results: results } ); }, messageID ); } catch (err) { console.error( "SING SEARCH ERROR:", err.response?.data || err.message ); return api.sendMessage( "⚠️ গান খুঁজতে সমস্যা হয়েছে।\n\n" + "🔄 কিছুক্ষণ পর আবার চেষ্টা করুন।", threadID, messageID ); } }, // ========================================= // REPLY HANDLER // ========================================= onReply: async function ({ api, event, Reply }) { const { threadID, messageID, body, senderID } = event; // অন্য কেউ reply করলে ignore if (senderID !== Reply.author) { return; } const index = parseInt(String(body).trim()) - 1; if ( isNaN(index) || index < 0 || index >= Reply.results.length ) { return api.sendMessage( "❌ Invalid choice.\n\n" + "Please reply with 1-5.", threadID, messageID ); } const selected = Reply.results[index]; // Search message remove try { await api.unsendMessage( Reply.messageID, threadID ); } catch {} return downloadAudio( api, threadID, messageID, selected.url, selected.duration ); } }; // ========================================= // DOWNLOAD AUDIO // ========================================= async function downloadAudio( api, threadID, messageID, url, duration = "N/A" ) { let waitMsg = null; let tempFilePath = null; try { // Processing message waitMsg = await api.sendMessage( "⏳ Processing Audio...\n" + "🎧 Please wait...", threadID ); // ======================================= // VEXO YOUTUBE MP3 // ======================================= const response = await axios.get( `${BASE_URL}/download/ytmp3`, { params: { url: url }, timeout: 120000, headers: { "User-Agent": "Mozilla/5.0", "Accept": "application/json" } } ); console.log( "SING DOWNLOAD RESPONSE:", JSON.stringify(response.data).slice( 0, 3000 ) ); const result = normalizeDownloadResult( response.data ); if (!result.url) { throw new Error( "No download URL returned by API" ); } // ======================================= // TEMP FILE // ======================================= tempFilePath = path.join( __dirname, `sing_${Date.now()}.mp3` ); // ======================================= // DOWNLOAD MP3 // ======================================= const audioResponse = await axios({ url: result.url, method: "GET", responseType: "stream", timeout: 180000, headers: { "User-Agent": "Mozilla/5.0" } }); await new Promise( (resolve, reject) => { const writer = fs.createWriteStream( tempFilePath ); audioResponse.data.pipe( writer ); writer.on( "finish", resolve ); writer.on( "error", reject ); audioResponse.data.on( "error", reject ); } ); // File check if ( !fs.existsSync( tempFilePath ) ) { throw new Error( "MP3 file was not created" ); } const fileSize = fs.statSync( tempFilePath ).size; if (fileSize < 1000) { throw new Error( "Downloaded MP3 is empty" ); } // Processing message delete try { if (waitMsg?.messageID) { await api.unsendMessage( waitMsg.messageID, threadID ); } } catch {} // ======================================= // SEND AUDIO // ======================================= return api.sendMessage( { body: `🎵 ${result.title || "Unknown Song"}\n` + `👤 ${result.author || "Unknown Artist"}\n` + `⏱️ ${duration}\n` + "━━━━━━━━━━━━━━━━━━\n" + "🎧 Enjoy your music!", attachment: createReadStream( tempFilePath ) }, threadID, function (err) { // Delete temp file try { if ( tempFilePath && fs.existsSync( tempFilePath ) ) { fs.unlinkSync( tempFilePath ); } } catch {} if (err) { console.error( "SING SEND ERROR:", err ); } }, messageID ); } catch (err) { console.error( "SING DOWNLOAD ERROR:", err.response?.data || err.message ); // Temp file cleanup try { if ( tempFilePath && fs.existsSync( tempFilePath ) ) { fs.unlinkSync( tempFilePath ); } } catch {} // Processing message delete try { if (waitMsg?.messageID) { await api.unsendMessage( waitMsg.messageID, threadID ); } } catch {} return api.sendMessage( "❌ Failed to download audio.\n\n" + "🔄 Please try another song.", threadID, messageID ); } } // ========================================= // NORMALIZE SEARCH RESULT // ========================================= function normalizeSearchResults(data) { let list = []; if (Array.isArray(data)) { list = data; } else if ( Array.isArray(data?.results) ) { list = data.results; } else if ( Array.isArray(data?.data) ) { list = data.data; } else if ( Array.isArray(data?.data?.results) ) { list = data.data.results; } else if ( Array.isArray(data?.result) ) { list = data.result; } else if ( Array.isArray(data?.items) ) { list = data.items; } return list .map((item) => { const url = item?.url || item?.videoUrl || item?.video_url || item?.link || ( item?.id?.videoId ? `https://www.youtube.com/watch?v=${item.id.videoId}` : null ) || ( item?.videoId ? `https://www.youtube.com/watch?v=${item.videoId}` : null ); const title = item?.title || item?.name || item?.snippet?.title || "Unknown Title"; const channel = item?.channel || item?.channelTitle || item?.author || item?.uploader || item?.snippet?.channelTitle || "Unknown"; const duration = item?.duration || item?.lengthText || item?.timestamp || "N/A"; const thumbnail = item?.thumbnail || item?.thumbnailUrl || item?.snippet?.thumbnails?.high?.url || item?.snippet?.thumbnails?.medium?.url || item?.snippet?.thumbnails?.default?.url || null; return { title, channel, duration, thumbnail, url }; }) .filter( item => item.url && /youtube\.com|youtu\.be/i.test( item.url ) ); } // ========================================= // NORMALIZE DOWNLOAD RESULT // ========================================= function normalizeDownloadResult(data) { const sources = [ data, data?.data, data?.result, data?.data?.result ]; let url = null; let title = null; let author = null; for ( const item of sources ) { if (!item) continue; if ( typeof item === "string" && /^https?:\/\//i.test(item) ) { url = item; break; } if ( typeof item !== "object" ) { continue; } url = url || item.downloadUrl || item.download_url || item.url || item.audioUrl || item.audio || item.link || item.file; title = title || item.title || item.name || item.song; author = author || item.author || item.artist || item.uploader || item.channel; if (url) break; } return { url, title, author }; }