How to create a telegram bot with Python in minutes

by Davide Mastromatteo
12 minute read

teaser

Creating a telegram bot with Python in minutes may seem like a clickbait title for a post, but trust me, it’s possible. If you’re going through a boring afternoon or you’re locked down due to COVID restriction and you want to do something different, keep reading and let’s create our first Python Telegram bot! :)

For this project, all you will have to use is the python-telegram-bot package that’s basically a wrapper around telegram APIs. Python Telegram Bot is fully compatible with Python 3.6+ and will make developing a Telegram Bot a piece of cake.

So, let’s start by installing this package (I strongly recommend to use a virtual environment for this kind of things: if you don’t know how to do it, just look at this old article or stay tuned, because I’m going to write something about it soon…

$ pip install python-telegram-bot –upgrade

Now that you have installed the package, let’s start with creating our first bot. And guess what? You don’t need any Python to create a bot, you just need to chat with the Telegram BotFather account.

Once you start chatting with the BotFather bot, you just need to issue the /newbot command and answer the questions of BotFather will ask you: the name of your bot (it’s the display name) and the username of your bot.

botfather1{: .align-center}

botfather2{: .align-center}

That’s it, your bot is ready! There is a lot of other stuff that you can ask to BotFather (like changing the profile pic of your bot, for example) but for this basic tutorial, we won’t need anything else.

Now, you’re going to face a tough decision: what will your bot do? The bot we will create with this example will give visitors information about their biorhythm. If you don’t know what a biorhythm is, check this page on Wikipedia and you will find out two important things:

  1. According to the theory of biorhythms “a person’s life is influenced by rhythmic biological cycles that affect his or her ability in various domains, such as mental, physical, and emotional activity”.
  2. The proposal has been independently tested and, consistently, no validity for it has been found. :)

So our bot will be as useful as reading the horoscope… that’s cool uh? :)

But don’t worry: at the end of the article, you will be able to program any kind of bots, either completely useless like this one or useful ones! :)

Now, let’s see how can we program the bot.

The python-telegram-bot package consists of a wrapper around Telegram APIs. The Telegram APIs are exposed via the telegram.Bot class. However, on top of this class, they have built the telegram.ext module, which will make your work a lot easier, allowing you to create a bot in minutes.

The telegram.ext module contains a lot of classes, but the most important two are telegram.ext.Updater and telegram.ext.Dispatcher. The Updater class is responsible for fetching new updates from Telegram and passing them to the Dispatcher class, which will handle them through a Handler class.

So let’s start coding!

 1# mastrobot_example.py
 2from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
 3
 4# function to handle the /start command
 5def start(update, context):
 6    update.message.reply_text('start command received')
 7
 8# function to handle the /help command
 9def help(update, context):
10    update.message.reply_text('help command received')
11
12# function to handle errors occured in the dispatcher 
13def error(update, context):
14    update.message.reply_text('an error occured')
15
16# function to handle normal text 
17def text(update, context):
18    text_received = update.message.text
19    update.message.reply_text(f'did you said "{text_received}" ?')
20
21def main():
22    TOKEN = "insert here your token and don't share it with anyone!"
23
24    # create the updater, that will automatically create also a dispatcher and a queue to 
25    # make them dialoge
26    updater = Updater(TOKEN, use_context=True)
27    dispatcher = updater.dispatcher
28
29    # add handlers for start and help commands
30    dispatcher.add_handler(CommandHandler("start", start))
31    dispatcher.add_handler(CommandHandler("help", help))
32
33    # add an handler for normal text (not commands)
34    dispatcher.add_handler(MessageHandler(Filters.text, text))
35
36    # add an handler for errors
37    dispatcher.add_error_handler(error)
38
39    # start your shiny new bot
40    updater.start_polling()
41
42    # run the bot until Ctrl-C
43    updater.idle()
44
45if __name__ == '__main__':
46    main()

This first example is quite trivial. In the main function, we have created the Updater class, which has automatically created for us a Dispatcher object, available through the .dispatcher property of the Updater class.

