@wasserstoff/mangi-tg-bot
TypeScript icon, indicating that this package has built-in type declarations

1.2.15 • Public • Published

@wasserstoff/mangi-tg-bot SDK

A powerful, flexible, and modern Telegram Bot SDK built with TypeScript. This SDK provides:

  • JWT authentication (fully or partially enforced)
  • Admin approval/authentication (for public or semi-public bots)
  • Full session management (CRUD helpers for custom variables)
  • Easy integration with Redis for session and approval state
  • Modern, type-safe API and middleware support
  • Professional Logging System (development and production-ready)
  • Robust Callback Query Handling:
    • Automatic callback query timeout prevention
    • Built-in timeout protection for long-running operations
    • Graceful error handling that prevents bot crashes
    • Support for image generation and other time-consuming tasks

🚀 Features

  • 🛡️ JWT Authentication: Secure your bot with JWT tokens. Enforce authentication on all routes (fully) or only on selected routes (partially).
  • 👥 Admin Approval Layer: Add an extra layer of admin approval for new users. Great for public or semi-public bots, clubs, or organizations.
  • 🗃️ Session CRUD Helpers: Easily manage custom session variables for each user, with built-in helpers for set/get/update/delete.
  • 💾 Redis-backed Session & Approval: All session and approval state is stored in Redis for performance and reliability.
  • 📝 Type-safe, Modern API: Built with TypeScript, with clear types and extensibility.
  • 📝 Professional Logging System:
    • Colorized, detailed logs in development
    • Optimized, minimal logs in production
    • Automatic context logging
    • Multiple log levels (debug, info, warn, error)
    • Timestamp and request tracking
    • Built with Pino for performance

📋 Prerequisites

  • Node.js (v14 or higher)
  • Redis
  • Telegram Bot Token (from @BotFather)

🛠️ Installation

npm install @wasserstoff/mangi-tg-bot

📖 Usage Examples

1. Basic Bot with JWT Authentication

import { Bot, AppConfig, CustomContext, logger } from '@wasserstoff/mangi-tg-bot';

const configWithJwtAuth: AppConfig = {
  botToken: 'YOUR_BOT_TOKEN',
  botMode: 'polling',
  botAllowedUpdates: ['message', 'callback_query'],
  redisUrl: 'YOUR_REDIS_URL',
  isDev: true,
  useAuth: 'fully', // All routes require JWT authentication
  jwtSecret: 'your_jwt_secret_here',
};

async function createJwtAuthBot() {
  logger.info('Starting bot with JWT authentication:', configWithJwtAuth);
  const bot = new Bot(configWithJwtAuth);
  await bot.initialize();
  const botManager = bot.getBotManager();

  botManager.handleCommand('start', async (ctx: CustomContext) => {
    await ctx.api.sendMessage(
      ctx.chat.id,
      'Welcome! You are authenticated with JWT.'
    );
  });

  botManager.handleCommand('whoami', async (ctx: CustomContext) => {
    await ctx.api.sendMessage(
      ctx.chat.id,
      `Your chat ID: <code>${ctx.from.id}</code>`,
      { parse_mode: 'HTML' }
    );
  });
}

createJwtAuthBot().catch(console.error);

2. Bot with Admin Authentication/Approval

import { Bot, AppConfig, CustomContext, logger } from '@wasserstoff/mangi-tg-bot';

const configWithAdminAuth: AppConfig = {
  botToken: 'YOUR_BOT_TOKEN',
  botMode: 'polling',
  botAllowedUpdates: ['message', 'callback_query'],
  redisUrl: 'YOUR_REDIS_URL',
  isDev: true,
  useAuth: 'none',
  adminAuthentication: true, // Enable admin approval system
  adminChatIds: [123456789, 987654321], // Replace with your admin Telegram chat IDs
};

async function createAdminAuthBot() {
  logger.info('Starting bot with admin authentication:', configWithAdminAuth);
  const bot = new Bot(configWithAdminAuth);
  await bot.initialize();
  const botManager = bot.getBotManager();

  botManager.handleCommand('start', async (ctx: CustomContext) => {
    await ctx.api.sendMessage(
      ctx.chat.id,
      'Welcome! If you see this, you are approved by an admin.'
    );
  });

  botManager.handleCommand('whoami', async (ctx: CustomContext) => {
    await ctx.api.sendMessage(
      ctx.chat.id,
      `Your chat ID: <code>${ctx.from.id}</code>`,
      { parse_mode: 'HTML' }
    );
  });

  botManager.handleCommand('secret', async (ctx: CustomContext) => {
    await ctx.api.sendMessage(
      ctx.chat.id,
      'This is a secret command only for approved users!'
    );
  });
}

createAdminAuthBot().catch(console.error);

3. Bot with Session CRUD Operations

import { Bot, AppConfig, CustomContext, logger } from '@wasserstoff/mangi-tg-bot';

const configWithSessionCrud: AppConfig = {
  botToken: 'YOUR_BOT_TOKEN',
  botMode: 'polling',
  botAllowedUpdates: ['message', 'callback_query'],
  redisUrl: 'YOUR_REDIS_URL',
  isDev: true,
  useAuth: 'none',
};

async function createSessionCrudBot() {
  logger.info('Starting bot with session CRUD example:', configWithSessionCrud);
  const bot = new Bot(configWithSessionCrud);
  await bot.initialize();
  const botManager = bot.getBotManager();

  botManager.handleCommand('setvar', async (ctx: CustomContext) => {
    ctx.session.setCustom('foo', 'bar');
    const foo = ctx.session.getCustom('foo');
    ctx.session.updateCustom({ hello: 'world', count: 1 });
    ctx.session.deleteCustom('count');
    await ctx.api.sendMessage(
      ctx.chat.id,
      `Session custom variable 'foo' set to '${foo}'. Updated and deleted 'count'.`
    );
  });

  botManager.handleCommand('getvar', async (ctx: CustomContext) => {
    const foo = ctx.session.getCustom('foo');
    await ctx.api.sendMessage(ctx.chat.id, `Current value of 'foo': ${foo}`);
  });
}

createSessionCrudBot().catch(console.error);

4. Combined Example: JWT Auth + Admin Auth + Session CRUD

import { Bot, AppConfig, CustomContext, logger } from '@wasserstoff/mangi-tg-bot';

const configCombined: AppConfig = {
  botToken: 'YOUR_BOT_TOKEN',
  botMode: 'polling',
  botAllowedUpdates: ['message', 'callback_query'],
  redisUrl: 'YOUR_REDIS_URL',
  isDev: true,
  useAuth: 'fully', // JWT auth required for all routes
  jwtSecret: 'your_jwt_secret_here',
  adminAuthentication: true, // Enable admin approval system
  adminChatIds: [123456789], // Replace with your admin Telegram chat IDs
};

async function createCombinedBot() {
  logger.info(
    'Starting combined bot with JWT, admin auth, and session CRUD:',
    configCombined
  );
  const bot = new Bot(configCombined);
  await bot.initialize();
  const botManager = bot.getBotManager();

  // Set up command menu
  botManager.setMyCommands([
    { command: 'start', description: 'Start the bot' },
    { command: 'whoami', description: 'Get your chat ID' },
    { command: 'setvar', description: 'Set session variables' },
  ]);

  // Only accessible if JWT is valid AND user is approved by admin
  botManager.handleCommand('start', async (ctx: CustomContext) => {
    await ctx.api.sendMessage(
      ctx.chat.id,
      'Welcome! You are authenticated and approved by an admin.'
    );
  });

  // Session CRUD helpers
  botManager.handleCommand('setvar', async (ctx: CustomContext) => {
    ctx.session.setCustom('foo', 'bar');
    const foo = ctx.session.getCustom('foo');
    ctx.session.updateCustom({ hello: 'world', count: 1 });
    ctx.session.deleteCustom('count');
    await ctx.api.sendMessage(
      ctx.chat.id,
      `Session custom variable 'foo' set to '${foo}'. Updated and deleted 'count'.`
    );
  });

  botManager.handleCommand('getvar', async (ctx: CustomContext) => {
    const foo = ctx.session.getCustom('foo');
    await ctx.api.sendMessage(ctx.chat.id, `Current value of 'foo': ${foo}`);
  });

  // Show user their chat ID (useful for admin setup)
  botManager.handleCommand('whoami', async (ctx: CustomContext) => {
    await ctx.api.sendMessage(
      ctx.chat.id,
      `Your chat ID: <code>${ctx.from.id}</code>`,
      { parse_mode: 'HTML' }
    );
  });

  // Example: Only approved users with valid JWT can access this command
  botManager.handleCommand('secret', async (ctx: CustomContext) => {
    await ctx.api.sendMessage(
      ctx.chat.id,
      'This is a secret command only for authenticated and approved users!'
    );
  });
}

createCombinedBot().catch(console.error);

🔧 Callback Query Handling & Long-Running Operations

The SDK now includes robust handling for callback queries and long-running operations. Here are the key improvements:

✅ Automatic Timeout Prevention

Callback queries are automatically answered immediately when received, preventing Telegram's 30-second timeout:

botManager.handleCallback((ctx) => ctx.callbackQuery.data === "generate_image", async (ctx: CustomContext) => {
  try {
    // Send a "processing" message first
    const processingMsg = await ctx.api.sendMessage(
      ctx.chat.id,
      "🔄 Generating your image... Please wait."
    );

    // Your long-running operation (e.g., image generation API call)
    const imageUrl = await generateImageFromAPI(prompt);
    
    // Send the result
    await ctx.api.sendPhoto(ctx.chat.id, imageUrl, {
      caption: "Here's your generated image! 🎨"
    });

    // Clean up processing message
    await ctx.api.deleteMessage(ctx.chat.id, processingMsg.message_id);
  } catch (error) {
    await ctx.api.sendMessage(
      ctx.chat.id,
      "❌ Sorry, there was an error. Please try again."
    );
  }
});

⏱️ Built-in Timeout Protection

All handlers have built-in timeout protection:

  • Callback queries: 25 seconds
  • Commands: 30 seconds
  • Messages: 30 seconds

🛡️ Error Handling

The SDK includes comprehensive error handling that prevents bot crashes:

// Errors are automatically caught and logged
// Users receive appropriate error messages
// The bot continues running even if individual operations fail

📋 Best Practices for Long-Running Operations

  1. Always answer callback queries immediately (handled automatically by the SDK)
  2. Send a processing message to keep users informed
  3. Use try-catch blocks for error handling
  4. Clean up temporary messages after completion
  5. Provide retry options when operations fail

🎨 Example: Image Generation Bot

botManager.handleMessage((ctx) => ctx.message.text === "generate", async (ctx: CustomContext) => {
  await ctx.api.sendMessage(
    ctx.chat.id,
    "🎨 Image Generation Demo\n\nClick the button below to generate an image:",
    {
      reply_markup: {
        inline_keyboard: [[{ text: "🖼️ Generate Image", callback_data: "generate_image" }]]
      }
    }
  );
});

botManager.handleCallback((ctx) => ctx.callbackQuery.data === "generate_image", async (ctx: CustomContext) => {
  try {
    // Send processing message
    const processingMsg = await ctx.api.sendMessage(
      ctx.chat.id,
      "🔄 Generating your image... Please wait."
    );

    // Simulate long-running operation
    await new Promise(resolve => setTimeout(resolve, 5000));

    // Send result
    await ctx.api.sendPhoto(
      ctx.chat.id,
      "https://via.placeholder.com/400x300/FF0000/FFFFFF?text=Generated+Image",
      {
        caption: "Here's your generated image! 🎨",
        reply_markup: {
          inline_keyboard: [[
            { text: "Generate Another", callback_data: "generate_image" },
            { text: "Done", callback_data: "done" }
          ]]
        }
      }
    );

    // Clean up
    await ctx.api.deleteMessage(ctx.chat.id, processingMsg.message_id);
  } catch (error) {
    await ctx.api.sendMessage(
      ctx.chat.id,
      "❌ Sorry, there was an error generating your image. Please try again.",
      {
        reply_markup: {
          inline_keyboard: [[{ text: "Try Again", callback_data: "generate_image" }]]
        }
      }
    );
  }
});

🗃️ Session Management (CRUD Helpers)

The SDK provides easy CRUD helpers for managing session variables in ctx.session.custom.

Session CRUD API

  • ctx.session.setCustom(key, value) — Set a variable in session.custom
  • ctx.session.getCustom(key) — Get a variable from session.custom
  • ctx.session.updateCustom({ ... }) — Update multiple variables in session.custom
  • ctx.session.deleteCustom(key) — Delete a variable from session.custom
  • ctx.session.save(callback) — Persist the session to Redis immediately (optional, usually auto-saved)

Example Usage in a Command Handler

botManager.handleCommand('setvar', async (ctx: CustomContext) => {
  // Set a simple variable
  ctx.session.setCustom('foo', 'bar');
  // Set a nested variable
  ctx.session.setCustom('profile.name', 'Alice');
  // Get a variable
  const foo = ctx.session.getCustom('foo');
  const name = ctx.session.getCustom('profile.name');
  // Update multiple variables (including nested)
  ctx.session.updateCustom({ 'hello': 'world', 'profile.age': 30 });
  // Delete a variable
  ctx.session.deleteCustom('profile.name');
  // Save session if available (optional)
  if (typeof ctx.session.save === 'function') {
    ctx.session.save(() => {});
  }
  await ctx.reply(`Session custom variable 'foo' set to '${foo}', name: '${name}'. Updated and deleted 'profile.name'.`);
});

📝 Command Menu Management

The SDK provides a convenient way to set up and manage your bot's command menu using the setMyCommands method. This allows you to define a list of commands that will appear in the bot's menu interface.

Setting Up Command Menu

botManager.setMyCommands([
  { command: 'start', description: 'Start the bot' },
  { command: 'help', description: 'Show help information' },
  { command: 'settings', description: 'Configure bot settings' }
]);

The command menu will be displayed to users when they open the bot's chat interface, making it easier for them to discover and use available commands.


🔍 Professional Logging System

The SDK includes a professional logging system built with Pino that automatically adapts to your environment:

  • In development mode (isDev: true), you get detailed, colorized logs
  • In production mode (isDev: false), logs are minimized to essential information

Logger Features

  • 🎨 Colorized Output: Development logs are colorized for better readability
  • Timestamp Information: Each log includes precise timestamp
  • 🔍 Debug Mode: Extensive debugging information in development
  • 🎯 Production Ready: Optimized, minimal logging in production
  • 📊 Log Levels: Supports multiple log levels (debug, info, warn, error)

Using the Logger

import { createSdkLogger } from '@wasserstoff/mangi-tg-bot';

// Create a logger instance
const logger = createSdkLogger(config.isDev);

// Usage examples
logger.info('Bot initialized successfully');
logger.debug('Processing update:', update);
logger.warn('Rate limit approaching');
logger.error('Connection failed:', error);

Automatic Context Logging

The SDK automatically includes logging in the bot context:

botManager.handleCommand('example', async (ctx: CustomContext) => {
  // Logs are automatically controlled by isDev setting
  ctx.logger.info('Processing example command');
  ctx.logger.debug('Session state:', ctx.session);
  
  await ctx.reply('Command processed!');
});

Production vs Development Logging

  • Development Mode (isDev: true):

    • Detailed debug information
    • Session state logging
    • Command processing details
    • Redis operations logging
    • Colorized, formatted output
  • Production Mode (isDev: false):

    • Critical errors only
    • Important state changes
    • Minimal operational logs
    • Optimized for performance

To switch between modes, simply set isDev in your configuration:

const config: AppConfig = {
  // ... other config options ...
  isDev: process.env.NODE_ENV !== 'production'
};

👥 Admin Authentication/Approval

Add an extra layer of admin approval for new users. This is ideal for public or semi-public bots, clubs, or organizations where you want to control who can use the bot.

  • New users are set to pending in Redis and cannot use the bot until approved.
  • Admins receive approval requests and can approve/deny users via inline buttons.
  • Only approved users (status member or admin) can interact with the bot.

How it works

  1. When a new user interacts with the bot, their status is set to pending in Redis.
  2. All admins (specified in adminChatIds) receive a message with Approve/Deny buttons.
  3. When an admin approves, the user's status is set to member and they are notified.
  4. Only users with status member or admin can use the bot; others are blocked until approved.

Example: Admin Approval

const configWithAdminAuth: AppConfig = {
  botToken: 'YOUR_BOT_TOKEN',
  botMode: 'polling',
  botAllowedUpdates: ['message', 'callback_query'],
  redisUrl: 'YOUR_REDIS_URL',
  isDev: true,
  useAuth: 'none',
  adminAuthentication: true,
  adminChatIds: [123456789, 987654321], // Replace with your admin Telegram chat IDs
};

const bot = new Bot(configWithAdminAuth);
const botManager = bot.getBotManager();
botManager.handleCommand('start', async (ctx: CustomContext) => {
  await ctx.reply('Welcome! If you see this, you are approved by an admin.');
});
botManager.handleCommand('whoami', async (ctx: CustomContext) => {
  await ctx.reply(`Your chat ID: <code>${ctx.from?.id}</code>`, { parse_mode: 'HTML' });
});
botManager.handleCommand('secret', async (ctx: CustomContext) => {
  await ctx.reply('This is a secret command only for approved users!');
});

🛡️ Automatic Context Safety (No More !)

The SDK now ensures that ctx.session, ctx.chat, and ctx.from are always present in your handlers. You can safely use ctx.session.whatever, ctx.chat.id, etc., without needing to write ctx.session! or add type guards.

This is handled automatically by the SDK's internal middleware and does not require any code changes for existing users.

Example:

botManager.handleCommand('start', async (ctx: CustomContext) => {
  // No need for ctx.session! or ctx.chat!
  ctx.session.setCustom('foo', 'bar');
  await ctx.api.sendMessage(ctx.chat.id, 'Welcome!');
});

📄 License

ISC

📚 GitHub Repository

This project is available on GitHub: https://github.com/AmanUpadhyay1609/-wasserstoff-mangi-tg-bot

Issues, feature requests, and contributions are welcome!

⚠️ Important: Registering Event Listeners Safely

Do NOT Attach Event Listeners Directly to the Bot Instance

Never use:

const botInstance = botManager.getBot();
botInstance.on("chat_member", (ctx) => { /* ... */ }); // ❌ This will cause errors!

Why?

  • The grammY framework (and this SDK) throw a runtime error if you try to add event listeners after the bot has started, or from within other listeners.
  • This can cause a memory leak and eventually crash your bot. The error message will look like:

    Error: It looks like you are registering more listeners on your bot from within other listeners! ...

✅ Correct Way: Use handleEvent on BotManager

Always use:

botManager.handleEvent("chat_member", async (ctx) => {
  // Your logic here
});
  • This ensures all listeners are registered before the bot starts, using the SDK's internal Composer.
  • Works for any event type supported by grammY (e.g., chat_member, my_chat_member, etc.).

Example

botManager.handleEvent("chat_member", async (ctx) => {
  console.log("A user joined or left the group:", ctx);
});

Summary:

  • ❌ Do NOT use botInstance.on(...) directly.
  • ✅ Use botManager.handleEvent(...) for all event listeners, including group events.
  • See src/example.ts for a working example.

Type-Safe Event Names with Autocompletion

The handleEvent method uses grammY's built-in FilterQuery type for the event name. This means:

  • You get autocompletion and type safety in your editor for all valid event names (like "message", "chat_member", "message:text", etc.).
  • You can't accidentally use an invalid event name—TypeScript will warn you.

Example:

botManager.handleEvent("chat_member", async (ctx) => { /* ... */ }); // ✅ autocompleted, type-checked

Tip: Start typing inside the quotes and your editor (VS Code, WebStorm, etc.) will suggest all valid grammY event names. You can also use arrays for multiple events.

What is FilterQuery?

Package Sidebar

Install

npm i @wasserstoff/mangi-tg-bot

Weekly Downloads

53

Version

1.2.15

License

ISC

Unpacked Size

180 kB

Total Files

110

Last publish

Collaborators

  • wasserstoff-india
  • soneshwar
  • amitpatel-wstf
  • ahmadkamran
  • adityashah
  • wstf_amanupadhyay1609
  • ankurdahiyawstf
  • ayushchaturvedi01