How to Create a Python Telegram Bot [Hacker Tutorial]

With the popularity of instant messaging platforms and advancements in AI chatbots have experienced explosive growth with as many as 80% of businesses wanting to use chat bots by 2020. This has created great opportunity for freelance Python developers as there is great need for the development of both simple and complex chat bots.

One of the more popular messaging platforms is Telegram with a reported 200 million monthly users.  Telegram provides an excellent API for chat bots allowing the user to not only communicate using text messages, but also multimedia content with images, and video, and rich content with HTML and javascript.  The API can even be used to manage purchases directly within Telegram.

Python is excellent for creating Telegram bots and the extremely popular Python Telegram bot framework makes this much easier allowing you by default to create bots that run asynchronously so you can easily code bots that can communicate with many users at the same time.

Let’s get started with making our first telegram bot in Python, the first thing we’ll need to do is download Telegram, create an account and communicate with the “Botfather”.  The Botfather is a chat bot created by telegram through which we can get our Telegram Bot API token.

To download telegram head to Telegram.org and download and install the appropriate version for your OS, install and create an account.

Now add the botfather as a contact, do this by clicking the menu icon selecting contacts, search for “botfather” and selecting the @botfather user.

A conversation will popup with a start button at the bottom, click the start button and you will receive a list of commands.

The command to create a new bot is /newbot enter “/newbot” and answer the prompts for naming your bot and you will receive a Telegram Bot API token.  You can name the bot whatever you like, but the bot username will need to be unique on Telegram.

Store the access token somewhere as we will need it to authorize our bot.

Installing The Python Telegram Bot Framework

For the bot creation we will be using Python version 3.7.  The Python Telegram Bot framework is compatible with Python 2.7 and above.

Before we get to the actual coding we will need to install the Python Telegram Bot framework the easiest way to do that is with:

$ pip install python-telegram-bot

If you prefer to use the source code you can find the project on Github.

Now with the Python Telegram Bot library installed let’s get started.

Connecting Your Bot To Telegram

The first thing you’ll need to do is have the bot connect to and authenticate with the telegram API.

We’ll import the Python logger library to make use of the Python Telegram Bot frameworks built in logging so we can see in real-time what is happening with the bot and if there are any errors.

Place the following code into a Python file, and place your telegram bot key where indicated in the update statement:

from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, Dispatcher
import logging

logging.basicConfig(format='%(levelname)s - %(message)s',
                    level=logging.DEBUG)
logger = logging.getLogger(__name__)

updater = None

def start_bot():
    global updater
    updater = Updater(
        '### YOUR TELEGRAM BOT AUTHENTICATION KEY HERE ###', use_context=True)

    updater.start_polling()
    updater.idle()

start_bot()

Now looking at the top portion of the code you can see that we imported a number of libraries from the telegram bot module, and set the logger to display any messages of debug priority or higher.

We’ve created an updater variable and this will hold the updater for our Telegram bot, which is placed in a global variable so that we can easily access it later from the UI. 

Updater provides an easy front end for working with the bot, and to start our new bot with the Updater we simply need to pass in the authentication key, and we’ll also pass in use_context=true to avoid any deprecation errors as context based callbacks are now the default for Python Telegram bot.

updater.start_polling() actually starts the bot, and after this is passed in the bot will begin to start polling Telegram for any chat updates on Telegram.  The bot will begin polling within its own separate threads so this will not halt your Python script.

We use the updater.idle() command here to block the script until the user sends a command to break from the Python script such as ctrl-c on windows.

When running this script you will be able to see messages that the bot and the updater have started, and a number of threads are running.  The bot will not do anything noticeable as of yet in Telegram.

Making The Bot Understand Commands

Let’s update the script and make our start_bot function look like this:

def start_bot():
    global updater
    updater = Updater(
        '### YOUR TELEGRAM BOT AUTHENTICATION KEY HERE ###', use_context=True)

    dispatcher = updater.dispatcher

    dispatcher.add_handler(CommandHandler('start', start))

    updater.start_polling()

    updater.idle()

We’ve added a dispatcher variable for clearer access to the dispatcher for our bot, we’ll use the dispatcher to add commands.

With the line dispatcher.add_handler(CommandHandler('start', start)) we have added a command handler that will execute when the user enters /start and will execute the callback function start.  This command automatically executes when you add a bot as a new contact and press the start button within Telegram .Now add in the function for our start command:

def start(update, context):
    s = "Welcome I Am The Finxter Chat Bot! Your life has now changed forever."
    update.message.reply_text(s)

Command handlers require both an Update and a CallBackContext parameter.  Through the Update we send updates to chat, here using update.message.reply_text automatically adds the reply only to the specific chat where the /start command was sent.

