go discord bot

Discord Bot Programming with Golang

Programming

Discord Bot Programming with Golang

Programming a Discord bot with Go is easier than you thought! Golang is very fast when working with API and since we are using Discord API while programming a bot, controlling our bot is simple with Go’s Discordgo package.

What does our bot do?

In this tutorial, we will create a Discord bot that can scrape the RSS feed of certain websites and then publish news posts in a specified channel. Also, users use the bot to add new websites to scrape the list.

Environment Preparation

First, we should prepare our environment to create Discord bot. I’ve created 3 files:

  • discord.go: This file will handle Discord API requests with discordgo package.
  • main.go: You know, this is our main file. We will start our program here.
  • scraper.go: This file will scrape the RSS Feed of selected websites.

Initiliaze Discord Bot

After creating our environment, we need to initialize our bot.

Let’s code our main.go file with a global “l” parameter. This parameter will be used to log application behavior. It may useful for debugging purposes. I’ve also called my ConnectToDC() function here. It is stored in the discord.go file.


package main

import (
	"log"
	"os"
)

var (
	outfile, _ = os.Create("rssdcbot.log")
	l          = log.New(outfile, "", log.LstdFlags|log.Lshortfile)
)

func main() {
	ConnectToDC()
}

I’ve implemented the following code into discord.go file. ConnectToDC function calls initSession function. initSession function uses discordgo package to create a new discord bot session. After creating the session, I’ve setted the session as a global variable with the “Dg” parameter. Because I want to use sessions from different files.

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/bwmarrin/discordgo"
)

var token = "YOUR-TOKEN"
var botChID = "CHANNEL-ID"
var Dg *discordgo.Session

func initSession() {
	// Initilaze the Discord Session
	dg, err := discordgo.New("Bot " + token)
	if err != nil {
		panic(err)
	}

	err = dg.Open()
	if err != nil {
		fmt.Println("Couldn't start Discord session! Error: ", err)
		l.Fatalf("Couldn't start Discord session! Error: %s", err)

	}

	Dg = dg

}

func ConnectToDC() {
	initSession()
	time.Sleep(time.Second * 1)

	//Register messageCreate func
	Dg.AddHandler(messageCreate)
	Dg.Identify.Intents = discordgo.IntentDirectMessageReactions

	//If the user press CTRL+C, exit.
	fmt.Println("Bot is running. To exit press CTRL+C")
	l.Println("[INFO] Bot is running.")
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
	<-sc

	Dg.Close()
	l.Println("[INFO] Bot stopped.")

}

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

}

In step 2, in the initialization step, I’ve coded but we don’t have any discord bot app for now. So, we need to create a discord bot app from https://discord.com/developers/applications.

Creating the Bot and Adding it to the Discord Server

    1. Visit https://discord.com/developers/applications
    2. Click “New Application” at the right top corner.
    3. Give the name of your app and create.
  1. Go to the “Bot” section from the left menu.
  2. Click the “Add Bot” button from the Buld-A-Bot section.
  3. After creating the bot use the “Reset Token” button and copy and note the token.
  4. Go to the Oauth2 > URL Generator section from the left menu.
  5. In scopes, select the “bot” checkbox. Then at the bottom, select the “Send Messages” checkbox.
  6. Copy the URL from the bottom side of the page and go to the URL.
  7. Add the bot to your Discord server. After adding the bot you should get the channel id that you want to send messages. To do this. Open Discord and go to the Settings > Advanced then open the Developer Mode. Then you can right-click a channel to copy ID.

Parsing RSS and Data Sending to Discord Server with the Bot

In previous steps, we’ve created our discord bot and added it to the server. Also, we’ve initialized it. Now, we will program our bot to parse RSS feed then send data to the server.

Firstly, let’s create our messageCreate function. The bot will be able to create messages be able to send it to the server.

func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
	if m.Content == "!rssbot" {
		s.ChannelMessageSend(m.ChannelID, "Hey! I'm here.")
		l.Printf("[INFO] %s called the bot.", m.Author)
	}

Now, when we type “!rssbot” from a channel on the server, our bot will type “Hey! I’m here.”. Then, let’s implement scraper code to scraper.go file.

package main

import (
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/mmcdole/gofeed"
)

type FeedItem struct {
	Title string
	URL   string
}

func readFile(fname string) string {
	databyte, err := ioutil.ReadFile(fname)
	if err != nil {
		panic(err)
	}
	return string(databyte)
}

