#Telegram-бот для CRM

🤖 Сложность: medium | Скоупы: crm | Стек: Node.js, grammy

#Что делаем

Создаём Telegram-бота, который периодически опрашивает CRM через Вайбкод Entity API и уведомляет менеджера о новых сделках. Бот отправляет форматированное сообщение с названием сделки, суммой и контактом. Это позволяет моментально реагировать на входящие лиды, не заходя в Bitrix24.

#Необходимо

  • API-ключ Вайбкод с правами crm
  • Telegram Bot Token (получить у @BotFather)
  • Node.js 18+
  • Пакеты: grammy, node-cron

#Полный код

javascript
// telegram-crm-bot.js
// Telegram-бот для уведомлений о новых сделках из Bitrix24

import { Bot } from 'grammy';
import cron from 'node-cron';

// Конфигурация из переменных окружения
const API_KEY = process.env.VIBE_API_KEY;
const BASE_URL = process.env.VIBE_BASE_URL; // например https://vibecode.bitrix24.tech
const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
const CHAT_ID = process.env.TELEGRAM_CHAT_ID; // ID чата менеджера

const bot = new Bot(BOT_TOKEN);

const HEADERS = {
  'X-Api-Key': API_KEY,
  'Content-Type': 'application/json',
};

// Храним ID последней обработанной сделки
let lastSeenDealId = 0;

// Запрос новых сделок через Entity API
async function fetchNewDeals() {
  try {
    const response = await fetch(
      `${BASE_URL}/v1/deals/search`,
      {
        method: 'POST',
        headers: HEADERS,
        body: JSON.stringify({
          filter: {
            id: { $gt: lastSeenDealId },
            // Примечание: на порталах с несколькими воронками стадия может быть
            // C2:NEW, C4:NEW и т.д. Используйте $contains или фильтрацию на клиенте.
            stageId: 'NEW',
          },
          sort: 'id',
          select: ['id', 'title', 'amount', 'currency', 'contactId', 'createdAt'],
        }),
      }
    );

    const { data } = await response.json();
    return data || [];
  } catch (error) {
    console.error('Ошибка при получении сделок:', error.message);
    return [];
  }
}

// Получаем имя контакта по ID
async function fetchContactName(contactId) {
  if (!contactId) return 'Не указан';

  try {
    const response = await fetch(`${BASE_URL}/v1/contacts/${contactId}`, {
      headers: { 'X-Api-Key': API_KEY },
    });

    const { data } = await response.json();
    return `${data.name || ''} ${data.lastName || ''}`.trim();
  } catch {
    return 'Не удалось загрузить';
  }
}

// Форматируем сообщение для Telegram
function formatDealMessage(deal, contactName) {
  return [
    `🆕 <b>Новая сделка</b>`,
    ``,
    `📋 <b>Название:</b> ${deal.title}`,
    `💰 <b>Сумма:</b> ${deal.amount || '0'} ${deal.currency || 'RUB'}`,
    `👤 <b>Контакт:</b> ${contactName}`,
    `📅 <b>Создана:</b> ${new Date(deal.createdAt).toLocaleString('ru-RU')}`,
    `🔗 <b>ID:</b> ${deal.id}`,
  ].join('\n');
}

// Проверяем новые сделки и отправляем уведомления
async function checkAndNotify() {
  console.log(`[${new Date().toISOString()}] Проверяем новые сделки...`);

  const deals = await fetchNewDeals();

  for (const deal of deals) {
    const contactName = await fetchContactName(deal.contactId);
    const message = formatDealMessage(deal, contactName);

    await bot.api.sendMessage(CHAT_ID, message, { parse_mode: 'HTML' });
    console.log(`Уведомление отправлено: сделка #${deal.id}`);

    // Обновляем последний обработанный ID
    lastSeenDealId = Math.max(lastSeenDealId, deal.id);
  }

  if (deals.length === 0) {
    console.log('Новых сделок нет');
  }
}

// Команды бота
bot.command('start', (ctx) => {
  ctx.reply('👋 Бот CRM-уведомлений запущен! Вы будете получать сообщения о новых сделках.');
});

bot.command('status', (ctx) => {
  ctx.reply(`📊 Последний обработанный ID сделки: ${lastSeenDealId}`);
});

// Запускаем опрос каждые 2 минуты
cron.schedule('*/2 * * * *', checkAndNotify);

// Запускаем бота
bot.start();
console.log('🤖 Telegram CRM-бот запущен');

#Как это работает

  1. Бот запускается и подключается к Telegram API через библиотеку grammy.
  2. Каждые 2 минуты cron-задача вызывает функцию checkAndNotify, которая запрашивает новые сделки через Entity API (POST /v1/deals/search) с фильтром по ID больше последнего обработанного.
  3. Для каждой новой сделки бот загружает имя контакта через GET /v1/contacts/:id.
  4. Полученные данные форматируются в HTML-сообщение и отправляются в указанный Telegram-чат.
  5. ID последней обработанной сделки сохраняется в памяти, чтобы не отправлять дубликаты при следующем опросе.

#Что можно улучшить

  • Сохранять lastSeenDealId в файл или Redis, чтобы не терять прогресс при перезапуске
  • Добавить инлайн-кнопки для быстрых действий прямо из Telegram (взять в работу, назначить ответственного)
  • Поддержать несколько менеджеров: маршрутизировать уведомления по ответственному в сделке
  • Добавить фильтры по сумме или типу сделки через команды бота