A Simple Telegram Time Tracker Bot Creation

Everyone has Telegram nowadays. All of us use this messenger daily — it’s convenient, easy to use, intuitive, secure, and of course we all love stickers. Besides the personal messaging, we often use group chats — with family, friends and colleagues. In addition to live ordinary users, there are also bots in Telegram. They are created to automate replies, meaning the bot responds to specific messages — commands and performs certain actions. An action can be either a simple greeting in response, or a chain of certain questions and answers to perform specific logic operations.

Personally I, for instance, constantly use @vkmusic_bot_news chatbot. It’s a simple bot for music search and listening. A user sends a message with the name of a composition or the author and the bot provides variants, which match the request.

In my example I asked for a song «show must go on» and received several variants to choose from. Having chosen the first variant, I received a track in the next message. It’s convenient that you can listen to music right in the chat with the bot. In such a way a bot can fully substitute a third-party app, like a player on your phone. You just search and listen to the music right away.

So we can see that the telegram chatbots are rather convenient and multifunctional feature of the messenger. That’s why this article is devoted to a simple chatbot creation. I decided to make a primitive time tracker to show you an example. It’s the simplest case of interaction with the telegram API.

Simple Telegram chat bot creation

So let’s start.

You can find the docs here: https://core.telegram.org/bots/api.

First, you should create and register the bot in the Telegram. Let’s find the all-bots Father @BotFather. It’s a bot for bot registration.

We inform him that we want to create our own chatbot:

    /newbot

With the next request he asks to create a name for the bot. Since my bot will track time, I give him the corresponding name and write:

timeTrackerBot

The next step is to create bot’s username (it will be used for search @username). The condition is — the username must end in bot or _bot. I write:

my_time_tracker_bot

It’s ready! We receive a token in response, it means that we have registered a new chat-bot.

Development

To start I decided to look what libraries the internet offers to us. The variants are multiple. The full list of the proposed options can be found here. My choice was TelegramBotApiBundle for Symfony. We install it:

    composer require borsaco/telegram-bot-api-bundle

and proceed.

The bundle supports the work with several bots at a time, besides, there is the option of adjustment (sending only to the developer) and working through proxy. For the test example, we do not need much, so we remove what we do not need.

    config/packages/telegram.yaml

I also carried the token out into the variable APP_TELEGRAM_TOKEN в .env

A bit of Theory

Telegram API, so to say works in two modes: you can get updates from the server through getUpdates() method or adjust the webHook. The first variant is good, because it’s extremely simple, but the drawback of it that you should constantly request server if there are any updates. The webHook variant allows not to think about how to get the updates, but to focus on their rendering. In this case Telegram will send the updates itself to the URL we’ll indicate.

The advantage of the second approach is obvious).

To register a Webhook we’ll do the following:

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

Webhook in the url value is a route, which we’ll make a bit later.

$botService — is an object of the service Borsaco\TelegramBotApiBundle\Service\Bot, which can be injected in any place of the project.

Important: telegram API only supports sending to https!

Let’s now check our Webhook: if you send a message to our telegram bot, it will send us such a data set in response:

    {
      "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"
          }
        ]
      }
    }

We are interested in the part with the message text. In this case, it is clear that I sent the bot «/ help».

Naturally, he does not answer.

The presence of entities in the response indicates that the message was perceived by the bot as a command (everything that starts with a slash and is written in Latin is a command for Telegram).

Let’s create a controller. It will be the entry point for the requests from the Telegram API where to the updates will come.

For convenience, I put all the logic into the MessageProcessor service, where I pass the string received from Telegram.

The essence is simple — there are 4 commands: help, start, stop, report.

To the «help» message or any other one that does not fit into this list, the bot should return a message with a list of the commands available.

Each command presupposes specific behavior.

There are also 2 situations when the answer will tell the user that this action is prohibited at the moment (until the time tracker is stopped — you can’t start a new one. Accordingly, while there is no activity — you can’t stop anything either).

I have added the following entities:

    • User, which has the fields name, telegramId and collection timeLines,
    • TimeLine, which has a starting date and an end date: startedAt, stopedAt.

That is, a user can have multiple timelines (which have a start time and a stop time).

We get a json string, and the first thing I do is decode the string and get the object.

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

Further on, I check if there is such a user in the database (the data about the user who wrote to the bot is taken from response-> message-> from). If there is no such a user, we create it.

Since we want that «help» and «/ help» commands to be perceived by the bot in the same way, we translate the text of the message from the user into lower case and delete the normal and the back slashes.

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

Now we check the received command — if it’s included in the list of commands that we can process. And now, based on what we’ve got, we send a reply.

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

If it’s either a «start» or «stop» command, we create a TimeLine for this user or end the current one accordingly and inform the user about it.

If this is a «report», we calculate the time in all timeline for today and send the total number of hours and minutes to the user. We send the messages with the sendMessage method, which accepts an array of parameters. Required parameters are: ’chat_id’ and ’text’.

A complete list of commands can be found here.

Since this is a test case, I limited myself to a simple switch / case for command selection.

    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]]);
    }

General view of the service is like this:

Thus, we’ve got a bot, which can help you track the time in course of the day.

Conclusion

To draw a line, we can safely say that the Telegram API is quite functional and easy to master. A big advantage is good documentation and the ready-made libraries available for the usage.