Процесс создания простого бота Телеграм для отслеживания времени

Телеграм есть у каждого. Все мы пользуемся этим мессенджером каждый день — он удобен и прост в использовании, интуитивно понятен, безопасен, и, конечно, все мы любим стикеры. Кроме личной переписки мы используем и групповые чаты — с семьей, с друзьями и коллегами. Кроме нас, обычных пользователей, в telegram есть боты. Они создаются для автоматизации ответов. То есть бот реагирует на конкретные сообщения — команды и выполняет какие-нибудь действия.

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

Я, например, постоянно пользуюсь чат-ботом @vkmusic_bot_news. Это простой бот для поиска и прослушивания музыки. Отправляешь сообщение с названием композиции или именем автора, и бот выдает тебе варианты по твоему запросу. В примере я запросил композицию «show must go on» и получил несколько вариантов на выбор. Выбрав первый вариант я получил трек в следующем сообщении. Удобно, что можно прослушивать прямо в чате с ботом. Таким образом чат-бот может заменить стороннее приложение — проигрыватель на телефоне. Ищешь и сразу же слушаешь.

Таким образом мы подошли к выводу, что телеграм чат-боты довольно удобная и многофункциональная штука. Поэтому в этой статье и пойдет речь о написании простого чат-бота. Для примера я решил сделать трекер времени — простейший пример взаимодействия с telergram api.

Создание простого чат-бота

И так начнем.

Документацию можно почитать тут https://core.telegram.org/bots/api.

Для начала нужно создать — зарегистрировать самого бота в telegram. Ищем отца всех ботов @BotFather — тоже бот для регистрации ботов.

Пишем ему, что хотим создать своего чат-бота.

    /newbot

Следующей командой он просит придумать название. Так как мой бот будет трекать время — название соответствующее. Пишу:

timeTrackerBot

Следующим шагом нужно задать юзернейм для бота (будет использоваться для поиска @username). Условие: username должен заканчиваться на bot или _bot. Пишу:

my_time_tracker_bot

Готово! В ответе нам приходит токен — это значит, что мы зарегистрировали нового чат-бота.

Разработка

Для начала я решил посмотреть какие библиотеки предлагает нам интернет.

Оказалось вариантов не так мало. Тут можно ознакомится со всем списком предлагаемых вариантов.

Мой выбор пал на  TelegramBotApiBundle для Symfony.

Устанавливаем.

    composer require borsaco/telegram-bot-api-bundle

идем дальше.

Бандл поддерживает работу с несколькими ботами одновременно. Кроме того есть вариант отладки (отправка только разработчику) и работа через прокси. Для тестового примера нам много не нужно — убираем все лишнее.

    config/packages/telegram.yaml

Также я вынес токен в переменную APP_TELEGRAM_TOKEN в .env

Немного теории

Telegram api, можно сказать, работает в двух режимах: можно самим получать обновления с сервера через метод getUpdates(), либо настроить webHook. Первый метод хорош тем, что он предельно прост, но плох тем, что нужно самостоятельно постоянно опрашивать сервер на предмет обновлений. Вариант с webhook же позволяет не думать о том, как получить обновления, а сосредоточится на их обработке. В этом случае telegram будет сам отправлять обновления на url, который мы укажем.

Преимущество второго подхода очевидны)

Для регистрации Webhook делаем следующее:

    $bot = $botService->getBot('timeTracker');
    $bot->setWebhook(
    	[
    'url'=>'https://myWebSite.com/webhook'
    ]
    );

webhook в значении url — это роут, который мы сделаем немного позже. $botService — это объект сервиса Borsaco\TelegramBotApiBundle\Service\Bot, который можно инжектить в любом месте проекта.

Важно: telegram api поддерживает отправку только на https!

Теперь проверим наш Webhook: если отправить какое-нибудь сообщение нашему боту telegram пришлет нам вот такой набор данных в ответе:

    {
      "update_id": 21406673,
      "message": {
        "message_id": 24,
        "from": {
          "id": 701891111,
          "is_bot": false,
          "first_name": "aleksei",
          "language_code": "ru"
        },
        "chat": {
          "id": 701891111,
          "first_name": "aleksei",
          "type": "private"
        },
        "date": 1580672814,
        "text": "/help",
        "entities": [
          {
            "offset": 0,
            "length": 5,
            "type": "bot_command"
          }
        ]
      }
    }

Нас интересует часть с текстом сообщения. В данном случае видно, что я отправил боту «/help». Естественно он пока ничего не отвечает.

Наличие в ответе entities говорит о том, что сообщение было воспринято ботом как команда (все, что начинается со слеша и пишется латиницей, — это для telegram команда)

Создаем контроллер. Это будет входная точка для запросов от telegram api, куда будут приходить обновления.

Для удобства я всю логику вынес в сервис MessageProcessor, куда передаю строку, полученную от telegram.

Смысл прост — есть 4 команды: help, start, stop, report.

На help или на любое другое сообщение, не подпадающее под этот список, бот должен вернуть сообщение с перечнем доступных команд.

На каждую команду есть свое поведение.

Также есть 2 ситуации, когда ответ будет говорить пользователю, что данное действие запрещено в данный момент (пока трекер времени не остановлен — нельзя начать новый. Соответственно — пока нет активного, остановить тоже ничего нельзя).

Я добавил такие сущности:

  • User, у которого есть поля name, telegramId и коллекция timeLines,
  • TimeLine, у которой есть дата начала и дата конца: startedAt, stopedAt.

То есть user может иметь несколько timelines (у которых есть время старта и время остановки).

Нам приходит json строка, первое что я делаю — это декодирую строку и получаю объект:

    $response = \json_decode((string)$telegramUpdate);

Дальше проверяю есть ли такой пользователь в базе (данные о пользователе, написавшем боту берутся из response->message->from). Если пользователя нет — создаем.

Так как мы хотим чтобы команда help и /help воспринималась ботом как одно и тоже, переводим текст сообщения от пользователя в нижний регистр и удаляем обычный и обратный слэш.

    $messageText = mb_strtolower($response->message->text);
    $messageText = str_replace([’\\’,/], ’’, $messageText);

Теперь проверяем полученную команду — входит ли она в список команд, которые мы можем обработать. И теперь на основании того, что нам прислали отправляем ответ.

    $messageCommand = $this->isSupports($messageText) ? $messageText : false;

Если это команды start или stop — создаем TimeLine для этого пользователя или завершаем текущий соответственно и сообщаем пользователю об этом.

Если это report — подсчитываем время во всех timeline за сегодня и отправляем суммарное количество часов и минут пользователю.

Сообщения отправляется методом sendMessage, который принимаем массив параметров. Обязательные параметры: ’chat_id’ и ’text’. Полный список команд можно посмотреть здесь .

Так как это тестовый пример, я ограничился простым switch / case для выбора команд.

    switch ($messageCommand) {
      case self::HELP_COMMAND :
         $this->bot->sendMessage(['chat_id' => $user->getTelegramId(),      'text' => self::ANSWERS[self::HELP_COMMAND]]);
         break;
      case self::START_COMMAND :
         if ($this->timelineService->doesActiveExist($user)) {
            $this->bot->sendMessage(['chat_id' => $user->getTelegramId(), 'text' => self::BAD_ANSWERS['existNotStoppedTimeLine']]);
            break;
         }
         $this->telegramService->startTimeForUser($user);
         $this->bot->sendMessage(['chat_id' => $user->getTelegramId(), 'text' => self::ANSWERS[self::START_COMMAND]]);
         break;
      case self::STOP_COMMAND :
         if ($this->timelineService->doesActiveExist($user)) {
            $this->bot->sendMessage(['chat_id' => $user->getTelegramId(), 'text' => self::ANSWERS[self::STOP_COMMAND]]);
            break;
         };
         $this->bot->sendMessage(['chat_id' => $user->getTelegramId(), 'text' => self::BAD_ANSWERS['timeLineNotFound']]);
         break;
      case self::REPORT_COMMAND :
         $timeForToday = $this->timelineService->getTodayTotalByUser($user);
         $this->bot->sendMessage(['chat_id' => $user->getTelegramId(), 'text' => \sprintf(self::REPORT_COMMAND, $timeForToday)]);
         break;
      default:
         $this->bot->sendMessage(['chat_id' => $user->getTelegramId(), 'text' => self::ANSWERS[self::HELP_COMMAND]]);
    }

Общий вид сервиса:

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

Вывод

Как итог можно смело сказать, что telegram api достаточно функционально и просто в освоении. Большим плюсом является хорошая документация и наличие готовых библиотек для использования.