Telegram бот для Битрикс. Уведомления. Заготовка

Для большинства случаев подойдет модуль Александра Горячкина, который лежит на маркетплейсе https://marketplace.1c-bitrix.ru/solutions/xzag.telegram/

Модуль бесплатный, за что ему говорим "Большое спасибо".

Но бывают проекты, где много нестандартных сценариев отправки уведомлений. По поводу и без. Для таких случаев создадим себе заготовку - болванку, которую потом можно будет масштабировать.

Итак, погнали.

Регистрируем бот Телеграм

Бот в Telegram делаем через @BotFather , как это сделать, в интернете найти не сложно.

На данном этапе, нам нужно получить API_KEY

 

Получаем ID чата

Бот может отправлять сообщения только пользователям, которые начали с ним диалог.

Чтобы получить chat_id:

  • Напишите боту любое сообщение.

  • Выполните GET-запрос:

    https://api.telegram.org/bot<ВАШ_API_КЛЮЧ>/getUpdates
  • В ответе найдите id в разделе chat.

    Пример:

    {
    "ok": true,
    "result": [{
      "update_id": 123,
      "message": {
        "chat": {"id": 123456789},
        "text": "Hello"
      }
    }]
    }

 

Организовываем структуру файлов

В папке local/php_interface/lib/ создаем пространство для чатботов, их может быть несколько, сейчас создаем папку Telegram и складываем файлы:

  • Telegram/TelegramBase.php - это будет наша база

  • Telegram/TelegramAlert.php - класс для уведомлений

  • И сюда же в будущем будем складывать логику для сущностей (Пользователь, Заказ, другие специфичные сценарии)

 

 

TelegramBase.php

<?php

namespace Chatbot\Telegram;

use Exception;
use InvalidArgumentException;

class TelegramBase
{
    private string $botToken;
    private string $chatId;
    private string $apiUrl;
    private int $timeout;
    private array $lastResponse;

    public function __construct(string $botToken, string $chatId, int $timeout = 30)
    {
        $this->validateBotToken($botToken);
        $this->validateChatId($chatId);

        $this->botToken = $botToken;
        $this->chatId = $chatId;
        $this->timeout = $timeout;
        $this->apiUrl = "https://api.telegram.org/bot{$this->botToken}/";
        $this->lastResponse = [];
    }

    /**
     * Отправляет сообщение о регистрации нового пользователя
     *
     * @param string $name Имя пользователя
     * @param string $email Email пользователя
     * @param string $phone Номер телефона пользователя
     * @return array Ответ от Telegram API
     * @throws Exception
     */
    public function sendUserRegistrationMessage(string $name, string $email, string $phone): array
    {
        $message = $this->formatRegistrationMessage($name, $email, $phone);
        return $this->sendMessage($message, 'HTML'); // Используем HTML вместо Markdown
    }

    /**
     * Отправляет сообщение в Telegram
     *
     * @param string $message Текст сообщения
     * @param string $parseMode Режим парсинга (Markdown, HTML, MarkdownV2)
     * @param array $additionalParams Дополнительные параметры
     * @return array Ответ от Telegram API
     * @throws Exception
     */
    public function sendMessage(
        string $message,
        string $parseMode = 'HTML',
        array $additionalParams = []
    ): array {
        $url = $this->apiUrl . 'sendMessage';

        $data = array_merge([
            'chat_id' => $this->chatId,
            'text' => $message,
            'parse_mode' => $parseMode
        ], $additionalParams);

        $response = $this->makeRequest($url, $data);
        $this->lastResponse = $response;

        if (!($response['ok'] ?? false)) {
            throw new Exception(
                'Ошибка отправки сообщения: ' .
                ($response['description'] ?? 'Неизвестная ошибка')
            );
        }

        return $response;
    }

    /**
     * Отправляет документ в Telegram
     *
     * @param string $document Путь к файлу или file_id
     * @param string $caption Подпись к документу
     * @return array
     * @throws Exception
     */
    public function sendDocument(string $document, string $caption = ''): array
    {
        $url = $this->apiUrl . 'sendDocument';

        $data = [
            'chat_id' => $this->chatId,
            'document' => $document
        ];

        if (!empty($caption)) {
            $data['caption'] = $caption;
        }

        $response = $this->makeRequest($url, $data);
        $this->lastResponse = $response;

        if (!($response['ok'] ?? false)) {
            throw new Exception(
                'Ошибка отправки документа: ' .
                ($response['description'] ?? 'Неизвестная ошибка')
            );
        }

        return $response;
    }

    /**
     * Форматирует сообщение о регистрации пользователя
     *
     * @param string $name
     * @param string $email
     * @param string $phone
     * @return string
     */
    private function formatRegistrationMessage(string $name, string $email, string $phone): string
    {
        return "🎉 <b>Новая регистрация!</b>\n\n" .
            "<b>Имя:</b> " . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . "\n" .
            "<b>Email:</b> " . htmlspecialchars($email, ENT_QUOTES, 'UTF-8') . "\n" .
            "<b>Телефон:</b> " . htmlspecialchars($phone, ENT_QUOTES, 'UTF-8') . "\n\n" .
            "<b>Время:</b> " . date('d.m.Y H:i:s');
    }

    /**
     * Выполняет HTTP запрос к Telegram API
     *
     * @param string $url
     * @param array $data
     * @return array
     * @throws Exception
     */
    private function makeRequest(string $url, array $data): array
    {
        // Попробуем использовать cURL, если доступен
        if (function_exists('curl_init')) {
            return $this->makeRequestWithCurl($url, $data);
        }

        // Fallback на file_get_contents
        return $this->makeRequestWithFileGetContents($url, $data);
    }

    /**
     * Выполняет запрос через cURL
     *
     * @param string $url
     * @param array $data
     * @return array
     * @throws Exception
     */
    private function makeRequestWithCurl(string $url, array $data): array
    {
        $ch = curl_init();

        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($data),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_USERAGENT => 'TelegramBot/1.0',
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/x-www-form-urlencoded'
            ]
        ]);

        $result = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);

        curl_close($ch);

        if ($result === false) {
            throw new Exception('Ошибка cURL: ' . $error);
        }

        if ($httpCode !== 200) {
            throw new Exception("HTTP ошибка: {$httpCode}");
        }

        return $this->parseResponse($result);
    }

    /**
     * Выполняет запрос через file_get_contents
     *
     * @param string $url
     * @param array $data
     * @return array
     * @throws Exception
     */
    private function makeRequestWithFileGetContents(string $url, array $data): array
    {
        $postData = http_build_query($data);

        $context = stream_context_create([
            'http' => [
                'method' => 'POST',
                'header' => [
                    'Content-Type: application/x-www-form-urlencoded',
                    'User-Agent: TelegramBot/1.0'
                ],
                'content' => $postData,
                'timeout' => $this->timeout
            ]
        ]);

        $result = @file_get_contents($url, false, $context);

        if ($result === false) {
            $error = error_get_last();
            throw new Exception('Не удалось выполнить запрос: ' . ($error['message'] ?? 'Неизвестная ошибка'));
        }

        return $this->parseResponse($result);
    }

    /**
     * Парсит ответ от Telegram API
     *
     * @param string $response
     * @return array
     * @throws Exception
     */
    private function parseResponse(string $response): array
    {
        $decoded = json_decode($response, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception('Ошибка декодирования JSON ответа: ' . json_last_error_msg());
        }

        return $decoded;
    }

    /**
     * Проверяет корректность настроек бота
     *
     * @return array Информация о боте
     * @throws Exception
     */
    public function getMe(): array
    {
        $url = $this->apiUrl . 'getMe';
        return $this->makeRequest($url, []);
    }

    /**
     * Валидирует токен бота
     *
     * @param string $token
     * @throws InvalidArgumentException
     */
    private function validateBotToken(string $token): void
    {
        if (empty($token)) {
            throw new InvalidArgumentException('Токен бота не может быть пустым');
        }

        if (!preg_match('/^\d+:[A-Za-z0-9_-]{35}$/', $token)) {
            throw new InvalidArgumentException('Некорректный формат токена бота');
        }
    }

    /**
     * Валидирует chat_id
     *
     * @param string $chatId
     * @throws InvalidArgumentException
     */
    private function validateChatId(string $chatId): void
    {
        if (empty($chatId)) {
            throw new InvalidArgumentException('Chat ID не может быть пустым');
        }
    }

    /**
     * Устанавливает новый chat_id
     *
     * @param string $chatId
     * @throws InvalidArgumentException
     */
    public function setChatId(string $chatId): void
    {
        $this->validateChatId($chatId);
        $this->chatId = $chatId;
    }

    /**
     * Получает текущий chat_id
     *
     * @return string
     */
    public function getChatId(): string
    {
        return $this->chatId;
    }

    /**
     * Получает последний ответ API
     *
     * @return array
     */
    public function getLastResponse(): array
    {
        return $this->lastResponse;
    }

    /**
     * Устанавливает таймаут запросов
     *
     * @param int $timeout
     */
    public function setTimeout(int $timeout): void
    {
        $this->timeout = max(1, $timeout);
    }
}

 

TelegramAlert.php

<?php

namespace Chatbot\Telegram;

use Exception;
use InvalidArgumentException;

class TelegramAlert
{
    private const BOT_TOKEN = '11111111111111'; // Токен, он же api key
    private const CHAT_IDS = [
        'main' => 987654321, // id чата куда шлем уведомления 
        // 'backup' => 987654321, // Резервный чат
    ];

    private const MESSAGE_TYPES = [
        'registration' => '🎉 <b>Новая регистрация!</b>',
        'order' => '🛒 <b>Новый заказ!</b>',
        'error' => '⚠️ <b>Системная ошибка!</b>',
        'notification' => '📢 <b>Уведомление</b>'
    ];

    private static ?TelegramBase $telegram = null;

    /**
     * Отправляет уведомление о регистрации нового пользователя
     *
     * @param string $name Имя пользователя
     * @param string $email Email пользователя
     * @param string $phone Номер телефона пользователя
     * @param string $chatKey Ключ чата из массива CHAT_IDS
     * @return bool Успешность отправки
     */
    public static function sendNewUserRegistration(
        string $name,
        string $email,
        string $phone,
        string $chatKey = 'main'
    ): bool {
        try {
            self::validateUserData($name, $email, $phone);

            $telegram = self::getTelegramInstance($chatKey);
            $message = self::formatRegistrationMessage($name, $email, $phone);

            $response = $telegram->sendMessage($message, 'HTML');

            return $response['ok'] ?? false;

        } catch (Exception $e) {
            self::logError('Ошибка отправки уведомления о регистрации', [
                'error' => $e->getMessage(),
                'name' => $name,
                'email' => $email,
                'phone' => $phone
            ]);
            return false;
        }
    }

    /**
     * Отправляет уведомление о новом заказе
     *
     * @param array $orderData Данные заказа
     * @param string $chatKey Ключ чата
     * @return bool
     */
    public static function sendNewOrder(array $orderData, string $chatKey = 'main'): bool
    {
        try {
            $telegram = self::getTelegramInstance($chatKey);
            $message = self::formatOrderMessage($orderData);

            $response = $telegram->sendMessage($message, 'HTML');

            return $response['ok'] ?? false;

        } catch (Exception $e) {
            self::logError('Ошибка отправки уведомления о заказе', [
                'error' => $e->getMessage(),
                'orderData' => $orderData
            ]);
            return false;
        }
    }