func ParseRSS() {
	blogList := [2]string{"<https://sibersaldirilar.com/feed>", "<https://pwnlab.me/feed>"}
	fp := gofeed.NewParser()
	fp.Client = &http.Client{Timeout: time.Second * 5}

	feed_items := make([]FeedItem, 1)

	for true {
		for k := 0; k < len(blogList); k++ {
			feed, err := fp.ParseURL(blogList[k])

			if err == nil {
				l.Printf("[INFO] RSS Parser started to running for %s", blogList[k])
				items := feed.Items
				for i := 0; i < len(items); i++ {
					if !strings.Contains(readFile("feed_item.list"), items[i].Link) {
						// Create a new FeedItem Obj
						feedItem := FeedItem{Title: items[i].Title, URL: items[i].Link}
						feed_items = append(feed_items, feedItem)

						// Send Title and Link info to the DC channel
						msg := "A new post has been published: **" + items[i].Title + "**\\n" + items[i].Link
						Dg.ChannelMessageSend(botChID, msg)

						// Write Link info to the feed_item.list file
						file, err := os.OpenFile("feed_item.list", os.O_APPEND|os.O_WRONLY, 0644)
						if err != nil {
							panic(err)
						}

						defer file.Close()

						if _, err := file.WriteString(items[i].Link + "\\n"); err != nil {
							l.Fatal(err)
						}

					} else {
						l.Printf("[WARN] The parser already parsed %s", items[i].Link)
					}
				}
			}
			feed_items = make([]FeedItem, 1)
		}

		time.Sleep(28800 * time.Second)
	}
}

After implementing the scrape.go file, I’ve added the “go ParseRSS()” line bottom of the initSession() function call in ConnectToDC() function.

func ConnectToDC() {
	initSession()
	go ParseRSS()
	time.Sleep(time.Second * 1)

	//Register messageCreate func
	Dg.AddHandler(messageCreate)
	Dg.Identify.Intents = discordgo.IntentDirectMessageReactions

	//If the user press CTRL+C, exit.
	fmt.Println("Bot is running. To exit press CTRL+C")
	l.Println("[INFO] Bot is running.")
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
	<-sc

	Dg.Close()
	l.Println("[INFO] Bot stopped.")

}

I’ve called ParseRSS with goroutines because I need it to work in parallel. It will run every 8 hours and check the RSS feed.

Getting User Commands with Discord Bot

Our Discord Go Bot, is now ready for getting commands from the users. Let’s explain how to get user commands with the discord bot?

I’ve used messageCreate function to get user commands. As you can see in the following code, I’ve

added new if blocks to this function.

func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
	if m.Content == "!rssbot" {
		s.ChannelMessageSend(m.ChannelID, "Hey! I'm here.")
		l.Printf("[INFO] %s called the bot.", m.Author)
	}

	if m.Content == "!rssbot help" {
		handleHelp(s, m)
	}

	if m.Content == "!rssbot print_feed_items" {
		handlePrintFeeds(s, m)
	}

}

I’ve created new functions to handle user commands that are named handleHelp and handlePrintFeeds.

  • handleHelp: Prints Go Discord Bot’s help menu.
  • handlePrintFeeds: Prints collected RSS feed URLs.

In discord.go file, I’ve implemented the handle functions bottom of the messageCreate function.

func handleHelp(s *discordgo.Session, m *discordgo.MessageCreate) {
	s.ChannelMessageSend(m.ChannelID, "Avaible commands:\\n-----------------------\\n!rssbot help					Prints this information.\\n!rssbot print_feed_items   		Prints parsed feed items.")
}

func handlePrintFeeds(s *discordgo.Session, m *discordgo.MessageCreate) {
	s.ChannelMessageSend(m.ChannelID, readFile("feed_item.list"))
}

Finally, our discord.go file should look like the following code.

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/bwmarrin/discordgo"
)

var token = "YOUR-TOKEN"
var botChID = "CHANNEL-ID"
var Dg *discordgo.Session

func initSession() {
	// Initilaze the Discord Session
	dg, err := discordgo.New("Bot " + token)
	if err != nil {
		panic(err)
	}

	err = dg.Open()
	if err != nil {
		fmt.Println("Couldn't start Discord session! Error: ", err)
		l.Fatalf("Couldn't start Discord session! Error: %s", err)

	}

	Dg = dg

}

func ConnectToDC() {
	initSession()
	go ParseRSS()
	time.Sleep(time.Second * 1)

	//Register messageCreate func
	Dg.AddHandler(messageCreate)
	Dg.Identify.Intents = discordgo.IntentDirectMessageReactions

	//If the user press CTRL+C, exit.
	fmt.Println("Bot is running. To exit press CTRL+C")
	l.Println("[INFO] Bot is running.")
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
	<-sc

	Dg.Close()
	l.Println("[INFO] Bot stopped.")

}

func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
	if m.Content == "!rssbot" {
		s.ChannelMessageSend(m.ChannelID, "Hey! I'm here.")
		l.Printf("[INFO] %s called the bot.", m.Author)
	}

	if m.Content == "!rssbot help" {
		handleHelp(s, m)
	}

	if m.Content == "!rssbot print_feed_items" {
		handlePrintFeeds(s, m)
	}

}

func handleHelp(s *discordgo.Session, m *discordgo.MessageCreate) {
	s.ChannelMessageSend(m.ChannelID, "Avaible commands:\\n-----------------------\\n!rssbot help					Prints this information.\\n!rssbot print_feed_items   		Prints parsed feed items.")
}

func handlePrintFeeds(s *discordgo.Session, m *discordgo.MessageCreate) {
	s.ChannelMessageSend(m.ChannelID, readFile("feed_item.list"))
}

Leave a Reply