slap

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Mar 18, 2024 License: BSD-3-Clause Imports: 13 Imported by: 0

README

Slap: Easily build Slack Apps with Go

A Slack application framework inspired by Slack's Bolt Framework and the net/http library.

Examples

Slash Commands
app.RegisterCommand("/hi", func(req *slap.CommandRequest) error {
    // Respond with 200 and an ephemeral message immediately
    req.AckWithAction(slap.CommandResponseAction{
        ResponseType: slap.RespondEphemeral,
        Text: "Hi, how are you?"
    })

    // Send another message!
    channel, ts, err := req.Client.PostEphemeral(req.Payload.ChannelID, req.Payload.UserID, slack.MsgOptionText("You said: " + req.Payload.Text))
    if err != nil {
        return err
    }

    // Open a modal!
    res, err := req.Client.OpenView(req.Payload.TriggerID, slack.ModalViewRequest{ 
        Type: "modal",
        CallbackID: "form-modal",
        Title: &slack.TextBlockObject{
            Type: "plain_text",
            Text: "Form"
        }
        ... 
    })

    return nil
})
View Submissions
app.RegisterViewSubmission("form-modal", func(req *slap.ViewSubmissionRequest) error {
    // Get a value from the view submission
    text := req.Payload.View.State.Values["text-input"]["text-input"]
    if !isTextValid(text) {
        // Respond with 200 and an error visible to the user
        req.AckWithAction(slap.ViewResponseAction{
            ResponseAction: slap.ViewResponseErrors,
            Errors: map[string]string{
                "text-input": "Please input a valid sentence"
            }
        })
        return nil
    }

    // Close the modal stack using the "clear" response action
    req.AckWithAction(slap.ViewResponseAction{
        ResponseAction: slap.ViewResponseClear
    })

    // Do something with the text value - save it to a store with the user's ID
    if err := store.save(req.Payload.User.ID, text); err != nil {
        return err
    }

    return nil
})

Block Actions
app.RegisterBlockAction("start-button", func(req *slap.BlockActionRequest) error {
    // Respond to Slack with 200
    req.Ack()

    // Open a modal!
    res, err := req.Client.OpenView(req.Payload.TriggerID, slack.ModalViewRequest{ 
        Type: "modal",
        CallbackID: "form-modal",
        Title: &slack.TextBlockObject{
            Type: "plain_text",
            Text: "Form"
        }
        ... 
    })

    return nil
})
Events API
app.RegisterEventHandler("message", func(req *slap.EventRequest) error {
    // Parse the inner Events API event
    var message slack.MessageEvent
    if err := json.Unmarshal(req.Payload.Event, &message); err != nil {
        return err
    }

    // Respond to Slack with 200
    req.Ack()

    // Ignore messages that your bot has sent or you will get stuck in recursive message hell
    if message.BotId != "" {
        return nil
    }

    // Do something with the message
    slog.Info("Received message", "message", message.Text)
    _, _, err := req.Client.PostMessage(message.Channel, slack.MsgOptionText("You wrote: " + message.Text, false))
    if err != nil {
        return err
    }

    return nil
})

Quick Start

  1. Create a Slack App at api.slack.com/apps and install it to your workspace (Settings -> Install App)
  2. Set the following environment variables from your Slack App Settings
    export BOT_TOKEN={Settings -> Install App -> Bot User OAuth Token}
    
    export SIGNING_SECRET={Settings -> Basic Information -> Signing Secret}
    
  3. Add the following to your main.go
    package main
    
    import (
        "net/http"
        "os"
    
        "github.com/jacob-ian/slap"
    )
    
    func main() {
        router := http.NewServeMux()
    
        app := slap.New(slap.Config{
            Router: router,
            BotToken: func(teamID string) (string, error) {
                return os.Getenv("BOT_TOKEN"), nil
            },
            SigningSecret: os.Getenv("SIGNING_SECRET"),
        })
    
        app.RegisterCommand("/start", func(req *slap.CommandRequest) error {
            req.AckWithAction(slap.CommandResponseAction{
                ResponseType: slap.RespondInChannel,
                Text:         "Hello world!",
            })
            return nil
        })
    
        server := &http.Server{
            Addr:    ":4000",
            Handler: router,
        }
        panic(server.ListenAndServe())
    }
    
  4. Run go run main.go
  5. Use ngrok to get a public URL for your local environment
  6. Update your Slack App Settings:
    1. Slash Commands -> Create New Command
      • Command: /start
      • Request URL: https://{YOUR NGROK URL}/commands
  7. Use the /start command in your Slack Client

Usage Guide

