Creating a Discord Message Recording Bot with Go

In this article, I’ll show you my adventure with the bot which I named as Zucc(From the popular “delet” memes :D)

This BOT is created and regularly used amongst friends, just for memes & fun.

Zucc Bot can be added to any server, and it automatically records messages and saves them to a database. In any server, you can fetch a random message of a user(even though the user is from another server), or you can draw

In case, if you are coming for Game Development tutorials and have no Go knowledge, here’s how you can install Go in your computer.
Install Go On Your Computer

Libraries & Tools I have used in this project are;

go get "github.com/bvinc/go-sqlite-lite/sqlite3"
go get "github.com/bwmarrin/discordgo"

And make sure to create your bot on Discord Developer Portal. Check this tutorial to see how you can create your bot. We will need a Bot Token for our app.

Creating Our Database First

I opened a blank folder in C:/myBot I somehow like this place. I’ve created my database with DB Browser for SQLite program. Simply clicking File > New Database is enough. Here in my database, I’ve created a TABLE with the fields below.

  • messageid : Message ID
  • guildid : Server’s ID
  • channelid : Channel’s ID
  • guildname : Server’s Name at the time that message was sent
  • channelname : Channel’s Name at the time that message was sent
  • senderid : The ID of the person who sent the message
  • sendername : The Name of the person who sent the message
  • time : The Time of the message, formatted as “2006-01-02 15:04:05”

You can CREATE this table from the editor below, or you can execute a SQL code under the Execute SQL tab. Make sure to select Type as TEXT because all our message fields are string.

From Visual Editor

OR with SQL Code

CREATE TABLE "messages" (
	"messageid"	TEXT,
	"guildid"	TEXT,
	"channelid"	TEXT,
	"guildname"	TEXT,
	"channelname"	TEXT,
	"senderid"	TEXT,
	"sendername"	TEXT,
	"message"	TEXT,
	"time"	TEXT
);

And that’s it. We’re done with our database now. We will access/write data to it via Go Sqlite Lite now.

Writing Our Bot’s Code

Luckily, Discordgo provides us all the tools we need. I will start by importing packages into my project.

package main

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

	"github.com/bvinc/go-sqlite-lite/sqlite3"
	"github.com/bwmarrin/discordgo"
)

I will then declare 3 variables to be used later.

//********** Insert your own Token Here **********//
var botToken = "----------------------------------"

//Global Variables for Discord Session & Sqlite Connection.
var dg *discordgo.Session
var conn *sqlite3.Conn

Here’s my main function. Here I will connect to zuccLife.db (as we created earlier). I added messageArrived function as the incoming message handler.

func main() {

	//Connect to the Sqlite Database
	var era error
	conn, era = sqlite3.Open("zuccLife.db")
	if era != nil {
		fmt.Println("Error opening the Database", era)
	}
	defer conn.Close()

	//Createa a Discord session
	var err error
	dg, err = discordgo.New("Bot " + botToken)
	if err != nil {
		fmt.Println("Error creating the Discord session", err)
		return
	}

	//If a Message Arrives(yummyyyyy..), direct it to the messageArrived function.
	dg.AddHandler(messageArrived)

	//Open a Websocket
	err = dg.Open()
	if err != nil {
		fmt.Println("Error opening the connection", err)
		return
	}

	// Wait here until CTRL-C or other term signal is received.
	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

	// Cleanly close down the Discord session.
	dg.Close()
}

Here’s my messageArrived script. Here, once I received the message I parsed it with variables. uID, uName, guildID, channelID, messageID, message, t as the current time, and v as the time’s formatted string.

//Whenever a Message Arrives, here I'll process it.
func messageArrived(s *discordgo.Session, m *discordgo.MessageCreate) {

	//It's Zucc's message, ignore...
	if m.Author.ID == s.State.User.ID {
		return
	}

	//Values parsed from the Incoming Message.
	uID := m.Author.ID
	uName := m.Author.Username
	guildID := m.GuildID
	guildNameEntr, _ := s.State.Guild(guildID)
	channelID := m.ChannelID
	channelNameEntr, _ := s.State.Channel(channelID)
	messageID := m.ID
	message := m.Content
	t := time.Now()
	v := t.Format("2006-01-02 15:04:05")

	//Record the message right away!
	var erol error
	erol = conn.Exec(`INSERT INTO messages VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, messageID, guildID, channelID, guildNameEntr.Name, channelNameEntr.Name, uID, uName, m.Content, v)
	if erol != nil {
		fmt.Println("I couldn't record the message. Oppss", erol)
		s.ChannelMessageSend(channelID, "What kind of message did you send "+uName+". I can't record it???")
	}

	//Update Bot Status. In Discord it'll look as (Zucc is listening to Fritsbie's private life...)
	dg.UpdateListeningStatus(uName + "'s private life...")

	//Now, let's parse the Message itself.
	switch {

	//!help command (types as !halp <MEMES>)
	case message == "!halp":
		s.ChannelMessageSend(channelID, "**Zucc commands** - *everything is secret, unlike your personal life*\n**!pistolZucc** : Displays DELET picture of Zucc.\n**zucced** : Zucc this\n**zuccLatest <username>** : Displays latest 3 messages of the <username> user\n**!zuccRandom <username>** : Displays a random message of the <username>")

	//Check if the message contains forsenKek emoji for 3x times.
	case strings.Contains(message, ":forsenKek: :forsenKek: :forsenKek:"):
		s.ChannelMessageSend(channelID, "Stop trolling "+uName)

	//Check if message contains !pistolZucc. It creates an embed message with Zucc image and a Title & Description.
	case strings.Contains(message, "!pistolZucc") == true:
		embed := &discordgo.MessageEmbed{
			//Author:      &discordgo.MessageEmbedAuthor{},
			Color:       11534336, // Red Colour
			Description: "You must delet this",
			Image: &discordgo.MessageEmbedImage{
				URL: "http://i65.tinypic.com/2125bm.jpg",
			},
			Timestamp: time.Now().Format(time.RFC3339),
			Title:     "ZUCC ALERT",
		}
		s.ChannelMessageSendEmbed(channelID, embed)

	//Check if the message contains zucced.
	case strings.Contains(message, "zucced") == true:
		embed := &discordgo.MessageEmbed{
			//Author:      &discordgo.MessageEmbedAuthor{},
			Color:       11534336, // Red Colour
			Description: "Everying is and will get zucced!",
			Image: &discordgo.MessageEmbedImage{
				URL: "http://i64.tinypic.com/16iikus.jpg",
			},
			Timestamp: time.Now().Format(time.RFC3339),
			Title:     "ZUCCED",
		}

		s.ChannelMessageSendEmbed(channelID, embed)

	//Check if the message contains !zuccLatest
	//Here I get Latest 3 messages of a user and sent it back.
	//Example !zuccLatest Martinsteiner
	//Retrieves Martinsteiner's latest 3 messages.
	case strings.Contains(message, "!zuccLatest "):
		//Parse the string, separate the command !zuccLatest(11 letters) + 1 and the username
		reinRune := message[12:len(message)]
		//Statement -> Select the data, Order by the time in  Descending order(means tha latest messages)
		stmt, err := conn.Prepare(`SELECT guildname, message, channelname, time FROM messages WHERE sendername = ? ORDER BY time DESC LIMIT 3`, reinRune)
		if err != nil {
			s.ChannelMessageSend(channelID, "**Zucc had some error** while fetching your private life...")
		}
		defer stmt.Close()

		var outPut strings.Builder
		outPut.WriteString("**Zucc recorded messages** for ||*" + reinRune + "*||")

		for {
			hasRow, err := stmt.Step()
			if err != nil {
				s.ChannelMessageSend(m.ChannelID, "**Zucc had some error** while fetching your private life...")
			}
			if !hasRow {
				break
			}

			var message string
			var guildname string
			var channelname string
			var time string
			err = stmt.Scan(&guildname, &message, &channelname, &time)
			if err != nil {
				s.ChannelMessageSend(m.ChannelID, "**Zucc had some error** while fetching your private life...")
			}

			outPut.WriteString("\n*")
			outPut.WriteString(guildname)
			outPut.WriteString("/")
			outPut.WriteString(channelname)
			outPut.WriteString(":")
			outPut.WriteString(time)
			outPut.WriteString("*\n**")
			outPut.WriteString(message)
			outPut.WriteString("**\n\n")
		}

		s.ChannelMessageSend(m.ChannelID, outPut.String())

		//Same as above, instead we get a random message.
		//It only gets 1 message
		//You can increase this number by editing LIMIT 1 to LIMIT 3 etc.
	case strings.Contains(message, "!zuccRandom "):
		reinRune := message[12:len(message)]

		stmt, err := conn.Prepare(`SELECT guildname, message, channelname, time FROM messages WHERE sendername = ? ORDER BY Random() LIMIT 1`, reinRune)
		if err != nil {
			s.ChannelMessageSend(channelID, "**Zucc had some error** while fetching your private life...")
		}
		defer stmt.Close()

		var outPut strings.Builder
		outPut.WriteString("**Zucc knows what you did in the past **||*" + reinRune + "*||")

		for {
			hasRow, err := stmt.Step()
			if err != nil {
				s.ChannelMessageSend(m.ChannelID, "**Zucc had some error** while fetching your private life...")
			}
			if !hasRow {
				break
			}

			var message string
			var guildname string
			var channelname string
			var time string
			err = stmt.Scan(&guildname, &message, &channelname, &time)
			if err != nil {
				s.ChannelMessageSend(m.ChannelID, "**Zucc had some error** while fetching your private life...")
			}

			outPut.WriteString("\n*")
			outPut.WriteString(guildname)
			outPut.WriteString("/")
			outPut.WriteString(channelname)
			outPut.WriteString(":")
			outPut.WriteString(time)
			outPut.WriteString("*\n**")
			outPut.WriteString(message)
			outPut.WriteString("**\n\n")
		}

		s.ChannelMessageSend(m.ChannelID, outPut.String())

	default:
		//I was going to something here
		//But I decided not to
		//Lol

	}

}

For writing data to our database, I used solid Exec function to write them in.

(`INSERT INTO messages VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, messageID, guildID, channelID, guildNameEntr.Name, channelNameEntr.Name, uID, uName, m.Content, v)

I prepared a Statement for fetching the messages. I separated the main message from the command (which starts with !commandName). Then I ran a query with it.

(`SELECT guildname, message, channelname, time FROM messages WHERE sendername = ? ORDER BY time DESC LIMIT 3`, reinRune)

And finally, I send the messages back to the Discord Channel. I formatted my messages to make them appear cool.

Discord has message formatting as well, writing a text so
**Hello**, this is the *Brave New World*!\nHere’s to many happy moments. becomes
Hello, this is the Brave New World!
Here’s to many happy moments.

  • **Text** : Makes it Bold
  • *Text* : Makes it Italic
  • \n : New line

Full Code

Share

Marty

Truely speaking, I don't know what am I doing most of the time.

Leave a Reply