write statefull bot telegram part2 header image with telegram icon

Bot Dev: Write a stateful Telegram bot – Part 2

In the first article, we implemented pretty simple Telegram bot, which gathers user requests to fix something. It’s already can be used in our business while we want to post-process requests with a separate tool. But, maybe, it would be too easy for us, so we will go ahead.

Application Requirements

In this article we will add next features to our bot:

  • initial authentication of our repairers
  • requests broadcasting to our repairers
  • request assignment to the repairer

Telegram Bot Deeplinking

Let’s imagine that we have some separate administrative tool for our bot. And there we defined several unique IDs and assigned them to our employees/friends ( friends-employees? Why not). And now we need to authenticate them in our bot, so we can send them fix requests from our customers. We can use deep linking from Telegram to solve this task.

In simple words: we will provide our repairers with a link to the bot which already contains some unique ID so we can identify each separate user. Let’s try this with simple start command which will return us our unique id.

And updated command router bellow:

To be able to communicate to our bot, in the root of our project directory, we start our nodejs application with the command:

If now we open link http://telegram.me/repairMyStuffBot?start=someUniqueKey (do not forget to replace bot name) we will see chat with our bot and button start:

telegram bot start

After clicking on start:

Telegram bot deep linking

Seems to work as expected. We do not have that admin tool and we still need some content for testing. Let’s add database seeding for our MongoDB. Database seeding is the initial seeding of a database with data – surprised, huh?

First, we will adjust config.

In this code snippet, we added extra field seedDB – we will seed DB only in development mode (we do not wanna to overwrite this collection on Production instance, right?).

Next, we want to work on the new model for our users.

The Model contains only 3 fields (ok, actually it’s 4, but we do not define _id field explicitly):

  • startId – unique id for user authentication
  • userID – associated telegram userId with id above
  • type – can have one of 2 values: REGULAR and REPAIRER. Each value will be used for a separate group of the users. Users which got link with correct start ID will get REPAIRER value, all others – REGULAR

And now, finally, seeding code:

Here we remove all items in users collection and insert several predefined repairers. So seeding is here, the model is here, but how about execute it? Let’s navigate to our index.js:

Nothing special here – we just execute seed method if seeding is enabled. Let’s run our application and check results.

Console output:

telegram bot start console

And now check MongoDB:

telegram bot mongodb seeding

We are done with database seeding.

User DAO

At this point, you have your mongoose scheme for User implemented and we can go ahead just with it. But the title of this section says User DAO! Why do we need dao? If it is implemented in a good way, we can suffer much less in case of persistence storage changes (and trust me, we will have this change =) )

For now, it will have 2 methods only. First method is for getting repairer by ID. Second – just for saving user.

So we are ready to use the power of deep linking. It’s time to rewrite start command.

Now if we open link with id from our database we will be registered as a repairer. You can access it with a link telegram.me/YourBotName?start=repairer1

telegram bot user registration (authentication)

And in MongoDB, we can see that userId has been assigned to the user entry.

telegram bot mongodb saved repairer

Broadcasting messages

Now, with recognized repairers, we will need to broadcast user requests in order to notify them.

Let’s first rearrange some files:

  • new folder src/command/request
  • fixRequest.js → new folder
  • myRequests.js → new folder
  • adapt src/command/index.js

We will need new method in our UserDao – method which will return all repairers.

Here we search for our menders who has not null userId field (so user already got a link and started a chat with our bot). And we are ready to implement broadcasting itself.

In this code snippet, we’re doing next: first, we find all registered menders and then we send given request to each of them.This command is not a standalone command, but will be triggered from the FixRequest. So we need update it as well.

Now, after request is saved, we broadcast it. Let’s test it.

To do this we will perform next steps:

  • register as repairer
  • send fix request (yes, not really real life situation, it’s just for testing purposes).
  • check message from the bot

telegram bot broadcast request

If we will stop at this point, is there anything that can go wrong? Definitely yes – 10 repairers can be interested in one request. And they do not know if somebody will arrive at the place faster. You need a mechanism of requests assignment so that our menders do not have such problems.

There are several options how to do this in telegram, but we will go with callback buttons.

Let’s check code bellow.

Method sendMessage can accept the third parameter as reply options. And one of the options may be inline_keyboard – 2-dimensional array. First dimension is rows and 2nd – columns.

In our case it’s only one button, so we do not care about this. What we care about is callback_data. It consists of 2 parts:

  • prefix ‘assignRequest_’ which will be used, to route user response to correct handler, which we will implement next
  • requestId – to determine which request should be assigned to the repairer, who will press the button.

So, when method handle is called we send this request to all repairers and also show button with text “Assign to me”. When repairer clicks on it – request id with prefix assignRequest_ will be sent back to the bot.

For testing, we will use the same (repairer) account for submitting a request and applying on it.

telegram bot callback button

Now we have button, but it does nothing, because we do not handle button click. For handling callback query messages (messages, our bot gets when user clicks on such buttons) we need to wrap it into our dto:

Nothing special here, so we will implement handler for such messages now.

Callback query handler has a different contract – instead of implementing handle method, it implements handleCallbackQuery.

Let’s go through the code:

  1. we get request id from the message. As long as message data contains current handler’s key – we need to remove it
  2. Then check if such request exists and is not assigned to another repairer. Then assign it.
  3. After changes are saved – notify repairer about this

In real life application, we would need to add more checks here (like if current user is a real repairer), but for the sake of simplicity we skipped it.

We are almost done here. Let’s check src/lib/messenger.js . Here we handle only “text” events. Now we need to handle callback queries as well. To do this, we need to add one row right after the text handling:

And new method:

Also, do not forget to add import for CallbackQueryMessage. And, finally, let’s add mapping to the router of our handlers:

We created another dictionary here for callback query handlers and separate method to get such handlers. And again, message data contains handler key as a prefix, so we need to get it from the data.

Let’s do final checks of what we have done – authenticate yourself as a repairer, and submit request. After you clicked on the button “Assign to me” you should see message:

telegram bot callback button submit

And let’s check MongoDB:

telegram bot saved state

We can see that request is assigned to the repairer.

Conclusion

So now have bot with pretty handy functionality. In the article, we learned some new features which are provided by Telegram API, like deep linking and callback buttons. In the next article we will add some humanity to it.

P.S.: will be happy to get questions from you and see you soon.

You can find the complete code in this GitHub repo.

One thought on “Bot Dev: Write a stateful Telegram bot – Part 2

Your thoughts are welcome