Slack API Settings

To use Slap, you will need to update your Slack App's settings at api.slack.com/apps.

Slash Commands

For all Slash Commands:

  • Request URL: https://{YOUR PUBLIC URL}/commands
Interactivity & Shortcuts
  • Turn on Interactivity
  • Request URL: https://{YOUR PUBLIC URL}/interactions
Event Subscriptions
  • Enable Events
  • Request URL: https://{YOUR PUBLIC URL}/events
    • Slap will automatically complete URL verification
Multiple Workspace Distribution

Slap supports app distribution to multiple workspaces with the BotTokenGetter in slap.Config:

app := slap.New(slap.Config{
    ...,
    BotToken: func(teamID string) (string, error) {
        token, err := db.GetBotTokenByTeamID(teamID)
        if err != nil {
            return "", err
        }
        return token, nil
    }
})

This allows for the fetching of a workspace's bot token from your store by the workspace's Team ID, which is then used by the Slack API client.

To Do

  • Add shortcut support
  • Add view_closed support
  • Add block_suggestion support
  • Add support for Gorilla Mux
  • Add support for Echo

Special Thanks

Thank you to the contributors at github.com/go-slack/slack for creating and maintaining the Slack API client and types which are needed to make Slap work.

Documentation

Overview

This package contains the Slap framework for developing Slack Applications.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Application

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

A Slap Application.

func New

func New(config Config) *Application

Creates a new Applcation with an http.ServeMux.

func (*Application) RegisterBlockAction

func (app *Application) RegisterBlockAction(actionID string, handler BlockActionHandler)

Registers a block action handler.

Panics if the actionID has already been registered.

func (*Application) RegisterCommand

func (app *Application) RegisterCommand(command string, handler CommandHandler)

Registers a slash command handler.

Panics if the slash command has already been registered.

func (*Application) RegisterEventHandler

func (app *Application) RegisterEventHandler(eventType string, handler EventHandler)

Registers an EventAPI event handler for a subscribed event type.

Panics if the eventType has already been registered.

func (*Application) RegisterViewSubmission

func (app *Application) RegisterViewSubmission(callbackID string, handler ViewSubmissionHandler)

Registers a view submission handler.

Panics if the callbackID has already been registered.

type BlockActionHandler

type BlockActionHandler func(req *BlockActionRequest) error

A function to handle a block action request

type BlockActionPayload

type BlockActionPayload struct {
	Container      slack.Container     `json:"container"`
	Actions        []slack.BlockAction `json:"actions"`
	Hash           string              `json:"hash"`
	BotAccessToken string              `json:"bot_access_token"`
	Enterprise     *string             `json:"enterprise,omitempty"`
	Channel        *struct {
		ID   string `json:"id"`
		Name string `json:"name"`
	} `json:"channel,omitempty"`
	Message *slack.MessageEvent      `json:"message,omitempty"`
	View    *slack.View              `json:"view,omitempty"`
	State   *slack.BlockActionStates `json:"state,omitempty"`
	// contains filtered or unexported fields
}

The payload of a Slack block action request

type BlockActionRequest

type BlockActionRequest struct {
	Payload BlockActionPayload
	// contains filtered or unexported fields
}

A block action request

func (*BlockActionRequest) Ack

func (event *BlockActionRequest) Ack()

Acknowledge Slack's request with Status 200

type BotTokenGetter

type BotTokenGetter func(teamID string) (string, error)

A function taking a Slack teamID (workspace ID) that returns the workspace's bot token as a string. For a non-distributed app, simply return your bot token.

type CommandHandler

type CommandHandler func(req *CommandRequest) error

A function to handle slash command requests

type CommandPayload

type CommandPayload struct {
	// Deprecated: The verification token.
	Token string
	// The command that was called
	Command string
	// The text after the command
	Text string
	// The Team ID of the workspace this command was used in.
	TeamID string
	// The domain name of the workspace.
	TeamDomain string
	// The Enterprise ID this workspace belongs to if using Enterprise Grid.
	EnterpriseID string
	// The name of the enterprise this workspace belongs to if using Enterprise Grid..
	EnterpriseName string
	// The ID of the channel the command was used in.
	ChannelID string
	// The name of the channel the command was used in.
	ChannelName string
	// The ID of the user calling the command.
	UserID string
	// Deprecated: The name of the user calling the command.
	UserName string
	// A temporary webhook URL that used to generate message responses.
	ResponseURL string
	// A short-lived ID that can be used to open modals.
	TriggerID string
	// Your Slack App's unique identifier.
	APIAppID string
}

The payload of a Slack slash command request

