@wasserstoff/mangi-tg-bot
Version:
A powerful Telegram Bot SDK with built-in authentication, session management, and database integration
342 lines • 14.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BotManager = void 0;
const grammy_1 = require("grammy");
const logger_1 = require("../logger");
const _1 = require(".");
const auth_1 = require("./middlewares/auth");
class BotManager {
bot;
config;
composer;
registeredCommands = [];
sdkLogger;
messageHandlers = [];
callbackHandlers = [];
messageHandlersWithAuth = [];
callbackHandlersWithAuth = [];
constructor(botToken, redisInstance, config) {
this.bot = (0, _1.createBot)(botToken, redisInstance, config);
this.composer = new grammy_1.Composer();
this.config = config;
this.sdkLogger = (0, logger_1.createSdkLogger)(config.isDev);
this.bot.use(this.composer);
// Register a single message dispatcher (non-auth)
this.composer.on("message", async (ctx, next) => {
// If this is a command, skip custom message handlers
if (ctx.message && ctx.message.text && ctx.message.text.startsWith("/")) {
// Let .command() handlers run
return await next();
}
for (const { filter, handler } of this.messageHandlers) {
if (filter(ctx)) {
if (this.config.isDev) {
this.sdkLogger.info(`Message handler matched and executed`);
}
try {
await handler(ctx);
}
catch (error) {
if (this.config.isDev) {
this.sdkLogger.error("Error handling message:", error);
}
await ctx.reply("Sorry, there was an error processing your message.");
}
return; // Only the first matching handler runs
}
}
await next();
});
// Register a single callback dispatcher (non-auth)
this.composer.on("callback_query", async (ctx, next) => {
for (const { filter, handler } of this.callbackHandlers) {
if (filter(ctx)) {
if (this.config.isDev) {
this.sdkLogger.info(`Callback handler matched and executed`);
}
try {
// Answer the callback query immediately to prevent timeout
await ctx.answerCallbackQuery();
// Now execute the handler function
await handler(ctx);
}
catch (error) {
if (this.config.isDev) {
this.sdkLogger.error("Error handling callback query:", error);
}
// Only try to answer callback query if it hasn't been answered yet
try {
await ctx.answerCallbackQuery({
text: "Error processing callback query",
});
}
catch (callbackError) {
// Ignore callback query errors as they might be due to timeout
if (this.config.isDev) {
this.sdkLogger.error("Error answering callback query:", callbackError);
}
}
}
return; // Only the first matching handler runs
}
}
await next();
});
// Register a single message dispatcher (auth)
this.composer.on("message", async (ctx, next) => {
if (!this.config.jwtSecret)
return await next();
// If this is a command, skip custom message handlers
if (ctx.message && ctx.message.text && ctx.message.text.startsWith("/")) {
return await next();
}
const authMiddleware = (0, auth_1.createAuthMiddleware)(this.config.jwtSecret);
for (const { filter, handler } of this.messageHandlersWithAuth) {
if (filter(ctx)) {
if (this.config.isDev) {
this.sdkLogger.info(`Auth message handler matched and executed`);
}
try {
await authMiddleware(ctx, async () => handler(ctx));
}
catch (error) {
if (this.config.isDev) {
this.sdkLogger.error("Error handling auth message:", error);
}
await ctx.reply("Sorry, there was an error processing your message with auth.");
}
return;
}
}
await next();
});
// Register a single callback dispatcher (auth)
this.composer.on("callback_query", async (ctx, next) => {
if (!this.config.jwtSecret)
return await next();
const authMiddleware = (0, auth_1.createAuthMiddleware)(this.config.jwtSecret);
for (const { filter, handler } of this.callbackHandlersWithAuth) {
if (filter(ctx)) {
if (this.config.isDev) {
this.sdkLogger.info(`Auth callback handler matched and executed`);
}
try {
// Answer the callback query immediately to prevent timeout
await ctx.answerCallbackQuery();
// Now execute the auth middleware and handler function
await authMiddleware(ctx, async () => handler(ctx));
}
catch (error) {
if (this.config.isDev) {
this.sdkLogger.error("Error handling auth callback:", error);
}
// Only try to answer callback query if it hasn't been answered yet
try {
await ctx.answerCallbackQuery({ text: "Error processing auth callback" });
}
catch (callbackError) {
// Ignore callback query errors as they might be due to timeout
if (this.config.isDev) {
this.sdkLogger.error("Error answering auth callback query:", callbackError);
}
}
}
return;
}
}
await next();
});
}
handleCommand(command, handler, message, buttons) {
if (this.config.isDev) {
this.sdkLogger.info(`Registering command /${command}`);
}
// Only register command once.
if (!this.registeredCommands.some((cmd) => cmd.command === command)) {
this.registeredCommands.push({
command: command,
description: `Execute /${command} command`,
});
}
this.composer.command(command, async (ctx) => {
try {
await handler(ctx);
if (message) {
await ctx.reply(message, {
parse_mode: "HTML",
reply_markup: buttons ? { inline_keyboard: buttons } : undefined,
});
}
if (this.config.isDev) {
this.sdkLogger.info(`Command /${command} executed successfully`);
}
}
catch (error) {
if (this.config.isDev) {
this.sdkLogger.error(`Error executing command /${command}:`, error);
}
await ctx.reply("Sorry, there was an error executing this command.");
}
});
}
async setMyCommands(commands) {
try {
await this.bot.api.setMyCommands(commands);
}
catch (error) {
if (this.config.isDev) {
this.sdkLogger.error("Error setting command menu:", error);
}
}
}
handleCallback(filter, handler) {
if (this.config.isDev) {
this.sdkLogger.info(`Registering callback handler`);
}
this.callbackHandlers.push({ filter, handler });
}
handleMessage(filter, handler) {
if (this.config.isDev) {
this.sdkLogger.info(`Registering message handler`);
}
this.messageHandlers.push({ filter, handler });
}
async start() {
try {
if (this.config.botMode === "webhook") {
if (this.config.isDev) {
this.sdkLogger.info("Starting bot in webhook mode...");
}
await this.bot.init();
await this.bot.api.setWebhook(this.config.botWebhookUrl, {
allowed_updates: this.config.botAllowedUpdates,
});
}
else if (this.config.botMode === "polling") {
if (this.config.isDev) {
this.sdkLogger.info("Starting bot in polling mode...");
}
await new Promise((resolve, reject) => {
this.bot
.start({
allowed_updates: this.config.botAllowedUpdates,
onStart: ({ username }) => {
if (this.config.isDev) {
this.sdkLogger.info("Bot running...", { username });
}
resolve();
},
})
.catch((error) => {
if (this.config.isDev) {
this.sdkLogger.error("Error starting bot:", error);
}
reject(error);
});
});
}
}
catch (error) {
if (this.config.isDev) {
this.sdkLogger.error("Error starting BotManager:", error);
}
throw error;
}
}
async stop() {
try {
await this.bot.stop();
if (this.config.isDev) {
this.sdkLogger.info("Bot stopped successfully");
}
}
catch (error) {
if (this.config.isDev) {
this.sdkLogger.error("Error stopping bot:", error);
}
throw error;
}
}
getBot() {
return this.bot;
}
handleCommandWithAuth(command, handler, message, buttons) {
if (!this.config.jwtSecret) {
if (this.config.isDev) {
this.sdkLogger.error("JWT secret not configured for authentication. Cannot register auth command.");
}
return;
}
if (this.config.isDev) {
this.sdkLogger.info(`Registering command with auth /${command}`);
}
if (!this.registeredCommands.some(cmd => cmd.command === command)) {
this.registeredCommands.push({
command: command,
description: `Execute /${command} command with authentication`,
});
}
const authMiddleware = (0, auth_1.createAuthMiddleware)(this.config.jwtSecret);
this.composer.command(command, async (ctx) => {
await authMiddleware(ctx, async () => Promise.resolve());
try {
await handler(ctx);
if (message) {
await ctx.reply(message, {
parse_mode: "HTML",
reply_markup: buttons ? { inline_keyboard: buttons } : undefined,
});
}
if (this.config.isDev) {
this.sdkLogger.info(`Auth command /${command} executed successfully`);
}
}
catch (error) {
if (this.config.isDev) {
this.sdkLogger.error(`Error executing auth command /${command}:`, error);
}
await ctx.reply("Sorry, there was an error executing this command with auth.");
}
});
}
handleCallbackWithAuth(filter, handler) {
if (!this.config.jwtSecret) {
if (this.config.isDev) {
this.sdkLogger.error("JWT secret not configured for authentication. Cannot register auth callback.");
}
return;
}
if (this.config.isDev) {
this.sdkLogger.info(`Registering auth callback handler`);
}
this.callbackHandlersWithAuth.push({ filter, handler });
}
handleMessageWithAuth(filter, handler) {
if (!this.config.jwtSecret) {
if (this.config.isDev) {
this.sdkLogger.error("JWT secret not configured for authentication. Cannot register auth message handler.");
}
return;
}
if (this.config.isDev) {
this.sdkLogger.info(`Registering auth message handler`);
}
this.messageHandlersWithAuth.push({ filter, handler });
}
/**
* Register a handler for any event type (e.g., 'chat_member', 'my_chat_member', etc.)
* This is the recommended way to add group or channel event listeners.
*
* @param event - grammY filter query string (e.g. 'message', 'chat_member', 'message:text', etc.)
* Type-safe and autocompleted in editors.
* @param handler - async function to handle the event
*/
handleEvent(event, handler) {
if (this.config.isDev) {
this.sdkLogger.info(`Registering handler for event: ${event}`);
}
this.composer.on(event, handler);
}
}
exports.BotManager = BotManager;
//# sourceMappingURL=BotManager.js.map