Creating a GIF sending Discord bot with Golang
Tags: go, bot, discord
Published: 2022-03-23 20:54:33

Discord has been used by almost every gamer, and nowadays, it's made its way into the lives of programmers. 
I myself got into Discord in late 2017, and ever since then, I found myself more drawn to using this social media service than any other app out there.
In any case, anyone who has ever used Discord knows that bots play a huge role. These bots are  AI-powered tools that can perform many actions, from community engagement to content moderating, to even streaming music from YouTube & Spotify.
In this article, we are going to be making a simple Discord Bot with Golang that you can add to your server. What will this Bot be doing, you might ask? Well, you can search GIFs by keywords, and you can share them with your buddies on your favorite server!<br>
anyway, let's get started!<br><br>
[Github Repo For this Project](https://github.com/xenbyte/Giffie)<br>
First things first, you need to let Discord know that you're not just some average Discord user, but that you're a developer! So you need to enable developer mode in your Discord account. Go to your Discord application, go to **Settings button**. Then, click on **Advanced** and then enable **Developer Mode**:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/46in28yuz0kkl9h85cie.gif)

Head off to the [Discord Developer Portal](https://discord.com/developers/applications) & create a new application. We'll name this one "Giffie" because it's about GIFs, and we want to be creative.
From there, head to the **Bot** tab and create a new bot and also give it the username "Giffie".
Finally, if we want to add this bot to our server, go to the **oAuth2** tab, select **URL generator**, under **scopes** check _bot_ and visit the generated URL on the bottom of the page:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/deicibb3cjbgec8ei3wx.gif)

You'll see the usual screen for adding custom bots to Discord servers. Select your server and you should see it on your server under offline users! See how easy that was. pretty cool right:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7g5a6rzuz2mq2mrllko.png)

Now comes the exciting part. Coding our bot! so grab a coffee and make yourself comfortable.<br><hr><br>
First, through the command-line, let's create our Go project:
```
$ mkdir giffie-bot 
$ cd giffie-bot
$ go mod init giffie
```
The commands above will create a `giffie-bot` directory and a `go.mod` file, which'll look something like this:
```
module giffie

go 1.17
```
There are several libraries out there to hit Discord’s API, each with its own traits and capabilities, but ultimately they all achieve the same thing. Since we are using Go, we'll use bwmarrin's [DiscordGo](https://github.com/bwmarrin/discordgo) library.<br>
According to this repo's readme:

> DiscordGo is a Go package that provides low level bindings to the Discord chat client API. DiscordGo has nearly complete support for all of the Discord API endpoints, websocket interface, and voice interface.

This library has more than what we need.
before we go any further, **what does our bot do**?

- Users will type a prefix followed by a keyword. (i.e `!search <keyword>` or `=search <keyword>`)
- Our bot will go ahead and search for a Gif related to that keyword and show that to the user and everyone to see in the server.<br>

The end results will look something like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7oqiwio6tnnrlzxw563p.gif)


So let's first install our needed libraries. Run the commands below:
```
$ go get -u github.com/bwmarrin/discordgo
$ go get -u github.com/joho/godotenv
```
The second line installs the library needed for using `.env` files, so we can safely & securely use tokens or keys without hard-coding it in our code. we'll talk about its use-case later.

Let's write our first lines of code! Create a `main.go` file in the `giffie-bot` directory right next to the `go.mod` file. We then initialize the package, called `main`, and all dependencies/libraries we need to import and use in our main file:
```go
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"

	"github.com/bwmarrin/discordgo"
	"github.com/joho/godotenv"
)
```

