๐Ÿšจ Shai-Hulud Strikes Again:More than 500 packages and 700+ versions compromised.Technical Analysis โ†’
Socket
Book a DemoInstallSign in
Socket

@rizzclub/channels

Package Overview
Dependencies
Maintainers
1
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@rizzclub/channels

Multi-channel messaging library with grammy-compatible API for Telegram, Webchat, WhatsApp, and SMS

latest
Source
npmnpm
Version
0.0.35
Version published
Weekly downloads
961
-19.51%
Maintainers
1
Weekly downloads
ย 
Created
Source

@rizzclub/channels

Multi-channel messaging library with a grammy-compatible API for Telegram, Webchat, WhatsApp, and SMS.

Features

  • ๐ŸŽฏ Grammy-compatible API - Drop-in replacement for grammy with the same interface
  • ๐Ÿ”Œ Multiple Channels - Telegram, Webchat, WhatsApp (coming soon), SMS (coming soon)
  • ๐Ÿš€ Cloudflare Workers - Optimized for serverless deployment
  • ๐Ÿ“ฆ TypeScript - Full type safety and autocompletion
  • ๐ŸŽจ Unified Interface - Write once, deploy to multiple channels

Code Style: camelCase Standard

IMPORTANT: This library uses camelCase for all property names and object keys:

  • โœ… callbackQuery, messageId, firstName, inlineKeyboard
  • โŒ callback_query, message_id, first_name, inline_keyboard

This applies to:

  • All TypeScript interfaces (Update, Message, CallbackQuery, etc.)
  • All internal data structures
  • Bot API methods and context properties

Exception - Grammy Filter Strings: Event filter strings remain snake_case for Grammy compatibility:

  • โœ… bot.on('callback_query:data', handler) - Filter string (snake_case)
  • โœ… ctx.callbackQuery.data - Data access (camelCase)
  • This maintains Grammy API compatibility while using TypeScript conventions internally

Adapters handle conversion: Each adapter (TelegramAdapter, WhatsAppAdapter, etc.) converts from the external API's format (usually snake_case) to our camelCase standard at the boundary.

Rationale: This library is platform-agnostic and follows TypeScript/JavaScript naming conventions, not Telegram-specific conventions. Filter strings are kept as Grammy DSL for API compatibility.

Installation

npm install @rizzclub/channels

Quick Start

โš ๏ธ IMPORTANT: Always use createBotHandler to prevent handler accumulation. This ensures handlers are registered only once, not on every request.

import { createBotHandler } from '@rizzclub/channels';

interface Env {
  BOT_TOKEN: string;
}

export default createBotHandler<Env>((bot, env) => {
  // Handlers are registered ONCE on first request
  bot.command('start', (ctx) => {
    return ctx.reply('Hello! I am your bot.');
  });

  bot.on('message:text', (ctx) => {
    return ctx.reply(`You said: ${ctx.text}`);
  });
}, {
  getToken: (env) => env.BOT_TOKEN
});
Alternative: Manual Bot Creation (Not Recommended)

โš ๏ธ Warning: This pattern re-registers all handlers on every request, causing handler accumulation and performance issues.

import { Bot, TelegramAdapter } from '@rizzclub/channels';

const adapter = new TelegramAdapter({
  token: process.env.TELEGRAM_TOKEN
});

const bot = new Bot(adapter);

bot.command('start', (ctx) => {
  return ctx.reply('Hello! I am your bot.');
});

bot.on('message:text', (ctx) => {
  return ctx.reply(`You said: ${ctx.text}`);
});

// Cloudflare Worker
export default {
  async fetch(request: Request, env: Env) {
    return bot.handleWebhook(request);
  }
};

Webchat Bot

import { createBotHandler, WebchatAdapter } from '@rizzclub/channels';

interface Env {
  WEBHOOK_SECRET: string;
  CALLBACK_URL: string;
}

export default createBotHandler<Env>((bot, env) => {
  bot.on('message:text', async (ctx) => {
    await ctx.reply(`Echo: ${ctx.text}`);
  });

  bot.callbackQuery(/^button:/, async (ctx) => {
    await ctx.answerAlert('Button clicked!');
  });
}, {
  createAdapter: (env) => new WebchatAdapter({
    webhookSecret: env.WEBHOOK_SECRET,
    callbackUrl: env.CALLBACK_URL
  })
});

Cloudflare Workers Best Practices

Why it's important: In Cloudflare Workers, the fetch() function is called on every request. If you create a new Bot instance and register handlers inside fetch(), you'll re-register all handlers on every request, causing:

  • ๐Ÿ“ˆ Handler accumulation - Middleware array grows unbounded (300 handlers ร— N requests)
  • ๐ŸŒ Performance degradation - Each request gets slower as middleware grows
  • ๐Ÿ’ธ Increased costs - More CPU time per request

Solution: Use createBotHandler to register handlers exactly once:

import { createBotHandler } from '@rizzclub/channels';

interface Env {
  BOT_TOKEN: string;
}

// โœ… CORRECT - Handlers registered ONCE on first request
export default createBotHandler<Env>((bot, env) => {
  bot.command('start', (ctx) => ctx.reply('Hello!'));
  bot.on('message:text', (ctx) => ctx.reply(`Echo: ${ctx.text}`));
  // ... all your handlers
}, {
  getToken: (env) => env.BOT_TOKEN
});

Compare with the problematic pattern:

// โŒ WRONG - Handlers re-registered on EVERY request
export default {
  async fetch(request: Request, env: Env) {
    const bot = new Bot(new TelegramAdapter({ token: env.BOT_TOKEN }));

    // These handlers are added to middleware array on EVERY request
    bot.command('start', (ctx) => ctx.reply('Hello!'));
    bot.on('message:text', (ctx) => ctx.reply(`Echo: ${ctx.text}`));

    return bot.handleWebhook(request);
  }
}

How it works:

  • On the first request, createBotHandler initializes the bot and registers all handlers
  • On subsequent requests, the same bot instance is reused (handlers already registered)
  • Bot instance persists across requests in the same Worker instance

Options:

  • getToken: (env) => string - Extract bot token from env (for TelegramAdapter)
  • createAdapter: (env) => ChannelAdapter - Create custom adapter (for other adapters)

API Reference

createBotHandler

function createBotHandler<Env = any>(
  registerHandlers: (bot: Bot, env: Env) => void | Promise<void>,
  options: {
    getToken?: (env: Env) => string;
    createAdapter?: (env: Env) => ChannelAdapter;
  }
): { fetch: (request: Request, env: Env, ctx: ExecutionContext) => Promise<Response> }

Creates a Cloudflare Workers handler with lazy bot initialization. See Cloudflare Workers Best Practices for details.

Bot

The Bot class is the main entry point, providing a grammy-compatible API.

Methods

  • bot.command(command, handler) - Handle commands (e.g., /start)
  • bot.on(event, handler) - Handle events (e.g., 'message:text')
  • bot.hears(trigger, handler) - Handle messages matching text/regex
  • bot.callbackQuery(data, handler) - Handle inline button callbacks
  • bot.use(middleware) - Add middleware
  • bot.filter(filter, handler) - Handle messages matching custom filter
  • bot.handleWebhook(request) - Process incoming webhook requests

Events

  • 'message' - Any message
  • 'message:text' - Text messages only
  • 'callback_query' - Inline button clicks
  • 'edited_message' - Message edits

Context

The Context object provides convenient access to update data and reply methods.

Properties

  • ctx.message - The message object
  • ctx.chat - The chat object
  • ctx.from - The user object
  • ctx.text - Message text
  • ctx.callbackQuery - Callback query object
  • ctx.callbackData - Callback query data
  • ctx.channel - Current channel type

Methods

  • ctx.reply(text, options?) - Reply to the message
  • ctx.send(text, options?) - Send without replying
  • ctx.editMessageText(text, options?) - Edit the message
  • ctx.deleteMessage() - Delete the message
  • ctx.answerCallbackQuery(options?) - Answer callback query
  • ctx.answerAlert(text) - Answer with alert popup
  • ctx.hasCommand(command?) - Check if message is a command

Adapters

TelegramAdapter

Wraps grammy for Telegram integration.

import { TelegramAdapter } from '@rizzclub/channels';

const adapter = new TelegramAdapter({
  token: 'YOUR_BOT_TOKEN'
});

Options:

  • token - Telegram bot token from @BotFather
  • bot? - Optional grammy Bot instance for advanced usage

WebchatAdapter

Custom adapter for web-based chat interfaces.

import { WebchatAdapter, InMemoryMessageStore } from '@rizzclub/channels';

const adapter = new WebchatAdapter({
  webhookSecret: 'your-secret',
  callbackUrl: 'https://your-app.com/api/send',
  callbackHeaders: {
    'Authorization': 'Bearer token'
  },
  messageStore: new InMemoryMessageStore()
});

Options:

  • webhookSecret? - Secret for validating webhook requests
  • callbackUrl? - URL to POST messages to
  • callbackHeaders? - Headers for callback requests
  • messageStore? - Store for tracking messages

Webhook Payload Format:

{
  type: 'message' | 'callback_query',
  sessionId: string,
  userId: string,
  userName?: string,
  timestamp: number,
  message?: {
    id: string,
    text?: string
  },
  callbackQuery?: {
    id: string,
    data: string,
    messageId: string
  }
}

WhatsAppAdapter (Coming Soon)

Placeholder for WhatsApp Business API integration.

SMSAdapter (Coming Soon)

Placeholder for SMS integration via Twilio/Vonage.

Multi-Channel Support

Handle multiple channels with shared bot logic using the built-in Router:

import {
  createRouter,
  TelegramAdapter,
  WebchatAdapter
} from '@rizzclub/channels';

// Shared bot setup
function setupBot(bot) {
  bot.command('start', (ctx) => {
    return ctx.reply(`Welcome to ${ctx.channel}!`);
  });

  bot.on('message:text', (ctx) => {
    return ctx.reply(`[${ctx.channel}] You said: ${ctx.text}`);
  });
}

// Cloudflare Worker
export default {
  async fetch(request: Request, env: Env) {
    const router = createRouter()
      .route('/telegram/webhook', new TelegramAdapter({ token: env.TELEGRAM_TOKEN }), setupBot)
      .route('/webchat/webhook', new WebchatAdapter({ webhookSecret: env.WEBHOOK_SECRET }), setupBot);

    return router.handleRequest(request);
  }
};

Option 2: Manual Routing

import {
  Bot,
  TelegramAdapter,
  WebchatAdapter,
  type ChannelAdapter
} from '@rizzclub/channels';

// Shared bot logic
function createBot(adapter: ChannelAdapter) {
  const bot = new Bot(adapter);

  bot.command('start', (ctx) => {
    return ctx.reply(`Welcome to ${ctx.channel}!`);
  });

  bot.on('message:text', (ctx) => {
    return ctx.reply(`[${ctx.channel}] You said: ${ctx.text}`);
  });

  return bot;
}

// Cloudflare Worker
export default {
  async fetch(request: Request, env: Env) {
    const url = new URL(request.url);

    // Telegram webhook
    if (url.pathname === '/telegram') {
      const bot = createBot(
        new TelegramAdapter({ token: env.TELEGRAM_TOKEN })
      );
      return bot.handleWebhook(request);
    }

    // Webchat webhook
    if (url.pathname === '/webchat') {
      const bot = createBot(
        new WebchatAdapter({ webhookSecret: env.WEBHOOK_SECRET })
      );
      return bot.handleWebhook(request);
    }

    return new Response('Not Found', { status: 404 });
  }
};

Inline Keyboards

Create interactive inline keyboards:

bot.command('menu', (ctx) => {
  return ctx.reply('Choose an option:', {
    replyMarkup: {
      inlineKeyboard: [
        [
          { text: 'โœ… Option 1', callbackData: 'opt1' },
          { text: 'โŒ Option 2', callbackData: 'opt2' }
        ],
        [
          { text: '๐Ÿ”— Visit Website', url: 'https://rizz.club' }
        ]
      ]
    }
  });
});

bot.callbackQuery('opt1', async (ctx) => {
  await ctx.answerAlert('You chose Option 1!');
  await ctx.editMessageText('Option 1 selected โœ…');
});

Reply Keyboards

Create reply keyboards (Telegram):

bot.command('keyboard', (ctx) => {
  return ctx.reply('Choose a category:', {
    replyMarkup: {
      keyboard: [
        [{ text: '๐Ÿ“ฑ Tech' }, { text: '๐ŸŽฎ Gaming' }],
        [{ text: '๐ŸŽจ Art' }, { text: '๐ŸŽต Music' }]
      ],
      resizeKeyboard: true,
      oneTimeKeyboard: true
    }
  });
});

Middleware

Add custom middleware for logging, authentication, etc:

// Logging middleware
bot.use(async (ctx, next) => {
  console.log(`Incoming from ${ctx.channel}: ${ctx.text}`);
  await next();
});

// Auth middleware
bot.use(async (ctx, next) => {
  const userId = ctx.from?.id;
  if (!userId) return;

  const isAuthorized = await checkAuth(userId);
  if (!isAuthorized) {
    return ctx.reply('Unauthorized');
  }

  await next();
});

TypeScript

Full TypeScript support with type inference:

import { Bot, Context, TelegramAdapter } from '@rizzclub/channels';

const adapter = new TelegramAdapter({ token: 'token' });
const bot = new Bot(adapter);

bot.on('message:text', (ctx: Context) => {
  // ctx.text is automatically typed as string | undefined
  if (ctx.text) {
    console.log(ctx.text.toUpperCase());
  }
});

Comparison with Grammy

@rizzclub/channels provides the same API as grammy while supporting multiple channels:

FeatureGrammy@rizzclub/channels
Telegramโœ…โœ…
WebchatโŒโœ…
WhatsAppโŒ๐Ÿšง Coming soon
SMSโŒ๐Ÿšง Coming soon
API compatibility-100%
TypeScriptโœ…โœ…
Cloudflare Workersโœ…โœ…

License

MIT

Contributing

Contributions welcome! Please open an issue or PR.

Keywords

telegram

FAQs

Package last updated on 21 Nov 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts