bobo | Slack bot kit for Golang

Posted: June 17, 2020

bobo is a modular Slack RTM bot kit for Golang.

To customize your bot, just add your own commands or written by other developers.

Link: https://github.com/eure/bobo

For Japanese developers: I already wrote the detailed article in Japanese here,

https://medium.com/eureka-engineering/slack-rtm-bot-bobo-6e7f60da744c


Overview

bobo runs as just a bot process. It does nothing as default.

bobo accepts optional data including an engine (so far, only supports Slack RTM) and commands.

You don't have to write the special codes to maintain the Slack bot. It's already in.

Let me show an example here.

If you want to have your slack bot and have it echo command, write the EchoCommand

package main

import (
	"github.com/eure/bobo/command"
)

// EchoCommand is an example command.
// This command says same text.
var EchoCommand = command.BasicCommandTemplate{
	// If you set help text, bot shows this text on `help` command.
	Help:           "reply same text",

	// If you set NoHelp=true, bot does not show this command on `help` command.
	NoHelp: false,

	// Bot can react and exec function by the `MentionCommand`
	// e.g.) @bot echo foo  // <- in a channel
	// e.g.) echo foo  // <- by DM
	MentionCommand: "echo",

	// write the reaction process
	GenerateFn: func(d command.CommandData) command.Command {
		c := command.Command{}

		// d.RawText       // "@bot <command> <other>" or "<command> <other>"
		// d.Text          // "@bot <command> <other>"
		// d.TextMention   // "@bot"
		// d.TextCommand   // "<command>"
		// d.TextOther     // "<other>"
		if d.TextOther == "" {
			return c
		}

		// '<@sender_id>' is a mention to you.
		text := fmt.Sprintf("<@%s> %s", d.SenderID, d.TextOther)

		// ReplyEngineTask is a task to send message in the same channel.
		task := command.NewReplyEngineTask(d.Engine, d.Channel, text)

		// you can add multiple tasks to the command.
		c.Add(task)
		return c
	},
}

And add the EchoCommand to entry point,

package main

import (
	"github.com/eure/bobo"
	"github.com/eure/bobo/command"
	"github.com/eure/bobo/engine/slack"
	"github.com/eure/bobo/log"
)

func main() {
	bobo.Run(bobo.RunOption{
		Engine: &slack.SlackEngine{},

		Logger: &log.StdLogger{
			IsDebug: bobo.IsDebug(),
		},

		CommandSet: command.NewCommandSet(
			// defalt example commands
			command.PingCommand,
			command.HelpCommand,

			// add your original commands
			EchoCommand,
		),
	})
}

Then build and run the binary with Slack token from environmental variables, a Slack bot process will run.

$ go build -o bin/bobo .
$ SLACK_RTM_TOKEN=xoxb-0000... ./bin/bobo

The image of the command is like this,

An image from Notion

CommandData and Task

CommandData is a parameter of the command. It has convenient fields.

type CommandData struct {
	// You can use (Slack) engine's API inside the tasks 
	Engine engine.Engine

	// This is what the user typed in slack.
	RawText     string // "@bot <command> <other>" or "<command> <other>"

	// Same as RawText basically, but when in DM, the mentioned text is complemented.
	Text        string // "@bot <command> <other>" (always)

	// 1st part of the text (mention)
	TextMention string // @bot

	// 2nd part of the text (command name)
	TextCommand string // <command>

	// 3rd and later part of the text (all of the text without 1st mention and 2nd command name parts)
	TextOther   string // <other>

	// flag of Direct Message
	IsDM        bool   // it's on DM or not

	// (Slack) UserID of this bot
	BotID      string

	// (Slack) UserID of the message sender
	SenderID   string

	// (Slack) user name of the message sender
	SenderName string

	// The channel name that the message sent
	Channel         string

	// Timestamp of the message thread
	ThreadTimestamp string

	// You don't need this in many cases, but you can access the other commands
	// e.g.) `help` command
	CommandSet *CommandSet
}

You can use these data and proceed and generate tasks.

Task itself is a interface so you just needs two methods.

type Task interface {
	GetName() string // return the task name(used in the error logs)
	Run() error      // run the process (main routine)
}

For example, let's see the replyEngineTask that send message to the target channel.

type replyEngineTask struct {
	engine  engine.Engine
	channel string
	text    string
}

func NewReplyEngineTask(e engine.Engine, channel, text string) *replyEngineTask {
	return &replyEngineTask{
		engine:  e,
		channel: channel,
		text:    text,
	}
}

// (omitted...)

func (t replyEngineTask) Run() error {
	return t.engine.Reply(t.channel, t.text)
}

NewReplyEngineTask is a constructor function and required data is set there,

And this task just execute only engine.Reply(channel, text) in the Run method.

Other Commands

I wrote some trivial commands below,

You can see the cmd/main.go and each commands to find what's going on.