Фреймворк grammY стал одним из наиболее популярных инструментов для разработки Telegram-ботов на Node.js благодаря отличной архитектуре, поддержке плагинов и высокой производительности. Однако написание базового «Hello World» сильно отличается от создания отказоустойчивого сервиса, готового к реальным нагрузкам и обработке бизнес-данных.
В этой статье мы разберем построение архитектуры бота для сбора лидов. Мы рассмотрим безопасную инициализацию через переменные окружения, организацию пошаговых диалогов (conversations), переключение между Long Polling и Webhook на базе Express, а также правильную обработку лимитов Telegram Bot API (ошибка 429).
1. Инициализация проекта и безопасная конфигурация
Никогда не храните токен бота в исходном коде. Для работы с конфигурацией в Node.js используется пакет dotenv, а сам токен выносится в файл .env. Убедитесь, что файл .env добавлен в .gitignore.
Для создания полноценного бота нам понадобятся пакеты grammy, @grammyjs/conversations (для управления сценариями диалога) и @grammyjs/auto-retry (для обработки лимитов API).
require('dotenv').config();
const { Bot, session } = require('grammy');
const botToken = process.env.BOT_TOKEN;
if (!botToken) {
throw new Error('Ошибка: Переменная окружения BOT_TOKEN не задана!');
}
const bot = new Bot(botToken);2. Организация пошагового диалога для сбора лидов
Типичная задача коммерческого бота — собрать контактные данные пользователя (имя, телефон, суть обращения) и передать их в CRM. В grammY для этого применяется официально поддерживаемый плагин conversations. Он позволяет писать асинхронный код диалога так, будто это линейный скрипт, не превращая логику в цепочку разрозненных callback-функций.
При этом важно предусмотреть валидацию входящих данных и возможность отмены сценария на любом шаге с помощью глобальных команд (например, /cancel).
const { conversations, createConversation } = require('@grammyjs/conversations');
// Подключаем сессии (необходимы для работы conversations)
bot.use(session({ initial: () => ({}) }));
/**
* Функция-сценарий пошагового сбора лида
*/
async function collectLeadConversation(conversation, ctx) {
await ctx.reply('Давайте оформим заявку! Как к вам обращаться?');
// Ожидаем текстовый ответ от пользователя
const nameContext = await conversation.waitFor('message:text');
const userName = nameContext.message.text.trim();
if (userName.startsWith('/')) {
await ctx.reply('Заполнение заявки отменено.');
return;
}
await ctx.reply(`Приятно познакомиться, ${userName}! Укажите ваш номер телефона:`);
// Ожидаем ответ с контактом или текстом
const phoneContext = await conversation.waitFor(['message:contact', 'message:text']);
let phoneNumber = '';
if (phoneContext.message.contact) {
phoneNumber = phoneContext.message.contact.phone_number;
} else {
phoneNumber = phoneContext.message.text.trim();
}
// Генерация уникального идентификатора лида
const leadId = Math.random().toString(36).substring(2, 9);
console.log('Новая заявка сохранена:', {
leadId,
telegramId: ctx.from.id,
name: userName,
phone: phoneNumber,
createdAt: new Date().toISOString()
});
await ctx.reply(`Спасибо, ${userName}! Ваша заявка №${leadId} успешно принята. Менеджер свяжется с вами.`);
}
// Регистрируем диалог в плагине
bot.use(createConversation(collectLeadConversation));
bot.use(conversations());
// Команда для запуска сценария
bot.command('lead', async (ctx) => {
await ctx.conversation.enter('collectLeadConversation');
});
bot.command('start', async (ctx) => {
await ctx.reply('Привет! Нажмите /lead, чтобы оставить заявку на консультацию.');
});3. Webhook vs Long Polling: настройка продакшн-сервера
На этапе разработки удобнее использовать Long Polling (bot.start()) — он не требует публичного IP-адреса и SSL-сертификата. Однако под высокой нагрузкой в продакшне стандартным решением является Webhook. Обработка вебхуков позволяет разворачивать бота в Serverless-инфраструктуре или масштабировать бэкенд за балансировщиком.
Для защиты вебхука необходимо использовать заголовки X-Telegram-Bot-Api-Secret-Token, передаваемые при регистрации URL через метод setWebhook.
const express = require('express');
const { webhookCallback } = require('grammy');
const app = express();
app.use(express.json());
const PORT = process.env.PORT || 3000;
const SECRET_TOKEN = process.env.WEBHOOK_SECRET || 'crypto-safe-secret-token';
if (process.env.NODE_ENV === 'production') {
// Продакшн: используем Express и Webhook с проверкой Secret Token
app.post(`/webhook/${botToken}`, (req, res, next) => {
const incomingToken = req.headers['x-telegram-bot-api-secret-token'];
if (incomingToken !== SECRET_TOKEN) {
return res.status(403).send('Forbidden: invalid secret token');
}
return next();
}, webhookCallback(bot, 'express'));
app.listen(PORT, () => {
console.log(`Webhook сервер запущен на порту ${PORT}`);
});
} else {
// Разработка: запуск Long Polling
bot.start({
onStart: (botInfo) => console.log(`Бот @${botInfo.username} запущен локально через Long Polling`),
});
}4. Борьба с лимитами Telegram API (Ошибка 429 / Retry-After)
Telegram Bot API накладывает жесткие ограничения: не более 30 сообщений в секунду суммарно и не более 1 сообщения в секунду в один и тот же чат. При массовых рассылках или резком наплыве пользователей API возвращает ошибку с HTTP-кодом 429 Too Many Requests и параметром retry_after.
Если игнорировать этот статус и продолжать отправку, Telegram может временно или навсегда заблокировать токен бота. В экосистеме grammY для автоматического урегулирования этой проблемы используется плагин @grammyjs/auto-retry. Он перехватывает ошибки 429 и автоматически откладывает повторный запрос на количество секунд, указанное сервером Telegram.
const { autoRetry } = require('@grammyjs/auto-retry');
// Подключаем auto-retry в API-клиент бота
bot.api.config.use(
autoRetry({
maxRetryAttempts: 3, // Максимальное количество повторных попыток
maxDelaySeconds: 60, // Максимальное время ожидания в секундах
})
);
// Пример безопасной отправки уведомления
async function sendNotification(chatId, text) {
try {
await bot.api.sendMessage(chatId, text, { parse_mode: 'HTML' });
} catch (error) {
console.error(`Не удалось доставить сообщение для ${chatId}:`, error.message);
}
}5. Архитектурные нюансы и чек-лист для запуска
- Идемпотентность вебхуков: Telegram гарантирует доставку обновлений, но при сбоях сети может прислать один и тот же
update_idповторно. Сохраняйте обрабатываемыеupdate_idв Redis или в БД с TTL для исключения дублей. - Грамотное завершение (Graceful Shutdown): При перезапуске приложения останавливайте Long Polling или закрывайте соединения с базой данных, чтобы избегать потери текущих состояний сессий.
- Валидация типов данных: При получении сообщений в сценариях всегда проверяйте их тип. Пользователь может отправить стикер, видео или файл вместо ожидаемого текста.
Если вам требуется профессиональная разработка сложных ботов или интеграция Telegram-решений с вашей корпоративной системой, обратитесь к специалистам BotCreator.