goat
command
song
No description
0
views
0
likes
0
installs
raw source
const axios = require("axios");
const fs = require("fs-extra");
const path = require("path");
const http = axios.create({
timeout: 60000,
maxRedirects: 5,
headers: {
"User-Agent":
"Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 Chrome/120 Mobile Safari/537.36",
"Accept": "*/*"
}
});
const baseApiUrl = async () => {
const res = await http.get(
"https://raw.githubusercontent.com/mahmudx7/HINATA/main/baseApiUrl.json"
);
const url =
res.data?.mahmud ||
res.data?.baseUrl ||
res.data?.url;
if (!url) {
throw new Error("Base API URL not found");
}
return String(url).replace(/\/+$/, "");
};
const safeRemove = async file => {
try {
if (file && await fs.pathExists(file)) {
await fs.remove(file);
}
} catch (_) {}
};
const getApiError = error => {
const status = error.response?.status;
const data = error.response?.data;
if (status) {
if (typeof data === "string") {
return `HTTP ${status}: ${data.slice(0, 300)}`;
}
return (
`HTTP ${status}: ` +
(
data?.message ||
data?.error ||
data?.msg ||
"Server returned an error"
)
);
}
return error.message || "Unknown error";
};
module.exports = {
config: {
name: "song",
version: "3.0",
author: "ASIF",
countDown: 5,
role: 0,
description: {
bn: "গান সার্চ ও অডিও ডাউনলোড",
en: "Search and download songs/audio",
vi: "Tìm kiếm và tải nhạc"
},
category: "music",
guide: {
bn:
"{pn} [গানের নাম বা YouTube লিংক]\n" +
"উদাহরণ: {pn} tui chinli na amay",
en:
"{pn} [song name or YouTube link]\n" +
"Example: {pn} stay justin bieber",
vi:
"{pn} [tên bài hát hoặc link YouTube]"
}
},
langs: {
bn: {
error: "❌ | সমস্যা হয়েছে: %1",
noResult:
'⭕ | "%1" এর জন্য কোনো গান পাওয়া যায়নি।',
choose:
"🎵 গানগুলোর তালিকা:\n\n%1\n" +
"━━━━━━━━━━━━━━\n" +
"👉 Reply করে 1-6 এর মধ্যে একটি সংখ্যা দিন।",
processing:
"⏳ | গান প্রস্তুত করা হচ্ছে...",
apiError:
"❌ | API কোনো valid download URL দেয়নি।",
serverError:
"❌ | Song API Server Error: %1"
},
en: {
error: "❌ | An error occurred: %1",
noResult:
'⭕ | No songs found for "%1".',
choose:
"🎵 Song Results:\n\n%1\n" +
"━━━━━━━━━━━━━━\n" +
"👉 Reply with a number from 1-6.",
processing:
"⏳ | Preparing song...",
apiError:
"❌ | API did not return a valid download URL.",
serverError:
"❌ | Song API Server Error: %1"
}
},
onStart: async function ({
api,
args,
event,
commandName,
getLang
}) {
const {
threadID,
messageID,
senderID
} = event;
const input = args.join(" ").trim();
if (!input) {
return api.sendMessage(
"🎵 Please enter a song name or YouTube link.",
threadID,
messageID
);
}
let apiUrl;
try {
apiUrl = await baseApiUrl();
} catch (error) {
console.log(
"[SONG BASE API ERROR]",
error.message
);
return api.sendMessage(
getLang(
"error",
"Unable to connect to Base API."
),
threadID,
messageID
);
}
/*
* YouTube URL detector
*/
const checkurl =
/^(?:https?:\/\/)?(?:m\.|www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=|shorts\/))((\w|-){11})(?:\S+)?$/i;
/*
* Direct YouTube link
*/
if (checkurl.test(input)) {
const match = input.match(checkurl);
if (!match?.[1]) {
return api.sendMessage(
getLang(
"error",
"Invalid YouTube URL."
),
threadID,
messageID
);
}
api.setMessageReaction(
"⌛",
messageID,
() => {},
true
);
return handleDownload(
api,
threadID,
messageID,
match[1],
apiUrl,
getLang
);
}
/*
* Search song
*/
try {
api.setMessageReaction(
"🔎",
messageID,
() => {},
true
);
const searchUrl =
`${apiUrl}/api/ytb/search?q=${encodeURIComponent(input)}`;
console.log(
"[SONG SEARCH]",
searchUrl
);
const res = await http.get(searchUrl);
console.log(
"[SONG SEARCH STATUS]",
res.status
);
const rawResults =
res.data?.results ||
res.data?.data?.results ||
res.data?.result ||
[];
if (!Array.isArray(rawResults)) {
console.log(
"[SONG SEARCH INVALID RESPONSE]",
res.data
);
throw new Error(
"Invalid search API response"
);
}
const results = rawResults
.filter(item => {
return (
item &&
(
item.id ||
item.videoId ||
item.video_id
)
);
})
.slice(0, 6);
if (results.length === 0) {
return api.sendMessage(
getLang(
"noResult",
input
),
threadID,
messageID
);
}
let msg = "";
const attachments = [];
const cacheDir =
path.join(
__dirname,
"cache"
);
await fs.ensureDir(cacheDir);
for (
let i = 0;
i < results.length;
i++
) {
const item =
results[i];
const title =
item.title ||
item.name ||
"Unknown Song";
const time =
item.time ||
item.duration ||
item.length ||
"Unknown";
const thumbnail =
item.thumbnail ||
item.thumb ||
item.image ||
item.thumbnailUrl ||
null;
msg +=
`${i + 1}. ${title}\n` +
`⏱️ ${time}\n\n`;
/*
* Thumbnail is optional.
* If it fails, song search continues.
*/
if (thumbnail) {
try {
const thumbPath =
path.join(
cacheDir,
`thumb_${senderID}_${Date.now()}_${i}.jpg`
);
const thumb =
await http.get(
thumbnail,
{
responseType:
"arraybuffer",
timeout:
15000
}
);
await fs.writeFile(
thumbPath,
Buffer.from(
thumb.data
)
);
attachments.push(
fs.createReadStream(
thumbPath
)
);
} catch (thumbError) {
console.log(
"[THUMBNAIL ERROR]",
thumbError.message
);
}
}
}
return api.sendMessage(
{
body:
getLang(
"choose",
msg
),
...(attachments.length
? {
attachment:
attachments
}
: {})
},
threadID,
(err, info) => {
/*
* Remove thumbnails
*/
for (
const stream
of attachments
) {
try {
if (
stream.path &&
fs.existsSync(
stream.path
)
) {
fs.unlinkSync(
stream.path
);
}
} catch (_) {}
}
if (
!err &&
info &&
global.GoatBot?.onReply
) {
global.GoatBot.onReply.set(
info.messageID,
{
commandName,
author:
senderID,
results,
apiUrl,
messageID:
info.messageID
}
);
}
},
messageID
);
} catch (error) {
console.log(
"========== SONG SEARCH ERROR =========="
);
console.log(
"Status:",
error.response?.status
);
console.log(
"URL:",
error.config?.url
);
console.log(
"Response:",
error.response?.data
);
console.log(
"Message:",
error.message
);
console.log(
"======================================="
);
const status =
error.response?.status;
if (status >= 500) {
return api.sendMessage(
getLang(
"serverError",
`HTTP ${status}`
),
threadID,
messageID
);
}
return api.sendMessage(
getLang(
"error",
getApiError(error)
),
threadID,
messageID
);
}
},
onReply: async function ({
event,
api,
Reply,
getLang
}) {
const {
results,
apiUrl,
author,
messageID
} = Reply;
if (
String(event.senderID) !==
String(author)
) {
return;
}
const choice =
parseInt(
String(
event.body
).trim()
);
if (
Number.isNaN(choice) ||
choice < 1 ||
choice > results.length
) {
return api.sendMessage(
`❌ Please choose a number between 1 and ${results.length}.`,
event.threadID,
event.messageID
);
}
const selected =
results[choice - 1];
const videoID =
selected.id ||
selected.videoId ||
selected.video_id;
if (!videoID) {
return api.sendMessage(
getLang(
"error",
"Video ID not found."
),
event.threadID,
event.messageID
);
}
try {
if (messageID) {
api.unsendMessage(
messageID
);
}
} catch (_) {}
api.setMessageReaction(
"⌛",
event.messageID,
() => {},
true
);
return handleDownload(
api,
event.threadID,
event.messageID,
videoID,
apiUrl,
getLang
);
}
};
/*
* ============================
* DOWNLOAD FUNCTION
* ============================
*/
async function handleDownload(
api,
threadID,
messageID,
videoID,
apiUrl,
getLang
) {
const cacheDir =
path.join(
__dirname,
"cache"
);
await fs.ensureDir(
cacheDir
);
const filePath =
path.join(
cacheDir,
`song_${Date.now()}_${Math.random()
.toString(36)
.slice(2)}.mp3`
);
try {
api.setMessageReaction(
"⏳",
messageID,
() => {},
true
);
/*
* Request audio information
*/
const getUrl =
`${apiUrl}/api/ytb/get?id=${encodeURIComponent(videoID)}&type=audio`;
console.log(
"[SONG DOWNLOAD API]",
getUrl
);
const res =
await http.get(
getUrl
);
console.log(
"[SONG DOWNLOAD STATUS]",
res.status
);
console.log(
"[SONG DOWNLOAD RESPONSE]",
JSON.stringify(
res.data,
null,
2
).slice(0, 5000)
);
/*
* Find data object
*/
const data =
res.data?.data ||
res.data?.result ||
res.data;
/*
* Find title
*/
const title =
data?.title ||
data?.name ||
data?.songName ||
`Song_${videoID}`;
/*
* Find download URL
*/
const downloadLink =
data?.downloadLink ||
data?.downloadUrl ||
data?.download_url ||
data?.audioUrl ||
data?.audio_url ||
data?.url ||
res.data?.downloadLink ||
res.data?.downloadUrl ||
res.data?.url;
/*
* No URL
*/
if (
!downloadLink ||
typeof downloadLink !==
"string"
) {
console.log(
"[SONG ERROR] API response has no download URL."
);
throw new Error(
"Unable to generate a download URL"
);
}
/*
* Validate URL
*/
if (
!/^https?:\/\//i.test(
downloadLink
)
) {
throw new Error(
"API returned an invalid download URL"
);
}
console.log(
"[SONG DOWNLOAD URL]",
downloadLink
);
/*
* Download audio
*/
const response =
await http.get(
downloadLink,
{
responseType:
"stream",
timeout:
180000,
validateStatus:
status =>
status >= 200 &&
status < 400
}
);
const writer =
fs.createWriteStream(
filePath
);
response.data.pipe(
writer
);
await new Promise(
(resolve, reject) => {
writer.on(
"finish",
resolve
);
writer.on(
"error",
reject
);
response.data.on(
"error",
reject
);
}
);
/*
* Check downloaded file
*/
if (
!(await fs.pathExists(
filePath
))
) {
throw new Error(
"Audio file was not created"
);
}
const stat =
await fs.stat(
filePath
);
if (
!stat.size ||
stat.size < 1000
) {
throw new Error(
"Downloaded audio file is empty or invalid"
);
}
/*
* Send song
*/
return api.sendMessage(
{
body:
getLang(
"success",
title
),
attachment:
fs.createReadStream(
filePath
)
},
threadID,
async () => {
api.setMessageReaction(
"✅",
messageID,
() => {},
true
);
/*
* Give Messenger time
* to finish reading file
*/
setTimeout(
async () => {
await safeRemove(
filePath
);
},
10000
);
},
messageID
);
} catch (error) {
console.log(
"========== SONG DOWNLOAD ERROR =========="
);
console.log(
"Status:",
error.response?.status
);
console.log(
"URL:",
error.config?.url
);
console.log(
"Response:",
error.response?.data
);
console.log(
"Message:",
error.message
);
console.log(
"=========================================="
);
await safeRemove(
filePath
);
api.setMessageReaction(
"❌",
messageID,
() => {},
true
);
const status =
error.response?.status;
if (status >= 500) {
return api.sendMessage(
getLang(
"serverError",
`HTTP ${status}`
),
threadID,
messageID
);
}
return api.sendMessage(
getLang(
"error",
error.message ||
"Download failed"
),
threadID,
messageID
);
}
}