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)