// code from goatstore // api: https://store.agi.bd const axios = require("axios"); const fs = require("fs-extra"); const path = require("path"); const http = axios.create({ timeout: 2000, maxRedirects: 5, headers: { "User-Agent": "Mozilla/5.0" } }); const BASE_API = "https://raw.githubusercontent.com/mahmudx7/HINATA/main/baseApiUrl.json"; const CACHE_DIR = path.join(__dirname, "cache"); const MAX_SIZE = 25 * 1024 * 1024; let cachedBaseURL = ""; let cachedAt = 0; async function getBaseURL() { if (cachedBaseURL && Date.now() - cachedAt < 300000) { return cachedBaseURL; } const { data } = await http.get(BASE_API); const url = data?.mahmud || data?.baseUrl || data?.url; if (!url) { throw new Error("API base URL পাওয়া যায়নি।"); } cachedBaseURL = String(url).replace(/\/+$/, ""); cachedAt = Date.now(); return cachedBaseURL; } function getError(error) { const data = error?.response?.data; if (typeof data === "string") { return data.slice(0, 200); } return ( data?.message || data?.error || error?.message || "Unknown error" ); } function extractResults(data) { const options = [ data, data?.results, data?.result, data?.data, data?.data?.results, data?.data?.result ]; return options.find(Array.isArray) || []; } function extractURL(data, depth = 0) { if (!data || typeof data !== "object" || depth > 5) { return null; } const keys = [ "download", "downloadUrl", "download_url", "downloadLink", "audio", "audioUrl", "audio_url", "directUrl", "url", "link" ]; for (const key of keys) { const value = data[key]; if ( typeof value === "string" && /^https?:\/\//i.test(value) ) { return value; } } for (const key of ["data", "result", "results"]) { if (data[key] && typeof data[key] === "object") { const url = extractURL(data[key], depth + 1); if (url) return url; } } return null; } function getVideoId(input) { try { const url = new URL( /^https?:\/\//i.test(input) ? input : `https://${input}` ); let id = null; if (url.hostname.endsWith("youtu.be")) { id = url.pathname.split("/").filter(Boolean)[0]; } else if ( /(^|\.)youtube\.com$/i.test(url.hostname) ) { id = url.searchParams.get("v") || url.pathname.match( /\/(?:shorts|embed|live)\/([^/?]+)/ )?.[1]; } return id && /^[a-zA-Z0-9_-]{11}$/.test(id) ? id : null; } catch { return null; } } function cleanTitle(title) { return String(title || "song") .replace(/[\\/:*?"<>|]/g, "") .replace(/\s+/g, " ") .trim() .slice(0, 70) || "song"; } function removeFile(file) { try { if (fs.existsSync(file)) { fs.removeSync(file); } } catch {} } async function downloadFile(url, filePath) { const response = await http.get(url, { responseType: "stream", timeout: 60000, maxContentLength: MAX_SIZE }); const contentType = response.headers["content-type"] || ""; if (/text\/html|application\/json/i.test(contentType)) { response.data.destroy(); throw new Error("API সরাসরি অডিও ফাইল দেয়নি।"); } const writer = fs.createWriteStream(filePath); let size = 0; await new Promise((resolve, reject) => { let done = false; const fail = (error) => { if (done) return; done = true; response.data.destroy(); writer.destroy(); removeFile(filePath); reject(error); }; response.data.on("data", (chunk) => { size += chunk.length; if (size > MAX_SIZE) { fail(new Error("Audio ফাইল 25 MB-এর বেশি।")); } }); response.data.on("error", fail); writer.on("error", fail); writer.on("finish", () => { if (done) return; done = true; if (!size) { removeFile(filePath); reject(new Error("Audio ফাইল খালি।")); return; } resolve(); }); response.data.pipe(writer); }); } module.exports = { config: { name: "sing", version: "1.0.0", author: "ASIF", countDown: 5, role: 0, description: { en: "Search and download a song directly", bn: "গান সার্চ করে সরাসরি ডাউনলোড" }, category: "music", guide: { en: "{pn} ", bn: "{pn} <গানের নাম অথবা YouTube URL>" } }, onStart: async function ({ message, args }) { const query = args.join(" ").trim(); if (!query) { return message.reply( "🎵 | গানের নাম লিখো!\n\nউদাহরণ: !sing Believer" ); } let loading; try { loading = await message.reply( "🔎 | 𝗦𝗢𝗡𝗚 𝗦𝗘𝗔𝗥𝗖\n" + "━━━━━━━━━━━━━━━━━━\n" + "⏳ | গান খোঁজা হচ্ছে..." ); const apiURL = await getBaseURL(); let videoId = getVideoId(query); let title = query; // Search if a song name was provided if (!videoId) { const { data } = await http.get( `${apiURL}/api/ytb/search`, { params: { q: query }, timeout: 20000 } ); const results = extractResults(data); if (!results.length) { throw new Error("কোনো গান পাওয়া যায়নি।"); } const item = results[0]; title = item?.title || item?.name || item?.videoTitle || query; videoId = item?.videoId || item?.videoID || item?.video_id || getVideoId( item?.url || item?.link || item?.videoUrl || "" ); if ( typeof videoId === "string" && !/^[a-zA-Z0-9_-]{11}$/.test(videoId) ) { videoId = getVideoId(videoId); } if (!videoId) { throw new Error( "প্রথম সার্চ রেজাল্ট থেকে YouTube ID পাওয়া যায়নি।" ); } } await message.reply( `🎵 | 𝗗𝗢𝗪𝗡𝗟𝗢𝗔𝗗𝗜𝗡𝗚\n` + `━━━━━━━━━━━━━━━━━━\n` + `🎧 | ${title}\n` + `⏳ | অডিও প্রস্তুত হচ্ছে...` ); const { data } = await http.get( `${apiURL}/api/ytb/get`, { params: { id: videoId, type: "audio" }, timeout: 30000 } ); const downloadURL = extractURL(data); if (!downloadURL) { throw new Error( "API response-এ সরাসরি audio URL পাওয়া যায়নি।" ); } await fs.ensureDir(CACHE_DIR); const filePath = path.join( CACHE_DIR, `${Date.now()}_${cleanTitle(title)}.mp3` ); try { await downloadFile(downloadURL, filePath); await message.reply({ body: `🎶 | 𝗦𝗢𝗡𝗚 𝗗𝗢𝗪𝗡𝗟𝗢𝗔𝗗𝗘𝗗\n` + `━━━━━━━━━━━━━━━━━━\n` + `🎧 | ${title}\n` + `👑 | Author: ASIF\n` + `✅ | Download Complete`, attachment: fs.createReadStream(filePath) }); } finally { setTimeout(() => removeFile(filePath), 15000); } } catch (error) { await message.reply( `❌ | গান ডাউনলোড করা যায়নি।\n\n` + `${getError(error)}` ); } finally { if (loading?.messageID) { try { await message.unsend(loading.messageID); } catch {} } } } };