Go
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
alerter Remove github.com/jarcoal/httpmock dependency Jun 10, 2018
develop Update dependency Jun 15, 2018
doc Fix label in UML Mar 8, 2018
examples Supervise worker queue size history in an example Jun 23, 2018
gitter Fix non-checked error Jun 23, 2018
log Use ioutil.Discard for log output on test Jun 15, 2018
plugins Remove github.com/jarcoal/httpmock dependency Jun 10, 2018
retry Use ioutil.Discard for log output on test Jun 15, 2018
slack Prefer retry.WithPolicy over other retrial methods Jun 10, 2018
testdata Polish weather command Apr 30, 2017
watchers Use ioutil.Discard for log output on test Jun 15, 2018
workers Use ioutil.Discard for log output on test Jun 15, 2018
.travis.yml Remove 1.6 support from .travis.yml Jul 31, 2018
LICENSE Add LICENSE file Feb 18, 2017
README.md Add links to supported chat services/protocols May 6, 2018
adapter.go Fix outdated document Dec 16, 2017
adapter_test.go Return error when skipping blocked input Feb 12, 2017
alert.go Recover when alert panics Feb 15, 2017
alert_test.go Recover when alert panics Feb 15, 2017
bot.go Fix outdated document Dec 16, 2017
bot_test.go Solve race condition on Command replacement Jul 8, 2017
bottype.go s/adapter/bot/g where it's appropriate Oct 23, 2016
bottype_test.go separate test files Oct 27, 2016
command.go Define sarah.Runner interface and hide its implementation Aug 19, 2017
command_test.go Suppress excessive stdout during test May 2, 2018
destination.go Add documents for core package Feb 12, 2017
doc.go Add doc.go May 1, 2017
error.go Return error when skipping blocked input Feb 12, 2017
error_test.go Return error when skipping blocked input Feb 12, 2017
input.go Add document for sarah.Input's method May 4, 2018
input_test.go Implement AbortCommand Feb 18, 2017
locker.go Accept config value instead of pointer and update Jul 23, 2017
output.go Add documents for core package Feb 12, 2017
runner.go Add a mechanism to return Runner status when needed Jun 17, 2018
runner_test.go Add a mechanism to return Runner status when needed Jun 17, 2018
scheduler.go Add missing test for scheduler Apr 23, 2017
scheduler_test.go Add missing test for scheduler Apr 23, 2017
status.go Add log output on multiple stop() calls. Jun 17, 2018
status_test.go Add a mechanism to return Runner status when needed Jun 17, 2018
storage.go Add link to reference implementation Apr 9, 2017
storage_test.go Fix typo Apr 23, 2017
task.go Define sarah.Runner interface and hide its implementation Aug 19, 2017
task_test.go Suppress excessive stdout during test May 2, 2018

README.md

GoDoc Go Report Card Build Status Coverage Status Maintainability

Sarah is a general purpose bot framework named after author's firstborn daughter.

While the first goal is to prep author to write Go-ish code, the second goal is to provide simple yet highly customizable bot framework.

Supported Chat Services/Protocols

Although a developer may implement sarah.Adapter to integrate with a desired chat service, some adapters are provided as reference implementations:

At a Glance

hello world

Above is a general use of go-sarah. Registered commands are checked against user input and matching one is executed; when a user inputs ".hello," hello command is executed and a message "Hello, 世界" is returned.

Below image depicts how a command with user's conversational context works. The idea and implementation of "user's conversational context" is go-sarah's signature feature that makes bot command "state-aware."

Above example is a good way to let user input series of arguments in a conversational manner. Below is another example that use stateful command to entertain user.

Following is the minimal code that implements such general command and stateful command introduced above. In this example, two ways to implement sarah.Command are shown. One simply implements sarah.Command interface; while another uses sarah.CommandPropsBuilder for lazy construction. Detailed benefits of using sarah.CommandPropsBuilder and sarah.CommandProps are described at its wiki page, CommandPropsBuilder.

For more practical examples, see ./examples.

package main

import (
	"fmt"
	"github.com/oklahomer/go-sarah"
	"github.com/oklahomer/go-sarah/slack"
	"golang.org/x/net/context"
	"math/rand"
	"strconv"
	"strings"
	"time"
)

