-
Notifications
You must be signed in to change notification settings - Fork 16
Default Bot and Adapter
sarah.Bot
implementation is responsible for receiving input from chat service, respond with propper command execution, sending output back to chat service. While sarah.Runner
is responsible for coordinating all components, this works as a bridge between go-sarah
's system and connected chat service. The interface is defined as below:
// Bot provides interface for each bot implementation.
// Instance of concrete type can be fed to sarah.Runner to have its lifecycle under control.
// Multiple Bot implementation may be registered to single Runner.
type Bot interface {
// BotType represents what this Bot implements. e.g. slack, gitter, cli, etc...
// This can be used as a unique ID to distinguish one from another.
BotType() BotType
// Respond receives user input, look for corresponding command, execute it, and send result back to user if possible.
Respond(context.Context, Input) error
// SendMessage sends message to destination depending on the Bot implementation.
// This is mainly used to send scheduled task's result.
// Be advised: this method may be called simultaneously from multiple workers.
SendMessage(context.Context, Output)
// AppendCommand appends given Command implementation to Bot internal stash.
// Stashed commands are checked against user input in Bot.Respond, and if Command.Match returns true, the
// Command is considered as "corresponds" to the input, hence its Command.Execute is called and the result is
// sent back to user.
AppendCommand(Command)
// Run is called on Runner.Run to let this Bot interact with corresponding service provider.
// For example, this is where Bot or Bot's corresponding Adapter initiates connection with service provider.
// This may run in a blocking manner til given context is canceled since a new goroutine is allocated for this task.
// When the service provider sends message to us, convert that message payload to Input and send to Input channel.
// Runner will receive the Input instance and proceed to find and execute corresponding command.
Run(context.Context, func(Input) error, func(error))
}
Developers are free to implement their own sarah.Bot
to have very own bot experiences. In most cases, however, some tasks such as sarah.Command
stashing and its execution on bot.Respond
is common. To ease sarah.Bot
implementation, this project extracts those common tasks and implements with sarah.defaultBot
. Other chat-service specific tasks are to be implemented by sarah.Adapter
. The struct sarah.defaultBot
is package private and its initialization can only be done by calling sarah.NewBot
, which takes sarah.Aapter
implementation and series of sarah.DefaultBotOption
s as arguments. Returned value is a sarah.Bot
interface so this can be fed to sarah.Runner
to work as a sarah.Bot
.
func NewBot(adapter sarah.Adapter, options ...sarah.DefaultBotOption) (sarah.Bot, error) {
...
}
As previously introduced, sarah.defaultBot
implements some common tasks that most bot implementations must deal with. This sarah.defaultBot
wraps given sarah.Adapter
implementation, handle common tasks internally, and delegate chat-service specific tasks to sarah.Adapter
.
Stashing group of sarah.Command
s and executing corresponding one against user input are example of such common tasks. defaultBot.AppendCommand
and defaultBot.Respond
take care of those tasks. Since the idea of user's conversational context and its implementation is go-sarah
's signature feature, sarah.defaultBot
also takes care of this when sarah.UserContextStorage
is passed on its initialization. When defaultBot.Respond
receives sarah.Input
, this searches for stored user's conversational context with Input.SenderKey
. If sarah.ContextualFunc
is returned from sarah.UserContextStorage
, default bot considers the user is in the middle of conversation, execute sarah.ContextualFunc
, and send returned sarah.Output
back to user. When sarah.UserContextFunc
is not returned, user is not in the conversational context so first matching sarah.Command
is executed. Stashed sarah.Command
s' Command.Match
is called in the appended order. To prioritize commands, call Bot.AppendCommand
in the order of priority. When commands are created from sarah.CommandProps
on the fly, pass sarah.CommandProps
s to sarah.Runner
in order of priority.
Other than those common tasks, sarah.defaultBot
simply delegates chat-service specific tasks to wrapped sarah.Adapter
; defaultBot.SendMessage
proxies arguments to Adapter.SendMessage
, defaultBot.Run
proxies arguments to Adapter.Run
. Remember defaultBot.Respond
also calls Adapter.SendMessage
internally to send output back to user.
Since all common tasks are handled by sarah.defaultBot
, sarah.Adapter
's interface is rather minimal:
// Adapter defines interface that each bot adapter implementation has to satisfy.
// Instance of its concrete struct and series of sarah.DefaultBotOptions can be fed to defaultBot via sarah.NewBot() to have sarah.Bot.
// Returned bot instance can be fed to Runner to have its life cycle managed.
type Adapter interface {
// BotType represents what this Bot implements. e.g. slack, gitter, cli, etc...
// This can be used as a unique ID to distinguish one from another.
BotType() BotType
// Run is called on Runner.Run by wrapping bot instance.
// On this call, start interacting with corresponding service provider.
// This may run in a blocking manner til given context is canceled since a new goroutine is allocated for this task.
// When the service provider sends message to us, convert that message payload to Input and send to Input channel.
// Runner will receive the Input instance and proceed to find and execute corresponding command.
Run(context.Context, func(Input) error, func(error))
// SendMessage sends message to corresponding service provider.
// This can be called by scheduled task or in response to input from service provider.
// Be advised: this method may be called simultaneously from multiple workers.
SendMessage(context.Context, Output)
}
sarah.Adapter
's implementation is responsible for receiving input from and sending output to corresponding chat-service. If persistent connection such as WebSocket is required, this is adapter's responsibility to sustain stable connection until bot context is canceled. When adapter can not proceed its operation, adapter may send error state to sarah.Runner
via func(error)
that is passed on Adapter.Run
. Runner then receives the critical state, sends alerts via registered sarah.Alerters and cancels bot context. Note that all components' life cycles are managed by sarah.Runner
.
Adapters for LINE, Slack, XMPP and Gitter are provided as reference implementations.
To have a grasp of overall architecture, have a look at Components.