Створення простого бота Telegram Time Tracker

Сьогодні кожен має Telegram. Усі ми користуємося цим месенджером щодня - він зручний, простий у використанні, інтуїтивно зрозумілий, безпечний і, звичайно, ми всі любимо стікери. Крім особистих повідомлень, ми часто використовуємо групові чати - з родиною, друзями та колегами. Крім звичайних користувачів, є також бот API Telegram. Вони створені для автоматизації відповідей, тобто API-бот Telegram відповідає на конкретні повідомлення - команди та виконує певні дії.

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

Особисто я, постійно користуюся чат-ботом @vkmusic_bot_news. Це простий бот для пошуку та прослуховування музики. Користувач надсилає повідомлення з назвою композиції або автора, а API-бот Telegram надає варіанти, які відповідають запиту.

У своєму прикладі я запросив пісню «show must go on» і отримав кілька варіантів на вибір. Вибравши перший варіант, я отримав трек у наступному повідомленні. Зручно, що слухати музику можна прямо в чаті з ботом. Таким чином бот може повноцінно замінити сторонній додаток, наприклад плеєр на телефоні. Ви просто шукаєте та одразу слухаєте музику.

Отже, бачимо, що API телеграм-ботів є досить зручною та багатофункціональною функцією месенджера. Тому ця стаття присвячена створенню простого чат-бота. Я вирішив зробити примітивний таймтрекер, щоб показати вам приклад. Це найпростіший випадок взаємодії з API телеграми.

Просте створення Telegram Bot API

Отже, почнемо.

Ви можете знайти документи тут: https://core.telegram.org/bots/api.

Для початку необхідно створити та зареєструвати бота в Telegram. Давайте знайдемо ботів Father @BotFather. Це бот для реєстрації ботів.

Повідомляємо йому, що хочемо створити власного чат-бота:

    /newbot

Наступним запитом він просить створити назву для бота. Так як мій бот буде стежити за часом, я даю йому відповідне ім'я і пишу:

timeTrackerBot

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

my_time_tracker_bot

Готово! У відповідь отримуємо токен, це означає, що ми зареєстрували нового чат-бота.

Розробка

Для початку я вирішив подивитися, які бібліотеки пропонує нам Інтернет. Варіантів безліч. З повним переліком запропонованих варіантів можна ознайомитися тут. Я вибрав TelegramBotApiBundle для Symfony. Встановлюємо:

    composer require borsaco/telegram-bot-api-bundle

і продовжуйте.

Пакет підтримує роботу з декількома телеграм-ботами API одночасно, крім того, є можливість налаштування (відправка тільки розробнику) і робота через проксі. Для тестового прикладу нам багато не потрібно, тому прибираємо непотрібне.

    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: якщо ви надішлете повідомлення нашому телеграм-боту, він надішле нам у відповідь такий набір даних:

    {
      "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». Очевидно, він не відповідає.

Наявність суб'єктів у відповіді свідчить про те, що повідомлення було сприйнято ботом як команда (все, що починається зі слеша і написано латиницею, є командою для Telegram).

Давайте створимо контролер. Це буде точка входу для запитів від Telegram API, куди надходитимуть оновлення.

Для зручності я заношу всю логіку в сервіс MessageProcessor, куди передаю рядок, отриманий з Telegram.

Суть проста - є 4 команди: допомогти, запустити, зупинити, повідомити.

Кожна команда передбачає певну поведінку.

Також є 2 ситуації, коли відповідь скаже користувачеві, що ця дія на даний момент заборонена (поки таймтрекер не зупинено - запустити новий не можна. Відповідно, поки немає активності - зупинити нічого не можна).

Я додав такі суб'єкти:

    • Користувач, який має поля name, telegramId і collection timelines,
    • Часова шкала, яка має дату початку та дату завершення: startedAt, stopedAt.

Тобто користувач може мати декілька часових шкал (які мають час початку та час зупинки).

Ми отримуємо рядок 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», ми створюємо часову шкалу для цього користувача або відповідно завершуємо поточну та інформуємо про це користувача.

Якщо це «report», ми обчислюємо час у всій шкалі часу на сьогодні та надсилаємо загальну кількість годин і хвилин користувачеві.

Ми надсилаємо повідомлення за допомогою методу 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 досить функціональний і простий в освоєнні. Великою перевагою є хороша документація та готові бібліотеки, доступні для використання.