Then, we’ve added some handlers for:

  • the /start command (we simply call the callback function start() that reply to the user with an informative message)
  • the /help command (we simply call the callback function help() that reply to the user with an informative message)
  • what happens if an error occurs while dispatching messages (we simply call the callback function error())
  • what happens if the user writes something that is not a command (we simply call the callback function text() that reply to the user with the same text received)

Finally, we have written the callback functions, that use the update object received to send messages to the user.

Let’s test this primitive bot. Start your bot with:

$ python mastrobot_example.py

and now, let’s start chatting with our new bot:

bot1{: .align-center}

It works!

Now, we wanted to create a bot to tell the user their daily biorhythm, right? This will be easy, we will use the /start command to get the birthday of the user as the chat starts, and then we will create a function to handle a new /biorhythm command to answer the user with its personal biorhythm.

Let’s start with the first part: knowing the user’s birthday.

The first thing to do is to change the function that handles the /start command. We will ask the user it’s birthday and we will call a function to start the interaction with the user. To make it simple, we will ask the user the year, the month, and the day they were born in.

1# function to handle the /start command
2def start(update, context):
3    first_name = update.message.chat.first_name
4    update.message.reply_text(f"Hi {first_name}, nice to meet you!")
5    start_getting_birthday_info(update, context)

As you can see, in the update parameter you will find also some useful information about the user, like its name. However, since this is just a starting tutorial to whet your appetite, I won’t discuss here the thousand of things you will be able to do with this package, if you want to know everything about the python-telegram-bot package, just check the official documentation.

Now, at the very beginning of our script, we will define a new variable STATE that will be used to understand what question the user is answering. Don’t worry if you can’t get it now, you will get it in a minute.

1STATE = None
2
3BIRTH_YEAR = 1
4BIRTH_MONTH = 2
5BIRTH_DAY = 3

Now, we need to implement the function start_getting:_birthday_info() we are calling in the start() function and that starts… getting birthday info from user :) :

1def start_getting_birthday_info(update, context):
2    global STATE
3    STATE = BIRTH_YEAR
4    update.message.reply_text(f"I would need to know your birthday, so tell me what year did you born in...")

As you can see, at the beginning we set the variable STATE to the value BIRTH_YEAR so that we will know, when the user will answer, that it was answering the birth year question. Then, we just send a message to ask for the year of birth.

Now, the user will answer with a normal text, right? So we need to change the text() function to wait for its answer:

 1def text(update, context):
 2    global STATE
 3
 4    if STATE == BIRTH_YEAR:
 5        return received_birth_year(update, context)
 6
 7    if STATE == BIRTH_MONTH:
 8        return received_birth_month(update, context)
 9
10    if STATE == BIRTH_DAY:
11        return received_birth_day(update, context)

Here in the text() function we just need to understand what’s the question the user is answering by using the STATE variable we defined before, and call a specific function to handle each answer.

These functions could be written like this:

 1def received_birth_year(update, context):
 2    global STATE
 3
 4    try:
 5        today = datetime.date.today()
 6        year = int(update.message.text)
 7        
 8        if year > today.year:
 9            raise ValueError("invalid value")
10
11        context.user_data['birth_year'] = year
12        update.message.reply_text(f"ok, now I need to know the month (in numerical form)...")
13        STATE = BIRTH_MONTH
14    except:
15        update.message.reply_text("it's funny but it doesn't seem to be correct...")
16
17def received_birth_month(update, context):
18    global STATE
19
20    try:
21        today = datetime.date.today()
22        month = int(update.message.text)
23
24        if month > 12 or month < 1:
25            raise ValueError("invalid value")
26
27        context.user_data['birth_month'] = month
28        update.message.reply_text(f"great! And now, the day...")
29        STATE = BIRTH_DAY
30    except:
31        update.message.reply_text("it's funny but it doesn't seem to be correct...")
32
33def received_birth_day(update, context):
34    global STATE
35
36    try:
37        today = datetime.date.today()
38        dd = int(update.message.text)
39        yyyy = context.user_data['birth_year']
40        mm = context.user_data['birth_month']
41        birthday = datetime.date(year=yyyy, month=mm, day=dd)
42
43        if today - birthday < datetime.timedelta(days=0):
44            raise ValueError("invalid value")
45
46        context.user_data['birthday'] = birthday
47        STATE = None
48        update.message.reply_text(f'ok, you born on {birthday}')
49
50    except:
51        update.message.reply_text("it's funny but it doesn't seem to be correct...")        

