Create your own Telegram bot with Django on Heroku – Part 3

This entry is part 3 of 11 in the series Create your own Telegram bot with Django on Heroku

⚠️ This article is outdated and discontinued since Heroku decided to no longer offer their free tiers as this article series suggests to use in August, 2022. Please see this post for details. ⚠️

In the previous part of this series, I explained how to register bots on Telegram, how to configure it and how to validate everything is working.

Today I will explain a bit more on how the HTTP API works, how the JSON data provided by the bots ist structured and I will introduce you to telepot, the Python module of my choice for interacting with Telegram bots using Python.

Requirements

By now, you should already have a bot registered and know it’s token. Also, you should have sent a few messages to your bot.
It is a good idea to re-send some messages to the bot shortly before you start pulling them from it since otherwise, chances are that they got removed from the Telegram servers in the meantime. From the Telegram API docs:

Incoming updates are stored on the server until the bot receives them either way, but they will not be kept longer than 24 hours.

We will use Python 3.6 for the next steps and I will work in a virtualenv. If you are not already familiar with virtualenv and virtualenvwrapper, please familiarize yourself with these tools first, since that is not covered in this series. But since these are so commonly used tools in the Python world, you won’t have any issues finding guides for this. My personal recommendation is “Python Virtual Environments – A Primer” by RealPython.

Creating a virtualenv

To not interfere with any Python on our system, I will prepare an empty directory on my system and create a new virtualenv for it:

telegrambot $ mkvirtualenv -p python3.6.5 telegram
Running virtualenv with interpreter 
...
(telegram) telegrambot $ pip install telepot
Collecting telepot
...
Installing collected packages: attrs, async-timeout, idna, idna-ssl, chardet, multidict, yarl, aiohttp, urllib3, telepot
Successfully installed aiohttp-3.3.2 async-timeout-3.0.0 attrs-18.1.0 chardet-3.0.4 idna-2.7 idna-ssl-1.1.0 multidict-4.3.1 telepot-12.7 urllib3-1.23 yarl-1.2.6
(telegram) telegrambot $ pip list
Package       Version
------------- -------
aiohttp       3.3.2  
async-timeout 3.0.0  
attrs         18.1.0 
chardet       3.0.4  
idna          2.7    
idna-ssl      1.1.0  
multidict     4.3.1  
pip           18.0   
pkg-resources 0.0.0  
setuptools    40.0.0 
telepot       12.7   
urllib3       1.23   
wheel         0.31.1 
yarl          1.2.6  
(telegram) telegrambot $

Now we should be ready to give this a Python test-drive!

Test-driving the bot with telepot

To start with something easy, the following code should print the messages received by the bot so far to the screen. Please note, that the code tries to extract the bot’s token from the environment variable “BOT_TOKEN”. To make this code work for you, please export your personal bot token to the environment variable “BOT_TOKEN” before launching the Python shell:

telegrambot $ export BOT_TOKEN="my_super_secret_token"

In the Python shell, we do the following to initialize the bot:

>>> import os
>>> import telepot
>>> from pprint import pprint
>>> bot = telepot.Bot(os.environ.get('BOT_TOKEN'))

As you can see, we are fetching the bot token from the previously exported environment variable “BOT_TOKEN”. This is a nice way to make sure it doesn’t get pushed to a code repository in the heat of the moment. Since this will be done like this when we are working with Heroku, it does not hurt to get used to it early.
“pprint” will come in handy in a minute, since it prints JSON like structures nicely.
The rest is pretty straightforward; you have a fully fledged bot object now called “bot”.
To control that we definitely have created this using the correct token, we can print its details:

>>> bot.getMe()
{'id': 667090674, 'is_bot': True, 'first_name': 'MoneyBot', 'username': 'demo76812bot'}
>>> pprint(bot.getMe())
{'first_name': 'MoneyBot',
 'id': 667090674,
 'is_bot': True,
 'username': 'demo76812bot'}

To show how handy “pprint” is, I have shown both variants here. It is not really hard to overlook this, but most certainly you will be happy to make it like this once you are printing multiple messages data structures, which is what we will do next: Let’s have a look at the messages the bot has received so far!

Polling the messages from the bot

The messages can be fetched from the bot while it was not switched to webhook – mode. If you haven’t done this, it should be disabled by default. To make sure, you can use “bot.deleteWebhook()”, but this is absolutely optional.

To grab all the messages from it, simply do the following:

pprint(bot.getUpdates())
[{'message': {'chat': {'first_name': 'Marc',
                       'id': REMOVED,
                       'last_name': 'Richter',
                       'type': 'private'},
              'date': 1533248344,
              'from': {'first_name': 'Marc',
                       'id': REMOVED,
                       'is_bot': False,
                       'language_code': 'de',
                       'last_name': 'Richter'},
              'message_id': 4,
              'text': 'Test'},
  'update_id': 941430900},
 {'message': {'chat': {'first_name': 'Marc',
                       'id': REMOVED,
                       'last_name': 'Richter',
                       'type': 'private'},
              'date': 1533248578,
              'from': {'first_name': 'Marc',
                       'id': REMOVED,
                       'is_bot': False,
                       'language_code': 'de',
                       'last_name': 'Richter'},
              'message_id': 5,
              'text': 'Test2'},
  'update_id': 941430901}]

