Skip to main content

Automated Facility Reservation With Python and Linux

My wife and I love to play racquetball. We're at the gym a few times a week, and it's become a treasured routine that I look forward to. Something I do NOT look forward to, however, is logging onto the rec center website to reserve a racquetball court. It beats having to make a phone call to reserve a court three times a week, but it's still a bit tedious. Sometimes I forget to make reservations far enough in advance, which means we've had to miss out on our favorite pastime on a few occasions. That's NOT fun!

racquetball gear

One day, after a long day of work, my wife and I arrived at the gym, excited to play some racquetball and release the day's stress. As we turned the corner toward the racquetball courts, we were met by a group of players talking to one another. We could see that three of the four courts were occupied, and began making our way to the empty court. The other players gave us a quick side-eye, and I could hear one of them half-whisper to the others: "Quick, let's grab the court before they do..."

So, the group scurried over to the door, hurriedly filed into the small court, and slammed the door. Most of them avoided making eye contact with me, but one glanced shyly and half-shrugged, as if to say "sorry... better luck next time."

After that, I was pretty fed up. I didn't confront the group - after all, they hadn't done anything wrong - but I decided I was done getting beaten out of a court. That was the day I decided to dust off my Python skills and build a script that would make sure I never forget to make reservations.

TL;DR

I automated the process of reserving racquetball courts using Python and Selenium, self-hosted the script on a Linux server, and set it to run automatically via a cron job. I encrypted the credentials I used to log into the reservation site, and set up Tailscale to securely access my server remotely for making small changes. I used ChatGPT to create a study plan that helped me learn the necessary Python skills. The script securely handles the encrypted login credentials and allows for easy schedule adjustments through a configuration file.


The Preparation

I had studied Python basics before as part of my Google Cybersecurity Certificate course, but I hadn't used it in any personal projects yet, so I still had a lot to learn. Here's the plan I came up with:

  1. Use Python and the Selenium browser automation library to build a script that can reserve racquetball courts for me.
  2. Create a simple configuration file that I can update when our schedule changes. It should allow me to schedule regular days, times, and specific courts, as well as allow me to set exception days so it doesn't make reservations for me when I won't be there.
  3. Put the script on a Linux server running in my homelab and create a cron job to run it automatically.

Writing the Script

Since this was my first personal Python project, I knew I'd have to learn a lot. Previously, when I needed something written in code, I asked ChatGPT to write the code for me, but I decided I wanted to learn the skills to do it myself. I needed to create a study plan to help me work up to my goal. So, instead of asking ChatGPT to write the code for me, I asked it to help me come up with a study plan, which it did marvelously! What made ChatGPT such a great tool for the job was it created a custom plan tailored to my project requirements. Here's what I asked it to do:

"Hey ChatGPT, create a study plan for me that will help me learn all the skills I need to successfully write a program in Python, using the Selenium browser automation library, that can automatically log into a website and perform some actions on my behalf. I also want you to generate a series of prompts for me to use in a separate conversation with an LLM. I want you to generate prompts that will start by explaining what I've explained to you, that I want to set up this script using Python, what I want it to do, etc. In the prompts, include instructions for the AI to give me guidance to help me learn the basics of Python as I go. Break the process up into small steps and generate separate prompts for each step, starting from the basics. Tell the AI to generate clues and tips for me with small lessons in the techniques and syntax that I'll need to understand to write the code in Python. Lesson 1 will have to start from level 0 because I don't know how to code in Python. Generate enough prompts for me to tell the AI to guide me through all the steps necessary to use this project as an intro to Python activity."

I got a great response with a learning plan I could feed to ChatGPT one step at a time. This left it up to me to decide when I was ready to continue, and gave me the flexibility to spend as long as I wanted on each step, ensuring that I had a strong grasp of each concept before moving on. In case you're curious to see the learning plan ChatGPT wrote for me (in the form of prompts I could use to get ChatGPT to TEACH me the things I needed to learn), check out the expandable section below.

NOTE

One important thing I did was make sure to tell ChatGPT NOT to give me code to include in my actual script. I said it could give me code demonstrating general concepts I was learning, but that it shouldn't be code I could use in my script. I wanted to make sure I wrote my own code so I could learn from the process.

My Study Plan

Prompt 1: Getting Started with Python Basics
I want to learn Python as a complete beginner. My goal is to create an automated script that logs into a website, navigates through pages, selects a date dynamically (next Monday), and completes a reservation. I need to start from zero and gradually build up my skills. Please guide me in learning the basics of Python, starting with variables, basic input/output, and how Python code is structured. Include small lessons and tips with examples. Keep it very simple and beginner-friendly.

Prompt 2: Introduction to Python Libraries
Now that I understand Python basics, I want to learn about libraries. Explain what libraries are in Python and how they are used. Please teach me how to install and import libraries. Use Selenium as an example since I will use it later in my project. Also, include a lesson on how to read documentation to understand what a library can do.

Prompt 3: Navigating the Web with Selenium
I’ve learned the basics of Python and how to use libraries. Now, please guide me in setting up Selenium for web automation. Start by showing me how to install Selenium and a browser driver like ChromeDriver. Then, teach me the basics of opening a webpage, locating elements, and interacting with them. Include a small lesson on HTML basics so I understand what I’m looking for when navigating a webpage’s structure.

Prompt 4: Using Python to Handle Dates
I want to learn how to calculate dates in Python so that my script can dynamically find the next Monday. Please teach me how to use Python’s datetime module for date calculations. Show me how to get today’s date, calculate the next Monday, and format dates as strings for use in my script.

Prompt 5: Logging into a Website
Now that I can interact with a webpage using Selenium, please teach me how to create a login process. Show me how to locate username and password input fields, fill them in with stored credentials, and simulate a button click to log in. Include a small lesson on securely handling login credentials (e.g., using environment variables or other methods).

Prompt 6: Navigating a Website with Selenium
I’d like to learn how to navigate through multiple pages on a website using Selenium. Please teach me how to locate and click links or buttons, handle dropdown menus, and wait for elements to load dynamically (e.g., using WebDriverWait). Provide lessons on debugging and troubleshooting common issues, like elements not being found.

Prompt 7: Selecting a Date Dynamically
My script needs to dynamically select a date on the website, specifically the next Monday. Teach me how to write code that uses the calculated date (from the datetime module) to select the appropriate option in a dropdown menu or calendar picker on the website. Include a lesson on how to use XPaths or CSS selectors for dynamic element selection.

Prompt 8: Handling Terms and Conditions
The reservation process requires me to agree to terms and conditions. Teach me how to locate and interact with checkboxes or pop-ups in Selenium. Include lessons on handling JavaScript-based elements and debugging when interactions don’t seem to work.

Prompt 9: Reviewing and Submitting the Reservation
Now that I can navigate the site, I want to learn how to review and confirm the reservation in the cart. Teach me how to locate and extract text or other information from elements on the page to confirm that I’ve selected the correct date, and then simulate a button click to finalize the reservation.

Prompt 10: Running the Script on a Schedule
My script should run automatically once a week. Teach me how to set up the script to run on a schedule using a task scheduler. On Linux, teach me how to use cron, and on Windows, teach me about Task Scheduler. Also, show me how to log errors or successes for later review.

Prompt 11: Running in Headless Mode
I want to run my Selenium script in headless mode on a server. Teach me how to configure Selenium to work in headless mode and troubleshoot common issues when running scripts in this mode.

Prompt 12: Wrapping Up and Best Practices
Now that I’ve built the script, help me review it for best practices. Teach me about writing clean, maintainable Python code, handling errors gracefully, and using comments to document the script. Provide tips on how to test the script periodically to ensure it continues to work if the website changes.

Running/Hosting the Script

I spent quite some time deciding the best way to run the script. Originally, I considered running it inside a Windows VM, using a program called Ui.Vision for the browser automation. I'd used Ui.Vision before for work, and it wouldn't require me to learn much code. Here's why I decided to learn to do it with Python instead of using Ui.Vision.

A diagram showing the elements of this project interacting
  1. Dynamic Calculation: Python has the advantage of using datetime to programmatically calculate and input dates. In Ui.Vision, I would need to script this using JavaScript or manually update the macro regularly.
  2. Website Complexity: If the website changes soon, Python's flexibility will make adapting the script easier than Ui.Vision.
  3. Personal Growth: Learning Python now could open doors to other automation projects, not just this one.
  4. Ui.Vision requires an open desktop environment since it needs an unlocked desktop to simulate real user commands. This means it would be hard to truly automate it such that it could run without my involvement.

Since I already had Ubuntu Server running headlessly in my homelab, I already had a place for the script to live, so I uploaded it to a git repository and cloned it to the server, where I then set up a cron job that runs the script every day.

Controlling/Altering the Behavior

I created a simple configuration file that allows me to specify days I'd like to attend regularly, courts I'd like to reserve, and days I'd like it to skip reservations. Here's what it looks like:

# excluded_days format is YYYY-MM-DD, YYYY-MM-DD (e.g. 2024-12-21, 2024-12-23)

[DEFAULT]
scheduled_days = Monday,Wednesday,Friday
excluded_days = 2024-12-26, 2024-12-28

[Monday]
court = Racquetball Court 1
time_1 = 1:00PM
time_2 = 2:50PM

[Wednesday]
court = Racquetball Court 2
time_1 = 8:00AM
time_2 = 8:50AM

[Friday]
court = Racquetball Court 4
time_1 = 3:00PM
time_2 = 4:50PM

This way, I don't have to alter the actual Python code every time I want to make a change to my schedule. I can just quickly ssh into the server from my phone and alter the config file as needed. And since the script runs every day, but refrains from making reservations on days it's not supposed to, I could easily decide to start reserving courts on a day I haven't reserved before just by adding it to my config file - again, without having to make changes to the Python code. In fact, I've now been running this script for quite a while now, and I haven't had to make any changes to it in the last four months.

Security

Since security is of paramount importance to me, I made sure to keep it in mind with this project. Firstly, I don't expose my homelab to the internet. Maybe one day my use case will require that, but so far I have had great success using Tailscale. I highly recommend you check out Tailscale if you're ever accessing any of your home or work resources remotely. It's super easy to use and quite secure.

I've also learned that committing secrets to Git repositories is ill-advised, so when I wrote the code to log into the website, I stored the login credentials in a separate, encrypted file on the server. I also stored the key in a separate file, which allows the program to quickly decrypt and retrieve the credentials when it runs, and protects my account by keeping the credentials encrypted at rest. Here's a snippet from the code that I used to retrieve and decrypt the credentials.

    # Load credentials
def load_credentials(file_path="credentials.txt"):
credentials = {}
with open(file_path, "r") as file:
for line in file:
if line.strip() and not line.startswith("#"):
key, value = line.strip().split("=", 1)
credentials[key] = value
return credentials

def load_encryption_key(file_path="encryption.key"):
with open(file_path, "rb") as key_file:
return key_file.read()

def decrypt_password(encrypted_password, key):
cipher = Fernet(key)
return cipher.decrypt(encrypted_password.encode()).decode()

try:
credentials = load_credentials()
logging.info("Successfully loaded credentials.")
except Exception as e:
logging.error(f"Failed to load credentials: {e}")
return

try:
key = load_encryption_key()
except Exception as e:
logging.error(f"Failed to load encryption key: {e}")
return

# Decrypt the password
try:
email = credentials.get("EMAIL")
encrypted_password = credentials.get("PASSWORD")
password = decrypt_password(encrypted_password, key)
except Exception as e:
logging.error(f"Failed to get or decrypt credentials: {e}")
return

Takeaways

I learned a ton from this project. Here are a few of the highlights:

  1. ChatGPT can be a great resource for identifying gaps in knowledge and creating a study plan tailored to your project goals.
  2. If you want to run unattended, automated, scheduled tasks in a browser, doing it with Python, Selenium, and Linux is a good way to go.
  3. Technology can be a powerful tool in removing obstacles to important things in your life. Don't be afraid to dig in and learn what it takes!