This post will talk through the steps on how to create your own Twitter bot in Go! We will be making use of go-twitter which is a Go client library for the Twitter API. Big thanks to the developers over there for making an easy to use library.
Prerequisites
- Twitter developer account
- Valid Golang installation
- Basic programming knowledge
Creating a Twitter API key
If you haven't already got a developer account on Twitter you will need to sign up. The steps to do this are documented here.
Once you have an approved developer account head over to the dashboard and create a new Project
.
Take note of your API key, API key secret, access token, access token secret as we will need these in a few moments time.
Project setup
mkdir twitter-bot
cd twitter-bot
// Change this to your Github
go mod init github.com/cameronldroberts/twitter-bot
Go....code!
Open the twitter-bot
directory in your favourite editor and create a file named main.go
.
Firstly we will focus on authenticating with twitter. We are going to store the keys in environment variables. It's not a good idea to have sensitive information stored in our code as when it reaches our source control (Github, Gitlab, etc) it may be visible to others.
Head over to the developer dashboard page where you should see the project you just created. On the left hand side click the name of your project under Projects & Apps
. Navigate to the Keys and tokens
page which is where we grab the API keys from.
We need four values CONSUMER_KEY
, CONSUMER_SECRET
, ACCESS_TOKEN
and ACCESS_TOKEN_SECRET
. These can all be found on the Keys and tokens
page. Once you have the values populate the export commands with the right values.
export CONSUMER_KEY=<API_KEY_VALUE>
export CONSUMER_SECRET=<API_KEY_SECRET_VALUE>
export ACCESS_TOKEN=<ACCESS_TOKEN_VALUE>
export ACCESS_TOKEN_SECRET=<ACCESS_TOKEN_SECRET_VALUE>
Once we have exported the environment variables we need to read them in via our Go code. The following code will do that and it will be the method we use when the code is complete
package main
import (
"fmt"
"os"
)
type Credentials struct {
ConsumerKey string
ConsumerSecret string
AccessToken string
AccessTokenSecret string
}
func main() {
creds := Credentials{
ConsumerKey: os.Getenv("CONSUMER_KEY"),
ConsumerSecret: os.Getenv("CONSUMER_SECRET"),
AccessToken: os.Getenv("ACCESS_TOKEN"),
AccessTokenSecret: os.Getenv("ACCESS_TOKEN_SECRET"),
}
fmt.Printf("%+v\n",creds)
}
This bit of the code can be divided into three sections
- Imports This is where we import other packages that we make use of in our code. It's only a short list at the moment but we will be adding a few by the time we have completed the Twitter bot!
- Struct We define a struct to store the keys that we need to use for authenticating with Twitter.
-
main
function This function is slightly special in that it is the entrypoint and will be called automatically when we run our program. For now we are just going to prove that we can read in our environment variables.
Copy the code snippet into main.go
and run it using the following command
go run main.go
This should output something similar to this
{randomvalue1 randomvalue2 randomvalue3 randomvalue4}
In your case each of the values should be referring to the keys from twitter.
Authenticating with Twitter
Now that we have exported our environment variables and are able to read them in we are ready to authenticate with Twitter.
func getClient(creds *Credentials) (*twitter.Client, error) {
// These values are the API key and API key secret
config := oauth1.NewConfig(creds.ConsumerKey, creds.ConsumerSecret)
// These values are the consumer access token and consumer access token secret
token := oauth1.NewToken(creds.AccessToken, creds.AccessTokenSecret)
httpClient := config.Client(oauth1.NoContext, token)
client := twitter.NewClient(httpClient)
verify := &twitter.AccountVerifyParams{
SkipStatus: twitter.Bool(true),
IncludeEmail: twitter.Bool(true),
}
user, _, err := client.Accounts.VerifyCredentials(verify)
if err != nil {
fmt.Println(err)
return nil, err
}
// print out the Twitter handle of the account we have used to authenticate with
fmt.Println("Successfully authenticated using the following account : ", user.ScreenName)
return client, nil
}
We can now call this function from within our main function. Replace this line(we don't need to log the credentials out anymore)
fmt.Printf("%+v\n",creds)
with
client, err := getClient(&creds)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
Search tweets
As I have mentioned previously the go-twitter client library makes it nice and easy to talk to the Twitter API in Go! Let's create our next function where we make use of the search functionality
func searchTweets(client *twitter.Client) error {
search, _, err := client.Search.Tweets(&twitter.SearchTweetParams{
Query: "bonjour",
})
if err != nil {
fmt.Println(err)
return err
}
fmt.Println("search : ", search)
return nil
}
Add the following line to our main()
function (at the bottom)
searchTweets(client)
and then run the code with go run main.go
Your terminal window will turn into what looks like a wall of text that doesn't make a lot of sense. This code works but there's definitely room for improvement! All of the tweets that were returned were in French (Not surprising given the search parameter) and the big wall of text is not very readable. Let's make some improvements to the searchTweets()
function.
Improving our search function
I'm not sure about you but unfortunately I'm unable to speak French so the first change we'll make is to add a language to the search parameters. We do this by adding the following parameter for English Lang: "en",
. This will bring back tweets where Twitter has detected the language to be English. Let's change our search parameter to become the following
search, _, err := client.Search.Tweets(&twitter.SearchTweetParams{
Query: "bonjour",
Lang: "en",
})
The second change is to parse the interesting bits of each tweet into a struct so we can ignore the bits we don't care about and more clearly see the bits we do care about! Let's try and keep things anonymous by only grabbing the tweet likes, retweets and the tweet itself. To do this let's create another struct but this time it will hold tweet data instead of credentials
type TweetData struct {
Tweet string
LikeCount int
RetweetCount int
}
When we use the client to search tweets we get a twitter.Search
object back which has an array of tweets and metadata. To grab out the values we want to keep we will loop over the array of tweets creating a struct to hold the data. We will print each struct so it makes it more readable than the previous wall of text!
New and improved...
func searchTweets(client *twitter.Client) error {
search, _, err := client.Search.Tweets(&twitter.SearchTweetParams{
Query: "bonjour",
Lang: "en",
})
if err != nil {
fmt.Println(err)
return err
}
for _, v := range search.Statuses {
tweet := TweetData{
Tweet: v.Text,
LikeCount: v.FavoriteCount,
RetweetCount: v.RetweetCount,
}
fmt.Printf("%+v\n", tweet)
}
return nil
}
Replace the existing searchTweets()
with the above snippet of code. Feel free to add/remove/change the SearchTweetParams
to something you find more interesting. You could change the query parameter to something else (maybe "Formula 1"
). You could also add on some additional parameters to the query.
At the moment this is a pretty simple use case but hopefully it gives you a foundation to build on. We could go on and build up an array of tweets and do some kind of processing on them. We could send tweets via the client when a certain condition is true.
TLDR
If you just want to skip ahead to the completed code snippet or want to use the completed snippet for reference, here it is. If you come straight here don't forget to make sure you have your API keys setup.
package main
import (
"fmt"
"os"
"github.com/dghubble/go-twitter/twitter"
"github.com/dghubble/oauth1"
)
type Credentials struct {
ConsumerKey string
ConsumerSecret string
AccessToken string
AccessTokenSecret string
}
type TweetData struct {
Tweet string
LikeCount int
RetweetCount int
}
func main() {
creds := Credentials{
ConsumerKey: os.Getenv("CONSUMER_KEY"),
ConsumerSecret: os.Getenv("CONSUMER_SECRET"),
AccessToken: os.Getenv("ACCESS_TOKEN"),
AccessTokenSecret: os.Getenv("ACCESS_TOKEN_SECRET"),
}
client, err := getClient(&creds)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
searchTweets(client)
}
func getClient(creds *Credentials) (*twitter.Client, error) {
// These values are the API key and API key secret
config := oauth1.NewConfig(creds.ConsumerKey, creds.ConsumerSecret)
// These values are the consumer access token and consumer access token secret
token := oauth1.NewToken(creds.AccessToken, creds.AccessTokenSecret)
httpClient := config.Client(oauth1.NoContext, token)
client := twitter.NewClient(httpClient)
verify := &twitter.AccountVerifyParams{
SkipStatus: twitter.Bool(true),
IncludeEmail: twitter.Bool(true),
}
user, _, err := client.Accounts.VerifyCredentials(verify)
if err != nil {
fmt.Println(err)
return nil, err
}
// print out the Twitter handle of the account we have used to authenticate with
fmt.Println("Successfully authenticated using the following account : ", user.ScreenName)
return client, nil
}
func searchTweets(client *twitter.Client) error {
search, _, err := client.Search.Tweets(&twitter.SearchTweetParams{
Query: "Formula 1",
Lang: "en",
})
if err != nil {
fmt.Println(err)
return err
}
for _, v := range search.Statuses {
tweet := TweetData{
Tweet: v.Text,
LikeCount: v.FavoriteCount,
RetweetCount: v.RetweetCount,
}
fmt.Printf("%+v\n", tweet)
}
return nil
}
That's it for this post, hopefully you enjoyed it and it made sense!
https://www.cameronroberts.dev/
Top comments (2)
Nice! one thing to note is that the library you are using currently, uses the v1.1 Twitter API, and there is more functionality available in the new v2 early access API - there are other Go libraries available for v2 already, such as github.com/g8rswimmer/go-twitter (actually I think there are 4 or 5 Go libraries for v2!) - might be worth a look.
Hey Andy! Thanks for the comment. I’ve been thinking about writing another post which makes use of the Twitter API so I’ll be sure to check those out and include V2 in the new post. Any cool features you would highlight?