bot

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 28, 2019 License: MIT Imports: 15 Imported by: 8

README

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()

Documentation

Overview

Example
package main

import (
	"fmt"
	"net/http"

	"github.com/botopolis/bot"
	"github.com/botopolis/bot/mock"
)

type ExamplePlugin struct {
	Username string
}

func (p *ExamplePlugin) Load(r *bot.Robot) { p.Username = "beardroid" }

func main() {
	// Ignore this - just example setup
	chat := mock.NewChat()
	chat.MessageChan = make(chan bot.Message)
	go func() { close(chat.MessageChan) }()

	// Install adapter and plugins
	robot := bot.New(chat, &ExamplePlugin{})
	robot.Logger = mock.NewLogger()

	// Respond to any message
	robot.Hear(bot.Regexp("welcome (\\w*)"), func(r bot.Responder) error {
		return r.Send(bot.Message{
			Text: "All hail " + r.Match[1] + ", our new overlord",
		})
	})

	// Respond to messages that are either DMed to the bot or start with the bot's name
	robot.Respond(bot.Regexp("topic (.*)"), func(r bot.Responder) error {
		return r.Topic(r.Match[1])
	})

	// Track when topics are updated
	robot.Topic(func(r bot.Responder) error {
		if r.Room == "announcements" {
			var announcements []string
			r.Brain.Get("announcements", &announcements)
			announcements = append(announcements, r.Match[1])
			r.Brain.Set("announcements", &announcements)
		}
		return nil
	})

	// Track the comings and goings of people
	robot.Enter(func(r bot.Responder) error { return nil })
	robot.Leave(func(r bot.Responder) error { return nil })

	// Create server endpoints
	robot.Router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello world"))
	})

	// Start the server and listening
	robot.Run()

	// Access plugins
	var plugin ExamplePlugin
	if ok := robot.Plugin(&plugin); ok {
		fmt.Println(plugin.Username)

	}
}
Output:

beardroid

Index

Examples

Constants

