So how do the 2016 US Presidential Candidates fare on Twitter? Let’s find out with Go!

Here’s how to build your very own dashboard that queries Twitter and finds out how many Twitter followers each candidate has. You can see the live dashboard here, below is a static screenshot:

Let's build this!

You need to have a verified Twitter account, and know a bit of Go. Let’s start!

Go to https://apps.twitter.com/ and create a new Twitter app. Generate an access token from the “Keys and Access Tokens” tab. We’ll need the Consumer Key, Consumer Secret, Access Token and Access Token Secret values.

Now let’s get the code from github to hack on:

go get github.com/rapidloop/followtheleader

Let’s edit the code and put in the keys that we got from Twitter earlier, then build and run:

$ vim src/github.com/rapidloop/followtheleader/main.go
$ go build github.com/rapidloop/followtheleader
$ ./followtheleader

If there were any errors while querying Twitter, it’ll be printed and the fun stops. Otherwise head over to http://localhost:8080/ and see who’s leading!

BTW, note that go build puts the binary in the current directory, while go get puts it in $GOPATH/bin.

We’re using this nice library to query Twitter. We initialize it this way:

anaconda.SetConsumerKey(CONSUMER_KEY)
anaconda.SetConsumerSecret(CONSUMER_SECRET)
api := anaconda.NewTwitterApi(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

And having done that, call the users.lookup Twitter API with the list of known Twitter handles of the presidential candidates.

users, err := api.GetUsersLookup(handles, url.Values{})

This returns, among other things, the count of followers for each Twitter account. We store this in a slice, and sort it by follower count:

t := make([]TwitterInfo, len(users))
for i, u := range users {
    t[i].Name = u.Name
    t[i].Followers = u.FollowersCount
}
sort.Sort(ByFollowers(t))

We want to keep fetching this information and updating it every minute in one (the main) goroutine, while other goroutines access this information to render HTML pages. The information therefore, is stored mutex-protected. The TwitterInfo slice created above is stored after locking a mutex, as in the code below. Note that we don’t copy the slice objects, the slice itself is stored and retrieved.

type Stats struct {
    sync.Mutex
    At      time.Time
    Twitter []TwitterInfo
}

func (s *Stats) Put(t []TwitterInfo) {
    s.Lock()
    defer s.Unlock()
    s.At = time.Now().In(TZ_ET)
    s.Twitter = t
}

func (s *Stats) Get() (time.Time, []TwitterInfo) {
    s.Lock()
    defer s.Unlock()
    return s.At, s.Twitter
}

var stats = &Stats{}

We want to show the time of fetch in HTML page, so we remember that during Put(). The .In(TZ_ET) converts the current time into Eastern (Daylight) Time. TZ_ET is a *time.Location that we loaded using:

TZ_ET, _ = time.LoadLocation("America/New_York")

We keep updating the information each minute, with a simple loop in the main goroutine:

for {
    time.Sleep(time.Minute)
    fetchStats(api)
}

Next up is the web server itself, which actually has fewer lines than the list of candidates:

var tmpl *template.Template

func startWeb() {
    tmpl = template.Must(template.New(".").Parse(html))
    http.HandleFunc("/", webServer)
    log.Fatal(http.ListenAndServe(LISTEN_ADDRESS, nil))
}

This loads a single template (defined as a multi-line string in file web.go), creates a handler to serve all URLs and starts a web server. (Actually, that is quite awesome.)

The handler gets the information we fetched above and renders the template with it:

type templateData struct {
    At      time.Time
    Twitter []TwitterInfo
}

func webServer(w http.ResponseWriter, r *http.Request) {
    a, t := stats.Get()
    if err := tmpl.Execute(w, templateData{a, t}); err != nil {
        log.Print(err)
    }
}

And finally, the template itself is a big raw string (~60 lines) stuck into the file. This lets our binary be self-contained. The HTML pulls in the Google Bar Chart library. The Twitter stats are rendered inline into Javascript arrays to form the data to be plotted.

That’s it! Poke around the code for more goodies that are not currently used, like the number of tweets and profile image URL for each candidate.

Here are some useful links: