slackline/app
A router for Slack apps. Register handlers for slash commands, events, block actions, shortcuts, view submissions and options, then pass it HTTP requests or Socket Mode frames.
let app =
app.new(signing_secret)
|> app.command("/feedback", fn(command) {
use <- app.ack_then
views.open(view: feedback_modal())
|> open.trigger_id(command.trigger_id)
|> httpc.send(client)
})
|> app.view_submission("feedback", fn(submission) {
let text =
view.input_text(submission.view, block_id: "thoughts", action_id: "thoughts")
case text {
Some("") | None -> app.errors([#("thoughts", "say something!")])
Some(_) -> app.ack()
}
})
|> app.action("preview", fn(_action, _payload) {
app.reply(interaction.ephemeral("Previewing"))
})
|> app.on_event(fn(event, _delivery) {
case event {
event.AppMention(..) -> todo
_ -> Nil
}
})
// in your HTTP server, with the raw body:
let handled = app.handle(app, request)
// send handled.response, then call handled.after()
Slack expects an acknowledgement within three seconds. Handlers return an
Ack at once. Put slow work, such as API calls, in a continuation with
app.ack_then or app.then. handle and handle_socket return it as
after, and the server runs it once the acknowledgement is sent. Event
handlers always run in after, so a server that skips after never runs
them.
Everything here is synchronous and does no IO of its own, so it works on both targets and with any HTTP server or WebSocket client.
Types
How an app acknowledges a request: an optional body, and work to run after the acknowledgement is sent.
pub opaque type Ack
Where an event came from. retry is set when Slack sends the event again.
Use event_id to skip events you already handled.
pub type Delivery {
Delivery(
team_id: option.Option(String),
enterprise_id: option.Option(String),
api_app_id: option.Option(String),
event_id: option.Option(String),
event_time: option.Option(Int),
authorizations: List(event.EnvelopeAuthorization),
is_ext_shared_channel: option.Option(Bool),
retry: option.Option(Retry),
)
}
Constructors
-
Delivery( team_id: option.Option(String), enterprise_id: option.Option(String), api_app_id: option.Option(String), event_id: option.Option(String), event_time: option.Option(Int), authorizations: List(event.EnvelopeAuthorization), is_ext_shared_channel: option.Option(Bool), retry: option.Option(Retry), )
The response to send back to Slack, and the work to run once it is sent.
after does nothing when no handler asked for follow-up work.
pub type Handled {
Handled(
response: response.Response(String),
after: fn() -> Nil,
)
}
Constructors
-
Handled(response: response.Response(String), after: fn() -> Nil)
A request from Slack, decoded.
pub type Incoming {
Events(event.Envelope)
Interactive(interaction.Interaction)
Command(command.SlashCommand)
SslCheck
}
Constructors
-
Events(event.Envelope)An Events API delivery, or a
url_verificationchallenge. -
Interactive(interaction.Interaction)An interactivity payload.
-
Command(command.SlashCommand)A slash command.
-
SslCheckOne of Slack’s periodic certificate checks for slash commands.
Why a request could not be handled.
pub type RequestError {
NotVerified(verify.VerifyError)
Malformed(String)
}
Constructors
-
NotVerified(verify.VerifyError) -
Malformed(String)The body was not something Slack sends.
Slack sends an event again when your app did not acknowledge it in time.
num counts the retries and reason says why, such as http_timeout.
pub type Retry {
Retry(num: Int, reason: String)
}
Constructors
-
Retry(num: Int, reason: String)
What to do after a Socket Mode frame.
pub type SocketOutcome {
Send(frame: String, after: fn() -> Nil)
Reconnect(socket_mode.DisconnectReason)
Ignore
}
Constructors
-
Send(frame: String, after: fn() -> Nil)Send this text frame back to acknowledge the message, then run
after. -
Reconnect(socket_mode.DisconnectReason)Slack is closing this connection: open a new one with
apps.connections.open. -
IgnoreNothing to send, as after a
hello.
Values
pub fn ack() -> Ack
Acknowledge with an empty body. Right for events and for most interactions.
pub fn ack_then(after: fn() -> a) -> Ack
Acknowledge with an empty body, then run after once the
acknowledgement is sent. Made for use:
app.command("/deploy", fn(command) {
use <- app.ack_then
deploy(command.text)
})
pub fn action(
app: App,
action_id: String,
handler: fn(
interaction.BlockAction,
interaction.BlockActionsPayload,
) -> Ack,
) -> App
Handles block_actions for elements with this action_id. The handler
gets the action that matched and the whole payload.
pub fn block_suggestion(
app: App,
action_id: String,
handler: fn(interaction.BlockSuggestionPayload) -> Ack,
) -> App
Handles block_suggestion requests for external selects with this
action_id.
pub fn command(
app: App,
name: String,
handler: fn(command.SlashCommand) -> Ack,
) -> App
Handles a slash command, such as "/hello".
pub fn dispatch(
app: App,
incoming: Incoming,
retry: option.Option(Retry),
) -> Ack
Runs the handlers for something that already arrived.
pub fn errors(errors: List(#(String, String))) -> Ack
Acknowledge a view_submission by showing errors on its input blocks,
given as #(block_id, message) pairs.
pub fn global_shortcut(
app: App,
callback_id: String,
handler: fn(interaction.GlobalShortcutPayload) -> Ack,
) -> App
Handles global shortcuts with this callback_id.
pub fn handle(
app: App,
request: request.Request(String),
) -> Handled
Verifies, decodes and dispatches an HTTP request, and builds the response
to send back to Slack. Send response, then run after.
pub fn handle_socket(app: App, frame: String) -> SocketOutcome
Decodes and dispatches one Socket Mode text frame.
pub fn message_shortcut(
app: App,
callback_id: String,
handler: fn(interaction.MessageShortcutPayload) -> Ack,
) -> App
Handles message shortcuts with this callback_id.
pub fn new(signing_secret: String) -> App
An app that verifies every HTTP request with your signing secret.
pub fn on_event(
app: App,
handler: fn(event.Event, Delivery) -> a,
) -> App
Handles every Events API event. You can add several handlers. They run in the order you added them, after the event is acknowledged.
pub fn on_interaction(
app: App,
handler: fn(interaction.Interaction) -> Ack,
) -> App
Handles any interaction that no other handler matched, such as a legacy dialog or interactive message. A second call replaces the first handler.
pub fn option_groups(groups: List(block.OptionGroup)) -> Ack
Acknowledge a block_suggestion with groups of options to show.
pub fn options(options: List(block.SelectOption)) -> Ack
Acknowledge a block_suggestion with the options to show.
pub fn parse_request(
request: request.Request(String),
) -> Result(Incoming, RequestError)
Decodes an HTTP request body from Slack. It does not check the signature.
handle does.
pub fn reply(message: interaction.ResponseMessage) -> Ack
Acknowledge a slash command, shortcut or block action with a message, as
if posted to its response_url.
pub fn then(ack: Ack, after: fn() -> a) -> Ack
Runs after once this acknowledgement is sent, after any work it
already had.
pub fn unverified() -> App
An app that does not verify HTTP requests. Use it for Socket Mode, where the connection itself is authenticated, and in tests.
pub fn update(view: view.View) -> Ack
Acknowledge a view_submission by replacing the submitted view.
pub fn view_closed(
app: App,
callback_id: String,
handler: fn(interaction.ViewClosedPayload) -> Ack,
) -> App
Handles view_closed for views with this callback_id (sent when the
view set notify_on_close).
pub fn view_response(
response: interaction.ViewSubmissionResponse,
) -> Ack
Acknowledge a view_submission with a response_action: show errors,
update or push a view, or clear the stack.
pub fn view_submission(
app: App,
callback_id: String,
handler: fn(interaction.ViewSubmissionPayload) -> Ack,
) -> App
Handles view_submission for views with this callback_id. Answer with
ack, errors, update, push or clear.