Bring your Telegram Chatbot to the next level
Bring your Telegram Chatbot to the next level
Discover advanced features that can make a difference
Telegram is a popular platform for developing chatbots: excellent documentation, a vibrant community, various libraries and tons of examples.
If you are getting started there are plenty of tutorials around, especially on Medium. Stackoverflow is also a great resource for answering questions and understanding issues (your author is often spotted there to try to help fellow developers out 🤓).
This article instead focuses on more advanced aspects related to the implementation of a Telegram bot using Python Telegram Bot:
Pull vs Webhook
Grab the chat ID
Display “Typing…” and suggest answers
Deep linking
Send media and files
Photo by Christian Wiediger on Unsplash
Polling mode to work locally
Telegram Bot can work with a Pull or with a Push mechanism (see further Webhooks). The pull mechanism is where the bot (your code) is checking regularly for new available messages on the server.
Everyone agrees this is not an ideal approach (unnecessary waste of resources, messages are discarded after 24 hours), but it is convenient when developing locally. (no tunneling software like ngrok
is required).
def main():
updater = Updater('myTelegramToken')
dp = updater.dispatcher
# define command handler
dp.add_handler(CommandHandler("help", help_command_handler))
# define message handler
dp.add_handler(MessageHandler(Filters.text, main_handler))
updater.start_polling()
Webhooks on production
Once the bot is ready for deployment it is time to enable the Webhook: doing so Telegram will push the updates to the registered Webhook endpoint and your service gets busy only when there are new incoming messages.
updater.start_webhook(listen="0.0.0.0",
port=3978,
url_path='myTelegramToken')
updater.bot.setWebhook('https://example.com/svc/myTelegramToken')
Note: webhook works nicely with a serverless architecture or with ‘on-demand’ nodes. I have deployed Telegram webhooks on Heroku Free Tier which goes to sleep after 30 minutes of inactivity, but if a new message is pushed the Heroky Dyno starts up and the message is consumed within a few seconds.
Note 2: depending on where the bot is deployed the port number needs to be configured. On Heroku, for example, the port is typically defined by the PORT environment variable and should never be hardcoded.
How to register a webhook
You can do this by calling a the setWebhook
URL: use your private token and pass the URL (must be HTTPS) of the webhook.
https://api.telegram.org/bot{myTelegramToken}/setWebhook?url=https://example.com/svc
Switch between Pull and Push mode
I find it extremely convenient to switch programmatically between polling and webhook using an environment variable: the default is polling
to work locally but, when deploying on the live system, it can be overridden (to webhook
).
# read MODE env variable, fall back to 'polling' when undefined
mode = os.environ.get("MODE", "polling")
if DefaultConfig.MODE == 'webhook':
# enable webhook
updater.start_webhook(listen="0.0.0.0",
port=3978,
url_path='myTelegramToken')
updater.bot.setWebhook('https://example.com/svc/myTelegramToken')
else:
# enable polling
updater.start_polling()
Obtain the Chat ID
Replying to an incoming message is pretty simple.
update.message.reply_text("Hi there!")
however, sometimes we want to be able to initiate the message without the user asking anything (for example a notification after a few days to re-engage our users).
Retrieve (and save) the chat_id
which is found in the JSON payload of the message sent by the user (yes, you need at least one message)
{'update_id': 527095032,
'message': {
'message_id': 412,
'date': 1615991013,
'chat': {
'id': 931365322,
'type': 'private',
'username': 'gcatanese',
....
When parsing the payload Python do not forget that different messages have slightly different JSON.
def get_chat_id(update, context):
chat_id = -1
if update.message is not None:
# text message
chat_id = update.message.chat.id
elif update.callback_query is not None:
# callback message
chat_id = update.callback_query.message.chat.id
elif update.poll is not None:
# answer in Poll
chat_id = context.bot_data[update.poll.id]
return chat_id
Suggested Actions
Guide the conversation providing the user with predefined options to choose from. This can be achieved using the InlineKeyboardMarkup
, a keyboard that appears along the message (like a question).
Image by author
Here is the Python snippet to create the options:
options = []
options.append(InlineKeyboardButton(text='Text', callback_data='1'))
options.append(InlineKeyboardButton(text='File', callback_data='2'))
options.append(InlineKeyboardButton(text='GoogleDoc', callback_data='3'))
options.append(InlineKeyboardButton(text='Gallery', callback_data='4'))
reply_markup = InlineKeyboardMarkup([options])
context.bot.send_message(chat_id=get_chat_id(update, context), text='What would you like to receive?', reply_markup=reply_markup)
It is important to understand that the response will be processed by a CallbackQueryHandler
and the incoming JSON payload is different from the plain text message.
# input from text message
text = update.message.text
# selection from callback
choice = update.callback_query.data
Note: Once a selection is made the Options disappear from the chat.
Note2: define callback_data
using a constant value which will be used to decide what to do. Unlike the text
on the button, this value should not change as it is not visible to the user.
choice = update.callback_query.data
if choice == '1':
# Choice 1: Text
update.callback_query.message.edit_text('You have chosen Text')
Show “Typing…”
Display the Typing indicator is a common feature in chatbots: users are informed a message is about to arrive, even if there is a little delay.
context.bot.send_chat_action(chat_id=get_chat_id(update, context), action=telegram.ChatAction.TYPING, timeout=1)
time.sleep(1)
Note: while displaying the Typing indicator I normally introduce a short delay to create the feeling of a real conversation.
Deep linking with an additional parameter
Deep linking is a mechanism that allows one to open a conversation with a given bot. This is useful to share the link to the bot on websites, emails or Social Media.
When defining the deep link it is possible to add parameter:
https://t.me/username_bot?start=signup
The link opens the Telegram application prompting start
the conversation with the bot. The additional parameter is passed to the CommandHandler
processing of the /start
command.
def start_command_handler(update, context):
param_value = context.args[0]
update.message.reply_text('Value is ' + param_value)
Send a File
The chatbot can send files (i.e. PDFs, Office) in different ways.
Use send_document
with the full URL to the file:
url='https://github.com/gc/TelegramBotDemo/raw/main/test.pdf'
context.bot.send_document(chat_id=get_chat_id(update, context), document=url)
Note: this approach requires a URL which points directly to the file.
Another approach is to first download locally the file and then send it with the same method send_document
.
# fetch from Google Drive
url = 'https://drive.google.com/file/d/0BZ3dWQ2ZXVOdE1V/view'
r = requests.get(url, allow_redirects=True)
# save local copy
open('file.ppt', 'wb').write(r.content)
# send file to user
context.bot.send_document(chat_id=get_chat_id(update, context), document=open('file.ppt', 'rb'), filename="Presentation.pptx")
Note: this approach is usually necessary when the URL does not point directly to a file, but it is an endpoint streaming out the content on demand.
Create a Media Gallery
A cool feature is sending a group of media like photos, videos and audio.
Image by author
list = []
# define list of files
list.append(InputMediaPhoto(media='https://../mintie.jpg', caption='Mint'))
list.append(InputMediaPhoto(media='https://../pinkie.png', caption='Pink'))
list.append(InputMediaPhoto(media='https://../orangie.png', caption='Orange'))
context.bot.send_media_group(chat_id=get_chat_id(update, context), media=list)
Note: the bot can send up to 10 media files at the same time.
Note2: each message counts within the Telegram Rate limits (30 messages per second) therefore you can send up to 3 messages with 10 media each (in total 30) but, in this case, you cannot add anything else in the same transaction (not even a simple text message).
Conclusion
I hope the article helps to understand some more advanced scenarios and discover features that can make your Telegram chatbot a little bit more special. My advice is to always consider first what the users are looking for (why would they chat? ) and then design it in a way that makes their conversation frictionless and enjoyable: striking a good balance determines the success of your chatbot.
Check out the GitHub repo where you can grab the code snippets shown above.
Happy chatting!