Building an Appointment Scheduling Telegram Bot with Python and Fauna
Have you ever wanted to build a telegram bot that would allow you to schedule or plan your appointments? If your answer is yes, then this article is just what you need. <!--more--> You would also use a serverless database system called FaunaDB to build our system, making your work easier.
What is Fauna?
Fauna is a client-side serverless document database that uses GraphQL and the Fauna Query Language (FQL) to support various data types and relational databases in a serverless API.
Prerequisites
To follow along with this tutorial, you need to have:
- A good understanding of Python.
- Faunadb, telegram and python_telegram_bot installed.
Installing the requirements
Run the command below in your command-line interface to install the prerequisites if you haven't.
pip install faunadb telegram python_telegram_bot
Setting up the Fauna database
Sign up on their website here and create a new database with a name of your choice. This database will house the collections, documents, and other database elements relevant to this article.
Setting up the Fauna collections
The two collections you will need to create are the Users
collection and the Appointment
collection. Visit here for information about creating the required collections.
Setting up the Fauna indexes
To easily access and scroll through data in the databse, we need to create a Fauna index. We need to create three indexes for the database, users_index
, appointment_index
, and appointment_today_index
.
Also visit these instructions on creating an index.
Connecting Fauna to Python
Setting up a Fauna API key
Visit here for information on how to create and setup an API key.
Creating the Telegram Bot
To learn how to create a telegram bot visit here.
Powering the bot with Python
Create a new Python file and give it any name of your choice. Now you have to import the required modules into your Python file.
import telegram
from telegram.ext import Updater
from telegram.ext import CommandHandler
from telegram.ext import MessageHandler, Filters
import pytz
from datetime import datetime, date
from faunadb import query as q
from faunadb.objects import Ref
from faunadb.client import FaunaClient
Now let's create a dispatcher and updater for your bot.
telegram_bot_token = "your-telegram-token"
client = FaunaClient(secret="your-fauna-secret-key")
updater = Updater(token=telegram_token, use_context=True)
dispatcher = updater.dispatcher
The dispatcher function is to handle and process the received message while the updater tracks, monitors, and reads messages sent to the bot and delivers them to the dispatcher.
Now let's start our bot using the Python code below.
def start(update, context):
chat_id = update.effective_chat.id
first_name = update["message"]["chat"]["first_name"]
username = update["message"]["chat"]["username"]
try:
client.query(q.get(q.match(q.index("users_index"), chat_id)))
context.bot.send_message(chat_id=chat_id, text="Welcome to Fauna Appointment Scheduler Bot \n\n To schedule an appointment enter /add_appointment \n To list al appointment enter /list_appointments \n To list all appoint,emts you have today enter /list_today_appointments")
except:
user = client.query(q.create(q.collection("Users"), {
"data": {
"id": chat_id,
"first_name": first_name,
"username": username,
"last_command": "",
"date": datetime.now(pytz.UTC)
}
}))
context.bot.send_message(chat_id=chat_id, text="Welcome to Fauna Appointment Scheduler Bot, your details have been saved đ \n\n To schedule an appointment enter /add_appointment \n To list al appointment enter /list_appointments \n To list all appointments you have today enter /list_today_appointments")
In the code above, you created a function called start
then passed update
and context
as parameters. update
is the telegram userâs data that is automatically passed from the dispatch handler. context
is the bot instance passed from the dispatch handler.
Then, you collected the chat_id
, first_name
, and username
from the updater then made a request to the FQL (Fauna Query Language) client using the users_index
to check if the user with the chat_id
already exists in the database.
If the user exists, you send a welcome message. Else, you create a user with the information provided. Next, you need to create a dispatch handler for the /start
command. A command in Telegram is any text that starts with /
.
You can also add other handlers such as text handler, image handler, regex handler, and more. For this tutorial, we are starting with a command handler that initializes our bot.
Copy and paste the code below at the end of your Python file to call the dispatch handler to connect a command handler to the âstartâ command.
The âstartâ command triggers the start
method. The updater checks for messages from the user.
dispatcher.add_handler(CommandHandler("start", start))
updater.start_polling()
Let's run our app by:
# python file name is app.py
python app.py
While your Python app is running, open your telegram app and search for the bot you created.
Open your bot and start it by typing in the /start
command. You will now receive a welcome message from the bot, as seen in the image below:
Creating new appointments
Now we will enable your telegram bot to create new appointments in the fauna database.
def add_appointment(update, context):
chat_id = update.effective_chat.id
user = client.query(q.get(q.match(q.index("users_index"), chat_id)))
client.query(q.update(q.ref(q.collection("Users"), user["ref"].id()), {"data": {"last_command": "add_appointment"}}))
context.bot.send_message(chat_id=chat_id, text="Enter the appointment event you want to add along with its due in this format(mm/dd/yyyy) date separated by a comma đ")
In the add_appointment
method, you made use of the chat_id
to query the FQL client using the users_index
index to check the user data that matches the chat_id
.
Next, you updated the last_command
field in the user's data to save the add_appointment
command the user sent. You then sent a message requesting the user to enter the name of the appointment and its due date separated by a comma.
Now you have to create another method called echo
that will collect the name and the due date for the new appointment, then save it to the database.
def echo(update, context):
chat_id = update.effective_chat.id
message = update.message.text
user = client.query(q.get(q.match(q.index("users_index"), chat_id)))
last_command = user["data"]["last_command"]
if last_command == "add_appointment":
events = client.query(q.create(q.collection("Appointments"), {
"data": {
"user_id": chat_id,
"event": message.split(",")[0],
"completed": False,
"date_due": message.split(",")[1]
}
}))
client.query(q.update(q.ref(q.collection("Users"), user["ref"].id()), {"data": {"last_command": ""}}))
context.bot.send_message(chat_id=chat_id, text="Successfully added appointment event đ")
In the echo
method you made use of the chat_id
to query the FQL client using the users_index
index to check the user data that matches the chat_id
.
Next, you checked if the last_command
in the data retrieved is an add_appointment
command. If it is, you query the FQL client to create an appointment using the Fauna create
method in the Appointments
collection with the information provided in the userâs message.
Finally, you need to create a command handler for the add_appointment
command and a message handler for the echo
method. The message handler filters the message and triggers the echo
method when the user enters a message (a message is any text that does not start with /
).
Note: The echo method only works when the last command was an
add_appointment
command.
dispatcher.add_handler(CommandHandler("add_appointment", add_appointment))
dispatcher.add_handler(MessageHandler(Filters.text, echo))
Listing appointments
The following part will provide the feature of listing all the appointments saved for a user.
def list_appointments(update, context):
chat_id = update.effective_chat.id
event_message = ""
events = client.query(q.paginate(q.match(q.index("appointment_index"), chat_id)))
for i in events["data"]:
event = client.query(q.get(q.ref(q.collection("Appointments"), i.id())))
if event["data"]["completed"]:
event_status = "Completed"
else:
event_status = "Not Completed"
event_message += "{}\nStatus:{} \nDate Due: {}\nUpdate Link: /update_{}\nDelete Link: /delete_{}\n\n".format(event["data"]["event"], event_status, event["data"]["date_due"], i.id(), i.id())
if event_message == "":
event_message = "You don't have any appointments saved, type /add_appointment to schedule one now đ"
context.bot.send_message(chat_id=chat_id, text=event_message)
In the list_appointments
method, you saved the chat_id
of the user and created an empty variable called event_message
. You then made use of the chat_id
to query the FQL client using the appointments_index
index to check the user data that matches the chat_id
.
Next, you looped through the data retrieved to check if the completed
field was set to True. If this was the case, you set the event_status
to âCompletedâ. Otherwise, you set it to âNot Completedâ.
You then sent a message to the user containing the event
, status
, update link and delete link for each appointment scheduled.
The update link is created by attaching /update_
to the appointmentâs id, while the delete link is created by attaching /delete_
to the appointmentâs id.
If the query retrieved contains no data, this means the event_message
is empty. Then, you sent a message to the user stating that the user has no appointments saved.
def list_today_appointments(update, context):
chat_id = update.effective_chat.id
event_message = ""
today = date.today()
date1=today.strftime("%m/%d/%Y")
events = client.query(q.paginate(q.match(q.index("appointment_today_index"), chat_id, date1 )))
for i in events["data"]:
event = client.query(q.get(q.ref(q.collection("Appointments"), i.id())))
if event["data"]["completed"]:
event_status = "Completed"
else:
event_status = "Not Completed"
event_message += "{}\nStatus:{} \nDate Due: {}\nUpdate Link: /update_{}\nDelete Link: /delete_{}\n\n".format(event["data"]["event"], event_status, event["data"]["date_due"], i.id(), i.id())
if event_message == "":
event_message = "You don't have no appointments saved, enter /add_appointment command to schedule one now đ"
context.bot.send_message(chat_id=chat_id, text=event_message)
The list_today_appointments
method is like the list_appointments
method with one striking difference, the appointment_today_index
. This index retrieves only data where the date_due
field matches the current date.
Let's create a command handler for both methods.
dispatcher.add_handler(CommandHandler("list_appointments", list_appointments))
dispatcher.add_handler(CommandHandler("list_today_appointments", list_today_appointments))
Updating appointments
Now you will enable your bot to update appointments in the database.
def update_appointment(update, context):
chat_id = update.effective_chat.id
message = update.message.text
event_id = message.split("_")[1]
event = client.query(q.get(q.ref(q.collection("Appointments"), event_id)))
if event["data"]["completed"]:
new_status = False
else:
new_status = True
client.query(q.update(q.ref(q.collection("Appointments"), event_id), {"data": {"completed": new_status}}))
context.bot.send_message(chat_id=chat_id, text="Successfully updated appointment status đ")
In update_appointment
method, you saved the chat_id
of the user and the message passed, then created event_id
by removing the update_
prefix from the message.
You then made use of the event_id
to query the FQL client to check for the appointment that matches the event_id
. You then checked if the completed
field of the appointment was set to True.
If this is the case, you set the new_status
variable to False. Otherwise, you set the new_status
variable to True.
Finally, you made a query to the FQL client to update the completed
field to the value of the new_status
variable using the Fauna update
method. You then sent a successful update message to the user.
You set the update_appointment
method to get triggered by a message handler that uses the regex filter to detect the update methodâs regex code. You then split the message to extract the appointment id. Copy and paste the code below to create the message handler.
dispatcher.add_handler(MessageHandler(Filters.regex("/update_[0-9]*"), update_appointment))
Deleting appointments
Now you will enable your bot to delete appointments from the database.
def delete_appointment(update, context):
chat_id = update.effective_chat.id
message = update.message.text
event_id = message.split("_")[1]
client.query(q.delete(q.ref(q.collection("Appointments"), event_id)))
context.bot.send_message(chat_id=chat_id, text="Successfully deleted appointmentđ")
The delete_appointment
method is like the update_appointment
method. However, the difference is that after retrieving the appointment by querying the Appointments
collection, you used the Fauna delete
method to delete the data from the database. Subsequently, you sent a message to alert the user of a successful appointment deletion.
You set the delete_appointment
method to get triggered by a message handler that uses the regex filter to detect the delete methodâs regex code. You then split the message to extract the appointment id. Copy and paste the code below to create the message handler.
dispatcher.add_handler(MessageHandler(Filters.regex("/delete_[0-9]*"), update_appointment))
Conclusion
In this article, we built an appointment scheduling telegram bot with Fauna's serverless database. We saw how easy it is to use Fauna as the database in our python application.
The source code of our bot is available on Github.
Happy coding!
Peer Review Contributions by: Ahmad Mardeni