Now when entering the chat with the bot, or typing the /start command you should get a reply like this:

Adding More Advanced Command That Reads Chat

The start command executes a function within our bot whenever a user types /start, but what if we want our bot to read and respond to chat rather than simply an executing a command?

Well, for that we use a different type of handler called a MessageHandler.

Add the following line to the start_bot function underneath the previous add_handler statement:

dispatcher.add_handler(MessageHandler(Filters.text, repeater))

This will add a MessageHandler to the bot, we also use a filter here so that this message handler Filters everything except text because the user could be posting something other than text in their messages (such as images or video).  For this handler we will create a callback function named repeater with the code:

def repeater(update, context):
    update.message.reply_text(update.message.text)

In our repeater we use the reply_text method, replying with update.message.text which sends the message chat text back to the user.

Turning A Bot Command Off Or On For The User

A common bot ability is to turn on or off specific commands, but there is a problem that arises which is that we cannot simply remove the Handler for the functionality as that would remove it for all users of the bot. 

Fortunately, Python Telegram Bot allows us to store user specific data using the context that is passed in to our callback functions. Let’s add another handler underneath the repeater handler:

dispatcher.add_handler(CommandHandler('echo', echo))

Now, we’ll first modify the repeater handler to check to see if the user’s text should be echoed:

def repeater(update, context):
    if context.user_data[echo]:
        update.message.reply_text(update.message.text)

Here we added the statement if context.user_data[echo]: before having the bot reply to the user.  Python Telegram Bot has a user_data dictionary that can be accessed using the context. 

This is specific to the user, and by using this we can make sure that if there are multiple users of the bot they are unaffected.

Now we’ll add in another function so the user can set the echo dictionary using the echo command in chat:

def echo(update, context):
    command = context.args[0].lower()
    if("on" == command):
        context.user_data[echo] = True
        update.message.reply_text("Repeater Started")
    elif("off" == command):
        context.user_data[echo] = False
        update.message.reply_text("Repeater Stopped")

In this callback function we gather the users extra command parameters from the context.   The users parameters are contained with context.args which provides an array based on the spaces from the user, in this function we check the first parameter passed by the user looking for on or off and change the user_data[echo] variable.

Posting Data  From The Web And Securing Commands

Python Telegram Bot makes it easy to reply with files from the web, such as photos, videos, and documents you simply need to give a url to the specified content type. 

We’ll use the Unbounce API to gather a free image based on user provided terms and post it into chat, and we’ll also use a Filter so that this command only works for your username. 

Add the following code to the start_bot() function:

dispatcher.add_handler(CommandHandler('get_image', get_image, filters=Filters.user(username="@YOUR_USERNAME")))

Replace YOUR_USERNAME with your username.

This code will execute the get_image function, but only if the username matches your own.  With this filter we are only passing in 1 username, but you could also pass in a list of usernames.

Now let’s create the get_image function:

def get_image(update, context):
    terms = ",".join(context.args).lower()
    update.message.reply_text(f"Getting Image For Terms: {terms}")
     command = context.args[0].lower()
    if("on" == command):
        context.user_data[echo] = True
        update.message.reply_text("Repeater Started")
    elif("off" == command):
        context.user_data[echo] = False
        update.message.reply_text("Repeater Stopped")

Like in the previous example we get the terms using the args variable from the context, but in this case we join the terms together with a , and convert to lowercase because that is what is required by the Unbounce API.

You can then get an image in the chat using /get_image and some keywords like this:

Adding A GUI

Now we have learned how to a bot running, and handling commands.  Thanks to the multi-threaded nature of Python Telegram Bot this bot can handle many users from all round the world.

In many projects on freelance sites such as Upwork the client does not care what language is used to create the bot.  However, they often want an interface for managing the bot so let’s create a simple interface that allows the bot owner to start and stop the bot.

To build our user interface we’ll use the PySimpleGUI library.  With PySimpleGUI you can create cross platform GUIs that work on Windows, Mac and Linux without any source code with an easily readable syntax and minimal boiler plate code.

To begin adding the GUI code let’s first remove the updater_idle() line from our start_bot function, so your start_bot function reads like this:

def start_bot():
    global updater
    updater = Updater(
        '### YOUR TELEGRAM BOT AUTHENTICATION KEY HERE ###', use_context=True)

    dispatcher = updater.dispatcher

    dispatcher.add_handler(CommandHandler('start', start))
    dispatcher.add_handler(MessageHandler(Filters.text, repeater))
    dispatcher.add_handler(CommandHandler('echo', echo))
    dispatcher.add_handler(CommandHandler('get_image', get_image, filters=Filters.user(username="@YOUR_USERNAME")))

    updater.start_polling()

By removing the updater.idle() line the bot no longer pauses the script after starting and runs in a separate thread until we decide to stop the bot or the main thread stops.

Now we’ll create a GUI, this GUI will consist of a status line to show whether the bot is currently turned on along with a start, and stop button, and a title like this:

To create this gui add the following code:

def gui():
    layout = [[sg.Text('Bot Status: '), sg.Text('Stopped', key='status')],
              [sg.Button('Start'), sg.Button('Stop'), sg.Exit()]]

    window = sg.Window('Finxter Bot Tutorial', layout)

    while True:                             
        event, _ = window.read()

        if event in (None, 'Exit'):
            break

    window.close()

Now to start the gui remove the start_bot() statement at the bottom of our script and replace with gui():

gui()

In the layout variable you can see that we’ve defined some text and button elements, and each element in the list shows up as a line within our UI.  Our first line consists of 2 items to show the status (we gave the second element a key so we can easily refer to it later), and our second line consists of three buttons.

The sg.Window function is where we provide our title, and layout.

The while True: loop is the standard PySimpleGUI event loop.

The window.read() function returns any GUI events along with any values passed along with the event (such as user inputted text), we won’t use any values in our loop so we pass them to the _ variable, you can pass a time to wait to the windows read function in milliseconds, passing nothing as we have makes the function wait until an event is triggered.

The if event in (None, ‘Exit’): statement executes if the user hits the Exit button or the user closes the window by another means (such as the close button in the corner of the window), in this case we simply break the loop.

Starting And Stopping The Bot From The GUI

Now if you start the script the start and stop buttons won’t actually do anything so we’ll add in the code to start and stop the script and update the status making our gui function look like this:

def gui():
    layout = [[sg.Text('Bot Status: '), sg.Text('Stopped', key='status')],
              [sg.Button('Start'), sg.Button('Stop'), sg.Exit()]]

    window = sg.Window('Finxter Bot Tutorial', layout)

    while True:
        event, _ = window.read()
            
        if event == 'Start':
            if updater is None:
                start_bot()
            else:
                updater.start_polling()
            window.FindElement('status').Update('Running')
        if event == 'Stop':
            updater.stop()
            window.FindElement('status').Update('Stopped')

        if event in (None, 'Exit'):
            break

    if updater is not None and updater.running:
        updater.stop()
    window.close()

Looking at this code you can see we added two different event conditions, the Start and Stop share the same names as our buttons, and when a button is pressed in PySimpleGUI an event is triggered based on the button name. 

In our start event we start the bot using start_bot if there is no updater yet, otherwise we execute the start_polling method of our updater as re-starting the updater in this way is much quicker than using start_bot to initialize the bot.

We also use the find_element function of the window to access the status text using the key we created of ‘status’ and change that to show the bot is running.

Turning GUI Buttons On And Off

Now if we start our script we get the user interface, and pressing the start button will start the bot, and pressing the stop button will stop the bot, but we get an error when we press the buttons out of order. 

We’ll remedy this situation by modifying our event loop to disable the start and stop buttons so the user can only do one or the other at the appropriate times.

        if event == 'Start':
            if updater is None:
                start_bot()
            else:
                updater.start_polling()
            window.FindElement('Start').Update(disabled=True)
            window.FindElement('Stop').Update(disabled=False)
            window.FindElement('status').Update('Running')
        if event == 'Stop':
            updater.stop()
            window.FindElement('Start').Update(disabled=False)
            window.FindElement('Stop').Update(disabled=True)
            window.FindElement('status').Update('Stopped')

You can see that we used the FindElement method on the buttons here, and then using the Update method changed the disabled variable which allows you to disable buttons.

If you start up now you’ll see that at first both buttons are enabled so we’ll have to make a small modification to the layout.  Change the layout variable to the following:

layout = [[sg.Text('Bot Status: '), sg.Text('Stopped', key='status')],
              [sg.Button('Start'), sg.Button('Stop', disabled=True), sg.Exit()]]

Buttons should now enable and disable appropriately within the GUI as shown:

And there we have it, a working GUI for our Telegram Bot.

Conclusion

We learned quite a bit in this tutorial, and now you can have a Python Telegram Bot that responds to commands, and even presents data shown from the web. 

We also created a simple UI for starting and stopping the bot.  With your new skills you’re now ready to answer those potential clients on freelancing sites looking for a Telegram Bot. 

About the Author

Johann D.

Johann is a Python freelancer specializing in web bots, and web scraping. He graduated with a diploma in Computer Systems Technology 2009. Upwork Profile.