joe: Index | Files | Directories

package joe

import ""

Package joe contains a general purpose bot library inspired by Hubot.


Package Files

adapter.go auth.go bot.go brain.go config.go error.go events.go message.go storage.go user.go


const ErrNotAllowed = Error("not allowed")

ErrNotAllowed is returned if the user is not allowed access to a specific scope.

const ErrNotImplemented = Error("not implemented")

ErrNotImplemented is returned if the user tries to use a feature that is not implemented on the corresponding components (e.g. the Adapter). For instance, not all Adapter implementations may support emoji reactions and trying to attach a reaction to a message might return this error.

func FinishEventContent Uses

func FinishEventContent(ctx context.Context)

FinishEventContent can be called from within your event handler functions to indicate that the Brain should not execute any other handlers after the calling handler has returned.

type Adapter Uses

type Adapter interface {
    Send(text, channel string) error
    Close() error

An Adapter connects the bot with the chat by enabling it to receive and send messages. Additionally advanced adapters can emit more events than just the ReceiveMessageEvent (e.g. the slack adapter also emits the UserTypingEvent). All adapter events must be setup in the RegisterAt function of the Adapter.

Joe provides a default CLIAdapter implementation which connects the bot with the local shell to receive messages from stdin and print messages to stdout.

type Auth Uses

type Auth struct {
    // contains filtered or unexported fields

Auth implements logic to add user authorization checks to your bot.

func NewAuth Uses

func NewAuth(logger *zap.Logger, store *Storage) *Auth

NewAuth creates a new Auth instance.

func (*Auth) CheckPermission Uses

func (a *Auth) CheckPermission(scope, userID string) error

CheckPermission checks if a user has permissions to access a resource under a given scope. If the user is not permitted access this function returns ErrNotAllowed.

Scopes are interpreted in a hierarchical way where scope A can contain scope B if A is a prefix to B. For example, you can check if a user is allowed to read or write from the "Example" API by checking the "" or "api.example.write" scope. When you grant the scope to a user you can now either decide only to grant the very specific "" scope which means the user will not have write permissions or you can allow people write-only access via "api.example.write".

Alternatively you can also grant any access to the Example API via "api.example" which includes both the read and write scope beneath it. If you choose to, you could also allow even more general access to everything in the api via the "api" scope. The empty scope "" cannot be granted and will thus always return an error in the permission check.

func (*Auth) Grant Uses

func (a *Auth) Grant(scope, userID string) (bool, error)

Grant adds a permission scope to the given user. When a scope was granted to a specific user it can be checked later via CheckPermission(…). The returned boolean indicates whether the scope was actually added (i.e. true) or the user already had the granted scope (false).

Note that granting a scope is an idempotent operations so granting the same scope multiple times is a safe operation and will not change the internal permissions that are written to the Memory.

The empty scope cannot be granted and trying to do so will result in an error. If you want to grant access to all scopes you should prefix them with a common scope such as "root." or "api.".

func (*Auth) Revoke Uses

func (a *Auth) Revoke(scope, userID string) (bool, error)

Revoke removes a previously granted permission from a user. If the user does not currently have the revoked scope this function returns false and no error.

If you are trying to revoke a permission but the user was previously granted a scope that contains the revoked scope this function returns an error.

func (*Auth) UserPermissions Uses

func (a *Auth) UserPermissions(userID string) ([]string, error)

UserPermissions returns all permission scopes for a specific user.

func (*Auth) Users Uses

func (a *Auth) Users() ([]string, error)

Users returns a list of user IDs having one or more permission scopes.

type Bot Uses

type Bot struct {
    Name    string
    Adapter Adapter
    Brain   *Brain
    Store   *Storage
    Auth    *Auth
    Logger  *zap.Logger
    // contains filtered or unexported fields

A Bot represents an event based chat bot. For the most simple usage you can use the Bot.Respond(…) function to make the bot execute a function when it receives a message that matches a given pattern.

More advanced usage includes persisting memory or emitting your own events using the Brain of the robot.

func New Uses

func New(name string, modules ...Module) *Bot

New creates a new Bot and initializes it with the given Modules and Options. By default the Bot will use an in-memory Storage and a CLI adapter that reads messages from stdin and writes to stdout.

The modules can be used to change the Memory or Adapter or register other new functionality. Additionally you can pass Options which allow setting some simple configuration such as the event handler timeouts or injecting a different context. All Options are available as functions in this package that start with "With…".

If there was an error initializing a Module it is stored and returned on the next call to Bot.Run(). Before you start the bot however you should register your custom event handlers.


b := joe.New("example",

b.Respond("ping", b.Pong)

err := b.Run()

func (*Bot) Respond Uses

func (b *Bot) Respond(msg string, fun func(Message) error)

Respond registers an event handler that listens for the ReceiveMessageEvent and executes the given function only if the message text matches the given message. The message will be matched against the msg string as regular expression that must match the entire message in a case insensitive way.

You can use sub matches in the msg which will be passed to the function via Message.Matches.

If you need complete control over the regular expression, e.g. because you want the patter to match only a substring of the message but not all of it, you can use Bot.RespondRegex(…). For even more control you can also directly use Brain.RegisterHandler(…) with a function that accepts ReceiveMessageEvent instances.

If multiple matching patterns are registered, only the first registered handler is executed.

func (*Bot) RespondRegex Uses

func (b *Bot) RespondRegex(expr string, fun func(Message) error)

RespondRegex is like Bot.Respond(…) but gives a little more control over the regular expression. However, also with this function messages are matched in a case insensitive way.

func (*Bot) Run Uses

func (b *Bot) Run() error

Run starts the bot and runs its event handler loop until the bots context is canceled (by default via SIGINT, SIGQUIT or SIGTERM). If there was an an error when setting up the Bot via New() or when registering the event handlers it will be returned immediately.

func (*Bot) Say Uses

func (b *Bot) Say(channel, msg string, args ...interface{})

Say is a helper function to makes the Bot output the message via its Adapter (e.g. to the CLI or to Slack). If there is at least one vararg the msg and args are formatted using fmt.Sprintf.

type Brain Uses

type Brain struct {
    // contains filtered or unexported fields

The Brain contains the core logic of a Bot by implementing an event handling system that dispatches events to all registered event handlers.

func NewBrain Uses

func NewBrain(logger *zap.Logger) *Brain

NewBrain creates a new robot Brain. If the passed logger is nil it will fallback to the zap.NewNop() logger.

func (*Brain) Emit Uses

func (b *Brain) Emit(event interface{}, callbacks ...func(Event))

Emit sends the first argument as event to the brain from where it is dispatched to all registered handlers. The events are dispatched asynchronously but in the same order in which they are send to this function. Emit does not block until the event is delivered to the registered event handlers. If you want to wait until all handlers have processed the event you can pass one or more callback functions that will be executed when all handlers finished execution of this event.

func (*Brain) HandleEvents Uses

func (b *Brain) HandleEvents()

HandleEvents starts the event handling loop of the Brain. This function blocks until Brain.Shutdown() is called and returned.

func (*Brain) RegisterHandler Uses

func (b *Brain) RegisterHandler(fun interface{})

RegisterHandler registers a function to be executed when a specific event is fired. The function signature must comply with the following rules or the bot that uses this Brain will return an error on its next Bot.Run() call:

Allowed function signatures:

// AnyType can be any scalar, struct or interface type as long as it is not
// a pointer.

// You can optionally accept a context as the first argument. The context
// is used to signal handler timeouts or when the bot is shutting down.
func(context.Context, AnyType)

// You can optionally return a single error value. Returning any other type
// or returning more than one value is not possible. If the handler
// returns an error it will be logged.
func(AnyType) error

// Event handlers can also accept an interface in which case they will be
// be called for all events which implement the interface. Consequently,
// you can register a function which accepts the empty interface which will
// will receive all emitted events. Such event handlers can optionally also
// accept a context and/or return an error like other handlers.
func(context.Context, interface{}) error

The event, that will be dispatched to the passed handler function, corresponds directly to the accepted function argument. For instance if you want to emit and receive a custom event you can implement it like this:

type CustomEvent struct {}

b := NewBrain(nil)
b.RegisterHandler(func(evt CustomEvent) {

If multiple handlers are registered for the same event type, then they are all executed in the order in which they have been registered.

You should register all handlers before you start the bot via Bot.Run(…). While registering handlers later is also possible, any registration errors will silently be ignored if you register an invalid handler when the bot is already running.

func (*Brain) Shutdown Uses

func (b *Brain) Shutdown(ctx context.Context)

Shutdown stops the event handler loop of the Brain and waits until all pending events have been processed. After the brain is shutdown, it will no longer accept new events. The passed context can be used to stop waiting for any pending events or handlers and instead exit immediately (e.g. after a timeout or a second SIGTERM).

type CLIAdapter Uses

type CLIAdapter struct {
    Prefix string
    Input  io.ReadCloser
    Output io.Writer
    Logger *zap.Logger
    Author string // used to set the author of the messages, defaults to os.Getenv("USER)
    // contains filtered or unexported fields

The CLIAdapter is the default Adapter implementation that the bot uses if no other adapter was configured. It emits a ReceiveMessageEvent for each line it receives from stdin and prints all sent messages to stdout.

The CLIAdapter does not set the Message.Data field.

func NewCLIAdapter Uses

func NewCLIAdapter(name string, logger *zap.Logger) *CLIAdapter

NewCLIAdapter creates a new CLIAdapter. The caller must call Close to make the CLIAdapter stop reading messages and emitting events.

func (*CLIAdapter) Close Uses

func (a *CLIAdapter) Close() error

Close makes the CLIAdapter stop emitting any new events or printing any output. Calling this function more than once will result in an error.

func (*CLIAdapter) React Uses

func (a *CLIAdapter) React(r reactions.Reaction, _ Message) error

React implements the optional ReactionAwareAdapter interface by simply printing the given reaction as UTF8 emoji to the CLI.

func (*CLIAdapter) RegisterAt Uses

func (a *CLIAdapter) RegisterAt(brain *Brain)

RegisterAt starts the CLIAdapter by reading messages from stdin and emitting a ReceiveMessageEvent for each of them. Additionally the adapter hooks into the InitEvent to print a nice prefix to stdout to show to the user it is ready to accept input.

func (*CLIAdapter) Send Uses

func (a *CLIAdapter) Send(text, channel string) error

Send implements the Adapter interface by sending the given text to stdout. The channel argument is required by the Adapter interface but is otherwise ignored.

type Config Uses

type Config struct {
    Context        context.Context
    Name           string
    HandlerTimeout time.Duration
    // contains filtered or unexported fields

Config is the configuration of a Bot that can be used or changed during setup in a Module. Some configuration settings such as the Logger are read only can only be accessed via the corresponding getter function of the Config.

func NewConfig Uses

func NewConfig(logger *zap.Logger, brain *Brain, store *Storage, adapter Adapter) Config

NewConfig creates a new Config that is used to setup the underlying components of a Bot. For the typical use case you do not have to create a Config yourself but rather configure a Bot by passing the corresponding Modules to joe.New(…).

func (*Config) EventEmitter Uses

func (c *Config) EventEmitter() EventEmitter

EventEmitter returns the EventEmitter that can be used to send events to the Bot and other modules.

func (*Config) Logger Uses

func (c *Config) Logger(name string) *zap.Logger

Logger returns a new named logger.

func (*Config) RegisterHandler Uses

func (c *Config) RegisterHandler(fun interface{})

RegisterHandler can be used to register an event handler in a Module.

func (*Config) SetAdapter Uses

func (c *Config) SetAdapter(a Adapter)

SetAdapter can be used to change the Adapter implementation of the Bot.

func (*Config) SetMemory Uses

func (c *Config) SetMemory(mem Memory)

SetMemory can be used to change the Memory implementation of the bot.

func (*Config) SetMemoryEncoder Uses

func (c *Config) SetMemoryEncoder(enc MemoryEncoder)

SetMemoryEncoder can be used to change the MemoryEncoder implementation of the bot.

type Error Uses

type Error string

Error is the error type used by Joe. This allows joe errors to be defined as constants following

func (Error) Error Uses

func (err Error) Error() string

Error implements the "error" interface of the standard library.

type Event Uses

type Event struct {
    Data       interface{}
    Callbacks  []func(Event)
    AbortEarly bool

An Event represents a concrete event type and optional callbacks that are triggered when the event was processed by all registered handlers.

type EventEmitter Uses

type EventEmitter interface {
    Emit(event interface{}, callbacks ...func(Event))

The EventEmitter can be used by a Module by calling Config.EventEmitter(). Events are emitted asynchronously so every call to Emit is non-blocking.

type InitEvent Uses

type InitEvent struct{}

The InitEvent is the first event that is handled by the Brain after the Bot is started via Bot.Run().

type Memory Uses

type Memory interface {
    Set(key string, value []byte) error
    Get(key string) ([]byte, bool, error)
    Delete(key string) (bool, error)
    Keys() ([]string, error)
    Close() error

The Memory interface allows the bot to persist data as key-value pairs. The default implementation of the Memory is to store all keys and values in a map (i.e. in-memory). Other implementations typically offer actual long term persistence into a file or to redis.

type MemoryEncoder Uses

type MemoryEncoder interface {
    Encode(value interface{}) ([]byte, error)
    Decode(data []byte, target interface{}) error

A MemoryEncoder is used to encode and decode any values that are stored in the Memory. The default implementation that is used by the Storage uses a JSON encoding.

type Message Uses

type Message struct {
    Context  context.Context
    ID       string // The ID of the message, identifying it at least uniquely within the Channel
    Text     string
    AuthorID string
    Channel  string
    Matches  []string    // contains all sub matches of the regular expression that matched the Text
    Data     interface{} // corresponds to the ReceiveMessageEvent.Data field
    // contains filtered or unexported fields

A Message is automatically created from a ReceiveMessageEvent and then passed to the RespondFunc that was registered via Bot.Respond(…) or Bot.RespondRegex(…) when the message matches the regular expression of the handler.

func (*Message) React Uses

func (msg *Message) React(reaction reactions.Reaction) error

React attempts to let the Adapter attach the given reaction to this message. If the adapter does not support this feature this function will return ErrNotImplemented.

func (*Message) Respond Uses

func (msg *Message) Respond(text string, args ...interface{})

Respond is a helper function to directly send a response back to the channel the message originated from. This function ignores any error when sending the response. If you want to handle the error use Message.RespondE instead.

func (*Message) RespondE Uses

func (msg *Message) RespondE(text string, args ...interface{}) error

RespondE is a helper function to directly send a response back to the channel the message originated from. If there was an error it will be returned from this function.

type Module Uses

type Module interface {
    Apply(*Config) error

A Module is an optional Bot extension that can add new capabilities such as a different Memory implementation or Adapter.

func WithContext Uses

func WithContext(ctx context.Context) Module

WithContext is an option to replace the default context of a bot.

func WithHandlerTimeout Uses

func WithHandlerTimeout(timeout time.Duration) Module

WithHandlerTimeout is an option to set a timeout on event handlers functions. By default no timeout is enforced.

func WithLogLevel Uses

func WithLogLevel(level zapcore.Level) Module

WithLogLevel is an option to change the default log level of a bot.

func WithLogger Uses

func WithLogger(logger *zap.Logger) Module

WithLogger is an option to replace the default logger of a bot.

type ModuleFunc Uses

type ModuleFunc func(*Config) error

ModuleFunc is a function implementation of a Module.

func (ModuleFunc) Apply Uses

func (f ModuleFunc) Apply(conf *Config) error

Apply implements the Module interface.

type ReactionAwareAdapter Uses

type ReactionAwareAdapter interface {
    React(reactions.Reaction, Message) error

ReactionAwareAdapter is an optional interface that Adapters can implement if they support reacting to messages with emojis.

type ReceiveMessageEvent Uses

type ReceiveMessageEvent struct {
    ID       string // The ID of the message, identifying it at least uniquely within the Channel
    Text     string // The message text.
    AuthorID string // A string identifying the author of the message on the adapter.
    Channel  string // The channel over which the message was received.

    // A message may optionally also contain additional information that was
    // received by the Adapter (e.g. with the slack adapter this may be the
    // *slack.MessageEvent. Each Adapter implementation should document if and
    // what information is available here, if any at all.
    Data interface{}

The ReceiveMessageEvent is typically emitted by an Adapter when the Bot sees a new message from the chat.

type ShutdownEvent Uses

type ShutdownEvent struct{}

The ShutdownEvent is the last event that is handled by the Brain before it stops handling any events after the bot context is done.

type Storage Uses

type Storage struct {
    // contains filtered or unexported fields

A Storage provides a convenient interface to a Memory implementation. It is responsible for how the actual key value data is encoded and provides concurrent access as well as logging.

The default Storage that is returned by joe.NewStorage() encodes values as JSON and stores them in-memory.

func NewStorage Uses

func NewStorage(logger *zap.Logger) *Storage

NewStorage creates a new Storage instance that encodes values as JSON and stores them in-memory. You can change the memory and encoding via the provided setters.

func (*Storage) Close Uses

func (s *Storage) Close() error

Close closes the Memory that is managed by this Storage.

func (*Storage) Delete Uses

func (s *Storage) Delete(key string) (bool, error)

Delete removes a key and its associated value from the memory. The boolean return value indicates if the key existed or not.

func (*Storage) Get Uses

func (s *Storage) Get(key string, value interface{}) (bool, error)

Get retrieves the value under the requested key and decodes it into the passed "value" argument which must be a pointer. The boolean return value indicates if the value actually existed in the Memory and is false if it did not. It is legal to pass <nil> as the value if you only want to check if the given key exists but you do not actually care about the concrete value.

func (*Storage) Keys Uses

func (s *Storage) Keys() ([]string, error)

Keys returns all keys known to the Memory.

func (*Storage) Set Uses

func (s *Storage) Set(key string, value interface{}) error

Set encodes the given data and stores it in the Memory that is managed by the Storage.

func (*Storage) SetMemory Uses

func (s *Storage) SetMemory(m Memory)

SetMemory assigns a different Memory implementation.

func (*Storage) SetMemoryEncoder Uses

func (s *Storage) SetMemoryEncoder(enc MemoryEncoder)

SetMemoryEncoder assigns a different MemoryEncoder.

type User Uses

type User struct {
    ID       string
    Name     string
    RealName string

User contains all the information about a user.

type UserTypingEvent Uses

type UserTypingEvent struct {
    User    User
    Channel string

The UserTypingEvent is emitted by the Adapter and indicates that the Bot sees that a user is typing. This event may not be emitted on all Adapter implementations but only when it is actually supported (e.g. on slack).


joetestPackage joetest implements helpers to implement unit tests for bots.
reactionsPackage reactions contains a list of generated reactions that are widely used in different chat applications on the internet.

Package joe imports 21 packages (graph) and is imported by 18 packages. Updated 2020-07-27. Refresh now. Tools for package owners.