A declarative framework for building Slack bots.
Slack provides a powerful API for building integrations and bots. However, a lot of interactions include writing callback code. In case of multiple chained interactions it can be difficult to track state of the conversation between individual callbacks. Serious callers only (Callers for short) aims to help with this.
The Callers framework draws inspiration from declarative UI frameworks such as Rect and Flutter. It provides you with two distinct components:
- A DSL for writing your bot's logic.
- A runtime that will run your DSL scripts against Slack API.
Callers framework itself is developed in Kotlin. It leverages Kotlin's type-safe builders features to define it's DSL. Further more it uses Kotlin's scripting support to offer a coding environment in IntelliJ IDEA. Lastly, the runtime component is wrapped in a Spring Boot application, packaged as a Docker image. This enables you to easily write your scripts with IDE support and ship them in a pre-built runtime environment, ready for deployment.
For now, you'll need to locally build the framework in order to try it out.
You will need locally installed Java in order to either build the framework or write call scripts. Framework itself compiles to Java version 1.8 (in order for the IntelliJ support to work). I suggest using Open JDK for your development needs.
Additionally, if you are interested in packaging your scripts as a Docker image you'll also need to install Docker.
Open a terminal inside the project root directory and run:
./gradlew clean build publish docker
This will:
- Build the project by pulling its dependencies, compiling the code and running tests.
- Publish the DSL library as a maven package in your local repository (usually found under
~/.m2
directory). - Build a docker image for the Spring boot app that's wrapping the framework runtime.
Produced artifacts:
- Maven package for the DSL:
hr.from.josipantolis.seriouscallersonly:api:0.1.0-SNAPSHOT
- Docker image for runtime:
hr.from.josipantolis/serious-callers-only:latest
You can write your Serious callers only scripts in 2 ways:
- Using a provided DSL library to declaratively define bot behaviour.
- Using exposed underlying classes to imperatively build the bot.
Example of DSL using script:
this register botJoinedChannelReplier { (channel, _) ->
replyPublicly {
+section("Hello there!".txt)
+context {
+"Posted in ${channel.mention}".md
+"Channel id: `${channel.id}`".md
}
}
}
This will make the bot post Hello there! in every channel it joins.
For more examples take a look at:
- DeclarativeBookPicker.call.kts for an example of a script that defines a slash command for picking a net book to read.
- ShowOff.call.kts for a script that's showcasing all available message formatting features of the framework.
The same Hello there! script from the previous section can be written programmatically as:
register(EventReplier.BotJoinedChannelReplier { (channel, _) ->
Reply.Message(
blocks = listOf(
Block.Section(
text = Element.Text.Plain("Hello there!")
),
Block.Context(
elements = listOf(
Element.Text.Markdown("Posted in ${channel.mention}"),
Element.Text.Markdown("Channel id: `${channel.id}")
)
)
)
)
})
Both scripts will result in the same bot behavior.
For more examples of imperative style take a look at:
- ImperativeBookPicker.call.kts which offers an imperative implementation of the book picker command.
- PlayCommand.call.kts for an example of a rich interaction bot that can play Tic Tac Toe and a guessing game.
When it comes to packaging and running your call scripts you again have 2 options:
- You can write only the bot scripts and run them using the provided Serious Callers Only docker image.
- Or you can use the runtime library and incorporate it in your own Kotlin or Java app.
The provided example project in this repository showcases the first approach. That one is generally quicker to implement, and it allows you to focus on defining your bot's behavior. If you pick this approach you'll need to:
- Declare a dependency on DSL library.
- Store your scripts in files with
*.call.kts
extension. - Run the scripts with docker run.
When you run the Serious Callers Only docker image it will expose 2 sets of endpoints:
http://localhost:3000/slack/events
which can be used as a webhook for Slack events.http://loalhost:3001/actuator
which runs Spring Actuator endpoints that you can use to monitor the state of your app. In addition to standard actuator stuff the Callers app adds 2 extra endpoints:/actuator/bot
will list all features of currently running bot/actuator/conversations
will list all currently ongoing conversations that your bot is engaged in
The second approach is implemented in the app module of this project. That is the Spring Boot app that is packages into the Serious Callers Only docker image. You can take the same approach if you already have or anticipate needing a full-blown app around your scripts. In this case you may not need to write the call scripts in their own files. All you need is to:
- Instantiate the
hr.from.josipantolis.seriouscallersonly.api.Bot
class with your bot's behavior. - Provide the bot to
hr.from.josipantolis.seriouscallersonly.runtime.slack.BotKt::slackApp
method that will set up the runtime. slackApp
method will return an instance ofcom.slack.api.bolt.App
that you can then use to run the bot.
When starting your bot locally you'll still need a public URL endpoint that will be accessible to Slack servers. For this you can use ngrok. It's a tool for exposing your local server on a temporary domain on the public internet. It will also provide an HTTPS endpoint, which is required by Slack.
No matter how you choose to implement or run your Callers powered bot, you'll need to configure it on Slack itself. The framework currently doesn't support installation to multiple workspaces, so auth is based on bot token.
To configure your bot:
- Visit Slack App configuration page.
- Create a new app (or pick one that already exists)
- Go to Basic Information (in the left menu) > Add features and functionality. Copy the Signing Secret from this page and:
- Enable Interactive Components by providing it with your apps public URL. If you are running the Serious Callers Only docker image and exposing it via ngrok your URL will be:
https://[some-hash].ngrok.io/slack/events
. - Define Slash Commands in case your bot exposes them. Your URL will be the same as in the previous step.
- Enable Event Subscriptions and subscribe to following bot events:
member_joined_channel
message.channels
message.groups
message.im
message.mpim
- Enable Permissions. Copy the Bot User OAuth Access Token from this page and add following Bot Token Scopes:
channels:history
channels:join
channels:read
channels:write
chat:write
commands
groups:history
groups:read
im:history
mpim:history
- Enable Interactive Components by providing it with your apps public URL. If you are running the Serious Callers Only docker image and exposing it via ngrok your URL will be:
After this your Slack config should be filled, and you should have Signing Secret and Bot User OAuth Access Token. You'll need to provide those values to the framework runtime. If you're running the docker image just set them as environment variables.
The Serious Callers Only is in early development phase. It's operational, but not yet production worthy. At this stage my goals are to:
- [] Validate the idea by building some larger projects with the framework.
- [] Add plugins to call scripts that will enable easy integration with stuff like Git repos, CI pipelines, Task trackers, Wikis, etc.
- [] Add support for Slack Modals and Home tab.
Despite the early stages of the project, it does adhere to semantic versioning. Currently, project is in per-release phase (with versions in 0.y.z
range), so you can expect some breaking changes to the API.
- Josip Antoliš - Initial development - Antolius
Distributed under the MIT License. See LICENSE.txt
for more information.
None of this would be possible without wonderful folks over at Slack, and particularly the slack-java-sdk.
Almost as crucial are the devs from JetBrains with their various Kotlin language features and scripting support on IntelliJ IDEA side.
Lastly, I've learned a great deal from Rodrigo Oliveira's Implementing the Gradle Kotlin DSL Kotlinconf 2019 talk.