I think, you already got an idea why “pprint” is a good choice here …
Anyways, let’s take a moment to analyze this JSON structure (you can also look at https://core.telegram.org/bots/api#message for details):

Each element seems to have two elements on the top level:

  • message
  • update_id

While “message” is another structure, “update_id” defines the unique id of this update; no other update has this id. Update IDs are counted up sequentially. So: Newer update IDs will always be of a higher number than older ones. This can be used to prevent a message gets processed several times; when we are in webhook-mode, it can possibly happen that an update is forwarded to our bot multiple times; keep that in mind for now.

Let’s stick to this getUpdates method a bit longer: If you now send another message to your bot and simply repeat this getUpdate code, you will notice that you receive this new update, but together with the previous ones. Normally, we do not want that, since these were processed already.
For this, you can tell getUpdate which updates you want to receive. We already learned from the JSON received before, that in our previous getUpdates call, the latest update ID received was “941430901” (align this to your bot’s IDs!). To just receive what has been sent to it after this, we simply provide this info to the parameters of getUpdates:

pprint(bot.getUpdates(offset=941430901+1))
[{'message': {'chat': {'first_name': 'Marc',
                       'id': REMOVED,
                       'last_name': 'Richter',
                       'type': 'private'},
              'date': 1533331684,
              'from': {'first_name': 'Marc',
                       'id': REMOVED,
                       'is_bot': False,
                       'language_code': 'de',
                       'last_name': 'Richter'},
              'message_id': 6,
              'photo': [{'file_id': 'AgADAgADAqkxGwtWKUtXfH8fbld5p1PJRg4ABOlI9pno9lMDktEEAAEC',
                         'file_size': 1358,
                         'height': 90,
                         'width': 67},
                        {'file_id': 'AgADAgADAqkxGwtWKUtXfH8fbld5p1PJRg4ABMf7egTc09grk9EEAAEC',
                         'file_size': 17479,
                         'height': 320,
                         'width': 240},
                        {'file_id': 'AgADAgADAqkxGwtWKUtXfH8fbld5p1PJRg4ABBz2HFGaVw65lNEEAAEC',
                         'file_size': 35988,
                         'height': 613,
                         'width': 460}]},
  'update_id': 941430902}]

As you can see, the previously received updates are not shown here, thanks to the “offset” parameter.

Working with the data received

Like I already said: These JSON structures are quite close to what Python’s dictionaries work like. You may work with them exactly like you would with a dictionary:

>>> my_dict = {'message': {'chat': {'first_name': 'Marc'}}}
>>> bots_answer = bot.getUpdates(offset=941430902+1)[0]
>>> type(my_dict)
<class 'dict'>
>>> type(bots_answer)
<class 'dict'>
>>> bots_answer['message']['from']['first_name']
'Marc'

Sending yourself a message from the bot

To show you another method, you now will send yourself a message from the bot.
You need your account id for this. You can extract this from the JSON output of the previously fetched updates: it’s in “message[‘from’][‘id’]”.

To send yourself a message, execute the following code:

pprint(bot.sendMessage('YOUR_ID', 'Hello father'))

Finally, you already have someone to talk to! Isn’t this just nice? ?

About the telepot documentation

I do not want to dive into showing an example for each and every method or parameter telepot knows; I just wanted to show these two examples to help you get an idea and an easy-to-follow-along example. For further details, you will need to consult the official telepot documentation.

That said, I have to give you an idea of the fact that this will not reveal the expected insights in all cases. As the author writes in that docs:

For a time, I tried to list the features here like many projects do. Eventually, I gave up.
Common and straight-forward features are too trivial to worth listing.

For the major count of requests, telepot is merely a 1:1 implementation of the Telegram HTTP-based bot API. And I made the experience, that often when you want to know what something in telepot supports or how something needs to be provided, you find a 1:1 equivalent in the Telegram API docs.

For example:
If you want to know more about the “getUpdates” method of telepot, you can look it up in the reference list of telepot. But even there, the author links to the Telegram docs at https://core.telegram.org/bots/api#getupdates. And see: It explaines all telepot parameters well.

Please keep this in mind when you are searching for additional info; sometimes you will find more detailed info in the Telegram docs instead.

Outlook for the next part of the series

For this part of the series, that’s it again!

Next time, I will show you how webhooks are working, how to configure your bot to use them and how to analyse the data Telegram sends towards the webhook URL.

I hope you enjoyed this part! Please let me know of all the things you either enjoyed or did not like that much and how I can do it better in the comments.

Series Navigation << Go back to previous part of this series (Part 2)Jump to next part of this series (Part 4) >>