Write a Telegram Bot in Python Flask

2020-10/telegram-chatbot.webp

In this post, we will make a Telegram bot with a Python/Flask Back-end. As example use case we will be making a chatbot where you can request more information about a Pokemon.

Creating a Telegram Bot

To create a Telegram Bot we first have to register it. Telegram actually has there own chatbot for this! The Botfather! You’ll need a Telegram account to talk to him. Afterwards, you can follow the link or search for @Botfather.

Afterwards you can use the /newbot command to create a new bot.

The first prompt will ask you for a display name. The second one will ask you for a username, this one has to be unique.

Afterwards you need to save the Access Token for later use in the Python code.

🔒 SECURITY TIP Make sure you never share your access token or other keys with other people. If you use a Version Control System like Github. Make sure you remove your keys before you commit your code!

Firebase

Set Up Python Dev Environment

  • Create a new folder
1mkdir telegram-pokedex
2cd telegram-pokedex
  • Create a Python Environment
1python -m venv .venv
2source .venv/bin/activate
  • Install Flask, Requests and a Telegram API Wrapper
1pip install flask
2pip install python-telegram-bot
3pip install requests

Back-end Code

Create a new file named [bot.py](http://bot.py) and paste the following code in it. You will need to enter the Access Token you got from the BotFather as the BOT_TOKEN.

You can run your code with the python bot.py command.

 1import re
 2import logging
 3import requests
 4
 5from telegram.ext import Updater, CommandHandler
 6
 7BOT_TOKEN = "YOUR_API_TOKEN"
 8
 9logging.basicConfig(
10    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
11)
12
13logger = logging.getLogger(__name__)
14
15def start(update, context):
16    """Send a message when the command /start is issued."""
17    update.message.reply_text(
18        'Hello, my Name is Professor Oak! \n' 
19        'I can give you more information on Pokemon!'
20    )
21
22def main():
23    updater = Updater(BOT_TOKEN, use_context=True)
24
25    # Get the dispatcher to register handlers
26    dp = updater.dispatcher
27
28    # on different commands - answer in Telegram
29    dp.add_handler(CommandHandler("start", start))
30
31    # Start the Bot
32    updater.start_polling()
33
34    # Run the bot until you press Ctrl-C or shutdown the process
35    updater.idle()
36
37if __name__ == '__main__':
38    main()

Explanation

In the main function, you’ll find an Updater, this is your connection to Telegram and will listen for new messages.

The dispatcher attaches logic to certain messages and commands. For example on the next line we add a command handler to the start command which means that when someone types the /start command in Telegram, your server will execute the start function.

In this case our start function will reply with a welcome message.

If your bot is running you can test this by searching your bot on Telegram and send it a /start message.

Firebase

Adding The Pokemon Lookup Functionality

PokéAPI is a great, free API if you want to use Pokemon Data. In our example, we want to call the API with a Pokemon name and it will return its PokeDex ID, some flavour text and an image of the Pokemon, just like a Pokedex.

To implement this functionality we’ll create a pokedex() function that gets the user query for the command, calls the API

Get the Pokemon from the user command

Telegram will trigger our function when somebody sends the /pokedex command in chat. When we check the message in our code we’ll see that it also sends the command in the message. So we will need to strip /pokedex from the message, to prevent errors we’ll also strip all the spaces and make the query lowercase.

Next up we’ll check if our query is not empty, the user might have sent just the command without a Pokemon name. To help them we’ll send a message back with an example query.

Call the API

If we explore the API documentation, we’ll find that the https://pokeapi.co/api/v2/pokemon/[POKEMON_NAME] route provides all the data except for the flavour text.

If we look a bit further we can find that the [https://pokeapi.co/api/v2/pokemon-species/[POKEMON_NAME]](https://pokeapi.co/api/v2/pokemon-species/[POKEMON_NAME]) link returns all the data we need, except for the image. Luckily we don’t have to call both API’s since the image link is the same for all Pokemon except for the Pokemon ID which is provided by this API so we can get all the data we need. All we need to do is append the user query from the previous step as [POKEMON_NAME] in the URL.

Check the API Response

The user might have sent a name of a Pokemon that doesn’t exist. In this case, the API will return a 404 Not Found error. When this happens we’ll send a message back that we couldn’t find the Pokemon.

Get the data from the response

If the Pokemon is found, the API will return a JSON object. We’ll first have to parse this. Afterwards, we can get all the data we need from the object.

To get the Pokemon image link will have to paste the ID in the link.

To get the Pokemon description we add a default “NO DESCRIPTION FOUND”. Afterwards we’ll go the flavor_text_entries object and look for the first English object. If there is one we’ll replace the default description with the English flavour text. We do this because the flavor_text_entries object has flavour texts in different languages.

Send a message back to the user

Now that we have all the required data we can format it and send it back to the user. To keep this tutorial simple, I just send it back in plain text. But Telegram does support Markdown, images, and much more, check out the docs at https://python-telegram-bot.readthedocs.io/en/stable/ for more information.

Add a Command Handler to the main function.

Last, we have to couple the pokedex command to the pokedex() function. You can do this by adding dp.add_handler(CommandHandler("pokedex", pokedex)) under the start command handler in the main function.

You can find the full code below or on Github.

 1import re
 2import logging
 3import requests
 4
 5from telegram.ext import Updater, CommandHandler
 6
 7BOT_TOKEN = "1396000569:AAHCXgPCh8UgPwkiPW-gp7PXw7RjYz2Mtgk"
 8
 9logging.basicConfig(
10    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
11)
12
13logger = logging.getLogger(__name__)
14
15def start(update, context):
16    """Send a message when the command /start is issued."""
17    update.message.reply_text(
18        'Hello, my Name is Professor Oak! \n' 
19        'I can give you more information on Pokemon!'
20    )
21
22def pokedex(update, context): 
23    """ Searches the Pokemon API for more information and return the data """
24
25    # Get the pokemon from the command and check if it's not empty
26
27    pokemon_query = update.message.text.replace('/pokedex', '').replace(" ", "").lower()
28
29    if len(pokemon_query) == 0:
30        update.message.reply_text(
31            'Please add a pokemon name to your command \n'
32            'Example: /pokedex Pikachu' 
33        ) 
34    else:
35
36        # Call the API
37        response = requests.get('https://pokeapi.co/api/v2/pokemon-species/' + pokemon_query +'/')
38
39        # Check if the data is found by the server
40        if response.status_code == 404:
41            update.message.reply_text(
42                "I could not find the pokemon: {pokemon_query}".format(pokemon_query=pokemon_query)
43            )     
44        else:
45            # Get the data from the response
46            pokemon_data = response.json()
47
48            pokemon_id = pokemon_data['id']
49            pokemon_name = pokemon_data['name'].capitalize()
50            pokemon_desc = "NO DESCRIPTION FOUND"
51            pokemon_image = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/" + str(pokemon_id) + ".png"
52
53            for desc in pokemon_data['flavor_text_entries']: 
54                if desc['language']['name'] == 'en':
55                    pokemon_desc = desc['flavor_text'].replace('\n', ' ')
56
57            # Send a message back to the user
58            update.message.reply_text(
59                "[{pokemon_id}] {pokemon_name} \n\n{pokemon_desc}\n{pokemon_image}".format(
60                    pokemon_id=pokemon_id, pokemon_name=pokemon_name, pokemon_desc=pokemon_desc, 
61                    pokemon_image=pokemon_image
62                )
63            ) 
64
65def main():
66    updater = Updater(BOT_TOKEN, use_context=True)
67     # Get the dispatcher to register handlers
68    dp = updater.dispatcher
69
70    # on different commands - answer in Telegram
71    dp.add_handler(CommandHandler("start", start))
72    dp.add_handler(CommandHandler("pokedex", pokedex))
73
74    # Start the Bot
75    updater.start_polling()
76
77    # Run the bot until you press Ctrl-C or shutdown the process
78    updater.idle()
79
80if __name__ == '__main__':
81    main()

Finishing Up

To finish up we can add a profile image to our Telegram bot. You can do this in the BotFather chat by using the /setuserpic command, selecting your bot and uploading a photo.

You can also host your bot on a service like Heroku.

The final product should look something like this:

Firebase