A Golang bot to count down the best day of your life - the holidays
Tags: go, aws, twitter, programming
Published: 2023-07-06 18:14:16

Returning with my desire to share knowledge and experience, today i will show a very cool project that i built in the last year.

At the time, I was in the middle of my degree. Although I liked the university and thought that academia is very important for the education of a great software developer (but not mandatory), the holidays are a time that every university student looks anxiously.

## The Idea 💡
Then, at the time as I already my studies in Go, and I have a idea, why not make a bot for twitter?🤔 To learn more about golang and make a cool project. 

A bot that makes a tweet every day telling how many days are left until college holiday, for me and people at my university accompany.

Repository of project -> https://github.com/Luisgustavom1/go-twitter-bot

Today that's what I want to show, I'll show the simple code of bot and how I built the infra in AWS to automate this process, obviously using the free tier🤑.

### Before start I have divided it into 4 main steps 📑:
- Create a main function that must receive a date and return how many days are left until this date.
- Connect with twitter to make a tweet.
- Configure the function in AWS Lambda.
- Give a way of execute lambda function every day.


## 1. The function
The code of this feature is very simple. 

I have a function to format date and another to subtract the date. 
```go
package main

import (
	"log"
	"math"
	"time"
)

type DaysRemaining int

func main() {
	VACATION_DATE := "2023/06/29 23:00:00 -03"

	vacationDateFormatted, err := getVacationDateFormatted(VACATION_DATE)
	if err != nil {
		log.Fatal(err)
	}
	daysToVacation := getDaysRemaining(vacationDateFormatted)

	log.Println("tweet", daysToVacation)
}

func getVacationDateFormatted(date string) (time.Time, error) {
	const shortFormat = "2006/01/02 15:04:05 -07"
    // Format date to this format and timezone 👆👆 
	dateParsed, err := time.Parse(shortFormat, date)

	if err != nil {
		return dateParsed, err
	}

	return dateParsed, nil
}

func getDaysRemaining(date time.Time) DaysRemaining {
	today := time.Now()
	duration := date.Sub(today)
    // Subtract today from vacation date 
	return DaysRemaining(math.Ceil(duration.Hours() / 24))
}
```
The main logic is basically that, in another post we can explore more about golang and its powers, but today i just want to give an overview of the code.

## 2. Connect with twitter
At the time I used this [client library](https://github.com/dghubble/go-twitter/) to abstract the twitter API, but unfortunately it is deprecated😭

So today maybe it is not the best option. You can venture into [twitter api](https://developer.twitter.com/en/docs/twitter-api/getting-started/about-twitter-api).  

In this step I had to generate api keys and access tokens. Every time I get to this part to integrate with third party, I get lost where get this secrets, so here are the ones from twitter [api key](https://developer.twitter.com/en/docs/authentication/oauth-1-0a/api-key-and-secret) and [access token](https://developer.twitter.com/en/docs/authentication/oauth-1-0a/obtaining-user-access-tokens).
```golang
package main

import (
    "log"
	"math"
	"time"
	"os"
        
    "github.com/dghubble/go-twitter/twitter"
	"github.com/dghubble/oauth1"
)

func main() {
    //...
    // Initial config to oauth 1.0a
	config := oauth1.NewConfig(
		os.Getenv("API_KEY"),
		os.Getenv("API_KEY_SECRET"),
	)
	token := oauth1.NewToken(
		os.Getenv("ACCESS_TOKEN"),
		os.Getenv("ACCESS_TOKEN_SECRET"),
	)
    
    // Create a http client with oauth 1.0a method
    httpClient := config.Client(oauth1.NoContext, token)

    // Create the client to access twitter api methods(endpoints)
	client := twitter.NewClient(httpClient)

    //...

	daysToVacation := getDaysRemaining(vacationDateFormatted)
	messageToTweet, err := generateMessageToTweet(daysToVacation)
	if err != nil {
		log.Fatal(err)
	}
      
    // Method to publish a tweet
	client.Statuses.Update(messageToTweet, nil)
}
``` 


## 3. Create the Lambda Function
At the time I only knew the concept about lambda functions so it would be one more tech that I would learn with this simple project.

Let's [create lambda function](https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/create/function). 
1. Choose `Author from scratch`.
2. Put the name of your bot.
3. Select the language and architecture.

![AWS dashboard to create a lambda function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iwla4aiyaj2gml4bd479.png)

Now, to work with Lambda using go, we have to change the code a little bit and use this [official aws tool](https://github.com/aws/aws-lambda-go/).

So we import the package, rename `main` function to `Handler` and create a new `main` that will execute the `.Start()` method, of aws tool.

The `Handler` function is method that manipulate the events. The `main` function is required, is the entry point where aws lambda will run our code.

```go
package main

import (
	// ...
    // Import the package
	"github.com/aws/aws-lambda-go/lambda"
)

type DaysRemaining int

func main() {
    // The `main` should start the Handler function
	lambda.Start(Handler)
}

// Out function that contains the logic
func Handler() {
	VACATION_DATE := "2023/06/29 23:00:00 -03"

	config := oauth1.NewConfig(
		os.Getenv("API_KEY"),
		os.Getenv("API_KEY_SECRET"),
	)
    // ...
}
```
> If you want developing something more complexity, with multiply events and features, you can access the event name and work with context in Handler function, [see more](https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html#:~:text=func-,HandleRequest,-(ctx%20context.Context%2C%20name%20MyEvent)%20(string%2C%20error)%3A%20This).

After finishing the code, don't forget of upload the code to AWS lambda, either by uploading the .zip file or by configuring an s3 bucket, and add the twitter keys in the environment variables (`Configuration` > `Environment variables`). 

## 4. Running every day
Someone once told me that
> "Developers are the laziest, because they want to automate everything"

Sice obviously I won't execute manually the lambda every day, we can use the AWS CloudWatch Events to trigger the function at the specific time.

Then let's add a trigger  
![AWS lambda dashboard to add a trigger](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sb38r0gb7agtgx33bxhm.png)
- Select `EventBridge (CloudWatch Events)`.
- Check `Create a new rule`.
- Check `Schedule Expression`.
- Put the cron expressions.

For my bot I use this cron `cron(00 09 ? * * *)`, It will run every day at 09:00 UTC, that is, 06:00 in GTM-3.
![Process to create the trigger](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5dv19ztt229gqx8mt4j8.png)

> If when creating your lambda function, you kept these configs checked (the default configs): `Permissions` > `Change default execution role` > `Create a new role with basic Lambda permissions`. You won't have to worry with the IAM role for lambda access the cloud watch, the AWS already create the role for you, [lambda permissions](https://docs.aws.amazon.com/lambda/latest/dg/lambda-permissions.html). 

That's it, see the bot perfil in [twitter](https://twitter.com/BotFeriasUfu).

Hope you are enjoying.

Follow me on social networks:
- [Linkedin](https://www.linkedin.com/in/luisgustavom1/)
- [Github](https://github.com/Luisgustavom1)