next, we'll initialize our `main` function, which'll create a Discord session, registers our handlers and runs our Bot.<br>
Before we write our `main` function, we'll need our Discord bot token. To get that, head over back to [Discord Developer Portal](https://discord.com/developers), click on **your application**, then on the left panel, click on **Bot** and the **Reset Token** button.( remember that if you forget or lose access to your token, you'll need to regenerate a new one. so be careful as much as you can)
After getting your token, create a `.env` file right next to `main.go`, so we can handle our [environment variables](https://en.wikipedia.org/wiki/Environment_variable).
That's where we'll be using the [Godotenv](https://github.com/joho/godotenv) library, which'll help us  load environment variables.
your `.env` file should look something similar to this: 
```
DISCORD_TOKEN=< your discord token >
```
So let's write our `main` function:
```go
func main() {
	// 1
	err := godotenv.Load(".env")
	Token := os.Getenv("DISCORD_TOKEN")
	if err != nil {
		log.Fatal(err)
	}
	
	// 2
	dg, err := discordgo.New("Bot " + Token)
	if err != nil {
		fmt.Println("Error creating a discord Session, ", err)
	}

	// 3
	dg.AddHandler(ready)
	dg.AddHandler(messageCreate)

	// 4
	err = dg.Open()
	if err != nil {
		fmt.Println("Error opening Discord Session, ", err)
	}
	fmt.Println("The bot is now running. Press CTRL-C to exit.")

	// 5
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
	<-sc
}
```

That's a lot of code. But I'll try and explain the important lines.


1. We first load our `.env` file with the `godotenv.Load()` function, and pass in our file's name as its argument.
After loading the file, we'll use the built-in `os` Go package to use our environment variable called `DISCORD_TOKEN` and assign it to the variable `Token`.
2. We then create a new Discord session using the provided bot token from the last step.
3. We register the `ready` & `messageCreate`  as callbacks for Event & MessageCreate events. Furthermore, We'll declare those functions later.
4. Open a websocket connection to Discord and begin listening. 
5. Wait here until CTRL-C or other term signal is received.

Pretty basic stuff, right? 
Now let's write our handlers.

Our first handler, the `ready` function, is a simple one. It'll update the status of our bot. 
The declaration of the function is as simple as it gets: 

```go
func ready(s *discordgo.Session, event *discordgo.Event) {
	s.UpdateGameStatus(0, "!search < keyword >")
}
```
the `UpdateGameStatus()` function(as the name suggests), will  update the status of the bot. To see this in action, comment out the `dg.addHandler(messageCreate)` line in your main function, and let's start our bot!
Run the below commands in the `giffie-bot` directory:
```bash
$ go build 
$ ./giffie
```
Pretty straightforward stuff. This'll build our project and run the generated binary file. 
Let's go back to our Discord server and check the right panel:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fy0qmase5hfo5q9rhe5q.png)

Well, what do you know? The bot is now online! With the status that we provided in our `UpdateGameStatus()` function!

But the bot is not going to do anything. We want it to listen to messages sent by users, and trigger a command every time a user types `!search` followed by a keyword.

Uncomment the `dg.addHandler(messageCreate)` line in our `main` function and let's create the `messageCreate` handler.

But before we do that, let me explain how are we going to search for a specific GIF based on a keyword.
We're going to be using an [API](https://en.wikipedia.org/wiki/API). More specifically, an API that'll help us find a GIF based on a word.
There are many GIF websites out there that offer a free API services, most notably [Giphy](https://giphy.com/) & [Tenor](https://tenor.com/).
We're going to be using Giphy, but if you prefer the Tenor API or any other service, feel free to use them, but we're going to stick to the Giphy API for this tutorial.
To read more on the Giphy & Tenor APIs, check out the below links:

- [Giphy SDK Documentation](https://developers.giphy.com/docs/sdk)
- [Tenor API Documentation](https://tenor.com/gifapi/documentation#quickstart)

So in order to use the Giphy API, we'll need a token.
Head over to [Giphy](https://developers.giphy.com/) and create an account if you don't already have one, and login.
On the developer dashboard, which'll look something like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/79rdsgc6av9pwvv1idcv.png)
Go ahead and click on the **Create an App** button, click on **Next Step**, and you'll be presented with a form. Fill it in and you'll be presented with your **API Key**:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40hiybad8qwj226n9meh.png)

Go back to your .env file and add this line:
```
GIPHY_TOKEN=< your Giphy key >
```

You'll now have access to your Giphey API using the `os.Getenv()` function.
Now how are we gonna be able to search for a GIF? 
After searching in the Giphy docs, I came across [this](https://developers.giphy.com/docs/api/endpoint#random) endpoint:
```
api.giphy.com/v1/gifs/random
```
We'll use this endpoint to get our desired Gifs link!
But in order to use this endpoint, we need to provide it with a **Required** request parameter, the `api_key` and a `tag`, which is our keyword.
Before writing the code, let's use `cURL` to see what we get when we use this endpoint.
Run the below command:
```
curl -X GET  "https://api.giphy.com/v1/gifs/random?api_key=<Giphy API KEY>&tag=<Keyword>"
```
If the request was a success, you'll receive a huge and complex JSON response.
We have to turn that JSON into a Golang **Struct**, so our code can understand our response.
We'll use [JSON-to-Go](https://mholt.github.io/json-to-go/). 
Copy and paste the JSON response in there, and you'll be presented with a neat but **huge** Golang struct. 
But don't worry about it, just copy the Struct and paste it in your code, right before the `main` function, and rename it to `GifSearch`.
That's a lot of work for just sending a `GET` request and deconstructing the JSON response. But believe me, you'll realize how it'll make your life easier in the future.

Now, back to our code.
In Go, we'll use the `http` built-in library for adding headers to our URL endpoint.(you can see that in Go, we don't need to install 3rd party packages because most of the useful stuff are built-in).
So let's write the `messageCreate` function:
```go
func messageCreate(s *discordgo.Session, message *discordgo.MessageCreate) {
	// 1
	err := godotenv.Load(".env")
	giphyToken := os.Getenv("GIPHY_TOKEN")
	if err != nil {
		log.Fatal(err)
	}
	// 2
	if message.Author.ID == s.State.User.ID {
		return
	}
	// 3
	command := strings.Split(message.Content, " ")
	// 4
	if command[0] == "!search" && len(command) > 1 {
		url := "https://api.giphy.com/v1/gifs/random"
		var result GifSearch
		// 5
		gifKeyword := strings.Join(command[1:], " ")

		// 6
		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			fmt.Println("Error in making a new Request", err)
		}
		query := req.URL.Query()
		query.Add("api_key", giphyToken)
		query.Add("tag", gifKeyword)
		req.URL.RawQuery = query.Encode()
		client := http.Client{}
		res, err := client.Do(req)
		if err != nil {
			fmt.Println("Error in getting a response, ", err)
		}
		body, _ := ioutil.ReadAll(res.Body)
		if err := json.Unmarshal(body, &result); err != nil {
			fmt.Println("Can not unmarshall JSON", err)
		}
		// 7
		s.ChannelMessageSend(message.ChannelID, result.Data.EmbedUrl)
		res.Body.Close()
	}
}
```

As you can see, most of the heavy work is done in this handler.
So let's go through the code step-by-step:

1. We'll first load the `GIPHY_TOKEN` environment variable from the `.env` file, just like we did last time, and assign it to the variable `giphyToken`.
2. this `if` statement is just to check if the messages sent, are by a user or by the Bot itself. if they indeed were sent by the Bot, just `return`.
3. This is Golang Data structure 101. Splits the message sent from a string into a slice separated by a whitespace. More in [here](https://pkg.go.dev/strings#Split)
4. this `if` statement will check if the first element of the slice is the string `!search` and if the length of the slice is more than one, so it at least contains a keyword, not just a prefix.
5. concatenates the the rest of the elements into a single string, and assigning it to the variable `gifKeyword`.
6. This is where the heavy-lifting is happening. It's kinda confusing  & the syntax may be a bit hard, but it's kinda easy. We just create a new `GET` request with the `http.NewRequest()` function and add our **Request Parameters**, with `req.URL.Query()`. Create a client, and send the request with `Client.Do(req)`, then receive and read the response with `ioutil.ReadAll(res.Body)` and unmarshal the JSON we got from the response according to the Struct we defined earlier. You may wanna read this again to understand the full functionality of this block of code.
7. As a final step, we'll just send the URL we got from the response to our server, using the `s.ChannelMessageSend()` function.

that's it!
That took a lot more effort than it should've, but the end result is worth it.
Build your project like we did last time, and run your code.
Go back to your server and in the text-box, type `!search` followed by anything you want. You'll see that the Bot will respond with a GIF!

## Conclusion:
There's way more amazing things you can do with a Discord bot! Even ones that uses databases and so on.
The Github repo for this tutorial can be found [here](https://github.com/xenbyte/Giffie).
Fore more resources, I'll leave a few links at the end of the post. be sure to check them out.

Thanks for reading. I hope this has been a helpful introduction on how to make a simple yet useful Discord bot. If it was helpful, do leave a comment or share this article around for more reach.



## Resources:
https://github.com/bwmarrin/discordgo
https://github.com/joho/godotenv
https://developers.giphy.com/docs/api/endpoint
https://pkg.go.dev/strings
https://mholt.github.io/json-to-go/