<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>untappd &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/untappd/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Sat, 26 Apr 2025 09:07:39 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>untappd &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[Untappd to Mastodon - Updated!]]></title>
		<link>https://shkspr.mobi/blog/2024/05/untappd-to-mastodon-updated/</link>
					<comments>https://shkspr.mobi/blog/2024/05/untappd-to-mastodon-updated/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 12 May 2024 11:34:19 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[MastodonAPI]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[untappd]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=50507</guid>

					<description><![CDATA[A few years ago, I wrote some code to post Untappd check-ins to Mastodon.  I&#039;ve recently updated it to also post a photo of the beer you&#039;re enjoying.      First up, you&#039;ll need a file called config.py to hold all your API keys:  instance = &#34;https://mastodon.social&#34; access_token          = &#34;…&#34; write_access_token    = &#34;…&#34; untappd_client_id     = &#34;…&#34; untappd_client_secret = &#34;…&#34;   Then a file called u…]]></description>
										<content:encoded><![CDATA[<p>A few years ago, I wrote some code to <a href="https://shkspr.mobi/blog/2023/03/posting-untappd-checkins-to-mastodon-and-other-services/">post Untappd check-ins to Mastodon</a>.  I've recently updated it to also post a photo of the beer you're enjoying.</p>

<iframe src="https://mastodon.social/@Edent/111784153251146118/embed" class="mastodon-embed" style="max-width: 100%; border: 0" width="400" height="600" allowfullscreen="allowfullscreen"></iframe>

<script src="https://mastodon.social/embed.js" async="async"></script>

<p>First up, you'll need a file called <code>config.py</code> to hold all your API keys:</p>

<pre><code class="language-python">instance = "https://mastodon.social"
access_token          = "…"
write_access_token    = "…"
untappd_client_id     = "…"
untappd_client_secret = "…"
</code></pre>

<p>Then a file called <code>untappd2mastodon.py</code> to do the job of grabbing your data, finding your latest check-in, then posting it to the Fediverse:</p>

<pre><code class="language-python">#!/usr/bin/env python
# -*- coding: utf-8 -*-

from mastodon import Mastodon
import json
import requests
import config

#  Set up access
mastodon = Mastodon( api_base_url=config.instance, access_token=config.write_access_token )

#       Untappd API
untappd_api_url = 'https://api.untappd.com/v4/user/checkins/edent?client_id=' + config.untappd_client_id + '&amp;client_secret='+ config.untappd_client_secret

r = requests.get(untappd_api_url)

untappd_data = r.json()

#       Latest checkin object
checkin = untappd_data["response"]["checkins"]["items"][0]
untappd_id = checkin["checkin_id"]

#       Was this ID the last one we saw?
check_file = open("untappd_last", "r")
last_id = int( check_file.read() )
print("Found " + str(last_id) )
check_file.close()

if (last_id != untappd_id ) :
        print("Found new checkin")
        check_file = open("untappd_last", "w")
        check_file.write( str(untappd_id) )
        check_file.close()
        #       Start creating the message
        message = ""

        if "checkin_comment" in checkin :
                message += checkin["checkin_comment"]

        if "beer" in checkin :
                message += "\nDrinking: " + checkin["beer"]["beer_name"]

        if "brewery" in checkin :
                message += "\nBy: "       + checkin["brewery"]["brewery_name"]

        if "venue" in checkin :
                if "venue_name" in checkin["venue"] :
                        message += "\nAt: "       + checkin["venue"]["venue_name"]
        #       Scores etc
        untappd_checkin_url = "https://untappd.com/user/edent/checkin/" + str(untappd_id)
        untappd_rating      = checkin["rating_score"]
        untappd_score       = "🍺" * int(untappd_rating)

        message += "\n" +  untappd_score + "\n" + untappd_checkin_url + "\n" + "#untappd"

        #       Get Image
        if checkin["media"]["count"] &gt; 0 :
                photo_url = checkin["media"]["items"][0]["photo"]["photo_img_lg"]
                download = requests.get(photo_url)
                with open("untappd.tmp", 'wb') as temp_file:
                        temp_file.write(download.content)
                media = mastodon.media_post("untappd.tmp", description="A photo of some beer.")
                mastodon.status_post(status = message, media_ids=media, idempotency_key = str(untappd_id))
        else:   
                #       Post to Mastodon. Use idempotency just in case something went wrong
                mastodon.status_post(status = message, idempotency_key = str(untappd_id))
else :
        print("No new checkin")
</code></pre>

<p>You can treat this code as being MIT licenced if that makes you happy.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=50507&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/05/untappd-to-mastodon-updated/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Posting Untappd Checkins to Mastodon (and other services)]]></title>
		<link>https://shkspr.mobi/blog/2023/03/posting-untappd-checkins-to-mastodon-and-other-services/</link>
					<comments>https://shkspr.mobi/blog/2023/03/posting-untappd-checkins-to-mastodon-and-other-services/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Thu, 02 Mar 2023 12:34:56 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[beer]]></category>
		<category><![CDATA[cider]]></category>
		<category><![CDATA[mastodon]]></category>
		<category><![CDATA[MastodonAPI]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[untappd]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=45036</guid>

					<description><![CDATA[I&#039;m a big fan of Untappd. It&#039;s a social drinking app which lets you check in to a beer and rate it. Look, we all need hobbies, mine is drinking cider.  You can see a list of everything I&#039;ve drunk over the 13 last years. Nearly 900 different pints!  After checking in, the app automatically posts to Twitter. But who wants to prop up Alan&#039;s failing empire? Not me! So here&#039;s some quick code to…]]></description>
										<content:encoded><![CDATA[<p>I'm a big fan of <a href="https://untappd.com">Untappd</a>. It's a social drinking app which lets you check in to a beer and rate it. Look, we all need hobbies, mine is drinking cider.  You can <a href="https://untappd.com/user/edent">see a list of everything I've drunk over the 13 last years</a>. Nearly 900 different pints!</p>

<p>After checking in, the app automatically posts to Twitter. But who wants to prop up Alan's failing empire? Not me! So here's some quick code to liberate your data and post it elsewhere.</p>

<p>There are two ways - APIs and Screen Scraping.</p>

<h2 id="api"><a href="https://shkspr.mobi/blog/2023/03/posting-untappd-checkins-to-mastodon-and-other-services/#api">API</a></h2>

<p>First up, a big disclaimer. Untappd <em>had</em> an API - but aren't accepting new users:</p>

<blockquote><p>Thank you for your interest in Untappd’s API. At this time, we are no longer accepting new applications for API access as we work to improve our review and support processes. We do not have a planned date to begin accepting new applications, so please check back soon.</p></blockquote>

<p>If you already have an API key (and I do) you can call your own data. This code saves the ID of the most recent checkin to a file. Each time it runs, it checks in the ID in the file is the same as what's returned by the API. If they are different, the post is published and the new ID is saved.</p>

<p>This is rough and ready code:</p>

<pre><code class="language-python">#!/usr/bin/env python
from mastodon import Mastodon
import json
import requests
import config

#  Set up access
mastodon = Mastodon( api_base_url=config.instance, access_token=config.write_access_token )

#   Untappd API - grab the most recent checkin
untappd_api_url = 'https://api.untappd.com/v4/user/checkins/edent?limit=1&amp;client_id=' + config.untappd_client_id + '&amp;client_secret='+ config.untappd_client_secret

r = requests.get(untappd_api_url)

untappd_data = r.json()

#   Latest checkin object
checkin = untappd_data["response"]["checkins"]["items"][0]
untappd_id = checkin["checkin_id"]

#   Was this ID the last one we saw?
check_file = open("untappd_last", "r")
last_id = int( check_file.read() )
print("Found " + str(last_id) )
check_file.close()

if (last_id != untappd_id ) :
    print("Found new checkin")
    check_file = open("untappd_last", "w")
    check_file.write( str(untappd_id) )
    check_file.close()
    #   Start creating the message
    message = ""

    if "checkin_comment" in checkin :
        message += checkin["checkin_comment"]

    if "beer" in checkin :
        message += "\nDrinking: " + checkin["beer"]["beer_name"]

    if "brewery" in checkin :
        message += "\nBy: "       + checkin["brewery"]["brewery_name"]

    if "venue" in checkin :
        if "venue_name" in checkin["venue"] :
            message += "\nAt: "       + checkin["venue"]["venue_name"]
    #   Scores etc
    untappd_checkin_url = "https://untappd.com/user/edent/checkin/" + str(untappd_id)
    untappd_rating      = checkin["rating_score"]
    untappd_score       = "🍺" * int(untappd_rating)

    message += "\n" +  untappd_score + "\n" + untappd_checkin_url + "\n" + "#untappd"
    print( message )
    #   Post to Mastodon. Use idempotency just in case something went wrong
    mastodon.status_post(status = message, idempotency_key = str(untappd_id))
else :
    print("No new checkin")
</code></pre>

<p>This doesn't do media or badges, but it's good enough to start with.</p>

<h2 id="screen-scraping"><a href="https://shkspr.mobi/blog/2023/03/posting-untappd-checkins-to-mastodon-and-other-services/#screen-scraping">Screen Scraping</a></h2>

<p>The Untappd HTML is pretty uniform, so using something like <a href="https://beautiful-soup-4.readthedocs.io/en/latest/">Beautiful Soup</a> is possible.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2023/02/untappd_src.png" alt="HTML source code of the page." width="791" height="367" class="aligncenter size-full wp-image-45037">

<p>Here's some code to get you started</p>

<pre><code class="language-python">from bs4 import BeautifulSoup
import requests

#   Set the URL and headers
url = "https://untappd.com/user/edent"
headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0'}

#   Get the HTML
response = requests.get(url)
page_html = response.text

#   Parse the HTML into soup
soup = BeautifulSoup(page_html, 'html.parser')

#   Grab the data from the first checkin

untappd_id = int( soup.find("div", class_="item")["data-checkin-id"] )

comment = soup.find("p", class_="comment-text").text

#   Steal the beer from the icon's alt text
beer = soup.find("a", class_="label").find("img")["alt"]

#   Rating is a data element
rating = soup.find("div", class_="caps")["data-rating"]

#   Was this ID the last one we saw?
check_file = open("untappd_last", "r")
last_id = int( check_file.read() )
check_file.close()

if (last_id != untappd_id ) :
    check_file = open("untappd_last", "w")
    check_file.write( str(untappd_id) )
    check_file.close()
    message = .....
</code></pre>

<p>And from there you should have enough to start posting your checkins everywhere.  Stick that code in a crontab and have it run periodically.</p>

<p>Cheers!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=45036&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/03/posting-untappd-checkins-to-mastodon-and-other-services/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[A Decade of Drinking Beer on Untappd]]></title>
		<link>https://shkspr.mobi/blog/2021/07/a-decade-of-drinking-beer-on-untappd/</link>
					<comments>https://shkspr.mobi/blog/2021/07/a-decade-of-drinking-beer-on-untappd/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Fri, 30 Jul 2021 11:34:32 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[beer]]></category>
		<category><![CDATA[data]]></category>
		<category><![CDATA[r]]></category>
		<category><![CDATA[untappd]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=39658</guid>

					<description><![CDATA[10 years ago, I asked an innocent question on Twitter.  Terence Eden is on Mastodon@edentIs there any service which will let me &#34;check in&#34; to a beer? Because this Chocolate Tom I&#039;m drinking is amazing.❤️ 0💬 2🔁 018:55 - Thu 21 July 2011  The answers came in swiftly - Untappd was the app to use.  So, a few minutes later:  Terence Eden is on Mastodon@edentI just earned the &#039;Newbie&#039; badge on @untappd!…]]></description>
										<content:encoded><![CDATA[<p>10 years ago, I asked an innocent question on Twitter.</p>

<blockquote class="social-embed" id="social-embed-94118367950155776" lang="en" itemscope="" itemtype="https://schema.org/SocialMediaPosting"><header class="social-embed-header" itemprop="author" itemscope="" itemtype="https://schema.org/Person"><a href="https://twitter.com/edent" class="social-embed-user" itemprop="url"><img class="social-embed-avatar social-embed-avatar-circle" src="data:image/webp;base64,UklGRkgBAABXRUJQVlA4IDwBAACQCACdASowADAAPrVQn0ynJCKiJyto4BaJaQAIIsx4Au9dhDqVA1i1RoRTO7nbdyy03nM5FhvV62goUj37tuxqpfpPeTBZvrJ78w0qAAD+/hVyFHvYXIrMCjny0z7wqsB9/QE08xls/AQdXJFX0adG9lISsm6kV96J5FINBFXzHwfzMCr4N6r3z5/Aa/wfEoVGX3H976she3jyS8RqJv7Jw7bOxoTSPlu4gNbfXYZ9TnbdQ0MNnMObyaRQLIu556jIj03zfJrVgqRM8GPwRoWb1M9AfzFe6Mtg13uEIqrTHmiuBpH+bTVB5EEQ3uby0C//XOAPJOFv4QV8RZDPQd517Khyba8Jlr97j2kIBJD9K3mbOHSHiQDasj6Y3forATbIg4QZHxWnCeqqMkVYfUAivuL0L/68mMnagAAA" alt="" itemprop="image"><div class="social-embed-user-names"><p class="social-embed-user-names-name" itemprop="name">Terence Eden is on Mastodon</p>@edent</div></a><img class="social-embed-logo" alt="Twitter" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0Aaria-label%3D%22Twitter%22%20role%3D%22img%22%0AviewBox%3D%220%200%20512%20512%22%3E%3Cpath%0Ad%3D%22m0%200H512V512H0%22%0Afill%3D%22%23fff%22%2F%3E%3Cpath%20fill%3D%22%231d9bf0%22%20d%3D%22m458%20140q-23%2010-45%2012%2025-15%2034-43-24%2014-50%2019a79%2079%200%2000-135%2072q-101-7-163-83a80%2080%200%200024%20106q-17%200-36-10s-3%2062%2064%2079q-19%205-36%201s15%2053%2074%2055q-50%2040-117%2033a224%20224%200%2000346-200q23-16%2040-41%22%2F%3E%3C%2Fsvg%3E"></header><section class="social-embed-text" itemprop="articleBody">Is there any service which will let me "check in" to a beer? Because this Chocolate Tom I'm drinking is amazing.</section><hr class="social-embed-hr"><footer class="social-embed-footer"><a href="https://twitter.com/edent/status/94118367950155776"><span aria-label="0 likes" class="social-embed-meta">❤️ 0</span><span aria-label="2 replies" class="social-embed-meta">💬 2</span><span aria-label="0 reposts" class="social-embed-meta">🔁 0</span><time datetime="2011-07-21T18:55:42.000Z" itemprop="datePublished">18:55 - Thu 21 July 2011</time></a></footer></blockquote>

<p>The answers came in swiftly - <a href="https://untappd.com">Untappd</a> was the app to use.  So, a few minutes later:</p>

<blockquote class="social-embed" id="social-embed-94126841610244096" lang="en" itemscope="" itemtype="https://schema.org/SocialMediaPosting"><header class="social-embed-header" itemprop="author" itemscope="" itemtype="https://schema.org/Person"><a href="https://twitter.com/edent" class="social-embed-user" itemprop="url"><img class="social-embed-avatar social-embed-avatar-circle" src="data:image/webp;base64,UklGRkgBAABXRUJQVlA4IDwBAACQCACdASowADAAPrVQn0ynJCKiJyto4BaJaQAIIsx4Au9dhDqVA1i1RoRTO7nbdyy03nM5FhvV62goUj37tuxqpfpPeTBZvrJ78w0qAAD+/hVyFHvYXIrMCjny0z7wqsB9/QE08xls/AQdXJFX0adG9lISsm6kV96J5FINBFXzHwfzMCr4N6r3z5/Aa/wfEoVGX3H976she3jyS8RqJv7Jw7bOxoTSPlu4gNbfXYZ9TnbdQ0MNnMObyaRQLIu556jIj03zfJrVgqRM8GPwRoWb1M9AfzFe6Mtg13uEIqrTHmiuBpH+bTVB5EEQ3uby0C//XOAPJOFv4QV8RZDPQd517Khyba8Jlr97j2kIBJD9K3mbOHSHiQDasj6Y3forATbIg4QZHxWnCeqqMkVYfUAivuL0L/68mMnagAAA" alt="" itemprop="image"><div class="social-embed-user-names"><p class="social-embed-user-names-name" itemprop="name">Terence Eden is on Mastodon</p>@edent</div></a><img class="social-embed-logo" alt="Twitter" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%0Aaria-label%3D%22Twitter%22%20role%3D%22img%22%0AviewBox%3D%220%200%20512%20512%22%3E%3Cpath%0Ad%3D%22m0%200H512V512H0%22%0Afill%3D%22%23fff%22%2F%3E%3Cpath%20fill%3D%22%231d9bf0%22%20d%3D%22m458%20140q-23%2010-45%2012%2025-15%2034-43-24%2014-50%2019a79%2079%200%2000-135%2072q-101-7-163-83a80%2080%200%200024%20106q-17%200-36-10s-3%2062%2064%2079q-19%205-36%201s15%2053%2074%2055q-50%2040-117%2033a224%20224%200%2000346-200q23-16%2040-41%22%2F%3E%3C%2Fsvg%3E"></header><section class="social-embed-text" itemprop="articleBody">I just earned the 'Newbie' badge on <a href="https://twitter.com/untappd">@untappd</a>! http://untp.it/p3POA0</section><hr class="social-embed-hr"><footer class="social-embed-footer"><a href="https://twitter.com/edent/status/94126841610244096"><span aria-label="0 likes" class="social-embed-meta">❤️ 0</span><span aria-label="0 replies" class="social-embed-meta">💬 0</span><span aria-label="0 reposts" class="social-embed-meta">🔁 0</span><time datetime="2011-07-21T19:29:22.000Z" itemprop="datePublished">19:29 - Thu 21 July 2011</time></a></footer></blockquote>

<p>In the last decade, how much beer and cider have I drunk?</p>

<p>I've written before about <a href="https://shkspr.mobi/blog/2018/11/extracting-your-data-from-untappd/">how to extract your data from Untappd using their API</a>.</p>

<p>(A few notes. I don't check in to every drink - I only tend to do so if it's a new beer. Some of these are only tasters of a beer - not a full pint. This is mostly an exercise in playing with R. Visit <a href="https://www.drinkaware.co.uk/">DrinkAware</a> if you'd like to help manage your alcohol consumption.)</p>

<h2 id="quick-stats"><a href="https://shkspr.mobi/blog/2021/07/a-decade-of-drinking-beer-on-untappd/#quick-stats">Quick Stats</a></h2>

<ul>
<li>985 check ins.</li>
<li>801 unique drinks</li>
<li>3.68 average rating</li>
<li>4.93 average ABV</li>
</ul>

<h2 id="graphs"><a href="https://shkspr.mobi/blog/2021/07/a-decade-of-drinking-beer-on-untappd/#graphs">Graphs</a></h2>

<p>Is there a correlation between how strong a drink is, and how much I like it?</p>

<pre><code class="language-R">library(jsonlite)
beer_data &lt;- read_json("untappd_data.json", simplifyVector = TRUE)

abv &lt;- beer_data$beer$beer_abv
scr &lt;- beer_data$rating_score

plot(abv, scr, main="ABV vs Score", xlab="ABV", ylab="Score")
abline(lm(scr~abv), col="red") # regression line (y~x)
</code></pre>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/07/scatter.png" alt="A very busy scatter graph." width="627" height="614" class="aligncenter size-full wp-image-39661">

<p>Hmmm... There's some week positive correlation there. But it's a bit muddled.  Let's turn that into a hexmap:</p>

<pre><code class="language-R">library(hexbin)
bin&lt;-hexbin(abv, scr, xbins=10, xlab="ABV", ylab="Score")
plot(bin, main="Hexagonal Binning")
</code></pre>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/07/hex.png" alt="A hex graph with a strong centre." width="637" height="549" class="aligncenter size-full wp-image-39660">

<p>Aha! A bit easier to see. Most of the beers I drink are in the 4-5% ABV. And there is some correlation. But, mostly, I just like beer and cider.  Hmmm... Which do I prefer?</p>

<p>Let's take a look at Cider first:</p>

<pre><code class="language-R">library(data.table)
beer_data &lt;- read_json("untappd_data.json", simplifyVector = TRUE)
cider &lt;- beer_data[grepl("Cider", beer_data$beer$beer_name), ]
cabv &lt;- cider$beer$beer_abv
cscr &lt;- cider$rating_score
</code></pre>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/07/Cider-Scatter.png" alt="Scatter plot with weak positive correlation." width="650" height="637" class="aligncenter size-full wp-image-39662">

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/07/Cider-Hex.png" alt="Hex plot." width="637" height="549" class="aligncenter size-full wp-image-39663">

<p>How much do I like Cider vs Beer?
Just beer (OK, also includes Mead and a few other not Ciders)</p>

<pre><code class="language-R">justbeer &lt;- beer_data[!grepl("Cider", beer_data$beer$beer_name), ]
boxplot(cscr, ylab="Score", main="Cider Scores", ylim=c(0,5))
</code></pre>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/07/cider-vs-beer.png" alt="Box and whisker diagrams." width="568" height="585" class="aligncenter size-full wp-image-39669">

<p>I like Cider a bit more than beer. Yup!</p>

<p>Let's plot that data on a map!  It's a bit more complicated because the JSON is nested.</p>

<pre><code class="language-R">library(jsonlite)
beer_data &lt;- read_json("untappd_data.json", simplifyVector = TRUE, flatten = TRUE)

venues_list &lt;- beer_data$venue
venues &lt;- as.data.frame(do.call(rbind, venues_list))
locations_list &lt;- venues$location
locations &lt;- as.data.frame(do.call(rbind, locations_list))
locations &lt;-subset(locations, venue_state!="Everywhere")
</code></pre>

<p>Display them on an interactive map:</p>

<pre><code class="language-R">library(sf)
library(mapview)
locations_sf &lt;- st_as_sf(locations, coords = c("lng", "lat"), crs = 4326)
mapview(locations_sf)
</code></pre>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/07/WorldMap.png" alt="Map of the world with dots all over it." width="1161" height="618" class="aligncenter size-full wp-image-39678">

<p>Let's zoom in on London:
<img src="https://shkspr.mobi/blog/wp-content/uploads/2021/07/London.png" alt="Points dotted all over Central London." width="643" height="528" class="aligncenter size-full wp-image-39677"></p>

<p>Yup! Looks about right.</p>

<p>Well, that was a fun afternoon of noodling with R. If you'd like to play with the data, you can <a href="https://shkspr.mobi/blog/wp-content/uploads/2021/07/untappd.zip">download a decade of my Untappd data in JSON format</a></p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=39658&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2021/07/a-decade-of-drinking-beer-on-untappd/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Extracting your data from Untappd]]></title>
		<link>https://shkspr.mobi/blog/2018/11/extracting-your-data-from-untappd/</link>
					<comments>https://shkspr.mobi/blog/2018/11/extracting-your-data-from-untappd/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sat, 03 Nov 2018 07:54:07 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[beer]]></category>
		<category><![CDATA[NaBloPoMo]]></category>
		<category><![CDATA[ODcamp]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[untappd]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=30474</guid>

					<description><![CDATA[I rate every pint I taste using the Untappd app. Think of it like TripAdvisor for lager, stout, cider, bitter, and all manner of other beery goodness.  Seriously, I&#039;ve reviewed over 600 different drinks  Recently, I decided to see if I could self-host my beer check-ins. The first step - extracting my own data from Untappd.  Pay To Play  If you become an Untappd Supporter for US$5 per month, you…]]></description>
										<content:encoded><![CDATA[<p>I rate every pint I taste using the <a href="https://untappd.com">Untappd</a> app. Think of it like TripAdvisor for lager, stout, cider, bitter, and all manner of other beery goodness.  Seriously, <a href="https://untappd.com/user/edent/">I've reviewed over 600 different drinks</a></p>

<p>Recently, I decided to see if I could self-host my beer check-ins. The first step - extracting my own data from Untappd.</p>

<h2 id="pay-to-play"><a href="https://shkspr.mobi/blog/2018/11/extracting-your-data-from-untappd/#pay-to-play">Pay To Play</a></h2>

<p>If you become an <a href="https://untappd.com/supporter">Untappd Supporter</a> for US$5 per month, you can extract your data in CSV or JSON.  I feel like I ought to be able to GDPR that and get it for free but, hey, a couple of quid doesn't seem like a bad way to support an app.</p>

<p>Here's a brief extract:</p>

<pre><code class="language-_">beer_name,brewery_name,beer_type,beer_abv,beer_ibu,comment,venue_name,venue_city,venue_state,venue_country,venue_lat,venue_lng,rating_score,created_at,checkin_url,beer_url,brewery_url,brewery_country,brewery_city,brewery_state,flavor_profiles,purchase_venue,serving_type,checkin_id,bid,brewery_id,photo_url
"Devon Mist (Cloudy Sparkling Cider)","Sandford Orchards","Cider - Other",4.5,0,"Inoffensive. Found in Aldi.",,,,,,,2.5,"2018-09-29 18:14:59",https://untappd.com/c/657661791,https://untappd.com/beer/163090,https://untappd.com/brewery/14080,England,Crediton,Devon,,,Bottle,657661791,163090,14080,https://untappd.s3.amazonaws.com/photo/2018_09_29/46269e6ba9db8ebc6c66efeddc28edd9_c_657661791_raw.jpeg
"Älska Passion Fruit Apple Cider","Swedish Cider Company ","Cider - Other Fruit",4,0,"Basically an alcopop. Bit sticky.",,,,,,,2,"2018-10-01 19:33:33",https://untappd.com/c/658763031,https://untappd.com/beer/2023147,https://untappd.com/brewery/107942,Sweden,Karlstad,,,,Bottle,658763031,2023147,107942,https://untappd.s3.amazonaws.com/photo/2018_10_01/1a193187913f2429c325c44fe3537b35_c_658763031_raw.jpeg
"War Horse","White Horse Brewery","Extra Special / Strong Bitter",4.9,0,"Gorgeous and light. Summery delicious.","Oxford Playhouse",Oxford,Oxfordshire,"United Kingdom",51.755,-1.26088,4,"2018-10-05 16:51:58",https://untappd.com/c/659797322,https://untappd.com/beer/240251,https://untappd.com/brewery/2678,England,Stanford-in-the-Vale,Oxfordshire,,,,659797322,240251,2678,
</code></pre>

<p>Fun fact, the data export didn't originally give a photo URL. So I asked nicely and now it is included for everyone!</p>

<p>But that's not <em>all</em> the data they hold about the check-in.</p>

<h2 id="api"><a href="https://shkspr.mobi/blog/2018/11/extracting-your-data-from-untappd/#api">API</a></h2>

<p>Untappd has a <a href="https://untappd.com/api/">brilliant API</a> which returns <em>much</em> more detail than the CSV export. And you don't even have to pay to use it.</p>

<p>Take a look at this check-in:</p>

<pre><code class="language-json">"checkin_id": 658763031,
"created_at": "Mon, 01 Oct 2018 18:33:33 +0000",
"checkin_comment": "Basically an alcopop. Bit sticky.",
"rating_score": 2,
"user": {
   "uid": 46296,
   "user_name": "edent",
   "first_name": "Terence",
   "last_name": "E.",
   "location": "UK",
   "is_supporter": 1,
   "url": "https:\/\/shkspr.mobi\/blog",
   "bio": "Long haired geek.",
   "relationship": null,
   "user_avatar": "https:\/\/untappd.akamaized.net\/profile\/5d928465e29fec4375a2990ca2767852_thumb.jpg",
   "is_private": 0,
   "contact": {
      "twitter": "edent",
      "foursquare": "56367",
      "facebook": "609836217"
   }
},
"beer": {
   "bid": 2023147,
   "beer_name": "Älska Passion Fruit Apple Cider",
   "beer_label": "https:\/\/untappd.akamaized.net\/site\/assets\/images\/temp\/badge-beer-default.png",
   "beer_style": "Cider - Other Fruit",
   "beer_slug": "swedish-cider-company-alska-passion-fruit-apple-cider",
   "beer_abv": 4,
   "beer_active": 1,
   "has_had": false
},
"brewery": {
   "brewery_id": 107942,
   "brewery_name": "Swedish Cider Company ",
   "brewery_slug": "swedish-cider-company",
   "brewery_page_url": "\/w\/swedish-cider-company\/107942",
   "brewery_type": "Cidery",
   "brewery_label": "https:\/\/untappd.akamaized.net\/site\/brewery_logos\/brewery-107942_ca21d.jpeg",
   "country_name": "Sweden",
   "contact": {
      "twitter": "",
      "facebook": "",
      "instagram": "",
      "url": "http:\/\/www.alskacider.com"
   },
   "location": {
      "brewery_city": "Karlstad",
      "brewery_state": "",
      "lat": 59.4109,
      "lng": 13.4854
   },
   "brewery_active": 1
},
"venue": [],
"comments": {
   "total_count": 0,
   "count": 0,
   "items": []
},
"toasts": {
   "total_count": 0,
   "count": 0,
   "auth_toast": false,
   "items": []
},
"media": {
   "count": 1,
   "items": [{
      "photo_id": 195061947,
      "photo": {
         "photo_img_sm": "https:\/\/untappd.akamaized.net\/photo\/2018_10_01\/1a193187913f2429c325c44fe3537b35_c_658763031_200x200.jpeg",
         "photo_img_md": "https:\/\/untappd.akamaized.net\/photo\/2018_10_01\/1a193187913f2429c325c44fe3537b35_c_658763031_640x640.jpeg",
         "photo_img_lg": "https:\/\/untappd.akamaized.net\/photo\/2018_10_01\/1a193187913f2429c325c44fe3537b35_c_658763031_1280x1280.jpeg",
         "photo_img_og": "https:\/\/untappd.akamaized.net\/photo\/2018_10_01\/1a193187913f2429c325c44fe3537b35_c_658763031_raw.jpeg"
      }
   }]
},
"source": {
   "app_name": "Untappd for iPhone - (V2)",
   "app_website": "http:\/\/untpd.it\/iphoneapp"
},
"badges": {
   "retro_status": false,
   "count": 1,
   "items": [{
      "badge_id": 2980,
      "user_badge_id": 401989193,
      "badge_name": "Johnny Appleseed  (Level 34)",
      "badge_description": "Grab a pot and put it on your head because you're a modern day Johnny Appleseed. That's 170 different ciders. Try 5 more to unlock Level 35.",
      "created_at": "Mon, 01 Oct 2018 18:33:33 +0000",
      "badge_image": {
         "sm": "https:\/\/untappd.akamaized.net\/badges\/bdg_appleseed_sm.jpg",
         "md": "https:\/\/untappd.akamaized.net\/badges\/bdg_appleseed_md.jpg",
         "lg": "https:\/\/untappd.akamaized.net\/badges\/bdg_appleseed_lg.jpg"
      }
   }]
}
</code></pre>

<p>Badges! Beer labels! Locations! Venues! Images! Data!</p>

<p>By default, the API only returns 25 check-ins per request. But it does come with a pagination option:</p>

<pre><code class="language-json">"pagination": {
   "since_url": "https:\/\/api.untappd.com\/v4\/user\/checkins\/edent?min_id=659797322",
   "next_url": "https:\/\/api.untappd.com\/v4\/user\/checkins\/edent?max_id=602463014",
   "max_id": 602463014
},
</code></pre>

<h2 id="python-power"><a href="https://shkspr.mobi/blog/2018/11/extracting-your-data-from-untappd/#python-power">Python Power!</a></h2>

<p>Here's a quick script which will download all your beer-data and create a single JSON file to use.
You will need to <a href="https://untappd.com/api/">register an API key</a> and generate your own <a href="https://untappd.com/api/docs#authentication">access token</a>.</p>

<pre><code class="language-python">import json
import requests

username     = "abc"
client_id    = "123"
client_secret= "456"
access_token = "789"

user = requests.get("https://api.untappd.com/v4/user/info/"+username+"?client_id="+client_id+"&amp;client_secret="+client_secret)
user_data = user.json()
total_checkins = user_data["response"]["user"]["stats"]["total_checkins"]

print("Total Checkins = " + str(total_checkins))

checkins = requests.get("https://api.untappd.com/v4/user/checkins/?access_token="+access_token)
checkins_data = checkins.json()
full_data = checkins_data["response"]["checkins"]["items"]

while (len(full_data) &lt; total_checkins):
    max_id   = checkins_data["response"]["pagination"]["max_id"]
    next_url = "https://api.untappd.com/v4/user/checkins/?max_id=" + str(max_id) + "&amp;access_token="+access_token
    print("Downloading " + next_url)
    checkins = requests.get(next_url)
    checkins_data = checkins.json()

    full_data.extend(checkins_data["response"]["checkins"]["items"])

with open("untappd_data.json", "w") as f:
    json.dump(full_data, f)
</code></pre>

<p>That will save a file with all your data. Happy drinking!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=30474&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2018/11/extracting-your-data-from-untappd/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[UI For Drunks]]></title>
		<link>https://shkspr.mobi/blog/2014/01/ui-for-drunks/</link>
					<comments>https://shkspr.mobi/blog/2014/01/ui-for-drunks/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Wed, 08 Jan 2014 12:02:44 +0000</pubDate>
				<category><![CDATA[usability]]></category>
		<category><![CDATA[beer]]></category>
		<category><![CDATA[drunk]]></category>
		<category><![CDATA[untappd]]></category>
		<guid isPermaLink="false">http://shkspr.mobi/blog/?p=9437</guid>

					<description><![CDATA[In app design, we often talk about designing for the user in context.  For example, a taxi app can&#039;t rely on a perfect GPS signal in a crowded city, a user in the countryside may not have brilliant bandwidth, battery life is not infinite so we should limit certain features when power levels are low.  The common theme in those examples is that we are designing for the phone&#039;s context, not the…]]></description>
										<content:encoded><![CDATA[<p>In app design, we often talk about designing for the user in context.  For example, a taxi app can't rely on a perfect GPS signal in a crowded city, a user in the countryside may not have brilliant bandwidth, battery life is not infinite so we should limit certain features when power levels are low.</p>

<p>The common theme in those examples is that we are designing for the <em>phone's</em> context, <strong>not</strong> the <em>user's</em> context.</p>

<p>We rarely say "let's introduce a left-handed option" or "do we need a night reading mode?" or "does the user have time to concentrate on this UI while driving?"  The user should be at the heart of our decision making, and her context should feature heavily in our conversations about UI.</p>

<p>A lot of modern "lifestyle" apps are focussed around getting people to bars and clubs - so why aren't designers creating interfaces for the "cognitively impaired?" i.e. Drunk!</p>

<p>I'd encourage you to watch this usability study from <a href="https://web.archive.org/web/20140111061732/http://threesheetsresearch.com/">Three Sheets Research</a> where they ask a drunk woman to navigate and interact with a moderately complex website.</p>

<iframe title="Cocktails and Customization" width="620" height="349" src="https://www.youtube.com/embed/ZU7ylC7WeaQ?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>

<p>Hilarious, right? But also accurate.  Users have many demands on their time - being distracted by a phone ringing, or an incoming email, or a bright and shiny object has the same effect as being drunk. They return to the user interface with reduced thinking capacity.</p>

<p>My favourite social drinking app is <a href="https://untappd.com/">Untappd</a>.  It lets me check in to beers and bars to let my friends know what I'm drinking and where I am.  It serves as a beer sommelier by aggregating my beer reviews and presenting me with places to go and try new and interesting brews.</p>

<p>The interface, however, doesn't fully respect the change that I go through the more I use the app. It starts off well enough.  A clean row of beers to choose from and a search bar.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2013/12/Untappd-Start-Screen-Normal-fs8.png" alt="Untappd Start Screen Normal-fs8" width="300" height="533" class="aligncenter size-large wp-image-9439">

<p>After a couple of beers, the interface becomes less useful:
<img src="https://shkspr.mobi/blog/wp-content/uploads/2013/12/Untappd-Slightly-Drunk-fs8.png" alt="Untappd Slightly Drunk-fs8" width="300" height="533" class="aligncenter size-full wp-image-9441">
The white on yellow colour scheme isn't helping things, some of the buttons are too small to hit first time and there seems to be a bunch of text which I just can't be bothered to read.  There's a character count - but the font is too small.  I'm pretty sure that's a camera icon, and I'm at least 75% certain I remember which shade of blue is Twitter and which is Facebook.</p>

<p>By the end of the night, the interface has morphed into this hot mess:
<img src="https://shkspr.mobi/blog/wp-content/uploads/2013/12/Untappd-Pissed-fs8.png" alt="Untappd Pissed-fs8" width="300" height="533" class="aligncenter size-large wp-image-9442"></p>

<p>We <a href="http://ux.stackexchange.com/questions/28891/how-do-drunk-users-act">know how drunk users act</a>.  Interfaces should adapt to the user's context.</p>

<p>If I am inebriated, perhaps those Facebook and Twitter buttons should disappear.  Make it easier for me to hit the correct button and harder for me to share my sozzled opinions with my social network.</p>

<p>Fonts need to be much larger and distinct.  Emboldened text with a strongly contrasting colour scheme is helpful.</p>

<p>Text is routinely ignored and the <a href="http://www.economist.com/node/17723028">tyranny of choice</a> can become insurmountable when <a href="http://www.theguardian.com/notesandqueries/query/0,5753,-2337,00.html">tired and emotional</a>.</p>

<p>Separate actions need to be strongly differentiated. Two brown buttons in close proximity makes no sense to someone rat-arsed.</p>

<p>Why is it so easy to make a bad decision when drunk? Oughtn't the interface say "I'm sorry Dave, I can't let you do that" - or at the very least give me a chance to undo my actions?</p>

<p>Interfaces don't <em>need</em> to stay static.  Most competent designers can build apps which shift when the screen is rotated from portrait to landscape mode.  Apps already have to cope with users setting their own font size and screen contrast - so why not go the whole hog and proactively adapt the interface based on how trollied the user is?</p>

<p>I've previously discussed why <a href="https://shkspr.mobi/blog/2013/01/are-designers-crazy/" title="Are Designers Crazy?">I think designers are crazy</a>.  And I can already hear some of the precious snowflakes crying out in horror at the butchery I am asking them to perform on their babies.</p>

<p>It's necessary.  If you know that your user is going to have difficulty touching small icons, will have unfocussed eyes, and may have several other sensory impairments - it is your <strong>duty</strong> to adapt that interface!</p>

<p>Here is a radically simplified version of one of the Untappd screens.  Yes, it's missing details (which the drunkard won't care about) - no, it's not pretty (I'm sure the beer goggles will make up for that) - but it's easy enough for anyone to use after a few cheeky beers.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2013/12/Untappd-Simplified-fs8.png" alt="Untappd Simplified-fs8" width="300" height="533" class="aligncenter size-full wp-image-9445"></p>

<p>Rather than going up to strangers in a coffee shop to ask them to beta test your latest UI, take the plunge and ask the folk propping up your local bar at midday.  If they can use it, <em>anyone</em> can.</p>

<p>Here's a hand cut-out-and-keep user story for you to sneak into your next agile session.
<img src="https://shkspr.mobi/blog/wp-content/uploads/2013/12/Drunk-User-Story-fs8.png" alt="Drunk User Story" width="611" height="259" class="aligncenter size-full wp-image-9447"></p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=9437&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2014/01/ui-for-drunks/feed/</wfw:commentRss>
			<slash:comments>12</slash:comments>
		
		
			</item>
	</channel>
</rss>
