Фреймворк 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.