In this post, I will explain how to create a Telegram bot in Python. Todoist is a great tool to manage to-do lists. Although this post can be just a general tutorial about creating a Telegram bot, I will go through connecting to Todoist API and bringing its power into Telegram, so you can learn more about working with real-world API and interacting with users in Telegram bots.
This post assumes you know basic Python. What you will learn includes:
- Creating a Telegram Bot with Telegram Bot Father
- Working with API in Python
- Writing Telegram bot script in Python
- Interacting with users in Telegram bot(showing buttons, replying to message, etc.) Creating a Telegram Bot First of all, we need to get a Telegram handle for our bot. Also, we need an access token to connect and interact with Telegram.
In your Telegram app, go to BotFather bot by clicking on this link or writing bot name in the Telegram search bar. In the BotFather, you can create a new bot with /newbot
command. Just talk to the Telegram BotFather and it will guide you. First, you need to provide a name for your bot, and then you can create a username for it, in which your bot will be accessible via that username as a link like this: telegram.me/<bot_username>
or a Telegram handle like @bot_username
After that, you will get a message with your bot data, including your bot’s access token for HTTP API. We will use this in the next steps.
Interacting with API in Python
To build a useful Telegram bot, You need to have a good knowledge of working with API.
I read a lot of tutorials for creating Telegram bots, but I wanted to write a bot that works with real-world API, not a bot to show some cat pictures(which is also very cool, by the way).
Todoist is a great tool for managing to-dos. Although it has a great mobile application, it may help you or your team to have your tasks in Telegram.
Also using Todoist API, you will learn how to interact with real-world APIs.
APIHandler Class
Todoist has an official Python API library. Using it you can easily interact with Todoist API. First, we are going to examine this official library. Then with the Python requests package, we are interacting with Todoist REST API. Because many of third-party API doesn’ thave such Python libraries, it’s important to learn how to use Python requests package to make HTTP requests for RESTful API.
Getting Todoist access token
Before we begin, you need to get an access token for Todoist API. Simply go to the Todoist app console and create a new app. After creating your app, you will see client id
and client secret
which you need to use in a production-level app for user authentication.
For now, we only need to get the access token for our tests. Scroll down and you can see your access token. Now let’s try it in action.
Todoist official Python library
Now install TodoistAPI
package with the Python pip running this command: pip install todoist-python
Using Todoist API library, it is very easy to get the projects:
from todoist.api import TodoistAPI
api_token = ""
result = TodoistAPI(api_token).sync()
project_list = result.state['projects']
Don’t forget to put your access token in api_token
variable. (test token)
Interacting with RESTful API using Python requests
To make an API call you need to use Python requests package. Install it using pip: pip install requests
The requests package has two main functions we can use to make get
or post
requests.
We can make a get request to get all tasks from Todoist.
import requests
api_url = "https://beta.todoist.com/API/v8"
api_token = ""
requests.get(
"%s/tasks" % api_url,
headers={
"Authorization": "Bearer %s" % api_token
}).json()
Or, make a post request to create a new task in Todoist
import requests
api_url = "https://beta.todoist.com/API/v8"
api_token = ""
requests.post(
"%s/tasks" % api_url,
data=json.dumps({
"content": task_content,
}),
headers={
"Content-Type": "application/json",
"X-Request-Id": str(uuid.uuid4()),
"Authorization": "Bearer %s" % api_token
}).json()
To have a clean code let’s create a class for our API handler.
from todoist.api import TodoistAPI
import requests
from datetime import datetime
import uuid
import json
class APIHandler:
def __init__(self, api_token, api_url):
# initiate a todoist api instance
self.api = TodoistAPI(api_token)
self.api_token = api_token
self.api_url = api_url
def get_project_list(self):
self.api.sync()
project_list = self.api.state['projects']
return project_list
def get_tasks_by_project(self, project_id):
tasks_list = requests.get(
"%s/tasks" % self.api_url,
params={
"project_id": project_id
},
headers={
"Authorization": "Bearer %s" % self.api_token
}).json()
return tasks_list
def create_project(self, project_name):
self.api.projects.add(project_name)
self.api.commit()
return True
def get_all_tasks(self):
tasks_list = requests.get(
"%s/tasks" % self.api_url,
headers={
"Authorization": "Bearer %s" % self.api_token
}).json()
return tasks_list
def get_today_tasks(self):
all_tasks = self.get_all_tasks()
today_tasks = []
today = datetime.today().date()
for task in all_tasks:
task_due = task.get('due')
if task_due:
task_due_date_string = task_due.get('date')
task_due_date = datetime.strptime(task_due_date_string, '%Y-%m-%d').date()
if task_due_date == today:
today_tasks.append(task)
return today_tasks
def create_task(self, task_content):
result = requests.post(
"%s/tasks" % self.api_url,
data=json.dumps({
"content": task_content,
}),
headers={
"Content-Type": "application/json",
"X-Request-Id": str(uuid.uuid4()),
"Authorization": "Bearer %s" % self.api_token
}).json()
return result
In __init__
function, we get two parameters to set api_token
and api_url
. Also, We create an instance of TodoisAPI
from Todoist Python library to use when it’s necessary.
All other functions are using requests
package get or post function to make an API call.
Now, we can get project lists, get all tasks, get tasks by project, or create a new task. If you need any other functions to interact with Todoist API, you can add that to the APIHandler class.
How to write Telegram bot in Python
In this step, we are going to write the main class for Telegram bot. I’m creating another class for bot functions. Before that, we need to install python-telegram-bot
, the python package that we are utilizing to create the Telegram bot.
Run pip installer command to install python-telegram-bot
:
pip install python-telegram-bot
The general structure for using python-telegram-bot
is like this
from telegram.ext import Updater, MessageHandler, CommandHandler, CallbackQueryHandler, Filters
bot_token = "YOUR_BOT_TOKEN"
updater = Updater(bot_token)
dp = updater.dispatcher
# Add command handler
dp.add_handler(CommandHandler('COMMAND_NAME', COMMAND_HANDLER))
# Add message handler
updater.dispatcher.add_handler(MessageHandler(Filters.all, MESSAGE_HANDLER))
# Add button handler
updater.dispatcher.add_handler(CallbackQueryHandler(BUTTON_HANDLER))
updater.start_polling()
updater.idle()
We need to create an instance of Updater
with bot_token
which is the access_token from the first step.
After that, for any command you define in your Telegram bot, you need to add a function to handle that command. This can be done by add_handler
function of updater.dispatcher
.
Also to interact with the bot users, we add a function to handle all receiving messages. Also, we add a function to handle every button a user taps on.
The final step is to call the start_polling
function and the idle
function of the updater
. This way our script is waiting for commands, messages or any button tap.
Todoistbot class
Let’s create a class for our main bot script. Starting with __init__
function, we are reading bot_token
, api_token
and api_url
configuration from the confi.ini
file. After that, we create an instance of updater
and an instance of APIHandler
from our APIHandler
class.
import configparser
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, MessageHandler, CommandHandler, CallbackQueryHandler, Filters
from APIHandler import APIHandler
class TodoistBot:
def __init__(self):
# Read Configs from file
config = configparser.ConfigParser()
config.read("config.ini")
# set Telegram bot token
bot_token = config['telegram']['bot_token']
# set Todoist API token
api_token = config['todoist']['api_token']
# set Todoist API URL
api_url = config['todoist']['api_url']
# initiate a Telegram updater instance
self.updater = Updater(bot_token)
# initiate a Todoist API handler
self.api = APIHandler(api_token, api_url)
def projects(self, bot, update):
chat_id = update.message.chat_id
project_list = self.api.get_project_list()
keyboard = []
for project in project_list:
keyboard.append(
[InlineKeyboardButton(project['name'], callback_data=project['id'])])
reply_markup = InlineKeyboardMarkup(keyboard)
bot.send_message(chat_id=chat_id, text="Choose a Project to see tasks list", reply_markup=reply_markup)
def main(self):
updater = self.updater
dp = updater.dispatcher
# Add command handlers
dp.add_handler(CommandHandler('projects', self.projects))
updater.start_polling()
updater.idle()
We added a handler for projects
command, which means every time a user writes /projects
or choose it in our Telegram bot, it runs the function projects
of our scripts.
How to show buttons for users in Telegram bot
First, you need to import InlineKeyboardButton
and InlineKeyboardMarkup
. As you can see in the projects
function, the first parameter of the InlineKeyboardButton
is the text of the button and callback_data
is the data taping on the button will return. You can add any button you need to show to a list and give it to the InlineKeyboardMarkup
object and pass the result with the send_message
function. Also, you can add a URL to the button to make it as a link button.
I added a static function to generate a reply-markup for the tasks list. I am using it in showing all tasks and showing today tasks list in my Telegram bot.
@staticmethod
def task_button_markup(tasks):
keyboard = []
for task in tasks:
keyboard.append(
[InlineKeyboardButton(task['content'], url=task['url'], callback_data=task['id'])])
markup = InlineKeyboardMarkup(keyboard)
return markup
Getting user input in Telegram bot
We have a general handler function for messages. This function will receive any messages a user sends in Telegram bot. Also, there is another handler for buttons, so when our bot shows a list of projects as Telegram bot buttons, The handler function receives the callback_data
which is registered with the button.
So every user input is either a command which runs a function in our script, a message which goes to general_handler
function, or a button tap which goes to button
function.
class TodoistBot:
# all other functions
# .
# .
# .
# handler for buttons
def button(self, bot, update):
query = update.callback_query
# general handler for messages
def general_handler(self, bot, update):
chat_id = update.message.chat_id
text = update.message.text
def main(self):
updater = self.updater
dp = updater.dispatcher
# Add command handlers
dp.add_handler(CommandHandler('projects', self.projects))
# other commands will goes here
# Add callback handlers for buttons
updater.dispatcher.add_handler(CallbackQueryHandler(self.button))
# general message handler
updater.dispatcher.add_handler(MessageHandler(Filters.all, self.general_handler))
updater.start_polling()
updater.idle()
To see what users want to do, I assigned some flags to our class. So if the user sends the /new_taks
command, I just set the new_task
flag to True.
class TodoistBot:
class Flags:
new_project = False
new_task = False
select_project_for_task = False
def __init__(self, flag=False):
self.new_project = flag
self.new_task = flag
self.select_project_for_task = flag
flags = Flags()
# All other functions
# ...
Now we can define a function to respond to the /new_task
command.
def new_task(self, bot, update):
chat_id = update.message.chat_id
self.flags.new_task = True
bot.send_message(chat_id=chat_id, text="enter name for new task")
And add the command handler to the dispatcher
(the db
variable) in the main function:
dp.add_handler(CommandHandler('newtask', self.new_task))
Now that we set the new_task
flag to the True, in the general_handler
function we can create a new task with the user’s input.
class TodoistBot:
class Flags:
new_task = False
def __init__(self, flag=False):
self.new_task = flag
flags = Flags()
def __init__(self):
...
def new_task(self, bot, update):
chat_id = update.message.chat_id
self.flags.new_task = True
bot.send_message(chat_id=chat_id, text="enter name for new task")
def general_handler(self, bot, update):
chat_id = update.message.chat_id
text = update.message.text
if self.flags.new_task:
if self.api.create_task(text):
bot.send_message(chat_id=chat_id, text="task created: " + text)
def main(self):
updater = self.updater
dp = updater.dispatcher
# Add command handlers
# .
# .
# .
dp.add_handler(CommandHandler('newtask', self.new_task))
# ...
# general message handler
updater.dispatcher.add_handler(MessageHandler(Filters.all, self.general_handler))
updater.start_polling()
updater.idle()
The same logic can be implemented for the new project command and the function.
The End
Thanks for reading this, here is the GitHub code too: click here
Comments (0)