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.
scratch/main> 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
Environmentwhere associatedEnvironment.Configvalues 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.named "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. ☕️