Close

Here at Box UK we often meet up at the end of the week for a pint down at the local pub. Our local changes what beers are on tap daily, so they use a TV to display what beers they have available together with useful information such as the price and type of beer.

Previously I thought these were just images displayed on the TV screen but one day I noticed the TVs were actually rendering a web page!

For internal comms at work we use HipChat, and we have Hubot installed to help us out with various tasks. For those who haven't met Hubot yet, he's a Node.js chatroom bot created by GitHub to do all manner of things from telling jokes to deploying code.

The grand plan

This got me thinking. I wondered if we could ask Hubot what beers are on at our local using the HTML page they use on the TV to get that information.

Hubot scripts are written in CoffeeScript and after a bit of wrangling I had my initial concept working. Hubot comes with a HTTP library for obtaining data from an address, a bit like curl. Using it is as easy as:

robot.respond /(what|which) beers are (on|available)/i, (msg) ->  
 msg.http('http://www.example.com').get() (err, res, body) ->    
   console.log body

This returns the body of the response for me to use; for me this was a load of HTML so I needed to parse this HTML to extract the data out.

I could do this with some funky regular expressions to strip out the tags and grab the data I'm looking for. But instead I used a library called cheerio. Cheerio accepts a string of HTML and uses jQuery like syntax to query it. This was really cool because I already knew jQuery fairly well so it was easy to get started.

Once I had the part of the page I wanted I could iterate over the table rows and get the data from the cells, adding them to a beers array.

$ = cheerio.load(body);
boardRows = $(".beer-board > table tr");
boardRows.each (i, el) ->  
  info = [];  
  $(this).children().each (i, el) ->    
    info.push $(this).text().trim();  

  beers[i] = "(beer)  " + info.join(", ");

Now that I had the information it was just a case of printing that to the page beer by beer.

msg.send "Hey there!\n At the taphouse on tap we have:";
msg.send beers.join("\n");
msg.send "Cheers! (beer)"

This worked really well; as you can see I have the beer name, the brewery, the percentage, price and type of beer. All this came from the beerboard!

Hubot untappd information

Untappd promises

We deployed this to the server and people started to use it, feedback was good but an interesting request that came in was to integrate Untappd ratings for each beer.

At first I added another http request to Untappd inside the boardRows.each loop which parsed Untappd in much the same way as I had done previously with the beer board. Unfortunately I forgot that Node and therefore Hubot runs its code asynchronously. This meant that the original beerboard request wouldn't wait for the Untappd ones to complete.

I got around this by using javascript promises via the Q library. Essentially a promise says “given I run this code, then do these things afterwards”. My Untappd promise looks a little bit like this:

getRating = (msg, query, info) ->    
    deferred = q.defer();    
    msg.http('https://untappd.com/search?q=' + query).get() (err, res, body) ->        
        if (err)            
            deferred.reject() # You broke your promise! :(        
        else            
            $ = cheerio.load(body);            
            rating = $('.results-container .beer-item:first-child .num');            

            # Format the rating into gold stars            
            rating = rating.text()            
            rating = rating.substring(1, rating.length-1)            

            message = '';            
            $i = 1            
            while $i <= Math.round(rating)                
                message += '(goldstar)'                
                $i++            

            # Push the rating to the info collection            
            info.push message unless !message            

            beers.push '(beer) ' + info.join(', ');            
            deferred.resolve(); # Say the promise was fufilled    

    return deferred.promise

This meant I could tell the script to wait for all the Untappd responses to finish before sending my response.

q.all(promises).then(() ->  
  msg.send "Hey there!\nAt the taphouse on tap we have:\n" + beers.join("\n") + 
"\nCheers! (beer)";
)

Finishing touches

To add a bit of character I added some suggested excuses from the devs that would appear in the event that something went wrong. These were picked at random and it adds a nice playful touch to the script.

excuses = [
  'No beers here. Go to the City Arms instead.',
  'Sorry, dunno what\'s on, we\'re all drunk.',
  'ZZZzzz...wha..no, drank it all, try somewhere else...zzzZZZzzzZZZzzz...'
]

In the end I had a lot of fun writing this script and it's proved quite handy, especially when the end of the week rolls around and you're curious to see what's available behind the bar!

Hubot beer information

You can find the script on GitHub or as a package on npm.

To stay up-to-date with the latest tech news and views, be sure to check out our weekly tech round-up posts - and sign up to our mailing list to have them delivered direct to your inbox.

About the author

Robert Rhoades

Robert Rhoades

Robert Rhoades is a Developer at Box UK. Rob works in multiple disciplines and is always on the hunt for the next big thing. In his spare time he can be found to be cycling, camping and general adventuring.

Related content

Tech round-up: Nov 25th

By Steve Anderson

Tech round-up: Jun 2nd

By Ian Jenkins

We're hiring. Let's talk. View available roles