As you can see, when we receive the user birth year, we just check if it is a valid value and in this case, we save it to the context.user_data[] dictionary, and then we go ahead setting the next value for the STATE variable and asking the next question.

When the last question is asked and we received the day of birth, we just create a date variable and store it in the context.user_data[] dictionary as well.

If the user enters an invalid value, we just tell them that it’s not correct and doesn’t change the value of the STATE variable, so the user is stuck with that question until it doesn’t answer correctly.

Ok, now we just need to handle the /biorhythm command and we’ve finished.

Let’s start adding a new command handler into our main() function:

1    dispatcher.add_handler(CommandHandler("biorhythm", biorhythm))

and let’s write the function that calculate the biorhythm:

 1# This function is called when the /biorhythm command is issued
 2def biorhythm(update, context):
 3    user_biorhythm = calculate_biorhythm(
 4        context.user_data['birthday'])
 5
 6    update.message.reply_text(f"Phisical: {user_biorhythm['phisical']}")
 7    update.message.reply_text(f"Emotional: {user_biorhythm['emotional']}")
 8    update.message.reply_text(f"Intellectual: {user_biorhythm['intellectual']}")
 9
10def calculate_biorhythm(birthdate):
11    today = datetime.date.today()
12    delta = today - birthdate
13    days = delta.days
14
15    phisical = math.sin(2*math.pi*(days/23))
16    emotional = math.sin(2*math.pi*(days/28))
17    intellectual = math.sin(2*math.pi*(days/33))
18
19    biorhythm = {}
20    biorhythm['phisical'] = int(phisical * 10000)/100
21    biorhythm['emotional'] = int(emotional * 10000)/100
22    biorhythm['intellectual'] = int(intellectual * 10000)/100
23
24    biorhythm['phisical_critical_day'] = (phisical == 0)
25    biorhythm['emotional_critical_day'] = (emotional == 0)
26    biorhythm['intellectual_critical_day'] = (intellectual == 0)
27
28    return biorhythm

As you can see I have written two different functions, one to handle the command and the other one to calculate the biorhythm to separate the responsibility of these functions.

So, here there’s the complete code of our bot:

  1# mastrobot_example2.py
  2import datetime
  3import math
  4from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
  5
  6STATE = None
  7BIRTH_YEAR = 1
  8BIRTH_MONTH = 2
  9BIRTH_DAY = 3
 10
 11# function to handle the /start command
 12def start(update, context):
 13    first_name = update.message.chat.first_name
 14    update.message.reply_text(f"Hi {first_name}, nice to meet you!")
 15    start_getting_birthday_info(update, context)
 16
 17def start_getting_birthday_info(update, context):
 18    global STATE
 19    STATE = BIRTH_YEAR
 20    update.message.reply_text(
 21        f"I would need to know your birthday, so tell me what year did you born in...")
 22
 23def received_birth_year(update, context):
 24    global STATE
 25
 26    try:
 27        today = datetime.date.today()
 28        year = int(update.message.text)
 29
 30        if year > today.year:
 31            raise ValueError("invalid value")
 32
 33        context.user_data['birth_year'] = year
 34        update.message.reply_text(
 35            f"ok, now I need to know the month (in numerical form)...")
 36        STATE = BIRTH_MONTH
 37    except:
 38        update.message.reply_text(
 39            "it's funny but it doesn't seem to be correct...")
 40
 41def received_birth_month(update, context):
 42    global STATE
 43
 44    try:
 45        today = datetime.date.today()
 46        month = int(update.message.text)
 47
 48        if month > 12 or month < 1:
 49            raise ValueError("invalid value")
 50
 51        context.user_data['birth_month'] = month
 52        update.message.reply_text(f"great! And now, the day...")
 53        STATE = BIRTH_DAY
 54    except:
 55        update.message.reply_text(
 56            "it's funny but it doesn't seem to be correct...")
 57
 58def received_birth_day(update, context):
 59    global STATE
 60
 61    try:
 62        today = datetime.date.today()
 63        dd = int(update.message.text)
 64        yyyy = context.user_data['birth_year']
 65        mm = context.user_data['birth_month']
 66        birthday = datetime.date(year=yyyy, month=mm, day=dd)
 67
 68        if today - birthday < datetime.timedelta(days=0):
 69            raise ValueError("invalid value")
 70
 71        context.user_data['birthday'] = birthday
 72        STATE = None
 73        update.message.reply_text(f'ok, you born on {birthday}')
 74
 75    except:
 76        update.message.reply_text(
 77            "it's funny but it doesn't seem to be correct...")
 78
 79# function to handle the /help command
 80def help(update, context):
 81    update.message.reply_text('help command received')
 82
 83# function to handle errors occured in the dispatcher
 84def error(update, context):
 85    update.message.reply_text('an error occured')
 86
 87# function to handle normal text
 88def text(update, context):
 89    global STATE
 90
 91    if STATE == BIRTH_YEAR:
 92        return received_birth_year(update, context)
 93
 94    if STATE == BIRTH_MONTH:
 95        return received_birth_month(update, context)
 96
 97    if STATE == BIRTH_DAY:
 98        return received_birth_day(update, context)
 99
100# This function is called when the /biorhythm command is issued
101def biorhythm(update, context):
102    print("ok")
103    user_biorhythm = calculate_biorhythm(
104        context.user_data['birthday'])
105
106    update.message.reply_text(f"Phisical: {user_biorhythm['phisical']}")
107    update.message.reply_text(f"Emotional: {user_biorhythm['emotional']}")
108    update.message.reply_text(f"Intellectual: {user_biorhythm['intellectual']}")
109
110def calculate_biorhythm(birthdate):
111    today = datetime.date.today()
112    delta = today - birthdate
113    days = delta.days
114
115    phisical = math.sin(2*math.pi*(days/23))
116    emotional = math.sin(2*math.pi*(days/28))
117    intellectual = math.sin(2*math.pi*(days/33))
118
119    biorhythm = {}
120    biorhythm['phisical'] = int(phisical * 10000)/100
121    biorhythm['emotional'] = int(emotional * 10000)/100
122    biorhythm['intellectual'] = int(intellectual * 10000)/100
123
124    biorhythm['phisical_critical_day'] = (phisical == 0)
125    biorhythm['emotional_critical_day'] = (emotional == 0)
126    biorhythm['intellectual_critical_day'] = (intellectual == 0)
127
128    return biorhythm
129
130def main():
131    TOKEN = "insert here your token and don't share it with anyone!"
132
133    # create the updater, that will automatically create also a dispatcher and a queue to
134    # make them dialoge
135    updater = Updater(TOKEN, use_context=True)
136    dispatcher = updater.dispatcher
137
138    # add handlers for start and help commands
139    dispatcher.add_handler(CommandHandler("start", start))
140    dispatcher.add_handler(CommandHandler("help", help))
141    # add an handler for our biorhythm command
142    dispatcher.add_handler(CommandHandler("biorhythm", biorhythm))
143
144    # add an handler for normal text (not commands)
145    dispatcher.add_handler(MessageHandler(Filters.text, text))
146
147    # add an handler for errors
148    dispatcher.add_error_handler(error)
149
150    # start your shiny new bot
151    updater.start_polling()
152
153    # run the bot until Ctrl-C
154    updater.idle()
155
156
157if __name__ == '__main__':
158    main()

It’s time to test it, right?

bot2{: .align-center}

Oh my gosh… I hope you won’t find too many mistakes in this article, but in that case bear with me, my intellectual cycle today is only at 28%… that’s not so good uh?

I will keep the bot alive, so you can try it if you want (the username is @mastro35_mastrobot) but if it doesn’t work … consider that it’s running on my raspberry pi and that it could be offline sometimes…

Ok guys, it’s enough for today! If you liked this article feel free to click on the buy me a coffee button or subscribe to become a member and support me on a monthly basis.

Happy coding and… stay safe! D.


Did you find this article helpful?


Buy me a coffee! Buy me a coffee!