type CommandRequest

type CommandRequest struct {

	// The paylaod of the Slack request
	Payload CommandPayload
	// contains filtered or unexported fields
}

A slash command request from Slack

func (*CommandRequest) Ack

func (event *CommandRequest) Ack()

Acknowledge Slack's request with Status 200

func (*CommandRequest) AckWithAction

func (req *CommandRequest) AckWithAction(action CommandResponseAction)

Immediately respond to Slack's slash command request with an action

type CommandResponseAction

type CommandResponseAction struct {
	// The type of response: "in_channel" or "ephemeral"
	ResponseType CommandResponseActionType `json:"response_type"`
	// Text to send in the response
	Text string `json:"text"`
	// Slack Block Kit Blocks to send in the response
	Blocks []slack.Block `json:"blocks,omitempty"`
}

An immediate action to be ran in response to a slash command

type CommandResponseActionType

type CommandResponseActionType string

The type in a command action response

const (
	RespondInChannel CommandResponseActionType = "in_channel"
	RespondEphemeral CommandResponseActionType = "ephemeral"
)

The allowed CommandResponseAction ResponseType values

type Config

type Config struct {
	// Required. Slap will overwrite the following POST routes:
	// "POST /interactions", "POST /events",
	// and "POST /commands".
	Router *http.ServeMux
	// Optional. Adds a path to the start of the Slack routes.
	PathPrefix string
	// Required. Method for fetching bot tokens
	// for a workspace based on its team ID
	BotToken BotTokenGetter
	// Required. The Slack webhook signing secret for your app
	SigningSecret string
	// A logger for the Slap Application
	Logger *slog.Logger
	// A generic, ephemeral error message to send the user
	// when a handler returns an error.
	//
	// Defaults to: "An error occurred".
	ErrorMessage string
}

Configuration options for the Slap Application

type EventHandler

type EventHandler func(req *EventRequest) error

type EventPayload

type EventPayload struct {
	Event json.RawMessage
	// contains filtered or unexported fields
}

type EventRequest

type EventRequest struct {
	Payload EventPayload
	// contains filtered or unexported fields
}

func (*EventRequest) Ack

func (event *EventRequest) Ack()

Acknowledge Slack's request with Status 200

type MessageEvent

type MessageEvent struct {
	slack.MessageEvent
	// contains filtered or unexported fields
}

func (*MessageEvent) IsBot

func (e *MessageEvent) IsBot() bool

type OuterEventType

type OuterEventType string
const (
	EventCallback   OuterEventType = "event_callback"
	UrlVerification OuterEventType = "url_verification"
	RateLimited     OuterEventType = "app_rate_limited"
)

type ViewResponseAction

type ViewResponseAction struct {
	// The type of response: "clear", "errors", "update", or "push"
	ResponseAction ViewResponseActionType `json:"response_action"`
	// The view for "update" or "push" actions
	View *slack.View `json:"view,omitempty"`
	// The blockID-error map for "errors" actions.
	//
	// Example:
	//  errors := map[string]string {
	//    "email-input-block": "Please enter a valid email address."
	//  }
	Errors map[string]string `json:"errors,omitempty"`
}

An immediate response action for a view submission

type ViewResponseActionType

type ViewResponseActionType string

A view response action ResponseType

const (
	// Clears the modal stack
	ViewResponseClear ViewResponseActionType = "clear"
	// Displays an error to the user in the modal
	ViewResponseErrors ViewResponseActionType = "errors"
	// Updates the view of the currently open modal
	ViewResponseUpdate ViewResponseActionType = "update"
	// Adds a new modal to the modal stack
	ViewResponsePush ViewResponseActionType = "push"
)

The ViewResponseAction ResponseType values

type ViewSubmissionHandler

type ViewSubmissionHandler func(req *ViewSubmissionRequest) error

A function to handle a view submission request

type ViewSubmissionPayload

type ViewSubmissionPayload struct {
	View slack.View `json:"view"`
	// contains filtered or unexported fields
}

The payload of the Slack view_submission request

type ViewSubmissionRequest

type ViewSubmissionRequest struct {
	Payload ViewSubmissionPayload
	// contains filtered or unexported fields
}

A Slack view submission request

func (*ViewSubmissionRequest) Ack

func (event *ViewSubmissionRequest) Ack()

Acknowledge Slack's request with Status 200

func (*ViewSubmissionRequest) AckWithAction

func (req *ViewSubmissionRequest) AckWithAction(action ViewResponseAction)

Immediately respond to Slack with a view response action

Directories

Path Synopsis
examples
app

Jump to

Keyboard shortcuts

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