Write a Slackbot with Unison Cloud
Slackbots are simple to write and deploy on the Cloud. By the end of this walk-through you'll have deployed a bot that can respond to messages or other Slack events and done the following:
- Deployed an Http service
- Given your service a static
ServiceName
- Loaded a secret value in secure
Environment.Config
- Used the Unison Slack library
Our bot is simple—it will respond to any message containing the word coffee with the coffee emoji ☕️, but feel free to add functionality as you like!
Prerequsites
- A Unison Cloud account
- A Slack Workspace where you have sufficient permissions to add a bot
Follow along with our video tutorial
Slack setup
Head to api.slack.com/apps and click "Create a new App". You're creating your bot "from scratch" so select that option and give your app a memorable name.
In the administrative menu, head to "OAuth and Permissions". For the purposes of this tutorial, you want the bot to be able to write emoji reactions and join channels.
Install the app in your chosen workspace, click "Allow" and you'll see a "Bot User OAuth token". Grab the value for this token and keep it a secret! For now, you can save it to an environment variable.
$ export SLACK_API_TOKEN="myTokenValueFromSlack"
Head to the "Basic Information" tab of the administrative menu and scroll down to "App Credentials. Copy the value for "Signing Secret" and save that as an environment variable as well.
$ export SLACK_SIGNING_SECRET="mySigningSecretValue"
Writing the Unison Slackbot
Start Unison with the ucm
command in your terminal. Then create a project in the UCM to house your Slackbot with project.create
. The UCM will download the standard lib, and once its finished you should install the Slack
library by issuing lib.install @runarorama/slack
.
.> project.create coffeebot
🎉 I've created the project coffeebot.
I'll now fetch the latest version of the base Unison
library...
coffeebot/main> lib.install @runarorama/slack
Storing encrypted values in the Cloud
Let's start by writing a function that stores your Slackbot's configuration values. This function will need to:
- Create an
Environment
where associatedEnvironment.Config
values will be set - Read your api token and secret from environment variables
- Save the values securely in the Cloud for future use
Secrets and other configuration values can be safely stored in the Cloud using Environment
and Environment.Config
. They work in concert: you write values to an Environment
and later read them with the Environment.Config
ability. This relationship means that Environment.Config
values are scoped to a particular Environment
, which provides basic access management, as only a service deployed to the same Environment
can access its Environment.Config
values.
config : '{Exception, IO} ()
config =
Cloud.main do
env = Environment.named "coffeebot"
Environment.setValue env "api-token" (getEnv "SLACK_API_TOKEN")
Environment.setValue env "signing-secret" (getEnv "SLACK_SIGNING_SECRET")
Remember the text values "api-token"
and "signing-secret"
! You'll need them to read these values again.
In the UCM console, use the run
command to save your values to the Cloud.
coffeebot/main> run config
Writing the CoffeeBot code
We'll be writing our coffee bot core logic next. The coffeeBot
function should:
- Scan every Slack message
- Check if contains the word "coffee"
- Add an emoji reaction to those messages, otherwise skip the message
Let's start with a signature:
coffeeBot : '{SlackWeb, SlackEvents, Exception, Log} ()
coffeeBot = todo "write coffeeBot"
The Slack library API has two abilities for interacting with Slack. They mirror the two main APIs published by Slack.
There's a SlackWeb
ability which maps to the Http Web API, and a SlackEvent
ability which allows you to respond to a subset of incoming Slack events from the Events API.
The Log
ability in the signature comes from the Cloud client. It's handy for debugging or gathering info.
From the SlackEvents
api, we can gather all the incoming messages with the api.message
function and skip the ones that we don't care about with SlackEvents.skip
:
coffeeBot : '{Exception, Log, SlackWeb, SlackEvents} ()
coffeeBot = do
message = !api.message
messageText = Text.toLowercase (Message.text message)
if Text.contains "coffee" messageText then
todo "react to message"
else
SlackEvents.skip
Then we'll use the react
function from the SlackWeb
api to add the "coffee" emoji in response.
coffeeBot : '{Exception, Log, SlackWeb, SlackEvents} ()
coffeeBot = do
message = !api.message
messageText = Text.toLowercase (Message.text message)
if Text.contains "coffee" messageText then
react message "coffee"
else
SlackEvents.skip
You'll notice this function just returns Unit
, and we haven't handled the two Slack abilities yet. The handler that transforms the Slack abilities into an Http service is called runBot
. runBot
expects the Text
config keys that you used for storing the slack token and signing secret. (It'll do the lookup in Cloud Environment.Config
for us.) Then it accepts the Slack interaction logic as its final argument.
runBot "api-token" "signing-secret" coffeeBot
We'll show how to call runBot
in a Cloud deployment next.
Deploying the CoffeeBot
The last function we'll need is a Cloud deployment function. It starts with a Cloud.main
block where we specify the Environment
for our service deployment. We'll want to use the same environment name, "coffeebot"
, since Environment
creation is idempotent and we need this service to have access to our Slack secret values.
coffeebot.deploy =
Cloud.main do
env = Environment.named "coffeebot"
todo "deploy http service"
Since runBot
turns our coffeeBot
Slack interactions into a function from HttpRequest
to HttpResponse
, it can be passed to the cloud client's deployHttp
function. This will return a ServiceHash
representing the code being deployed.
coffeebot.deploy =
Cloud.main do
env = Environment.named "coffeebot"
serviceHash = deployHttp env
(runBot "api-token" "signing-secret" coffeeBot)
todo "name service"
Finally, let's give the ServiceHash
value—which will vary depending on the code being deployed—a static, human readable service name.
coffeebot.deploy =
Cloud.main do
env = Environment.named "coffeebot"
serviceHash = deployHttp env
(runBot "api-token" "signing-secret" coffeeBot)
name = ServiceName.crnamedeate "coffeebot"
ServiceName.assign name serviceHash
At this point, update
your codebase to save your work and then issue the run
command in the UCM.
coffeebot/main> update
coffeebot/main> run coffeebot.deploy
The Cloud will deploy your Slackbot, and when you converse with your coworkers about coffee, it should react accordingly. ☕️