Golang Twitter Bot

I’ve heard loads of positive things about Googles programming language Golang, and curious as I am I decided to explore it. When I’m learning new things I like to get started using it instantly hands-on. I am a frequent user of Twitter and thought what if I can increase my followers with a Golang Twitter Bot?

Golang Twitter Bot

My theory was that many users will follow you back as a thank you if you follow them. So what if we could exploit this and later after a set amount of time automatically unfollow the user in hopes that the user will miss that we unfollowed them. So this is what I decided to do using the Twitter API provided by dghubble.

So that’s the background story behind the project.

Golang

First Impressions

My first impression of the language was how easy it was to get started. After a quick installation Golang which comes with a big set of standard libraries, I was ready to go. Below I will mention some noticeable changes for me, someone coming from Java.

Project Structure and Package Management

Different languages have different type of recommended project structures, and Golang has a quite unique one. If you type go env in a terminal you get a list of environment variables linked to your Golang installation. The interesting variable here is GOPATH and inside here it’s recommended to put your projects. For example, $GOPATH/src/github.com/snieking/go-twitter-bot.

And because of this, it becomes very easy to install and import external packages, as Golang will install these packages into the $GOPATH and also search for them there. So for example, to install the Twitter Client library that I used in my project, all I had to do was.

  1. Run the command: go get github.com/dghubble/go-twitter/twitter
  2. Import the package: import "github.com/dghubble/go-twitter/twitter"

No external dependency manager like Maven or Gradle is required, that’s quite nice!

Package Private or Public

Something that surprised me was how Golang handles public and private variables, functions and types. In Java, we declare a class, a method or a variable public in order to make it accessible outside of the package. In Golang you simply give it a name with a capital letter at the start.

func IsAccessibleOutsidePackage() bool {
    return true
}
func isAccessibleOutsidePackage() bool {
    return false
}

Also, everything inside the same package is reachable to other files inside the same package. So for example.

twitter.go

func createConnection(twitterConf TwitterAccess) {
    config := oauth1.NewConfig(twitterConf.ConsumerKey, twitterConf.ConsumerSecret)
    token := oauth1.NewToken(twitterConf.AccessToken, twitterConf.AccessSecret)

    // http.Client will automatically authorize requests
    httpClient := config.Client(oauth1.NoContext, token)

    // Twitter client
    client = *twitter.NewClient(httpClient)
}

Is fully reachable from my separate file logic.go because they are both in the same package.

Multiple Returns

Something else interesting and very useful is that a function can have multiple return variables. It is a common pattern in Golang to have a function, for example, return the result in one variable, the HTTP status in another variable and an error if it occurred in a third variable.

// Unfollows a provided user.
func unfollow(user string) {
    _, _, err := client.Friendships.Destroy(&twitter.FriendshipDestroyParams{ScreenName: user})
    checkError("Failed to unfollow\n", err)
}

In the example above that unfollows a Twitter user, you can see that the Friendships Destroy method returns 3 variables, but in this case, I am only interested in the last one, if an error occurred in the API call. If you don’t care about some return variable, you can ignore it by assigning it to an underscore.

That’s Cool, But What About the Golang Twitter Bot?

Right, since it was my first time using Golang and also the first time I blogged about it I wanted to briefly mention what I discovered and learned. And the language really is very different from Java, definitely for the better as Golang requires very little boilerplate code which I hate. Anyways, let’s get back to the Golang Twitter Bot that I implemented.

So the goal was to:

  1. Search for new relevant Twitter users to follow
  2. Follow them (if I don’t already follow them)
  3. Store the user and at what timestamp I followed them
  4. Unfollow old users that I previously followed

It was extremely easy to implement all the functions towards Twitter as the Twitter API was very well documented. My biggest issue was how to store users that I followed and how to read them. Initially, I started out with Couchbase as my database as I previously have a lot of experience with it, but not in Golang so it was fun to explore the SDK for Golang. But as you probably already thought, it is quite overkilling it for storing basically 2 fields. And it would be stupid for a small Twitter Bot to require a Couchbase installation. Instead, I decided to go with simply writing it to file in a csv format which is very easy to parse.

Write and Read CSV File in Golang

All of the packages required to do this exists in the standard library.

import (
    "bufio"
    "encoding/csv"
    "io"
    "os"
    "strconv"
)

My code to write my list of user entities to file looks like the following.

func writeListOfFollowsToFile(userEntities []UserEntity) {
    file, err := os.Create("follows.csv")
    checkError("Cannot create file\n", err)
    defer file.Close()

    writer := csv.NewWriter(file)
    defer writer.Flush()
    for _, value := range userEntities {
        timestamp := strconv.FormatInt(value.FollowedTimestamp, 10)
        strWrite := []string{value.ScreenName, timestamp}
        err := writer.Write(strWrite)
        writer.Flush()
        checkError("Cannot write to file", err)
    }
}

The keyword defer was also new for me. It makes sure that at the end of the function no matter what happens, that the action is performed. In this case, we want to make sure that the file is closed and that we flush our writer.

And for reading from our csv file the function looks like the following.

func readFromFile(filePath string) []UserEntity {
    csvFile, err := os.Open(filePath)

    if err != nil {
        csvFile, _ = os.Create(filePath)
    }
    defer csvFile.Close()

    reader := csv.NewReader(csvFile)
    var userEntities []UserEntity

    for {
        line, err := reader.Read()
        if err == io.EOF {
            break
        }
        checkError("Failed to read lines in file\n", err)
        i, _ := strconv.ParseInt(line[1], 10, 64)
        userEntities = append(userEntities, UserEntity{
            ScreenName:        line[0],
            FollowedTimestamp: i,
        })
    }

    return userEntities
}

Final Words

It was surprisingly easy and quick to write everything in Golang and it definitely won’t be the last time that I use Golang. There really wasn’t that many lines of code required at all, and doing this in Java would be a lot more.

You can find the source code for the Golang Twitter Bot on my GitHub, over here. You will also find instructions there on how to use the bot if you would like to.

This is definitely not the last time that I will write about Golang!

2 thoughts on “Golang Twitter Bot”

  1. Why do you use reader := csv.NewReader(bufio.NewReader(csvFile)) instead of reader := csv.NewReader((csvFile)

    csv.Reader already wraps the file inside a bufio.NewReader

    1. Thanks for the tip! I will update that. The reason is that I didn’t know, I am sure there are many more improvements that I can do as well!

Leave a Reply