laserdisc-io / slack4s   2.2.0

MIT License GitHub

A functional scala library for easily constructing slack bots

Scala versions: 3.x 2.13

slack4s

CI codecov GitHub release (latest SemVer)

A pure functional library for easily building slack bots, built on cats 3.x, http4s and the Slack Java SDK.

Right now, slash commands are supported (contributions welcome!). This library takes care of

  • Interacting with Slack's API
  • Encoding/decoding payloads
  • Verifying signature validity
  • Handling background callbacks for longer running commands

Simply provide your business logic and you've got a deployable app!

Example

Tutorial

Quickstart

Add the following dependency:

libraryDependencies += "io.laserdisc" %% "slack4s" % latestVersion

In this example, our slash command handler will take the form of a persistent HTTP service.

With your signing secret in hand, getting a skeleton handler up and running is as simple as:

import cats.effect.{IO, IOApp}
import io.laserdisc.slack4s.slashcmd.*

object MySlackBot extends IOApp.Simple {

  val secret: SigningSecret = SigningSecret.unsafeFrom("your-signing-secret") // demo purposes - please don't hardcode secrets  
  
  override def run: IO[Unit] = SlashCommandBotBuilder[IO](secret).serve

}

Set your slash command's Request URL to

https://{your-base-url}/slack/slashCmd

Issue your slash command, e.g. /slack4s foo and you should get the default response:

default response screenshot with placeholder message

The builder has some more useful functions if you need to customize your deployment further:

SlashCommandBotBuilder[IO](secret)
  .withCommandMapper(testCommandMapper)                   // your mapper impl, see next section
  .withBindOptions(port = 9999, address = "192.168.0.1")  // by default, binds to 0.0.0.0:8080
  .withHttp4sBuilder{                                    
    // offer the chance to customize http4s' BlazeServerBuilder used under the hood 
    // USE WITH CAUTION; it overrides any settings set by slack4s     
    _.withIdleTimeout(10.seconds)
     .withMaxConnections(512)
  }
  .serve

If your container runtime (e.g. k8s, AWS ECS) needs a health check endpoint, use:

https://{your-base-url}/healthCheck

Implementing Your Mapper Logic

Create an implementation of CommandMapper[F] and pass it to the builder as follows e.g.:

val myMapper: CommandMapper[F] = .. // see the next section

SlashCommandBotBuilder[IO](secret)
  .withCommandMapper(myMapper)
  .serve

CommandMapper[F] is a type alias for:

SlashCommandPayload => F[Command[F]]

Your implementation of this effect defines your business logic. The input is the slash command request from Slack. The output defines how to respond to that request.

Slack4s will only evaluate this effect if the incoming request's slack signature has been validated against your signing secret.

SlashCommandPayload

  • This Java class provided by Slack's Java SDK (a dependency of this library) models the incoming request.
  • Of primary interest is the text field, containing the arguments to you /command as provided by the user.
  • Other fields provide contextual information about the call, such as userId, channelName, etc.

⚠️ Slack4s' validation of the request signature only proves that the request originated from slack. You still need to apply your own level of (dis)trust to the text value, as it comes verbatim from the user.

Not only should you sanitize this input appropriately, you'll also have to handle/strip any formatting it contains. Slack users tend to copy and paste slack text into commands - don't be surprised to get formatted input, e.g. *homer simpson* instead of homer simpson.

Command[F]

This is the description of how - and when - to handle the user's request. The scaladoc on Command[F] should have all the information you need, but at a high level, It defines the following fields:

  • handler: F[ChatPostMessageRequest]
    • The effect to evaluate in response to the input.
    • ChatPostMessageRequest is the Slack API Java model representing the response message.
      • Importing io.laserdisc.slack4s.slack._ gives you a bunch of convenience functions to build this reponse object (e.g. slackMessage(...), markdownWithImgSection(...), etc.)
      • You can explore with the possible response structures by playing with the excellent Slack Block Kit Builder.
  • responseType: ResponseType
  • logId: LogToken
    • "NA" by default, this token used in slack4s's logs when processing this particular command (useful for log filtering)