Gopher Academy Blog

Community Contributed Go Articles and Tutorials

December 29, 2017

Contributed by

Create a Slack bot with golang

Create a Slack bot with golang

Introduction

In this post we’ll look at how to set up a quick Slack bot that receives messages (either direct or
from channel) and replies to the user. I’ve been an IRC user for many years and always loved setting up
bots, whether for sports scores, weather, or something else entirely. Recently I’ve actually had an
opportunity to implement my first Slack bot and figured I would document the process for others! You
can find all of the code for this post listed here,
and PRs are certainly welcome :D

For this assignment we’ll need a few things, not all of which are covered in this post. I invite the
reader to take a look at the installation practices for the other software dependencies based on their
specific environment needs. Here I’ll be using Fedora 26 (4.14.6-200.fc26.x86_64) along with these tools:

You’ll either need to set up an ngrok listener for your chosen localhost port, or develop on a server
that it externally routable (e.g. DigitalOcean droplet). In my case here I’m developing on my laptop but
would deploy permanently on a droplet.

The Slack API

Initial Configuration

The Slack API is well flushed out and spells out what specific
payloads to anticipate for any particular object. There are a number of calls you can develop
your bot to address, but in our case here we’ll look at using the Real Time Messaging API
(RTM) and specifically the chat.postMessage and chat.postEphemeral
methods.

Before any of our code is working we’ll need to set up an app within slack itself. Navigate to the
app registration tool to create a new application within your
workspace. Here I’ve created the NHL Scores app within my workspace.

Once done you’ll be presented with a number of options for your new application. Here we’ll need to create
a Bot User that will act as our listener within the workspace. My example is called nhlslackbot and
will be visible to all users within the workspace once mounted.

We’ll need to generate an OAuth token for our user in order to actually connect with the Slack API. To do so
click on the OAuth & Permissions section to Install App to Workspace which will prompt you to authorize
access and generate the tokens you’ll use. You’ll need to copy the Bot User OAuth Access Token somewhere local,
but always make sure this is not shared anywhere! This token is secret and should be treated like your
password!

Lastly we’ll need to set up the Interative Components of our application and specify the ngrok (or other)
endpoint that the API will send responses to. In my case, I’ve added a custom ngrok value here called
https://sebtest.ngrok.io/. This endpoint is where we’ll receive all correspondence from Slack itself, and this is how
we’ll be able to process any incoming messages from the channels.

With that all sorted, we can finally dig into the code!

Code components

The crux of the code is how we handle receiving messages from the slack connection. Using the Bot User OAuth
Access Token to establish the initial connection, we must continuously poll the system for incoming messages.
The API gives us the ability to trigger off of a number of event types, such as:

Hello Events

Connected Events

Presence Change Events

Message Events

and many more

The beauty of this verbosity is that we can trigger messages on a number of different use-cases, really
giving us the ability to tailor the bot to our specific needs. For this example, we’ll look at using the
*slack.MessageEvent type to support both indirect (within channel using @) or direct messages. From the
library, The primary poll for message events leverages the websocket handler and just loops over events
until we’ve received one that we want:

Once we confirm that the message is indeed directed to us, we pass the event handler along to our askIntent
function. Remember that this is a contrived example that’s just going to send back NHL game scores to the
user, iff they acknowledge that specific intent. We could build up an entire workflow around this user
interaction that would send different paths depending on user choices to our prompts, or have no prompts
at all! Those different cases are outside the scope of this introductory post, so for now we just want to
send back a quick Yes v No prompt and handle accordingly.

To do precisely that, our handler askIntent will process the message and genreate an chat.postEphemeral
message to send back to the event user (aka the person asking for details). The “ephemeral” post is one that’s
directed only to the requester. Though other users will see the initial request to the bot if within the
same channel, the subsequent interaction with the bot will only be done between the user and the bot. From
the docs:

This method posts an ephemeral message, which is visible only to the assigned user in a specific public channel, private channel, or private conversation.

With that in mind, we set up the initial response payload using the attachments spec from the API, defining a set of actions that the user is able to choose. For this
part of the conversation the user must reply Yes or No for whether they’d like us to retrieve the most
recent scores. If No, we reply with a basic note and continue listening; if Yes then let’s retrieve the
scores!

The attachments in the snippet above present the user with the following dialog:

If the user selects No, thanks! then we reply with a basic message:

This part of the interaction is precisely where the ngrok endpoint comes into play. The user’s interaction
is not directly with our code, but instead with slack itself. The message and interaction is passed through
slack and on to us at the redirect URL we specified earlier, in my case https://sebtest.ngrok.io which
routes to our internal localhost:9191 interface, and from there to our postHandler as defined in our
webapp router.

The tricky part here is to process the payload portion of the JSON response from the API. The POST that
slack returns back to our URL is a payload form that contains a bevy of information for our interaction. In
this case, the user’s response (either Yes or No) as well as a callbackID which we actually passed
in our original mesage prompt to the user! This is incredibly useful, especially as you have more and more
users interacting with your bot as you can specify unique actions based on the trigger. For example,
if the user selects Yes we could send subsequent ephemeral messages to ask for a specific date, or maybe
a certain team? We could even define the callback value as a key to a function map that would then trigger
some kind of other workflow altogether (like posting to a blog resource, or checking DB credentials, etc). The
options are indeed endless, but for the scope of this contrived example we just stick
to the scores from last night.

A key component to note here is the http response code; if you do not specify the http.StatusOK value
in your prompt back to the API, the error message you may want to convey to the user gets eaten by the system.
The default slackbot will absorb that message and reply to you (with an ephemeral message no less) with the
status code, but not the messages. Long story short, whatever message you’d like to actually send back to
the requester should have an http.StatusOK header.

Lastly, if our user has selected the Yes option we call out to our NHL stats api and process the results
for the user!