avatarYash Prakash

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

5552

Abstract

bot to a test discord server you should have for trying out the bot as we built it.</b></p><p id="bc18">Now, let’s start our Go application with a <code>main.go</code> file.</p><div id="06e6"><pre><span class="hljs-built_in">touch</span> main.go</pre></div><p id="b2ea">The first thing we want to do is to define:</p><ul><li>the <code>package</code></li><li>the <code>init</code> function</li><li>the <code>main</code> function</li></ul><p id="9b1b">The <code>main</code> function is necessary as the program won’t run without it. The <code>init</code> function provides a great way to perform some actions before running our app, such as: reading our environment variable(s) from the disk.</p><p id="5642">So, let’s do that:</p><blockquote id="a28a"><p><b>Protip:</b> In VSCode, after enabling the Go extension, if you start typing the functions, you’ll get the imports handled by the extension. So there’s no need to type the imports by yourself.</p></blockquote><p id="1600">Now let’s do the following:</p><ul><li>define our package</li><li>make a Token variable to contain the bot token as a global variable</li><li>read the bot token from the environment variable in the <code>init</code> function</li></ul><div id="fc1f"><pre><span class="hljs-keyword">package</span> <span class="hljs-title">main</span></pre></div><div id="fbc5"><pre><span class="hljs-keyword">var</span> Token <span class="hljs-built_in">string</span></pre></div><div id="8168"><pre><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span> { <span class="hljs-comment">// load env variables</span> err := godotenv.Load() <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> { log.Fatal(<span class="hljs-string">"Error loading .env file."</span>) } }</pre></div><blockquote id="62f4"><p>Note: I am not showing the imports for better readability.</p></blockquote><p id="65a5">Now, it’s time to make a new <code>Bot</code> in our main function.</p><p id="827c">In Discordgo, we have a concept of “sessions” for the bot. It is essentially is a type of client you want to create from the library. You can either create a <b>Bot</b> or an <b>OAuth2</b> client.</p><p id="54a6">So you define sessions a little differently for them:</p><ul><li>If the Token is for a bot, it must be prefixed with “Bot “</li><li>If the Token is for an OAuth2 client, it must be prefixed with “Bearer “</li></ul><div id="d7c3"><pre>func main() { <span class="hljs-keyword">Token</span> = os.Getenv(<span class="hljs-string">"TOKEN"</span>) <span class="hljs-comment">// Create a new Discord session using the provided bot token.</span> bot, <span class="hljs-keyword">err</span> := discordgo.New(<span class="hljs-string">"Bot "</span> + <span class="hljs-keyword">Token</span>) <span class="hljs-keyword">if</span> <span class="hljs-keyword">err</span> != nil { fmt.Println(<span class="hljs-string">"Error creating Discord session,"</span>, <span class="hljs-keyword">err</span>) <span class="hljs-keyword">return</span> }</pre></div><p id="da14">Now we need to register a “Handler” to read messages from the server our bot has permissions in.</p><div id="c494"><pre><span class="hljs-comment">// Register the messageCreate func as a callback for MessageCreate events.</span> bot<span class="hljs-selector-class">.AddHandler</span>(messageCreate)</pre></div><div id="90bb"><pre><span class="hljs-comment">// We care about receiving message events.</span> bot<span class="hljs-selector-class">.Identify</span><span class="hljs-selector-class">.Intents</span> = discordgo.IntentsGuildMessages</pre></div><p id="ed5f">You’ll see a read lined messageCreate here as that function hasn’t been defined by us yet.</p><p id="ddda">And run the bot by the Open function.</p><div id="4413"><pre><span class="hljs-comment">// Open a websocket connection to Discord and begin listening.</span> <span class="hljs-keyword">err</span> = bot.<span class="hljs-keyword">Open</span>() <span class="hljs-keyword">if</span> <span class="hljs-keyword">err</span> != nil { fmt.Println(<span class="hljs-string">"error opening connection,"</span>, <span class="hljs-keyword">err</span>) <span class="hljs-keyword">return</span> }</pre></div><p id="c882">Perfect! Now, remember, we also need a way to close this session safely for our bot to exit if we want it to exit.</p><p id="862c">We do that through the concept of <b>Go signals</b>.</p><p id="9794">Sometimes, in scenarios like this, we’d like our Go programs to intelligently handle <a href="https://en.wikipedia.org/wiki/Unix_signal">Unix signals</a>. For instance,</p><ul><li>Ctrl C in the terminal should make the bot session terminate : This means we need a <b>SIGINT</b> signal to be read by our Go program.</li><li>We also want it to gracefully shutdown when it receives a <code>SIGTERM </code>signal. This is another UNIX to quit a command line process.</li></ul><p id="4667">Here’s an excerpt from the wiki page on <b>SIGTERM</b>:</p><blockquote id="2965"><p>The SIGTERM signal is sent to a process to request its <b>termination</b>. This allows the process to perform nice termination releasing resources and saving state if appropriate. SIGINT is nearly identical to SIGTERM.</p></blockquote><p id="f4d0">So here, we will handle signals in <b>Go channels.</b></p><div id="f88f"><pre>fmt<span class="hljs-selector-class">.Println</span>(<span class="hljs-string">"Bot is now running. Press CTRL-C to exit."</span>) s

Options

c := <span class="hljs-built_in">make</span>(chan os<span class="hljs-selector-class">.Signal</span>, <span class="hljs-number">1</span>) signal<span class="hljs-selector-class">.Notify</span>(sc, syscall<span class="hljs-selector-class">.SIGINT</span>, syscall<span class="hljs-selector-class">.SIGTERM</span>, os<span class="hljs-selector-class">.Interrupt</span>, os.Kill) <-sc</pre></div><p id="e53e">We first make a new channel called <b>sc </b>to signify that we want to listen to the OS signals. Next, we notify the signal for all the signals we want to listen to, namely: SIGINT, SIGTERM, and common Operating system interrupt and kill signals.</p><p id="cea8">Next, what the unique code snippet <code><-sc</code> does is that it halts the program from executing the next line(s) of code until one of those defined signals is received.</p><p id="eeed">This is very important for us because immediately after this code, we define a way to close our Bot session:</p><div id="8ef0"><pre>// <span class="hljs-keyword">Close</span> the discord bot <span class="hljs-keyword">session</span>. bot.<span class="hljs-keyword">Close</span>() } // <span class="hljs-keyword">close</span> main <span class="hljs-keyword">function</span></pre></div><p id="ef0e">Now that we have done all of that, we can close our main function and start writing the <b>messageCreate</b> function to handle incoming messages on the discord server.</p><div id="8d05"><pre><span class="hljs-variable">func</span> <span class="hljs-title function_">messageCreate</span>(<span class="hljs-params">s</span> *<span class="hljs-params">discordgo</span>.<span class="hljs-params">Session</span>, <span class="hljs-params">m</span> *<span class="hljs-params">discordgo</span>.<span class="hljs-params">MessageCreate</span>) {</pre></div><p id="ac72">We want our bot to do the following:</p><ul><li>reply to ping messages with pong</li><li>reply to pong messages with ping</li><li>NOT reply to any of the messages sent by itself</li></ul><p id="1ff0">Here’s the way to do that:</p><div id="186e"><pre><span class="hljs-comment">// Don't reply to self</span> <span class="hljs-keyword">if</span> m<span class="hljs-selector-class">.Author</span><span class="hljs-selector-class">.ID</span> == s<span class="hljs-selector-class">.State</span><span class="hljs-selector-class">.User</span><span class="hljs-selector-class">.ID</span> { return } <span class="hljs-comment">// If the message is "ping" reply with "Pong!"</span> <span class="hljs-keyword">if</span> m<span class="hljs-selector-class">.Content</span> == <span class="hljs-string">"ping"</span> { s<span class="hljs-selector-class">.ChannelMessageSend</span>(m<span class="hljs-selector-class">.ChannelID</span>, <span class="hljs-string">"Pong!"</span>) }</pre></div><div id="11a2"><pre> <span class="hljs-comment">// If the message is "pong" reply with "Ping!"</span> <span class="hljs-keyword">if</span> m<span class="hljs-selector-class">.Content</span> == <span class="hljs-string">"pong"</span> { s<span class="hljs-selector-class">.ChannelMessageSend</span>(m<span class="hljs-selector-class">.ChannelID</span>, <span class="hljs-string">"Ping!"</span>) } }</pre></div><p id="62ac">Great! So we reached the end of our coding session, (pun intended).</p><p id="3648">Let’s test our bot!</p><p id="3396">You can do either of the following in the terminal:</p><div id="b4d0"><pre><span class="hljs-attribute">air</span></pre></div><p id="9bc8">or</p><div id="e285"><pre><span class="hljs-keyword">go</span> run main.<span class="hljs-keyword">go</span></pre></div><p id="3e18">I prefer <code>air</code> as it displays helpful colourful messages.</p><p id="f49e">You should see the following:</p><figure id="f907"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*OrpEbNEJLAxeWf0x.png"><figcaption></figcaption></figure><p id="072b">You can test the bot in the server:</p><figure id="dc24"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*zc8l7FY8lqsa_Bm7.png"><figcaption></figcaption></figure><p id="5854">Now, if you do <b>Ctrl-C</b>, the bot will exit gracefully, with <b>air</b>.</p><figure id="cfff"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*Kw3hECS39oCpkiyR.png"><figcaption></figcaption></figure><h1 id="23f2">A few parting words…</h1><p id="2d90">In this Part 1 of the series, we learned how to get started with writing a basic discord bot which sends messages. I hope you find this useful!</p><p id="9d2d">We will be exploring much more complex scenarios for our bot in the future parts of this tutorial series.</p><p id="60ed">Until then, happy learning!</p><blockquote id="4d47"><p><i>Explore the bot’s GitHub repository <a href="https://github.com/yashprakash13/Go-Basics">here</a>.</i></p></blockquote><p id="d514">Subscribe to medium to never miss any of my posts! 👇</p><div id="337f" class="link-block"> <a href="https://ipom.medium.com/membership"> <div> <div> <h2>Join Medium with my referral link - Yash Prakash</h2> <div><h3>Read every story from Yash Prakash (and thousands of other writers on Medium). Your membership fee directly supports…</h3></div> <div><p>ipom.medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*n3ZnVICa7urRj7Sw)"></div> </div> </div> </a> </div></article></body>

Building a Golang Discord Bot- Part 1: Setup

Learn Go development through a real world project

Photo by Chris Lawton on Unsplash

Welcome to this new series on learning software development in Golang. Beginning from this article, we will be going into the path of learning the Go programming language while building a real world project that we can later deploy on a server as a useful service.

So without further delay, let’s get started on the project!

But before that…

Make sure to go through my Go utilities article to set up your development environment nicely! 👇🏻

Golang Development Utilities You Should Know

Setting up our Project and Dependencies

The focus of this project is upon building a utility discord bot that can do certain small but useful things within our server. But we can discuss about what those useful things may comprise of later, first, let’s see how to set up our project and install some of the libraries we will be using.

Go to your Go development folder on your local machine and enter:

mkdir discord_bot
cd discord_bot

We now want to initialize a new Go project in our root project folder:

go init

This now builds us a new go.mod file in our project directory. You’ll see that it contains the go version and the simple module path on your local machine like so:

module yashprakash13/Go-Basics/discord_bot
go 1.19

Now, let’s install the required libraries.

The first one is the Discord API wrapper library called Discordgo.

go get github.com/bwmarrin/discordgo

Install the library to deal with environment variables GoDotEnv:

go get github.com/joho/godotenv

And lastly, do a Air init inside the project so that we can run our project with constant reloading:

If you’ve followed the guide I’ve mentioned earlier, you must know what I’m talking about.

air init

This creates a .air.toml file for us. Great, we are ready to go!

A simple ping-pong bot to start with

Your go.mod file should look somewhat like this now (excluding the repo path of course):

module yashprakash13/Go-Basics/discord_bot
go 1.19
require (
	github.com/bwmarrin/discordgo v0.26.1 
	github.com/gorilla/websocket v1.4.2 
	github.com/joho/godotenv v1.4.0 
	golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
	golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 
)

The first thing you wanna do is go to discord developer portal and make yourself a new application:

This is how it’ll look like when you make one. Then go the sidebar and make a new Bot.

Now, enable the following intents for the Bot:

Finally, copy the Bot token and in your project directory, do:

touch .env

Paste the Token in your .env file.

Also, invite your discord bot to a test discord server you should have for trying out the bot as we built it.

Now, let’s start our Go application with a main.go file.

touch main.go

The first thing we want to do is to define:

  • the package
  • the init function
  • the main function

The main function is necessary as the program won’t run without it. The init function provides a great way to perform some actions before running our app, such as: reading our environment variable(s) from the disk.

So, let’s do that:

Protip: In VSCode, after enabling the Go extension, if you start typing the functions, you’ll get the imports handled by the extension. So there’s no need to type the imports by yourself.

Now let’s do the following:

  • define our package
  • make a Token variable to contain the bot token as a global variable
  • read the bot token from the environment variable in the init function
package main
var Token string
func init() {
	// load env variables
	err := godotenv.Load()
	if err != nil {
		log.Fatal("Error loading .env file.")
	}
}

Note: I am not showing the imports for better readability.

Now, it’s time to make a new Bot in our main function.

In Discordgo, we have a concept of “sessions” for the bot. It is essentially is a type of client you want to create from the library. You can either create a Bot or an OAuth2 client.

So you define sessions a little differently for them:

  • If the Token is for a bot, it must be prefixed with “Bot “
  • If the Token is for an OAuth2 client, it must be prefixed with “Bearer “
func main() {
	Token = os.Getenv("TOKEN")
	// Create a new Discord session using the provided bot token.
	bot, err := discordgo.New("Bot " + Token)
	if err != nil {
		fmt.Println("Error creating Discord session,", err)
		return
	}

Now we need to register a “Handler” to read messages from the server our bot has permissions in.

// Register the messageCreate func as a callback for MessageCreate events.
	bot.AddHandler(messageCreate)
// We care about receiving message events.
	bot.Identify.Intents = discordgo.IntentsGuildMessages

You’ll see a read lined messageCreate here as that function hasn’t been defined by us yet.

And run the bot by the Open function.

// Open a websocket connection to Discord and begin listening.
	err = bot.Open()
	if err != nil {
		fmt.Println("error opening connection,", err)
		return
	}

Perfect! Now, remember, we also need a way to close this session safely for our bot to exit if we want it to exit.

We do that through the concept of Go signals.

Sometimes, in scenarios like this, we’d like our Go programs to intelligently handle Unix signals. For instance,

  • Ctrl C in the terminal should make the bot session terminate : This means we need a SIGINT signal to be read by our Go program.
  • We also want it to gracefully shutdown when it receives a SIGTERM signal. This is another UNIX to quit a command line process.

Here’s an excerpt from the wiki page on SIGTERM:

The SIGTERM signal is sent to a process to request its termination. This allows the process to perform nice termination releasing resources and saving state if appropriate. SIGINT is nearly identical to SIGTERM.

So here, we will handle signals in Go channels.

fmt.Println("Bot is now running.  Press CTRL-C to exit.")
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
	<-sc

We first make a new channel called sc to signify that we want to listen to the OS signals. Next, we notify the signal for all the signals we want to listen to, namely: SIGINT, SIGTERM, and common Operating system interrupt and kill signals.

Next, what the unique code snippet <-sc does is that it halts the program from executing the next line(s) of code until one of those defined signals is received.

This is very important for us because immediately after this code, we define a way to close our Bot session:

// Close the discord bot session.
	bot.Close()
} // close main function

Now that we have done all of that, we can close our main function and start writing the messageCreate function to handle incoming messages on the discord server.

func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {

We want our bot to do the following:

  • reply to ping messages with pong
  • reply to pong messages with ping
  • NOT reply to any of the messages sent by itself

Here’s the way to do that:

// Don't reply to self
	if m.Author.ID == s.State.User.ID {
		return
	}
	// If the message is "ping" reply with "Pong!"
	if m.Content == "ping" {
		s.ChannelMessageSend(m.ChannelID, "Pong!")
	}
	// If the message is "pong" reply with "Ping!"
	if m.Content == "pong" {
		s.ChannelMessageSend(m.ChannelID, "Ping!")
	}
}

Great! So we reached the end of our coding session, (pun intended).

Let’s test our bot!

You can do either of the following in the terminal:

air

or

go run main.go

I prefer air as it displays helpful colourful messages.

You should see the following:

You can test the bot in the server:

Now, if you do Ctrl-C, the bot will exit gracefully, with air.

A few parting words…

In this Part 1 of the series, we learned how to get started with writing a basic discord bot which sends messages. I hope you find this useful!

We will be exploring much more complex scenarios for our bot in the future parts of this tutorial series.

Until then, happy learning!

Explore the bot’s GitHub repository here.

Subscribe to medium to never miss any of my posts! 👇

Programming
Github
Golang
Tutorial
Development
Recommended from ReadMedium