func main() {
	// Setup slack adapter.
	slackConfig := slack.NewConfig()
	slackConfig.Token = "REPLACE THIS"
	adapter, err := slack.NewAdapter(slackConfig)
	if err != nil {
		panic(fmt.Errorf("faileld to setup Slack Adapter: %s", err.Error()))
	}

	// Setup storage.
	cacheConfig := sarah.NewCacheConfig()
	storage := sarah.NewUserContextStorage(cacheConfig)

	// A helper to stash sarah.RunnerOptions for later use.
	options := sarah.NewRunnerOptions()

	// Setup Bot with slack adapter and default storage.
	bot, err := sarah.NewBot(adapter, sarah.BotWithStorage(storage))
	if err != nil {
		panic(fmt.Errorf("faileld to setup Slack Bot: %s", err.Error()))
	}
	options.Append(sarah.WithBot(bot))

	// Setup .hello command
	hello := &HelloCommand{}
	bot.AppendCommand(hello)

	// Setup properties to setup .guess command on the fly
	options.Append(sarah.WithCommandProps(GuessProps))

	// Setup sarah.Runner.
	runnerConfig := sarah.NewConfig()
	runner, err := sarah.NewRunner(runnerConfig, options.Arg())
	if err != nil {
		panic(fmt.Errorf("failed to initialize Runner: %s", err.Error()))
	}

	// Run sarah.Runner.
	runner.Run(context.TODO())
}

var GuessProps = sarah.NewCommandPropsBuilder().
	BotType(slack.SLACK).
	Identifier("guess").
	InputExample(".guess").
	MatchFunc(func(input sarah.Input) bool {
		return strings.HasPrefix(strings.TrimSpace(input.Message()), ".guess")
	}).
	Func(func(ctx context.Context, input sarah.Input) (*sarah.CommandResponse, error) {
		// Generate answer value at the very beginning.
		rand.Seed(time.Now().UnixNano())
		answer := rand.Intn(10)

		// Let user guess the right answer.
		return slack.NewStringResponseWithNext("Input number.", func(c context.Context, i sarah.Input) (*sarah.CommandResponse, error) {
			return guessFunc(c, i, answer)
		}), nil
	}).
	MustBuild()

func guessFunc(_ context.Context, input sarah.Input, answer int) (*sarah.CommandResponse, error) {
	// For handiness, create a function that recursively calls guessFunc until user input right answer.
	retry := func(c context.Context, i sarah.Input) (*sarah.CommandResponse, error) {
		return guessFunc(c, i, answer)
	}

	// See if user inputs valid number.
	guess, err := strconv.Atoi(strings.TrimSpace(input.Message()))
	if err != nil {
		return slack.NewStringResponseWithNext("Invalid input format.", retry), nil
	}

	// If guess is right, tell user and finish current user context.
	// Otherwise let user input next guess with bit of a hint.
	if guess == answer {
		return slack.NewStringResponse("Correct!"), nil
	} else if guess > answer {
		return slack.NewStringResponseWithNext("Smaller!", retry), nil
	} else {
		return slack.NewStringResponseWithNext("Bigger!", retry), nil
	}
}

type HelloCommand struct {
}

var _ sarah.Command = (*HelloCommand)(nil)

func (hello *HelloCommand) Identifier() string {
	return "hello"
}

func (hello *HelloCommand) Execute(context.Context, sarah.Input) (*sarah.CommandResponse, error) {
	return slack.NewStringResponse("Hello!"), nil
}

func (hello *HelloCommand) InputExample() string {
	return ".hello"
}

func (hello *HelloCommand) Match(input sarah.Input) bool {
	return strings.TrimSpace(input.Message()) == ".hello"
}

Overview

go-sarah is a general purpose bot framework that enables developers to create and customize their own bot experiences with any chat service. This comes with a unique feature called "stateful command" as well as some basic features such as command and scheduled task. In addition to those features, this provides rich life cycle management including live configuration update, customizable alerting mechanism, automated command/task (re-)building and concurrent command/task execution.

go-sarah is composed of fine grained components to provide above features. Those components have their own interfaces and default implementations, so developers are free to customize bot behavior by supplying own implementation.

component diagram

Follow below links for details: