SMU Facility Booking System

Telegram facility booking bot

Jul 2021

Source Code

Skills: Python • Selenium • Telegram API • Webscraping

Personal Project

6 min read


This is not a guide, but rather a personal documentation of my process building this project.

Introduction

This project was created during the after the first wave of the Covid-19 period, and the school was starting to open up and allow hybrid teaching modes. Facilities were being booked up very quickly and most of the time, facilities during popular time slots (e.g., 2-5pm) had to be booked 3-4 days in advance.

Back then, the Facility Booking System (FBS) for SMU does not have a mobile-friendly interface yet. Hence, the purpose for starting this project was to create a solution for my personal use, to automate the booking of a facility every week, booking a facility up to two weeks in advance.

The project is nothing fancy, but serves as a learning opportunity for me to work with Telgram’s Botfather API, as well as learn how to webscrape information and automate testing using Python’s Selenium Webdriver.

Process

Generate an API key using Telegram’ Botfather API, which can be done by following this guide here. Following the pyTelegramBotAPI docs, we will have to initialize the bot using this key like so -

fbs_bot.py
import telebot

# Enter your TELEBOT API KEY within the ""
bot = telebot.TeleBot("", parse_mode=None) # Can set parse_mode by default. HTML or MARKDOWN

Next, in order to store user inputs, we create a simple User class like so -

fbs_bot.py
class User:
  def __init__(self, chat_id):
    self.driver = webdriver.Chrome(ChromeDriverManager().install())
    self.login = False
    self.username = ""
    self.password = ""
    self.date_delta = ""
    self.building_type = ""
    self.facility_type = ""
    self.co_bookers = []
    self.start_time = None
    self.end_time = None
Defining User model
Inspecting HTML

In order to target the various form elements within the interface, a lot of inspecting of HTML elements had to be done, which was entirely new to me as I have little to no experience in web development and debugging.

login error text
Inspecting html elements

The first challenge here was to key into telegram the user details in order to access the system. Once the user has confirmed their details, the script will automatically open a new window and do some validation checks before proceeding - which looks something like that.

fbs_bot.py
 elif msg == 'Yes, confirm.':
  username = user.username
  password = user.password
  driver = user.driver
  bot.send_message(chat_id, "Logging in, please wait a moment...")
  driver.maximize_window()
  driver.get("https://fbs.intranet.smu.edu.sg/home")
  login(driver, username, password)
  try:
    driver.find_element_by_xpath('//*[@id="errorText"]')
    bot.reply_to(message, f"Incorrect user ID or password. Type the correct user ID and password, and try again.")
    msg = bot.send_message(chat_id, f"Please enter your email again: ")
    bot.register_next_step_handler(msg, getPassword)
    driver.quit()
  except NoSuchElementException:
    today = '-'.join(str(date.today()).split('-')[::-1])
    bot.send_message(chat_id, "Login successful!")
    user.login = True
    bot.send_message(chat_id, f"Today is {today}, which date would you like to book a facility on?\n\nClick on /setdate type /setdate.")
Login validation

After logging in, the interface looked a little something like this -

fbs interface
Interface for SMU FBS

Subsequently, most of the script will use Selenium’s find_element_by_x method to locate elements in a page in order to proceed to the next step in booking.

Bypassing iFrames

Another challenge which blocked me for a while was the use of inline frames (iframes), which prevents the script from targeting an element and ‘clicking’ on it properly. You can read more here on how to handle iframes when using Selenium, which was what I did as well.

fbs_bot.py
driver = user.driver
time.sleep(1)
frameBottom = driver.find_element_by_xpath('//*[@id="frameBottom"]')
driver.switch_to.frame(frameBottom)
bookingOverlay = driver.find_element_by_xpath('//*[@id="frameContent"]')
driver.switch_to.frame(bookingOverlay)
time.sleep(1)

# set Date
dateToBook = getDate(user.date_delta) # parameters is the number of days in the future
dateToBookPath = '//*[@title="' + dateToBook + '"]'
openDate = driver.find_element_by_xpath('//*[@id="DateBookingFrom_c1_textDate"]').click()
Handling of iFrame using 'switch_to' method

Most of the booking process can be completed by repeating the same steps again

  • Obtaining user input from telegram
  • Locating the HTML element on the site
  • Selecting and filling the form with user input
Catching exceptions

Another caveat of using an automation testing suite like Selenium is that there may be multiple instances where the software is executing faster than the browser can load, which may result in a NoSuchElementException or something similar. There are multiple ways to handle this issue, such as using exception handling, explicit and implicit waits, or Python’s time.sleep() method. More on a possible solution can be read here.

fbs_bot.py
# search Availability
searchAvailability = driver.find_element_by_xpath('//*[@id="CheckAvailability"]/span').click()
# randomly click a square # 431
try:
  clickTime = WebDriverWait(driver, 15).until(EC.element_to_be_clickable((By.XPATH, '//*[@id="dps"]/div[3]/div[3]/div/div[2]/div[431]'))).click()
  time.sleep(2)
  makeBooking = driver.find_element_by_xpath('//*[@id="btnMakeBooking"]').click()
  bot.reply_to(message, f"Building confirmed! You selected {user.buildingType}.")
  bot.send_message(chat_id, "To select time slot, click /time or type /time.")
except ElementClickInterceptedException:
  bot.send_message(chat_id, "Oops! There is no available GSR slot for this date.")
  bot.send_message(chat_id, "Logging out...")      
  driver.quit()
  bot.send_message(chat_id, f"To book a facility again, please type /book. Sorry! You will have to re-login again :(")
Handling exceptions
Calendar

Another point learnt during this project was the concept of never trusting the users to enter the perfect input. This was especially apparent to me when trying to figure out how to get users to key in the correct date format. Should it be "DD-MM-YYYY" or "MM-DD-YYYY", or both? Ultimately, I decided to implement a more "dummy proof" input method for the users through a calendar module.

The calendar module implemented within this app can be found here.

calendar inteface
Calendar user input
Conclusion

This was a really interesting project and I was able to learn a great deal of things by doing it, like

  • Using callback functions
  • Exception handling
  • Inspecting HTML elements and debugging
  • Storing and passing user inputs to the next step (form handling)
  • Input and form validation

Unfortunately, due to privacy and security concerns, I was not able to allow others to use this application since users were required to input sensitive information such as their password, in order to make use of the application.

Nevertheless, it was a good learning opportunity for me to learn some of the key concepts in web development . While there are definitely better way to do things, it was not a priority at that point in time, so please do not be too critical of the script!😬😬

Code can be found here.

Chew Yi Xin 2023  •  Last updated Feb 2024