Skip to content

Instantly share code, notes, and snippets.

@ntkhang03
Last active July 21, 2025 09:27
Show Gist options
  • Save ntkhang03/e3e2268f09ecd3d671b8baf703b5b180 to your computer and use it in GitHub Desktop.
Save ntkhang03/e3e2268f09ecd3d671b8baf703b5b180 to your computer and use it in GitHub Desktop.
GoatBot V3 Command
import {
BaseExecuteCommand,
CommandConfig,
ExecuteCommandContext,
HandleMessageContext
} from "./BaseExecuteCommand";
import { runCommand } from "@/utils/runCommand";
import fs from "fs";
import path from "path";
import os from "os";
import { v4 as uuidv4 } from "uuid";
import logger from "@/utils/logger";
import ThreadSettingService from "@/config/prisma/services/threadSetting.service";
import prisma from "@/config/prismaClient";
export default class Autodown implements BaseExecuteCommand {
config: CommandConfig = {
name: "autodown",
aliases: ["adown"],
role: "admin",
category: "utility",
cooldown: 5,
description: {
en: "Enable/disable auto-downloading YouTube videos/audio from URLs in messages",
vi: "Bật/tắt tự động tải video/âm thanh YouTube từ URL trong tin nhắn"
},
usage: {
en: [
"{pn} on [-v | -a]: Enable auto-download (video or audio)",
"{pn} off: Disable auto-download"
],
vi: [
"{pn} on [-v | -a]: Bật tự động tải (video hoặc âm thanh)",
"{pn} off: Tắt tự động tải"
]
},
example: {
en: ["{pn} on -v", "{pn} on -a", "{pn} off"],
vi: ["{pn} on -v", "{pn} on -a", "{pn} off"]
},
langs: {
en: {
alreadyEnabled: "❌ Auto-download is already enabled for {{type}}.",
enabled: "✅ Auto-download enabled for {{type}} in this thread.",
alreadyDisabled: "❌ Auto-download is already disabled.",
disabled: "✅ Auto-download disabled in this thread.",
invalidArgs: "❌ Please use 'on -v', 'on -a', or 'off'.",
downloading: "📥 Downloading {{type}}... Please wait.",
downloadFailed: "❌ Failed to download {{type}} ({{url}}).",
sendFailed: "❌ Failed to send file."
},
vi: {
alreadyEnabled: "❌ Tự động tải đã được bật cho {{type}}.",
enabled: "✅ Đã bật tự động tải {{type}} trong nhóm này.",
alreadyDisabled: "❌ Tự động tải đã được tắt.",
disabled: "✅ Đã tắt tự động tải trong nhóm này.",
invalidArgs: "❌ Vui lòng sử dụng 'on -v', 'on -a', hoặc 'off'.",
downloading: "📥 Đang tải {{type}}... Vui lòng chờ.",
downloadFailed: "❌ Tải {{type}} thất bại ({{url}}).",
sendFailed: "❌ Gửi file thất bại."
}
}
};
async execute({ args, message, getLang, event }: ExecuteCommandContext) {
const threadSetting = (
await ThreadSettingService.getOrCreate(
{
threadId: event.threadID,
commandName: this.config.name
},
{}
)
).settings as Record<string, any>;
const whereCondition = {
threadId_commandName: {
threadId: event.threadID,
commandName: this.config.name
}
};
if (args[0]?.toLowerCase() === "on") {
if (args.length < 2 || !["-v", "-a"].includes(args[1]?.toLowerCase())) {
return void message.send(getLang("invalidArgs"));
}
const type = args[1].toLowerCase() === "-v" ? "video" : "audio";
if (
threadSetting.autodown?.enabled &&
threadSetting.autodown?.type === type
) {
return void message.send(getLang("alreadyEnabled", { type }));
}
await prisma.threadSetting.update({
where: whereCondition,
data: {
settings: {
...threadSetting,
autodown: { enabled: true, type }
}
}
});
return void message.send(getLang("enabled", { type }));
} else if (args[0]?.toLowerCase() === "off") {
if (!threadSetting.autodown?.enabled) {
return void message.send(getLang("alreadyDisabled"));
}
await prisma.threadSetting.update({
where: whereCondition,
data: {
settings: {
...threadSetting,
autodown: { enabled: false, type: null }
}
}
});
return void message.send(getLang("disabled"));
} else {
return void message.send(getLang("invalidArgs"));
}
}
async handleMessage({ message, event, getLang }: HandleMessageContext) {
const threadSetting = (
await ThreadSettingService.getById({
threadId: event.threadID,
commandName: this.config.name
})
)?.settings as Record<string, any>;
const settings = threadSetting?.autodown;
if (!settings?.enabled || !settings.type) {
return;
}
const urls = await this.extractUrls(event.body);
for (const url of urls) {
await this.download({ message, getLang, url, type: settings.type });
}
}
private async extractUrls(text: string): Promise<string[]> {
const urlRegex = /https?:\/\/[^\s/$.?#].[^\s]*/g;
const urls_ = text.match(urlRegex) || [];
const urls = Array.from(new Set(urls_));
const validUrls: string[] = [];
await Promise.all(
urls.map(async (url: string) => {
try {
const result = await runCommand("yt-dlp", ["--simulate", url], {
timeout: 5000,
log: false
});
if (result.code === 0) {
validUrls.push(url);
} else {
logger.warn("AUTO DOWN", `Invalid URL ${url}`, result.stderr);
}
} catch (err) {
logger.error("AUTO DOWN", `Invalid URL ${url}:`, err);
}
})
);
return validUrls;
}
private async download({
message,
getLang,
url,
type
}: {
message: ExecuteCommandContext["message"];
getLang: ExecuteCommandContext["getLang"];
url: string;
type: "audio" | "video";
}) {
const maxSizeBytes = 25 * 1024 * 1024;
const tempPath = path.join(
os.tmpdir(),
`${uuidv4()}.${type === "audio" ? "mp3" : "mp4"}`
);
try {
// Step 1: Dump metadata
const infoResult = await runCommand("yt-dlp", ["--dump-json", url], {
timeout: 10000,
log: false
});
if (infoResult.code !== 0) {
throw new Error("Failed to get metadata");
}
const info = JSON.parse(infoResult.stdout);
const formats: any[] = info.formats || [];
const title = info.title || "unknown title";
// Step 2: Chọn định dạng phù hợp để tải
let selectedFormat: any = null;
if (type === "video") {
selectedFormat = formats
.filter(
(f) =>
f.filesize &&
f.filesize <= maxSizeBytes &&
f.vcodec !== "none" &&
f.acodec !== "none" &&
f.ext === "mp4"
)
.sort((a, b) => b.height - a.height)[0]; // ưu tiên chất lượng cao nhất trong giới hạn
} else {
selectedFormat = formats
.filter(
(f) =>
f.filesize &&
f.filesize <= maxSizeBytes &&
f.vcodec === "none" &&
["m4a", "mp3", "webm", "opus"].includes(f.ext)
)
.sort((a, b) => b.abr - a.abr)[0]; // bitrate cao nhất trong giới hạn
}
if (!selectedFormat) {
await message.send(
`❌ Không tìm thấy định dạng nào dưới 25MB để tải: ${url}`
);
return;
}
await message.send(getLang("downloading", { type }));
const ytArgs = [
"-f",
selectedFormat.format_id,
url,
"-o",
tempPath,
"--no-playlist",
"--no-progress"
];
const result = await runCommand("yt-dlp", ytArgs, {
timeout: 90_000,
log: false
});
if (result.code !== 0) {
throw new Error(result.stderr || "yt-dlp failed");
}
const stats = fs.statSync(tempPath);
if (stats.size > maxSizeBytes) {
fs.unlinkSync(tempPath);
await message.send(
`❌ File vượt quá giới hạn 25MB sau khi tải: ${(stats.size / 1048576).toFixed(2)}MB`
);
return;
}
// Gửi file kèm link chất lượng cao
let bestDownloadUrl = null;
const bestFormat = formats
.filter((f) => !!f.url && f.acodec !== "none")
.sort((a, b) => (b.filesize || 0) - (a.filesize || 0))[0];
if (bestFormat?.url) {
bestDownloadUrl = bestFormat.url;
}
await message.reply({
body: bestDownloadUrl
? `🎬 ${title}\n📎 Link chất lượng cao: ${bestDownloadUrl}`
: `🎬 ${title}`,
attachment: fs.createReadStream(tempPath)
});
fs.unlink(tempPath, (err) => {
if (err) {
logger.error(`Failed to delete temp file ${tempPath}:`, err);
}
});
} catch (err) {
logger.error(`${type} download error:`, err);
await message.send(
getLang("downloadFailed", {
type: type === "audio" ? "âm thanh" : "video",
url
})
);
if (fs.existsSync(tempPath)) {
fs.unlink(tempPath, (err) => {
if (err) {
logger.error(`Failed to delete temp file ${tempPath}:`, err);
}
});
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment