Fullstack Almanac

Write A Telegram Bot In Python Flask

Cover Image for Write A Telegram Bot In Python Flask

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!

Fetching a bot token trough the botfather,

Set Up Python Dev Environment

  • Create a new folder
mkdir telegram-pokedex 
cd telegram-pokedex
  • Create a Python Environment
python -m venv .venv 
source .venv/bin/activate
  • Install Flask, Requests and a Telegram API Wrapper
pip install flask 
pip 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.

import re
import logging
import requests

from telegram.ext import Updater, CommandHandler

BOT_TOKEN = "YOUR_API_TOKEN"

logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
)

logger = logging.getLogger(__name__)

def start(update, context):
    """Send a message when the command /start is issued."""
    update.message.reply_text(
        'Hello, my Name is Professor Oak! \n' 
        'I can give you more information on Pokemon!'
    )

def main():
    updater = Updater(BOT_TOKEN, use_context=True)

    # Get the dispatcher to register handlers
    dp = updater.dispatcher

    # on different commands - answer in Telegram
    dp.add_handler(CommandHandler("start", start))

    # Start the Bot
    updater.start_polling()

    # Run the bot until you press Ctrl-C or shutdown the process
    updater.idle()

if __name__ == '__main__':
    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.

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] 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.

import re
import logging
import requests

from telegram.ext import Updater, CommandHandler

BOT_TOKEN = "1396000569:AAHCXgPCh8UgPwkiPW-gp7PXw7RjYz2Mtgk"

logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
)

logger = logging.getLogger(__name__)

def start(update, context):
    """Send a message when the command /start is issued."""
    update.message.reply_text(
        'Hello, my Name is Professor Oak! \n' 
        'I can give you more information on Pokemon!'
    )

def pokedex(update, context): 
    """ Searches the Pokemon API for more information and return the data """

    # Get the pokemon from the command and check if it's not empty

    pokemon_query = update.message.text.replace('/pokedex', '').replace(" ", "").lower()

    if len(pokemon_query) == 0:
        update.message.reply_text(
            'Please add a pokemon name to your command \n'
            'Example: /pokedex Pikachu' 
        ) 
    else:

        # Call the API
        response = requests.get('https://pokeapi.co/api/v2/pokemon-species/' + pokemon_query +'/')

        # Check if the data is found by the server
        if response.status_code == 404:
            update.message.reply_text(
                "I could not find the pokemon: {pokemon_query}".format(pokemon_query=pokemon_query)
            )     
        else:
            # Get the data from the response
            pokemon_data = response.json()

            pokemon_id = pokemon_data['id']
            pokemon_name = pokemon_data['name'].capitalize()
            pokemon_desc = "NO DESCRIPTION FOUND"
            pokemon_image = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/" + str(pokemon_id) + ".png"

            for desc in pokemon_data['flavor_text_entries']: 
                if desc['language']['name'] == 'en':
                    pokemon_desc = desc['flavor_text'].replace('\n', ' ')

            # Send a message back to the user
            update.message.reply_text(
                "[{pokemon_id}] {pokemon_name} \n\n{pokemon_desc}\n{pokemon_image}".format(
                    pokemon_id=pokemon_id, pokemon_name=pokemon_name, pokemon_desc=pokemon_desc, 
                    pokemon_image=pokemon_image
                )
            ) 

def main():
    updater = Updater(BOT_TOKEN, use_context=True)
     # Get the dispatcher to register handlers
    dp = updater.dispatcher

    # on different commands - answer in Telegram
    dp.add_handler(CommandHandler("start", start))
    dp.add_handler(CommandHandler("pokedex", pokedex))

    # Start the Bot
    updater.start_polling()

    # Run the bot until you press Ctrl-C or shutdown the process
    updater.idle()

if __name__ == '__main__':
    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:

TaggedChatbotFlask