Hacking My Gym With Node.js
With pandemic measures in place, gyms everywhere have taken precautions to only let a small number of people work out at a time. My gym in particular did this using a website or mobile app, that allows members to book slots. Each days’ slots were opened up for booking exactly a week before the booking day, so the slots for October 14th are opened at midnight on October 7th for example.
The timing that bookings opened was really annoying for me because I typically go to bed at around 10.30pm and when I’m up at 6.30am, all the slots are booked! I didn’t want to have to wait until midnight every night to do the booking, so I decided to find a way to automate the process.
Investigation
First off, I had to do a bit of digging to figure out if this is even possible. I popped open the developer tools in my browser and checked what happens when I made a booking.
After hitting the book button, I saw this POST request that does the booking. Great! Looking closer at the request
The ClubId
is unique for the gym branch that I go to, and I guessed that TimeslotId
is a unique identifier for the time slot that I just booked. The ClubId
won’t change (since I go to the same club every time), but I’ll need a way to find the TimeslotId
every day. I thought the front end must be getting the that info since it knows which one to send back to the server. Sure enough, I found this request in the network tab:
GET /club-occupancy/club-workout-schedule?club=268&day=2020-10-08&studio=Gym%20Floor
Which gives a response that looked like this
Perfect, the TimeslotId
looks exactly like the Id
field in each of these objects. So given a ClubId
, a date and time, I can make a booking.
Prototype
When working with APIs, I like to make sure everything works with a program like Insomnia. It’s kind of like the popular Postman app, I’ve grown to prefer it because of its native GraphQL features and nice interface.
☹️ The request was unauthorized, meaning I probably have to do a login request and pass in some authorization parameters as well. I went back to the web page to grab the login endpoint and used it to grab a cookie. Voila:
And now trying to make a booking:
Amazing, I got an email shortly after saying my booking was successful. Now to just put all this into JavaScript 😌.
Writing code
Diving right in, I need three functions for the three API calls (login
, getBookingSlots
and makeBooking
). Starting with the login function, it needs to accept a username and password and return a cookie that the other two functions can use for authorization.
I use the FormData
library to format data for the request since the endpoint expects Form URL Encoded data. I am also using Axios
for the requests since it has a nice promise based interface. I looked at the formatted cookies that were used by Insomnia, and mimicked them using a reducer to remove the extra pieces. Next, using this I need a function to grab the booking slots for a specified club, date and time.
I opted to use day.js for handling dates because it makes it really easy to format dates, and do date calculations. Earlier, I showed the response from this endpoint comes back as an object that kind of looks like
{
MorningList: { ... },
AfternoonList: { ... },
...
}
This time of day separation is pretty useless so I reduce it down, with ES6 spread syntax, into just one array of time slots that can then be filtered easily. Okay last helper function, one to actually do the booking.
All the pieces are now in place, I need to actually orchestrate the calls. I needed this code to run once every morning right around midnight on the days 1 week before I wanted to go to the gym. Day.js comes in really useful here for doing some date math. To run once per day, I used the built in setTimeout
function. I get the next time to try to make a booking as 30 seconds past midnight the following day.
There’s a bit more stuff going on here. I have an array of the days I actually want to go to the gym, and if the current day is not one of those, I just try again a few seconds after midnight tomorrow.
Next, the functions I created earlier are called in sequence to login, get the bookings and actually create the booking on my behalf. I’d like a booking at 7.30am every time, so I find the time slot with that time specified.
There is a bit of error handling, where if the request fails for whatever reason, the entire process is retried after waiting for 1 minute. If it fails 5 times, it gives up and just tries tomorrow.
Problems
There were a few problems I encountered while working on and using this. Firstly, I wanted to have the script running all the time, so I decided to throw it onto a Heroku instance. However, I soon found that since it was idle for so long, my free dyno was put to sleep 😬. To get around this, I ended up creating an express server in the app and having it ping itself every 5 minutes to keep the dyno alive.
The second problem I faced was a realization that happened after the app tried to make a booking on a public holiday. There were no slots available, and the program ended up crashing when I tried to read the error message off of an undefined response. So now I have no time booked for the day after the holiday 🙃. This one made me realize that it’s probably a good idea to have my console.log
messages go to slack instead of just being logged into the void. That’s one improvement I’ll eventually come around to.
Conclusion
The full source is available here. This was a pretty fun little way to make my life a bit easier and didn’t require a lot of work to get done. I’ve found that I enjoy tinkering with little things like this that are actually useful to me. I hope you enjoyed the read.
Cheers