Creating a Telegram bot with Micronaut

Share This Post

Share on facebook
Share on linkedin
Share on twitter
Share on email

Telegram is a well known instant messaging application used worldwide for millions of users, so no presentation is needed here. Today on behalf of Apiumhub team we are going to focus on a special feature: bots, third-parties applications that run inside Telegram. Users can interact with bots via messages and commands. These applications can have multiple usages: getting some information, purchasing items, chat support for ecommerces,… you name it. And today’s article is about how to create a Telegram bot with Micronaut.

Creating a Telegram bot with Micronaut

To see how they work, we are going to create a telegram bot with some basic commands for fun. The project will be a bot that returns information about a requested population. We are getting this data from an open third party API. The service will be created using the Micronaut framework to see if it is suitable for our needs.

To have our bot up and running we need 2 pieces:

  1. A telegram account which will be our bot
  2. A webservice that will interact with the bot

Let’s see the steps to create the whole project.

Create the bot

Telegram offers a bot that creates the new accounts: the botfather
Interacting with the botfather via your telegram account, typing some commands (/newbot to start), it will create your bot.

Screenshot 2021 01 28 at 10.37.41

The botfather will create an access token which will be needed to interact with the telegram API.

Configure the webservice

To receive the requests from our bot, Telegram offers two options:

1.- long polling – our webservice will be connected to Telegram servers all the time just waiting for a request
2.- Webhooks – Our service has to provide an API where telegram will call everytime there is a new message.

We are going to use webhooks, since it’s simpler and consumes less resources (our webservice is idle just waiting for new connections, instead of having one open connection the whole time).

To configure the url a simple call to telegram api is needed:


curl -F "url=https://YOURDOMAIN.EXAMPLE/WEBHOOKLOCATION" https://api.telegram.org/bot/setWebhook

Attention!

  • The url must be with https
  • The port can only be one of these: 443, 80, 88 or 8443
  • For security reasons, it may be a good idea to allow only requests from telegram networks.

Our bot is already configured and any message that we send to it will be received to our webservice.

How to reply to the requests

To reply our bot with the response, Telegram also gives us 2 options:

1-. Reply the response in the same request

Screenshot 2021 01 28 at 10.41.04

2.- Reply the request with a HTTP 200 and send a new request to the API.

Screenshot 2021 01 28 at 11.33.18

Since our webservice will interact with third providers and we can’t know how long it will be their response, we’ll choose the second option: we’ll send an immediate OK and we’ll send a POST to the API with the response once we have the data.

Create the webservice

To create the webservice we are going to use Micronaut. It’s a JVM framework (it can be written in Java, Kotlin or Groovy) that promises low memory consumption and offers a non-blocking http server which seems very convenient for our use case: we need a light framework that replies the OK immediately while it processes the request. This asynchronous call will be handled via RxJava since Micronaut supports reactive streams.

With their tool micronaut launch is very easy to obtain a skeleton for our application. It allows us to choose language, build tools, test framework and extras if needed.

With the framework up and running, we create the controller that will handle the requests:


@Controller("/bot")
class BotController {
    @Post("/")
    fun index(@Body request: String): HttpResponse {
        processRequest(request)
        return HttpResponse.ok()
    }
}

* La anotación @Controller define el controlador con la ruta /bot
* @Post define el método para manejar Http POST como se espera.
* La petición se procesa de forma asíncrona y el 200 OK se responde inmediatamente.

To manage the telegram protocol, we are going to use one open source library that has already implemented all the requests and responses for the Telegram API . Many of them written in different languages are listed by telegram.

Implement the logic of the bot

For our testing purposes, we are going to use the Open Data offered by the Statistical Institute of Catalonia to get some information about cities, towns and villages of Catalonia. Using their Population search API and The municipality in figures API we will be able to request data about a specific city and show it to the user.

The steps would be that first the user searches for a name with /town XXX and the bot will respond with a list of possible city names with a link next to it. If the user clicks on any link confirming the name of the place, the population information will be shown.

