spanner

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Feb 4, 2024 License: MIT Imports: 1 Imported by: 0

README

Spanner for Slack

Spanner is a Go framework for building interactive Slack applications.

The framework adopts a model inspired by Streamlit with a single event handling loop to construct your UI and to respond to user input - so you can iterate faster and focus on your business logic!

Getting Started

Create a Slack app

In order to develop an app, you'll need to create one in your Slack workspace. You can find instructions for creating a Socket mode app at: https://api.slack.com/apis/connections/socket#creating

Setup

Import the framework into your project with `go get``:

go get github.com/theothertomelliott/spanner

Then import the API and Slack application package into your app:

import (
  "github.com/theothertomelliott/spanner"
  "github.com/theothertomelliott/spanner/slack"
)
Your application

Now you can define an application and run it:

app, err := slack.NewApp(
    slack.AppConfig{
        BotToken: botToken,
        AppToken: appToken,
    },
)
if err != nil {
    log.Fatal(err)
}

err = app.Run(func(ctx context.Context, ev spanner.Event) {
    // TODO: Handle events here
    return nil
})
if err != nil {
    log.Fatal(err)
}

Where botToken and appToken are the tokens you created for your Slack application.

Handling Events

The function passed in to app.Run is your event handling function, and will be called every time Slack sends an event to your app. In the example above, our app will ignore every event, so let's do something with messages coming in.

err = app.Run(func(ctx context.Context, ev spanner.Event) {
    if msg := ev.ReceiveMessage(); msg != nil && msg.Text() == "hello" {
        reply := msg.SendMessage()
        reply.Text(fmt.Sprintf("Hello to you too: %v", msg.User()))
    }
})

The code above will listen for messages in any channel your bot is in, and if the text of a messge is exactly "hello", will respond with a greeting.

UI Elements

You can also easily add UI elements to your messages. Let's add a dropdown to our message and do something with the option the user chooses.

err = app.Run(func(ctx context.Context, ev spanner.Event) {
    if msg := ev.ReceiveMessage(); msg != nil && msg.Text() == "hello" {

        reply := ev.SendMessage()
        reply.Text(fmt.Sprintf("Hello to you too: %v", msg.User()))

        letter := reply.Select("Pick a letter", spanner.SelectOptions("a", "b", "c"))
        if letter != "" {
            ev.SendMessage().Text(fmt.Sprintf("You chose %q", letter))
        }
    }
})

Event Lifecycle

Events received by a Spanner app go through 2 phases: Handling and Finishing.

Handling is when your handler function is called, which uses the event to specify how to respond. Calls to perform actions like SendMessage or JoinChannel are deferred until the Finishing phase.

Finishing is when actions are actually performed in the order they were declared in the Handling phase.

Custom Events

You can send custom events to your Spanner event handler to allow for use cases like cron tasks or sending message in response to third-party events.

The SendCustom function allows you to send an event with an arbitrary map[string]interface{} payload:

_ = app.SendCustom(context.Background(), slack.NewCustomEvent(map[string]interface{}{
    "field1": "value1",
}))

This event may then be received in your handler, and you can send messages in response:

if custom := ev.ReceiveCustomEvent(); custom != nil {
    msg := ev.SendMessage("C062778EYRZ")
    msg.Markdown(fmt.Sprintf("You sent %+v", custom.Body()))
}

Error Handling

The handler function may not return an error. If you call functions that may error out during handling, it is recommended to provide feedback to your user via messages and other interactive elements.

Because actions are not performed until after your handler function returns, error handling for these actions can be deferred by specifying a callback function. Messages and other action-related types have an ErrorFunc function that allows you to specify this callback:

badMessage := ev.SendMessage("invalid_channel")
badMessage.PlainText("This message will always fail to post")
badMessage.ErrorFunc(func(ctx context.Context, ev spanner.ErrorEvent) {
    errorNotice := ev.SendMessage(msg.Channel().ID())
    errorNotice.PlainText(fmt.Sprintf("There was an error sending a message: %v", ev.ReceiveError()))
})

This function is called during the Finishing phase when an action fails, this effectively starts a new event cycle so you can send messages to report the error. When an action fails, all subsequent actions for the current event are aborted.

Interceptors

You can specify interceptors to capture lifecycle events, which allows you to add common logging, tracing or other instrumentation to your event handling.

The interceptors are specified as parameters of your app config:

slack.AppConfig{
    BotToken:   botToken,
    AppToken:   appToken,
    // ...
    EventInterceptor: func(ctx context.Context, process func(context.Context)) {
        log.Println("Event received")
        process(ctx)
    },
    HandlerInterceptor: func(ctx context.Context, eventType string, handle func(context.Context)) {
        log.Println("Handling event type: ", eventType)
        handle(ctx)
    },
    FinishInterceptor: func(ctx context.Context, actions []spanner.Action, finish func(context.Context) error) error {
        log.Printf("Finishing with %d actions", len(actions))
        return finish(ctx)
    },
    ActionInterceptor: func(ctx context.Context, action spanner.Action, exec func(context.Context) error) error {
        log.Println("Performing action: ", action.Type())
        return exec(ctx)
    },
},

Interceptors cannot influence the specific handling done or actions performed, but it can abort a step in event handling by not calling the provided function.

Examples

A set of examples can be found in the examples directory.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Action added in v0.1.6

type Action interface {
	HasError

	Type() string
	Data() interface{}
}

type ActionInterceptor added in v0.1.7

type ActionInterceptor func(ctx context.Context, action Action, next func(context.Context) error) error

ActionInterceptor intercepts a single action to allow for instrumentation. The next function must be called to perform the action itself. The configuration for the action cannot be changed.

type App

type App interface {
	Run(EventHandlerFunc) error
	SendCustom(context.Context, CustomEvent) error
}

App is the top level for a chat application. Call Run with an event handling function to start the application.

type BlockUI

type BlockUI interface {
	NonInteractiveBlockUI
	InteractiveBlockUI
}

BlockUI allows the creation of Slack blocks in a message or modal.

type Channel

type Channel interface {
	ID() string
	Name(context.Context) string
}

type CustomEvent

type CustomEvent interface {
	Body() map[string]interface{}
}

type EphemeralSender added in v0.1.6

type EphemeralSender interface {
	SendEphemeralMessage(text string)
}

type ErrorEvent added in v0.1.9

type ErrorEvent interface {
	SendMessage(channelID string) ErrorMessage
	ReceiveError() error
}

type ErrorFunc added in v0.1.9

type ErrorFunc func(ctx context.Context, ev ErrorEvent)

type ErrorMessage added in v0.1.9

type ErrorMessage interface {
	NonInteractiveMessage
}

type Event

type Event interface {
	ReceiveConnected() bool
	ReceiveCustomEvent() CustomEvent
	ReceiveMessage() ReceivedMessage
	ReceiveSlashCommand(command string) SlashCommand

	JoinChannel(channelID string)
	SendMessage(channelID string) Message
}

Event represents an event received from the Slack platform. It provides functions representing each type of event that can be received. For example, ReceivedMessage will return a message that may have been received in this event. Functions will return nil if the current event does not match the type of event.

type EventHandlerFunc added in v0.1.5

type EventHandlerFunc func(context.Context, Event)

EventHandlerFunc represents a function that processes chat events from Spanner. This function will be called multiple times and is responsible both for creating UI elements and responding to the input received.

type EventInterceptor added in v0.1.8

type EventInterceptor func(ctx context.Context, process func(context.Context))

type FinishInterceptor added in v0.1.7

type FinishInterceptor func(ctx context.Context, actions []Action, finish func(context.Context) error) error

FinishInterceptor intercepts the finishing step of handling an event to allow for instrumentation. The finish function must be called to perform the required actions. The set of actions cannot be changed.

type HandlerInterceptor added in v0.1.8

type HandlerInterceptor func(ctx context.Context, eventType string, handle func(context.Context))

type HasError added in v0.1.9

type HasError interface {
	ErrorFunc(ErrorFunc)
}

type InteractiveBlockUI added in v0.1.9

type InteractiveBlockUI interface {
	TextInput(label string, hint string, placeholder string) string
	MultilineTextInput(label string, hint string, placeholder string) string
	Divider()
	Select(title string, options []Option) string
	MultipleSelect(title string, options []Option) []string
	Button(label string) bool
}

type Message

type Message interface {
	BlockUI
	HasError

	Channel(channelID string)
}

Message represents a message that can be sent to Slack. Messages are constructed using BlockUI commands.

type Metadata

type Metadata interface {
	User() User
	Channel() Channel
}

Metadata provides information common to all events.

type Modal interface {
	BlockUI

	SubmitButton(title string) ModalSubmission
	CloseButton(title string) bool
}

Modal represents a Slack modal view. It can be used to create blocks and handle submission or closing of the modal.

type ModalCreator

type ModalCreator interface {
	Modal(title string) Modal
}

ModalCreator is an interface that can be used to create Slack modal views.

type ModalSubmission

type ModalSubmission interface {
	PushModal(title string) Modal
}

ModalSubmission handles a modal being submitted. It can be used to send a response message or push a new modal onto the stack.

type NonInteractiveBlockUI added in v0.1.9

type NonInteractiveBlockUI interface {
	Header(message string)
	PlainText(text string)
	Markdown(text string)
}

type NonInteractiveMessage added in v0.1.9

type NonInteractiveMessage interface {
	NonInteractiveBlockUI
	HasError

	Channel(channelID string)
}

type Option

type Option struct {
	Label       string
	Description string
	Value       string
}

Option defines an option for select or checkbox blocks.

func Options

func Options(options ...string) []Option

Options is a convenience function to create a set of options from a list of strings. The strings are used as both the label and value. The descriptions are left empty.

type ReceivedMessage

type ReceivedMessage interface {
	Metadata
	Text() string
}

ReceivedMessage represents a message received from Slack.

type SlashCommand

type SlashCommand interface {
	EphemeralSender
	Metadata
	ModalCreator
}

SlashCommand represents a received slash command. Messages and modal views may be created in response to the command.

type User

type User interface {
	ID() string
	Name(context.Context) string
	RealName(context.Context) string
	Email(context.Context) string
}

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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