Files
fymious_tg_bot/tg_filebrowser_bot.js
2025-10-01 22:50:58 +03:00

109 lines
3.8 KiB
JavaScript

require("dotenv").config();
const { Telegraf } = require("telegraf");
const fs = require("fs").promises;
const path = require("path");
const stream = require("stream");
const { pipeline } = require("stream/promises");
const BOT_TOKEN = process.env.BOT_TOKEN;
const BASE_DIR = process.env.BASE_DIR || "/srv/files";
const ALLOWED = (process.env.ALLOWED_TELEGRAM_IDS || "")
.split(",")
.filter(Boolean)
.map((x) => Number(x));
if (!BOT_TOKEN) {
console.error("Missing BOT_TOKEN in .env");
process.exit(1);
}
const bot = new Telegraf(BOT_TOKEN);
function allowed(ctx) {
const id = ctx.from && ctx.from.id;
if (!id) return false;
if (ALLOWED.length === 0) return true;
return ALLOWED.includes(id);
}
function safeJoin(base, target) {
const resolved = path.resolve(base, "." + path.sep + target);
if (!resolved.startsWith(path.resolve(base)))
throw new Error("Access denied");
return resolved;
}
bot.use(async (ctx, next) => {
try {
if (!allowed(ctx)) return ctx.reply("Access denied");
return await next();
} catch (err) {
console.error(err);
try {
await ctx.reply("Error: " + String(err.message));
} catch (e) {}
}
});
bot.start((ctx) =>
ctx.reply("File manager bot ready. Use /help to list commands"),
);
bot.help((ctx) =>
ctx.reply(
"/ls [path]\n/get <path>\n/put (send file with caption: /put <path>)\n/mv <src> <dst>\n/mkdir <path>\n/rm <path>",
),
);
bot.command("ls", async (ctx) => {
const arg = ctx.message.text.split(" ").slice(1).join(" ") || ".";
const dir = safeJoin(BASE_DIR, arg);
const entries = await fs.readdir(dir, { withFileTypes: true });
const lines = entries.map((e) => `${e.isDirectory() ? "d" : "-"}\t${e.name}`);
await ctx.replyWithMarkdownV2("`" + lines.join("\n") + "`");
});
bot.command("mkdir", async (ctx) => {
const arg = ctx.message.text.split(" ").slice(1).join(" ");
if (!arg) return ctx.reply("Usage: /mkdir <path>");
const target = safeJoin(BASE_DIR, arg);
await fs.mkdir(target, { recursive: true });
await ctx.reply("Created " + arg);
});
bot.command("mv", async (ctx) => {
const parts = ctx.message.text.split(" ").slice(1);
if (parts.length < 2) return ctx.reply("Usage: /mv <src> <dst>");
const src = safeJoin(BASE_DIR, parts[0]);
const dst = safeJoin(BASE_DIR, parts[1]);
await fs.rename(src, dst);
await ctx.reply("Moved");
});
bot.command("rm", async (ctx) => {
const arg = ctx.message.text.split(" ").slice(1).join(" ");
if (!arg) return ctx.reply("Usage: /rm <path>");
const target = safeJoin(BASE_DIR, arg);
const stat = await fs.lstat(target);
if (stat.isDirectory()) await fs.rmdir(target, { recursive: true });
else await fs.unlink(target);
await ctx.reply("Removed " + arg);
});
bot.command("get", async (ctx) => {
const arg = ctx.message.text.split(" ").slice(1).join(" ");
if (!arg) return ctx.reply("Usage: /get <path>");
const target = safeJoin(BASE_DIR, arg);
await ctx.replyWithDocument({
source: target,
filename: path.basename(target),
});
});
bot.on("document", async (ctx) => {
const caption = (ctx.message.caption || "").trim();
let destPath = ".";
if (caption.startsWith("/put"))
destPath = caption.split(" ").slice(1).join(" ") || ".";
const fileId = ctx.message.document.file_id;
const fileName = ctx.message.document.file_name || "file";
const filePath = safeJoin(BASE_DIR, path.join(destPath, fileName));
const fileLink = await ctx.telegram.getFileLink(fileId);
const res = await fetch(fileLink.href);
if (!res.ok) throw new Error("download failed");
await fs.mkdir(path.dirname(filePath), { recursive: true });
const dest = await fs.open(filePath, "w");
await pipeline(res.body, dest.createWriteStream());
await dest.close();
await ctx.reply("Saved to " + path.relative(BASE_DIR, filePath));
});
bot.launch();
process.once("SIGINT", () => bot.stop("SIGINT"));
process.once("SIGTERM", () => bot.stop("SIGTERM"));