Jul 2021
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.
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.
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 -
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 -
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
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.
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.
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.")
After logging in, the interface looked a little something like this -
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.
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.
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()
Most of the booking process can be completed by repeating the same steps again
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.
# 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 :(")
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.
This was a really interesting project and I was able to learn a great deal of things by doing it, like
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.