Inspiration

About a year ago, I (James) hackily scraped the Wetherspoons app to find the drink that would get me the most drunk for the least amount of money. The victor (2x Kopparberg Sweet Vintage Pear at £6.20 for 7 units) was a staple for me since then, but costs are rising and I tragically have to drink in London, so a more up-to-date and flexible solution was needed.

What it does

Optimal Pint is (or will be) an interface to find the optimal drink at any given Wetherspoons. Optimality here is defined as £/unit.

How we built it

We started by MITMing the Wetherspoons app in an Android Emulator to figure out how its API worked, so that we could create a scraper to update a SQLite database. This scraper was written in Go, to provide a REST backend for a Svelte frontend.

Challenges we ran into

  • The Wetherspoons API is genuinely awful
    • The app starts with pulling info on every Wetherspoons in all of the UK, which is convenient for us, but the JSON response is massive and full of random stuff that we don't care about. We were able to make minimal structs to only extract what we wanted, but interpreting these JSON blobs was awful.
    • Once you've selected a "venue", there's quite a journey to get to the drinks. First, you have to fetch the list of "sales areas" in the pub. I didn't directly encounter any pub that has more than one (most pubs just declare "bar"), but our scraper must still handle cases where venues can sell drinks from different places. Then, you have to fetch the menus that the sales area provides. Not all pubs had active drink menus during some scraping, so this had to be handled. Finally, you could find the ID of the drinks menu and fetch every drink in one big request (explains why the app is so slow).
    • The drinks response is a gargantuan 6 megabytes with JSON full of edge cases and extra data. Staring at this blob to get basic info like the price of the drink was miserable, and required multiple intermediary structs.
    • The API doesn't directly have a field for "units", just a string that says what the ABV is, and usually the units (you can see this string directly in the app - it's displayed under drinks). Parsing this string was a bit fiddly due to edge cases where some drinks also listed how many milliliters they were.
    • Most drinks have multiple "portion" options, such as half pints or small glasses of wine. We decided to only consider the largest volume, assuming that half pints would always cost half of a full pint.
    • This was partially because we'd have to consider every portion type in every drink for £/unit calculation
    • Additional care was needed to account for bundle deals, such as 2 for £6.20. If such a deal is present, we assume that you'll always be taking advantage of it.
  • Go is a strange language

    • This was my (still James) first time really using Go.
    • What the fuck is zeroing???? When decoding JSON into a struct, any missing fields will just be zeroed out instead of actually throwing an error. This added a lot of back and forth finding which specific request wasn't decoding properly because I'd have to hunt for zeroes or empty strings instead of actually getting an error in a language that I was told had good error handling. I never thought that I'd miss Python's type system, but saying "this can be nil sometimes is the bare minimum. Use gookit validate. >:(
  • We coerced Joe into learning Svelte for the frontend

    • I think he had fun, but the search function was amazingly slow

Accomplishments that we're proud of

  • You find the most optimal drink at your location of choice.
  • You can also find a top 100 list of the most optimal drinks in the country.

What we learned

  • The optimal drink in all of the UK is the Greene King Ruddles Best at 99p(!) for 1.9 units. This astonishing price is only available at The Ralph Fitz Randal in Richmond, North Yorkshire.
  • The optimal drink in The Joseph Else is "Black Hole - Milky Way" at £2.10 for 3.4 units
  • Both of these options beat the previous victor by ~20p/unit, saving me vast amounts of money

What's next for Optimal Pint: A Few Small Beers

  • Greene King support
  • Rewrite the backend in a sane language
  • SwiftUI frontend 😍😍😍

Built With

+ 1 more
Share this project:

Updates