Skip to content

botopolis/bot

Repository files navigation

botopolis

GoDoc Build Status Test Coverage

A hubot clone in Go! botopolis is extendable with plugins and works with different chat services.

Usage

See example_test.go for usage details.

Example program

Here's an example of a program you can write with botopolis. If you've used Hubot before, you'll see a familiar API.

package main

import (
	"github.com/botopolis/bot"
	"github.com/botopolis/slack"
)

func main() {
	// Create a bot with a Slack adapter
	robot := bot.New(
		slack.New(os.Getenv("SLACK_TOKEN")),
	)

	// Create a Listener for messages to the bot with the word "hi"
	r.Respond(bot.Regexp("hi", func(r bot.Responder) error {
		// Respond to the sender of the message
		r.Reply("hello to you too!")
	})

	// Run the bot
	r.Run()
}

Listeners

You have the following listeners at your disposal to interact with incoming messages:

// Listens for any message
robot.Hear(bot.Matcher, func(bot.Responder) error)
// Listens for messages addressed to the bot
robot.Respond(bot.Matcher, func(bot.Responder) error)
// Listens for topic changes
robot.Topic(func(bot.Responder) error)
// Listens for people entering a channel
robot.Enter(func(bot.Responder) error)
// Listens for people leaving a channel
robot.Leave(func(bot.Responder) error)

Matchers

You'll notice that some listeners take a bot.Matcher. Matchers will run before the callback to determine whether the callback should be fired. There are a few matchers provided by botopolis and you can write your own as long as it matches the function signature.

// Match the text of the message against a regular expression
bot.Regexp("^hi")
// Match a subset of the text
bot.Contains("hello")
// Match the user of a message
bot.User("jean")
// Match the room of a message
bot.Room("general")

Callback and Responder

Callbacks are given a bot.Responder. The Responder holds a reference to the incoming bot.Message and exposes a few methods which simplify interacting with the chat service.

type Responder struct {
	Name string
	User string
	Room string
	Text string
	// abridged
}

// Send message
func (r Responder) Send(Message) error
// DM message
func (r Responder) Direct(string) error
// Reply to message
func (r Responder) Reply(string) error
// Change topic
func (r Responder) Topic(string) error

Brain

bot.Brain is a store with customizable backends. By default it will only save things in memory, but you can add or create your own stores such as botopolis/redis for data persistence.

r := bot.New(mock.NewChat())
r.Brain.Set("foo", "bar")

var out string
r.Brain.Get("foo", &out)
fmt.Println(out)
// Output: "bar"

HTTP

botopolis also has an HTTP server built in. One great usecase for this is webhooks. It exposes gorilla/mux for routing requests.

r := bot.New(mock.NewChat())
r.Router.HandleFunc("/webhook/name", func(w http.ResponseWriter, r *http.Request) {
		r.Send(bot.Message{Room: "general", Text: "Webhook incoming!"})
		w.Write([]byte("OK"))
	},
).Methods("POST")

Plugins

botopolis allows for injection of plugins via bot.New(chat, ...bot.Plugin). It implements a Plugin interface that allows plugin writers to make use of the full bot.Robot runtime on load.

type ExamplePlugin struct { PathName string }

// Load conforms to Plugin interface
func (p ExamplePlugin) Load(r *bot.Robot) {
	r.Router.HandleFunc(e.PathName, func(w http.ResponseWriter, r *http.Request) {
		// special sauce
	}).Methods("GET")
}

// Unload can optionally be implemented for graceful shutdown
func (p ExamplePlugin) Unload(r *bot.Robot) {
	// shutting down
}

Here are a couple examples of working plugins:

Configuration

There are very few configuration options in botopolis itself, as it relies on the introduction of plugins.

Server port

You can set the server port with an environment variable PORT:

PORT=4567 ./botopolis

Custom logging

You can set up your own logger as long as it satisfies the Logger interface.

robot := bot.New(mychat.Plugin{})
robot.Logger = MyCustomLogger{}
robot.Run()