    /**
     * Отправляет системное уведомление
     *
     * @param string $message Текст сообщения
     * @param string $type Тип сообщения
     * @param string $chatKey Ключ чата
     * @return bool
     */
    public static function sendSystemNotification(
        string $message,
        string $type = 'notification',
        string $chatKey = 'main'
    ): bool {
        try {
            $telegram = self::getTelegramInstance($chatKey);
            $formattedMessage = self::formatSystemMessage($message, $type);

            $response = $telegram->sendMessage($formattedMessage, 'HTML');

            return $response['ok'] ?? false;

        } catch (Exception $e) {
            self::logError('Ошибка отправки системного уведомления', [
                'error' => $e->getMessage(),
                'message' => $message,
                'type' => $type
            ]);
            return false;
        }
    }

    /**
     * Отправляет уведомления во все доступные чаты
     *
     * @param string $message Текст сообщения
     * @param string $type Тип сообщения
     * @return array Результаты отправки по каждому чату
     */
    public static function sendToAllChats(string $message, string $type = 'notification'): array
    {
        $results = [];

        foreach (array_keys(self::CHAT_IDS) as $chatKey) {
            $results[$chatKey] = self::sendSystemNotification($message, $type, $chatKey);
        }

        return $results;
    }

    /**
     * Получает экземпляр TelegramBase
     *
     * @param string $chatKey
     * @return TelegramBase
     * @throws InvalidArgumentException
     */
    private static function getTelegramInstance(string $chatKey): TelegramBase
    {
        if (!isset(self::CHAT_IDS[$chatKey])) {
            throw new InvalidArgumentException("Неизвестный ключ чата: {$chatKey}");
        }

        $chatId = (string) self::CHAT_IDS[$chatKey];

        // Создаем новый экземпляр для каждого чата
        return new TelegramBase(self::BOT_TOKEN, $chatId);
    }

    /**
     * Валидация данных пользователя
     *
     * @param string $name
     * @param string $email
     * @param string $phone
     * @throws InvalidArgumentException
     */
    private static function validateUserData(string $name, string $email, string $phone): void
    {
        if (empty(trim($name))) {
            throw new InvalidArgumentException('Имя пользователя не может быть пустым');
        }

        if (empty(trim($email)) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException('Некорректный email адрес');
        }

        if (empty(trim($phone))) {
            throw new InvalidArgumentException('Номер телефона не может быть пустым');
        }
    }

    /**
     * Форматирует сообщение о регистрации
     *
     * @param string $name
     * @param string $email
     * @param string $phone
     * @return string
     */
    private static function formatRegistrationMessage(string $name, string $email, string $phone): string
    {
        return self::MESSAGE_TYPES['registration'] . "\n\n" .
            "<b>Имя:</b> " . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . "\n" .
            "<b>Email:</b> " . htmlspecialchars($email, ENT_QUOTES, 'UTF-8') . "\n" .
            "<b>Телефон:</b> " . htmlspecialchars($phone, ENT_QUOTES, 'UTF-8') . "\n\n" .
            "<b>Время:</b> " . date('d.m.Y H:i:s') . "\n" .
            "<b>Сайт:</b> " . ($_SERVER['HTTP_HOST'] ?? 'Неизвестно');
    }

    /**
     * Форматирует сообщение о заказе
     *
     * @param array $orderData
     * @return string
     */
    private static function formatOrderMessage(array $orderData): string
    {
        $message = self::MESSAGE_TYPES['order'] . "\n\n";

        if (isset($orderData['id'])) {
            $message .= "🆔 <b>Номер заказа:</b> " . htmlspecialchars($orderData['id'], ENT_QUOTES, 'UTF-8') . "\n";
        }

        if (isset($orderData['customer'])) {
            $message .= "👤 <b>Клиент:</b> " . htmlspecialchars($orderData['customer'], ENT_QUOTES, 'UTF-8') . "\n";
        }

        if (isset($orderData['total'])) {
            $message .= "💰 <b>Сумма:</b> " . htmlspecialchars($orderData['total'], ENT_QUOTES, 'UTF-8') . " ₽\n";
        }

        if (isset($orderData['items_count'])) {
            $message .= "📦 <b>Товаров:</b> " . htmlspecialchars($orderData['items_count'], ENT_QUOTES, 'UTF-8') . "\n";
        }

        $message .= "\n⏰ <b>Время:</b> " . date('d.m.Y H:i:s');

        return $message;
    }

    /**
     * Форматирует системное сообщение
     *
     * @param string $message
     * @param string $type
     * @return string
     */
    private static function formatSystemMessage(string $message, string $type): string
    {
        $header = self::MESSAGE_TYPES[$type] ?? self::MESSAGE_TYPES['notification'];

        return $header . "\n\n" .
            htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . "\n\n" .
            "⏰ <b>Время:</b> " . date('d.m.Y H:i:s') . "\n" .
            "🌐 <b>Сервер:</b> " . ($_SERVER['HTTP_HOST'] ?? 'Неизвестно');
    }

    /**
     * Логирует ошибки
     *
     * @param string $message
     * @param array $context
     */
    private static function logError(string $message, array $context = []): void
    {
        $logMessage = date('[Y-m-d H:i:s] ') . $message;

        if (!empty($context)) {
            $logMessage .= ' | Context: ' . json_encode($context, JSON_UNESCAPED_UNICODE);
        }

        $logMessage .= PHP_EOL;

        // Логируем в файл или используем системный логгер
        error_log($logMessage, 3, __DIR__ . '/telegram_errors.log');
    }

    /**
     * Проверяет работоспособность бота
     *
     * @return bool
     */
    public static function healthCheck(): bool
    {
        try {
            $telegram = self::getTelegramInstance('main');
            $response = $telegram->getMe();

            return $response['ok'] ?? false;

        } catch (Exception $e) {
            self::logError('Ошибка проверки работоспособности бота', [
                'error' => $e->getMessage()
            ]);
            return false;
        }
    }

    /**
     * Получает информацию о боте
     *
     * @return array|null
     */
    public static function getBotInfo(): ?array
    {
        try {
            $telegram = self::getTelegramInstance('main');
            $response = $telegram->getMe();

            return $response['result'] ?? null;

        } catch (Exception $e) {
            self::logError('Ошибка получения информации о боте', [
                'error' => $e->getMessage()
            ]);
            return null;
        }
    }
}

 

Пример использования

Отправим уведомление о том, что зарегистрировался новый пользователь

в файл с эвентами добавляем код , перехватываем события OnAfterUserRegister и OnAfterUserSimpleRegister

$eventManager->addEventHandler(
    'main',
    'OnAfterUserRegister',
    [Helper\UserHelper::class, 'telegramBotAlertAfterNewUserRegistration']
);
$eventManager->addEventHandler(
    'main',
    'OnAfterUserSimpleRegister',
    [Helper\UserHelper::class, 'telegramBotAlertAfterNewUserRegistration']
);

 

Метод telegramBotAlertAfterNewUserRegistration()

public static function telegramBotAlertAfterNewUserRegistration(array &$arFields):void
{
    TelegramAlert::sendNewUserRegistration(
        $arFields['NAME'],
        $arFields['EMAIL'],
        $arFields['PHONE_NUMBER'],
    );
}

 

Еще варианты до кучи

// Отправка уведомления о регистрации
TelegramAlert::sendNewUserRegistration(
    'Иван Петров', 
    'ivan@example.com', 
    '+7 999 123-45-67'
);

// Отправка уведомления о заказе
TelegramAlert::sendNewOrder([
    'id' => '12345',
    'customer' => 'Иван Петров',
    'total' => '15000',
    'items_count' => '3'
]);

// Системное уведомление
TelegramAlert::sendSystemNotification(
    'Произошла критическая ошибка в системе',
    'error'
);

// Проверка работоспособности
if (TelegramAlert::healthCheck()) {
    echo "Бот работает нормально";
}

И получаем уведомление из Битрикс в Telegram