Как привязать чат-бота в Telegram к текущему пользователю в Laravel

27 июня 2019

В большинстве инструкций по работе с телеграм-ботом не сказано про то, как получить тот самый RECIPIENT_CHAT_ID, в который надо слать сообщения из PHP.

Вот типичный пример отправки сообщения из Laravel-приложения в Телеграм:

Route::post('bot/sendmessage', function() {
    Telegram::sendMessage([
        'chat_id' => 'RECIPIENT_CHAT_ID',
        'text' => 'Привет, мир!'
    ]);
    return;
});

Предположим, что наша задача — слать уведомления пользователю в телеграм, если на сайте ему ответили на комментарий. Такое уведомление нужно отправлять только конкретному пользователю, а не всем сразу.

Простой способ для привязки пользователя на сайте и чата с ботом в телеграме — использование ссылки-инвайта для добавления чат-бота. Вы даёте пользователю специальную ссылку с параметром, и когда он на неё кликает — открывается чат с ботом, бот получает инструкцию «начать работу» и отправляет информацию на сервер вместе с тем самым параметром из ссылки, а также CHAT_ID. По этому параметру вы находите в своей базе пользователя и назначаете ему CHAT_ID.

Показываем пользователю на сайте ссылку:

https://telegram.me/arutyunov_bot?start=asd123

Когда пользователь перейдёт по ссылке, у него откроется чат 1-на-1 с ботом. Как только пользователь нажмёт кнопку Start, бот получит сообщение вида:

/start PAYLOAD

Вместо PAYLOAD и будет тот самый токен. В нашем примере это asd123. Теперь вам нужно обработать это сообщение на своём сервере.

Вот пример для обработчика, который привязан к Webhook:

if ($message->getText()) {
    if (strpos('/start', $message->getText()) !== false) {
        $textStrings = explode(' ', $message->getText());

        if (isset($textStrings[1])) {
            $token = $textStrings[1];
            $chatId = $message->getChat()->getId();

            if ($token) {
                $user = User::find(['telegram_token' => $token]);

                if ($user !== null) {
                    $user->telegram_chat_id = $chatId;
                    return response()->json(['status' => $user->save()]);
                }
            }
        }
    }
}

В официальной документации Телеграм есть описание этого механизма, который называется Deep linking.

В телеграме каждый чат имеет свой уникальный CHAT_ID. Обратите внимание, что чат с ботом будет иметь числовой CHAT_ID больше 0, а если вы добавили бота в группу, то CHAT_ID будет отрицательный.

Комментарии 14
  1. Александр 3 ноября 2019

    Здравствуйте, Кирилл.
    Спасибо, Вы мне очень помогли.

  2. Кирилл Арутюнов 3 ноября 2019

    Рад, что материал оказался полезным 🙂

  3. Александр 4 ноября 2019

    Кирилл, подскажите пожалуйста, в какой части кода, необходимо прописывать ответ серверу Telegram?
    Мне нужно после каждого обновления возвращать header («HTTP/1.1 200 OK»);
    Сейчас на мой сайт, с телеграмма ежесекундно приходят десятки запросов типа:
    «POST /токен-моего-бота/webhook HTTP/1.1» 500
    Спасибо.

  4. Кирилл Арутюнов 4 ноября 2019

    Лучше в адресе вебхука не указывать токен бота, это не безопасно. Вы можете указать вебхук так: https://api.telegram.org/bot{my_bot_token}/setWebhook?url=https://mysite.ru/bot/telegram_webhook. В таком случае телеграм будет слать вам POST-запросы на адрес https://mysite.ru/bot/telegram_webhook. Собственно если у вас Laravel, то настраиваете роуты, контроллер и обрабатываете ответ от телеграма. Если не Laravel, то обрабатываете по-своему. Но суть везде одна и та же — у вас входящий ПОСТ-запрос, который надо разобрать и в ответ что-то отдать. В случае успеха не забыть код 200, иначе телеграм будет засыпать сервер запросами.

  5. Александр 4 ноября 2019

    Кирилл, спасибо Вам за ответ.
    У меня Laravel 5.8
    Использую компонент irazasyed/telegram-bot-sdk
    Бот рабочий, на мои сообщения — отвечает.
    Проблема как раз в ответе 200 ОК, телеграм сервер уже засыпает мой сайт запросами.
    Прочитал, что нужно вернуть ему header («HTTP/1.1 200 OK»);
    Но где это прописывать? После sendMessage?
    Не могли бы Вы привести часть своего кода, где вы возвращаете серверу код 200.
    Спасибо.

  6. Кирилл Арутюнов 4 ноября 2019

    У меня это примерно так происходит:
    $this→sendMessage ($chatId, $text, $replyMarkup);
    return 'OK';

    Можно и так:
    return response ('OK', 200)→header ('Content-Type', 'text/plain');
    Сначала отправляете сообщение, потом посылаете response с нужными заголовками. SendMessage, по сути, это же curl-запрос к телеграму.

    Единственное, могут быть тонкости при использовании Async-настроек в этом пакете — тут ничего не смогу подсказать, потому что не работал с этим пакетом.

  7. Александр 4 ноября 2019

    Кирилл, благодарю Вас!
    Последний вопрос, если позволите.
    Если отключить webHook, и потом заново его включить setWebHook, от бота приходят старые сообщения.
    Читал, что в случае с getUpdates, нужно отправить update_id последнего обновления api.telegram.org/bot/getUpdates?offset=
    и тогда, бот не будет присылать старые сообщения.
    А как это реализовать с webHook?
    Спасибо.

  8. Кирилл Арутюнов 4 ноября 2019

    Не сталкивался с такой задачей. Я бы в крайнем случае дописал какой-нибудь обработчик, в котором по timestamp’у отсёк бы старые сообщения и в ответ на такие запросы просто возвращал бы 200 ОК и ничего больше не делал бы. Ещё для исключения ддоса со стороны серверов телеграма, если что-то идёт не так, мы писали middleware, который кидал в кеш md5 всех полей запроса и каждый входящий запрос проверял, и если такой md5 уже был в кеше, то вебхук ничего не делал и отдавал 200 ОК. Костыль, но позволял фильтровать запросы от Телеграма, чтобы сервак не перегружался :-).

  9. Александр 4 ноября 2019

    Спасибо, Кирилл. Костыли у меня как раз хорошо получаются в последнее время:)

  10. Andrei 2 февраля 2020

    Добрый день, а что это за переменная, где она обяляется? $message

  11. Кирилл Арутюнов 6 февраля 2020

    Я использовал пакет irazasyed/telegram-bot-sdk для Laravel, с помощью которого можно было сделать так:

    $telegram = new Api (config ('services.telegram-bot-api.token'));
    $updates = $telegram→getWebhookUpdates ();
    $message = $updates→getMessage ();

  12. Foxtes 1 июля 2020

    Отличная статья, спасибо!

  13. Алексей 19 января 2021

    Кирилл добрый день. Подскажите пожалуйста, для тестирования данного сервиса локально — нужно ли использовать https:// протокол? Или же можно протестировать это всё локально через OpenServer?

  14. Кирилл Арутюнов 21 января 2021

    Алексей, https использовать обязательно, без него телеграм не позволяет прописать вебхук. Более того, если вы локально поднимаете сервер, то вам нужно настроить так, чтобы он был доступен снаружи. Сервера телеграма будут слать запросы по адресу вебхука. По-моему, на Опенсервере есть такая возможность, но нужно покопаться в настройках.

Добавить комментарий