Creating a Slack bot with F# and Suave in less than 5 minutes
Published
Slack has quickly gained a lot of popularity and became one of the leading team communication tools for developers and technology companies. One of the main compelling features was the great amount of integrations with other tools and services which are essential for development teams and project managers. Even though the list of apps seems to be huge, sometimes you need to write your own custom integration and Slack wants it to be as easy and simple as possible. How easy and how fast this can be done I will show you in this blog post.
A simple slash command
In most cases you will probably need to create one or more new slash commands which can be used by slack users to perform some actions.
For this tutorial let's assume I would like to create a new slash command to hash a given string with the SHA-512 algorithm. I would like my slack users to be able to type /sha512 <some string>
into a channel and a slack bot to reply with the correct hash code.
The easiest way to achieve this is to create a new web service which will perform the hashing of the string and integrate it with the Slash Commands API.
Building an F# web service which integrates with Slash commands
Let's begin with the web service by creating a new F# console application and installing the Suave web framework NuGet package.
The Slash Commands API will make a HTTP POST request to a configurable endpoint and submit a bunch of data
which will provide all relevant information to perform our action. First I want to model a SlackRequest
type which will represent the incoming POST data from the Slash Commands API:
type SlackRequest =
{
Token : string
TeamId : string
TeamDomain : string
ChannelId : string
ChannelName : string
UserId : string
UserName : string
Command : string
Text : string
ResponseUrl : string
}
For this simple web service the only two relevant pieces of information are the token and the text which get submitted. The token represents a secret string value which can be used to validate the origin of the request and the text value represents the entire string which the user typed after the slash command. For example if I type /sha512 dusted codes
then the text property will contain dusted codes
in the POST data.
Inside this record type I'm also adding a little helper function to extract the POST data from a Suave.Http.HttpContext
object:
static member FromHttpContext (ctx : HttpContext) =
let get key =
match ctx.request.formData key with
| Choice1Of2 x -> x
| _ -> ""
{
Token = get "token"
TeamId = get "team_id"
TeamDomain = get "team_domain"
ChannelId = get "channel_id"
ChannelName = get "channel_name"
UserId = get "user_id"
UserName = get "user_name"
Command = get "command"
Text = get "text"
ResponseUrl = get "response_url"
}
Next I'll create a function to perform the actual SHA-512 hashing:
let sha512 (text : string) =
use alg = SHA512.Create()
text
|> Encoding.UTF8.GetBytes
|> alg.ComputeHash
|> Convert.ToBase64String
Finally I will create a new Suave WebPart to handle an incoming web request and register it with a new route /sha512
which listens for POST requests:
let sha512Handler =
fun (ctx : HttpContext) ->
(SlackRequest.FromHttpContext ctx
|> fun req ->
req.Text
|> sha512
|> OK) ctx
let app = POST >=> path "/sha512" >=> sha512Handler
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
With that the entire web service - even though very primitive - is completed. The entire implementation is less than 60 lines of code:
open System
open System.Security.Cryptography
open System.Text
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
type SlackRequest =
{
Token : string
TeamId : string
TeamDomain : string
ChannelId : string
ChannelName : string
UserId : string
UserName : string
Command : string
Text : string
ResponseUrl : string
}
static member FromHttpContext (ctx : HttpContext) =
let get key =
match ctx.request.formData key with
| Choice1Of2 x -> x
| _ -> ""
{
Token = get "token"
TeamId = get "team_id"
TeamDomain = get "team_domain"
ChannelId = get "channel_id"
ChannelName = get "channel_name"
UserId = get "user_id"
UserName = get "user_name"
Command = get "command"
Text = get "text"
ResponseUrl = get "response_url"
}
let sha512 (text : string) =
use alg = SHA512.Create()
text
|> Encoding.UTF8.GetBytes
|> alg.ComputeHash
|> Convert.ToBase64String
let sha512Handler =
fun (ctx : HttpContext) ->
(SlackRequest.FromHttpContext ctx
|> fun req ->
req.Text
|> sha512
|> OK) ctx
let app = POST >=> path "/sha512" >=> sha512Handler
[<EntryPoint>]
let main argv =
startWebServer defaultConfig app
0
Now I just need to build, ship and deploy the application.
Configuring Slash Commands
Once deployed I am ready to add a new Slash Commands integration.
-
Go into your team's Slack configuration page for custom integrations.
e.g.:https://{your-team-name}.slack.com/apps/manage/custom-integrations
-
Pick Slash Commands and then click on the "Add Configuration" button:
- Choose a command and confirm by clicking on "Add Slash Command Integration":
- Finally type in the URL to your public endpoint and make sure the method is set to POST:
- Optionally you can set a name, an icon and additional meta data for the bot and then click on the "Save Integration" button.
Congrats, if you've got everything right then you should be able to go into your team's Slack channel and type /sha512 test
to get a successful response from your newly created Slack integration now.
If you are interested in a more elaborate example with token validation and Docker integration then check out my glossary micro service.