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!
anyway, let's get started!
Github Repo For this Project
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:
Head off to the Discord Developer Portal & 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:
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:
Now comes the exciting part. Coding our bot! so grab a coffee and make yourself comfortable.
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 library.
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.
The end results will look something like this:
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:
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.
Before we write our main
function, we'll need our Discord bot token. To get that, head over back to Discord Developer Portal, 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.
That's where we'll be using the 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:
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.
- We first load our
.env
file with thegodotenv.Load()
function, and pass in our file's name as its argument. After loading the file, we'll use the built-inos
Go package to use our environment variable calledDISCORD_TOKEN
and assign it to the variableToken
. - We then create a new Discord session using the provided bot token from the last step.
- We register the
ready
&messageCreate
as callbacks for Event & MessageCreate events. Furthermore, We'll declare those functions later. - Open a websocket connection to Discord and begin listening.
- 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:
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:
$ 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:
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. 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 & Tenor.
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:
So in order to use the Giphy API, we'll need a token.
Head over to Giphy and create an account if you don't already have one, and login.
On the developer dashboard, which'll look something like this:
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:
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 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.
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:
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:
- We'll first load the
GIPHY_TOKEN
environment variable from the.env
file, just like we did last time, and assign it to the variablegiphyToken
. - 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, justreturn
. - This is Golang Data structure 101. Splits the message sent from a string into a slice separated by a whitespace. More in here
- 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. - concatenates the the rest of the elements into a single string, and assigning it to the variable
gifKeyword
. - 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 thehttp.NewRequest()
function and add our Request Parameters, withreq.URL.Query()
. Create a client, and send the request withClient.Do(req)
, then receive and read the response withioutil.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. - 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.
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/
Top comments (1)
Good article. Hope to see more.