So far, we already need to handle 2 possible requests, let’s write them.

1.- Get the list of cities


{...}
    private fun processRequest(request: String) {
        val message: Message = BotUtils.parseUpdate(request).message()
        val chatId: Long = message.chat().id()
        val messageReceived = message.text()
        when (messageReceived.split(" ")[0]) {
            "/start" -> sendResponse(chatId, "This is the very beginning")
            "/town" -> getCityNamesList(messageReceived, chatId)
            else -> sendResponse(chatId, messageReceived.reversed())
        }
    }
{...}
  private fun getCityNamesList(messageReceived: String, chatId: Long) {
        val search = messageReceived.split(" ").drop(1).joinToString("%20")
        val body: Flowable
        try {
            val client = RxHttpClient.create(URL("https://api.idescat.cat"))
            val towns: HttpRequest = HttpRequest.GET("/pob/v1/sug.txt?p=q/$search;tipus/mun")
            body = client.retrieve(towns).doOnError { sendResponse(chatId, "Error. Please try another name") }
        } catch (e: Exception) {
            sendResponse(chatId, "Nothing found. Please try another name")
            return
        }
        body.subscribe {
            val split = it.split("\n")
            val eq = "These are the cities found:\n" + split.map { "${cleanName(it)} /${it.base64encode()}" }
                .joinToString("\n")
            sendResponse(chatId, eq)
        }
    }
  private fun sendResponse(chatId: Long, reply: String): SendResponse? {
        val bot = TelegramBot(TELEGRAM_TOKEN)
        return bot.execute(SendMessage(chatId, reply))
    }

The /town XX command will cause a request to the external API to get the list of cities. RxHttpClient will create a Flowable that eventually will return the response from the external call. We’ll capture that event because we have subscribed to it (body.subscribe). Doing so, the process won’t wait for its response and won’t block the whole request. 

The async response is sent via the Telegram library which only needs as a configuration the access token that the botFather created for us. 

2.- Get the information of a given city


private fun getCityInfo(chatId: Long, messageReceived: String) {
        val cityDecoded: String
        try {
            cityDecoded = messageReceived.replace("/", "").base64decode()
        } catch (e: Exception) {
            sendResponse(chatId, "invalid text")
            return
        }
        val client = RxHttpClient.create(URL("https://api.idescat.cat"))
        val towns: HttpRequest = HttpRequest.GET("/emex/v1/dades.json?i=f171,f36,f42&lang=en&id=${cityDecoded}")
        client.retrieve(towns).subscribe {
            val data = Gson().fromJson(it, Fitxes::class.java)
            val texts = data.fitxes.indicadors.i.map { "${it.c}: ${it.v.split(",").first()}" }
                .joinToString("\n")
            sendResponse(chatId, "Information about ${cleanName(cityDecoded)} \n${texts}")
        }
    }

/XXXX where XXX is the base64 coded name will cause a request to the external API to get information for that specific city.

Again we create a flowable and subscribe to it to get the results async and send them back to Telegram.

Bot is working

Here we can see a screenshot of the bot, searching information about Barcelona. As a bonus track, any unexisting command will return the string reversed.

bot is working

Conclusion
That was a fun project to know how Telegram bot with Micronaut  work and discover that there are endless possibilities for this kind of bots. The usage of the Micronaut framework suited very well for this specific case, but for sure it can be a tool to have in mind for other cases specially when asynchronous processes need to be handled.

Leave a Reply

Your email address will not be published. Required fields are marked *

Subscribe To Our Newsletter

Get updates from our latest tech findings

About Apiumhub

Apiumhub brings together a community of software developers & architects to help you transform your idea into a powerful and scalable product. Our Tech Hub specialises in Software ArchitectureWeb Development & Mobile App Development. Here we share with you industry tips & best practices, based on our experience.

Popular posts
Free PDF with Software Architecture Interviews

Have a challenging project?

We Can Work On It Together