View Source
const (
	// DefaultMessage assigns our message to robot.Hear()
	DefaultMessage messageType = iota
	// Response assigns our message to robot.Respond()
	Response
	// Enter assigns our message to robot.Enter()
	Enter
	// Leave assigns our message to robot.Leave()
	Leave
	// Topic assigns our message to robot.Topic()
	Topic
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Brain

type Brain struct {
	// contains filtered or unexported fields
}

Brain is our data store. It defaults to just saving values in memory, but can be given a Store via Brain.SetStore(), to which values are written. Brain is threadsafe - and assumes the same of Store.

func NewBrain

func NewBrain() *Brain

NewBrain creates a Brain

func (*Brain) Delete

func (b *Brain) Delete(key string) error

Delete removes the given key from memory and Brain's Store

func (*Brain) Get

func (b *Brain) Get(key string, i interface{}) error

Get retrieves a value from the store and sets it to the interface It tries the memory store first, and falls back to Brain's Store

func (*Brain) Keys added in v0.4.1

func (b *Brain) Keys() []string

Keys returns all the keys in the Brain's cache.

func (*Brain) Set

func (b *Brain) Set(key string, i interface{}) error

Set assigns the given value in memory and to Brain's Store

func (*Brain) SetStore

func (b *Brain) SetStore(s Store)

SetStore assigns a Store to Brain

type Chat

type Chat interface {
	Plugin
	Username() string
	Messages() <-chan Message
	Send(Message) error
	Reply(Message) error
	Topic(Message) error
	Direct(Message) error
}

Chat is the interface for chat integrations

type Logger

type Logger interface {
	Debug(values ...interface{})
	Debugf(format string, values ...interface{})

	Info(values ...interface{})
	Infof(format string, values ...interface{})

	Warn(values ...interface{})
	Warnf(format string, values ...interface{})

	Error(values ...interface{})
	Errorf(format string, values ...interface{})

	Fatal(values ...interface{})
	Fatalf(format string, values ...interface{})

	Panic(values ...interface{})
	Panicf(format string, values ...interface{})
}

Logger is the interface Robot exposes

Example
package main

import (
	"fmt"

	"github.com/botopolis/bot"
	"github.com/botopolis/bot/mock"
)

type logStuff struct{}

func (logStuff) Load(r *bot.Robot) {
	r.Logger.Error("One")
	r.Logger.Panic("Two")
	r.Logger.Fatal("Three")
}

func main() {
	// Ignore this - just example setup
	chat := mock.NewChat()
	chat.MessageChan = make(chan bot.Message)
	go func() { close(chat.MessageChan) }()

	logger := mock.NewLogger()
	logger.WriteFunc = func(level mock.Level, v ...interface{}) {
		switch level {
		case mock.ErrorLevel:
			fmt.Println("Error log")
		case mock.PanicLevel:
			fmt.Println("Panic log")
		case mock.FatalLevel:
			fmt.Println("Fatal log")

		}
	}
	b := bot.New(chat, logStuff{})
	b.Logger = logger
	b.Run()
}
Output:

Error log
Panic log
Fatal log

type Matcher

type Matcher func(r *Responder) bool

Matcher determines whether the bot is triggered

func Contains

func Contains(t string) Matcher

Contains matches a subset of the message text

func Regexp

func Regexp(expression string) Matcher

Regexp matches the message text with a regular expression

func Room

func Room(c string) Matcher

Room is a Matcher function that matches the Room exactly

func User

func User(u string) Matcher

User is a Matcher function that matches the User exactly

type Message

type Message struct {
	Type  messageType
	User  string
	Room  string
	Text  string
	Topic string

	// Envelope is the original message that the bot is reacting to
	Envelope interface{}
	// Params is for adapter-specific parameters
	Params interface{}
}

Message is our message wrapper

type Plugin

type Plugin interface {
	Load(*Robot)
}

Plugin is anything that can be installed into Robot

type Responder

type Responder struct {
	*Robot
	Message

	// Only relevant for Regexp
	Match []string
}

Responder is the object one receives when listening for an event

func (*Responder) Direct

func (r *Responder) Direct(text string) error

Direct responds via direct message to the user

func (*Responder) Reply

func (r *Responder) Reply(text string) error

Reply responds to a message

func (*Responder) Send

func (r *Responder) Send(m Message) error

Send sends a message. It defaults to sending in the current room

func (*Responder) Topic

func (r *Responder) Topic(topic string) error

Topic changes the topic

type Robot

type Robot struct {
	// Chat adapter
	Chat Chat
	// Data store
	Brain *Brain
	// HTTP Router
	Router *mux.Router
	// Logger with levels
	Logger Logger
	// contains filtered or unexported fields
}

Robot is the central structure for bot

func New

func New(c Chat, plugins ...Plugin) *Robot

New creates an instance of a bot.Robot and loads in the chat adapter Typically you would install plugins before running the robot.

func (*Robot) Enter

func (r *Robot) Enter(h hook)

Enter is triggered when someone enters a room

func (*Robot) Hear

func (r *Robot) Hear(m Matcher, h hook)

Hear is triggered on any message event.

func (*Robot) Leave

func (r *Robot) Leave(h hook)

Leave is triggered when someone leaves a room

func (*Robot) ListPlugins added in v0.5.0

func (r *Robot) ListPlugins() []Plugin

ListPlugins returns a slice of all loaded plugins.

func (*Robot) Plugin

func (r *Robot) Plugin(p Plugin) bool

Plugin allows you to fetch a loaded plugin for direct use See ExamplePluginUsage

func (*Robot) Respond

func (r *Robot) Respond(m Matcher, h hook)

Respond is triggered on messages to the bot

func (*Robot) Run

func (r *Robot) Run()

Run is responsible for: 1. Loading internals (chat, http) 2. Loading all plugins 3. Running RTM Slack until termination 4. Unloading all plugins in reverse order 5. Unloading internals in reverse order

func (*Robot) Topic

func (r *Robot) Topic(h hook)

Topic is triggered when someone changes the topic

func (*Robot) Username

func (r *Robot) Username() string

Username provides the robot's username

type Store

type Store interface {
	// Get is called on Brain.Get() when the key doesn't exist in memory
	Get(key string, object interface{}) error
	// Set is always called on Brain.Set()
	Set(key string, object interface{}) error
	// Delete is always called on Brain.Delete()
	Delete(key string) error
}

Store is what the brain saves information in for longer term recollection. Brain loads what it needs to in memory, but in order to have persistence between runs, we can use a store.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL