With COVID vaccines becoming more available to younger age groups, I decided to start looking to schedule a vaccination appointment for myself. Unfortunately, I found that trying to schedule an appointment was a game of constantly hitting the refresh button.
The local pharmacy chain in my area that I was trying to get vaccinated at provided a sign up form online for appointments, but there was no way to be notified when new appointments were published. Lore circulated on local Twitter saying, “new appointments are published at midnight, check then!” Or, “check around 8 AM and 4 PM, that’s when they release new appointments!” Clearly, there was no official answer.
After a lucky refresh, I finally found a pharmacy with availability. Straight to the sign up page I went. I selected an appointment time, filled out about 10 minutes worth of forms, and finally hit the submit button. Instead of getting a sweet confirmation of my newly reserved appointment, I received an error that said the appointment time had already been taken. Clearly, someone completed their forms faster than I did and was able to get their appointment scheduled before mine. “No worries, I’ll just select another timeslot,” I naively thought. Going back to the time selection page, I found that all the previous appointments that were available had disappeared. Growing increasingly frustrated, I thought that there must be another way. And, of course, like most things, there is. Luckily, I’m a CS student that is completely remote (thanks pandemic), so I had plenty of time on my hands to devise a solution.
Ultimately, out of the goodness of my heart and a little spite, I created a Twitter bot that automatically tweets when new vaccine appointments are found at my local pharmacy chain. It has been extremely successful and has helped plenty of people get signed up for vaccine appointments. Here, I’ll show you how I did that.
This account tweets when new vaccine appointments become available at HyVee pharmacies in the Omaha/Lincoln/Council Bluffs area. Appointments go fast so enabling notifications is recommended.
— HyVee Vaccine Tracker (@HyveeTracker) March 30, 2021
You'll still need to register for an appointment by using the link in each tweet.
Exploring the API
The first thing I had to do was figure out how the appointment sign up page worked on the pharmacy’s website. After playing around in the web inspector for a bit, I found that a GET request was being made to the endpoint https://www.hy-vee.com/my-pharmacy/api/graphql
. Now, this looked like a GraphQL API call, but I don’t have experience in GraphQL. Thankfully, it was easy to figure out. The body that was being sent in the request looked like this:
This didn’t look too complicated. There’s a named operation which describes itself, some location variables, and they query itself. Great, let’s try playing around with those variables.
I found that by changing the radius, along with te latitude and longitude, I was able to search for available pharmacies with vaccine appointments in an arbitrary area.
Let’s also look at what the response is to this request before we move forward.
Nice. It looks like a simple array of pharmacies, with each pharmacy object containing location information about the pharmacy, as well as if it has vaccine appointments available. Too easy.
Scraping the API
Now that we can get pharmacies with available appointments, we need continuously make GET requests to the API to find pharmacies with new openings.
Choosing a backend
Really any backend would work for something like this. I ended up going with Golang (no pun intended), because it is really a no-frills language and there are plenty of libraries available on GitHub, one of which being a Twitter API library.
Mocking out the API
The first thing I needed to do before I could start performing Hy-Vee API requests is mock out the objects that are returned from the API. Thankfully, GoLang makes this dead simple with struct annotation.
Here are a few examples of the structs.
Creating domain structs
At this point, I could just use the above mocked objects for my application domain. In fact, in my original version, I did just this. But, this isn’t good programming practice because it ties my application’s domain to the API’s domain. What if Hy-Vee changes the way a pharmacy is represented? Then my application domain has to change by extension. A better approach is to create a domain just for my application, and use the adapter pattern to change from one domain representation to another.
Here is an example of my domain structs:
Performing an API request
Now that I had the domain for the API mocked out, I could try to perform a request. For this, I created a simple function
This functions returns a slice of pharmacies in API representation. I also made a helper function (not shown here), that converts a pharmacy API slice to an pharmacy domain slice.
Tweeting
In order to start tweeting new vaccine appointments, I needed to setup a new Twitter account. I also needed to request a developer account under the new account so I could get access to Twitter’s API. I won’t cover this process here, as Twitter provides great documentation and support for this process.
Not reinventing the wheel
To call Twitter’s API, I could have done the same process as I described above, but for Twitter’s endpoints. This is a lot of work, and somebody has probably already done this for us. Sure enough, after one search on GitHub, I found go-twitter.
Sending a Tweet
Next, I made a Twitter struct which has a function called Deliver. Deliver accepts a pharmacy and tweets out information about that pharmacy. I also made a helper function called pharmacyToTweet, which converts the important information contained within a Pharmacy to a formatted string.
Below, I’ve left out the oauthConfig and token variables since these are obviously secret values. A more robust application might retrieve these values from a config file or even a secrets manager.
Putting It All Together
Now that I had the ability to get information about pharmacies from Hy-Vee’s API and tweet about them, I could then move on to tying these components together.
Running periodically
The whole point of this project was to scan the Hy-Vee API at regular intervals. But what is regular? 10 seconds? 5 minutes? 10 minutes? Keep in mind, these vaccine appointments go fast so if I waited too long, I might miss a window of new appointments. I also wanted to be nice to Hy-Vee and not make too many requests to their API.
I decided on an interval of 60 seconds. I found this to be a perfect balance between not missing new appointments and not making too many requests. Thats only 60 requests an hour.
Originally, my idea for making API requests periodically looked a little something like this
This definitely worked, and was simple, but I found it to be a little lackluster. I always try to teach myself something new when it comes to making a new project, so I decided to use Go’s time.Ticker
.
The benefit of using a Ticker
over just time.Sleep()
is that the Ticker gets us really close to running exactly on our selected interval.
For example, with the sleep method, let’s say our pharmacy update takes 10 seconds. The update would take place, and then the program would sleep for 60 seconds. This means our updating process is only being performed every 70 seconds.
The internals of Ticker
take this problem into account. So, if our update takes 10 seconds, the next scheduled interval will be adjusted to 50 seconds, bringing us to a grand total of a 60s interval.
Keep in mind, with a Ticker
, our interval does not tick right away. So, that is why I need to call updatePharmacies()
before starting the function that consumes the ticker. That way we don’t have to wait 60 seconds for the first update to occur.
Update logic
Okay, so now I had the ability to run a function periodically, but what does our update function actually look like? Above, I called updatePharmacies(pharmacyRepo)
. We’ll see what that function looks like below.
I also have a helper struct, interface, and type.
What’s neat is that a Bot
contains multiple Deliverers
. Each time new appointments are detected, the Deliver()
function is called for each Deliverer. I’m not listing it here, but I have a simple ConsolePrinter
which is a Deliverer which simply prints information about the pharmacy to the console. This helps for debugging.
The logic for detecting new appointments is fairly simple. When the initial update is performed, all found pharmacies are loaded into a map with the key being their ID and the value being the pharmacy object itself. Whenever a new update is performed, a lookup is performed for the existing key for each pharmacy, and if vaccination appointments are now available, and they weren’t before, that pharmacy gets delivered using each deliverer.
For simplicities sake, I’ve used a simple map as my pharmacy repository. A more robust application might consider the use of a database to persist pharmacy data.
Closing Thoughts
I really enjoyed this project. To get an initial, crude version up and running it only took me about an hour. After making some improvements by refactoring and switching to time.Ticker
I put in another couple of hours. This application is hosted on a tiny AWS EC2 instance, and costs next to nothing to operate.
This Twitter bot has helped a lot of people sign up for their vaccination appointments so far, and that is truly rewarding. To see an application I’ve developed actually impact peoples’ lives for the better is priceless, and has given me motivation to continue making applications that others find useful.
The full project is available here on GitHub.
Shout out to @itsjakehansen for the amazing vaccine bot that helped me and my bf get our first shots today, and I was able to help a friend with immune issues get an appointment for Friday. He tried for a month and got nowhere until @HyveeTracker thank you so much, Jake
— Jaycie (@jaycre_leigh) April 5, 2021
Hey Omaha friends, looking to get vaccinated?
— Austin Gaule (@austinomaha) April 5, 2021
Literally, follow @HyveeTracker and you'll probably have an appointment by end of today or tomorrow.
Don't know who developed this thing, but it's extremely on point. I've helped a couple friends get their shot through this!
With vaccine eligibility opening more and more, I know people are trying to schedule an appointment.@HyveeTracker is a bot that tracks when Hy-Vees have appointments open on their website.
— Matt Serwe KETV (@MattSerweKETV) April 3, 2021
Might be a good follow if you're still trying to get something set up!
I can’t love this enough!! I set his appointment for Monday. Thank you!!
— Victoria Parks 🇨🇦🇺🇸 (@ParksThriller) April 4, 2021
Finally scored a vaccine appointment for my husband, thanks to @HyveeTracker Great work, @itsjakehansen --thank you! https://t.co/QSXEyEj8TE
— elliejo (@jomamaliza) April 2, 2021