Recently I tried to create a Slack bot. It's job was to read messages and, if 'that would be great' was detected in the content, respond to the message with a picture of Bill Lumbergh from Office Space (yeah, I'm a funny guy). But I found out that the learning resources are somewhat scattered around the Internet. It was difficult for a person not familiar with Slack API and with bots in general to quickly create nothing more than a simple bot. I finally put together the information from different sources and decided to describe the process here.
This post will show you how to integrate with Slack in two ways: using bot users and outgoing webhooks. You don't have to know anything about Slack or Python frameworks, but basic Python skills are required (also a Heroku account or your own server would be helpful). The code will be simple and will do the one and only task that I mentioned: detect the phrase and respond with an image. Let't get right to it!
Slack allows you to create bot users. They are very similar to normal users, except they can be controlled using the API token.
To create a new bot user, visit this link (of course, you have to be a full member of your team to do that). First, you need to pick a name for your bot:
Then, you can access your bot's settings. It is possible (and advised!) to give it a nice name and a proper icon. But the important part here is the token:
This will be required for your Python code to post messages to Slack channels. From now on, I'm going to assume that your token is
Now that your bot is created, go ahead and invite it:
The bot should be more than happy to accept the invitation:
First, you need to create a new virtualenv and install
mkvirtualenv -p /usr/bin/python3 slack-bot pip install slackclient
Next you have to actually create a Slack client, using your bot's token. For security reasons set the environment variable with the token:
and then use it in Python:
from slackclient import SlackClient import os token = os.environ.get('SLACKBOT_LUMBERGH_TOKEN') slack_client = SlackClient(token)
There are many methods provided by
slackclient (you can check the documentation). For this example, we are going to use just 3 of them.
To start working with Slack use
rtm_connect. It will open a websocket connection and start to listen for events.
if slack_client.rtm_connect(): # proceed else: print('Connection failed, invalid token?')
To get the list of events, use
rtm_read. It will return a list of events since the last call.
events = slack_client.rtm_read() for event in events: # process event
There are different types of events. You want to intercept those that have a
message type, come from a specific channel and contain some text:
if ( 'channel' in event and 'text' in event and event.get(type) == 'message' ): # this is the event you are looking for
Now that you have an actual message, you can check if the text contains the phrase 'that would be great'. If so, you can post a new message to a Slack channel the message came from. To do this, use
link = '<https://cdn.meme.am/instances/400x/33568413.jpg|That would be great>' slack_client.api_call( 'chat.postMessage', channel=event['channel'], text=link, as_user='true:' )
Some things might require an explaination:
'chat.postMessage'defines that type of the API call you are going to make (in this case, you want to post a message)
as_user='true:'will make your bot's messages appear as they were sent by a normal Slack user
You code by far should look like this:
from slackclient import SlackClient import time import os token = os.environ.get('SLACKBOT_LUMBERGH_TOKEN') slack_client = SlackClient(token) link = '<https://cdn.meme.am/instances/400x/33568413.jpg|That would be great>' if slack_client.rtm_connect(): while True: events = slack_client.rtm_read() for event in events: if ( 'channel' in event and 'text' in event and event.get('type') == 'message' ): channel = event['channel'] text = event['text'] if 'that would be great' in text.lower() and link not in text: slack_client.api_call( 'chat.postMessage', channel=channel, text=link, as_user='true:' ) time.sleep(1) else: print('Connection failed, invalid token?')
sleep(1) fragment was added to slow down the loop a bit. Notice that the text is also checked for the presence of the link itself (otherwise your bot would start answering its own messages).
Assuming that you named your file
main.py you can now run the program:
and see your bot in action:
It works quite nicely, except for that awful endless loop. That is not how the code should look like. If only there was a way to react to actual messages instead of reading all the events...
Fortunately, Slack provides another way of integrating with other services: webhooks. Thanks to them you can receive a call each time a message is sent to a channel.
Go to Outgoing WebHooks page and click Add Outgoing WebHooks integration. You will be redirected to the Edit configuration page, and you will immediately notice some limitations:
So, is it even worth the effort to use this outgoing webhook instead of a bot user? I think it is. Infinite loops without breaking conditions are evil and you should avoid them. Besides, the downsides are not really that troublesome (you probably will use this integration on one or two channels anyway, and setting up the server with Heroku is quite easy).
Let's proceed with the configuration. Select the channel and leave the Trigger Word(s) section empty (you don't want to restrict the messages that will be answered). I'm going to assume for a moment that you have your own public IP address and that it is
18.104.22.168 (don't worry, in just a moment you will deploy your program to Heroku and that will take care of the public IP problem). Put
22.214.171.124/lumbergh in the URL(s) field. You can also customize the name and the icon.
There is another important section here: Token. It contains the token that will be added to each API call send to the URLs you provided. You will get back to it in a moment.
Click Save Settings button. Notice that you don't need to invite an integration to a channel, it will be added automatically when you create the webhook (also, unlike bot users, integrations can have names starting with a capital letter):
The new version of your program will not require
slackclient at all. Instead, you are going to use
pip install flask
A very simple application would look like this:
from flask import Flask app = Flask(__name__) if __name__ == '__main__': app.run(host='0.0.0.0') # make the app externally visible
This of course will not do anything useful, so let's create an endpoint:
@app.route('/lumbergh', methods=['POST']) def lumbergh(): text = request.form.get('text', '') if 'that would be great' in text.lower() and link not in text: return jsonify(text=link) return Response(), 200
Now every time Slack calls
126.96.36.199/lumbergh, the program will check if the message contains the phrase 'it would be great'. If so, a link to the image will be returned. Notice that you no longer need to use
api_call here: the response with a text will be automatically converted to a new message by Slack.
The code of your program should look like this:
from flask import Flask, request, Response, jsonify app = Flask(__name__) link = '<https://cdn.meme.am/instances/400x/33568413.jpg|That would be great>' @app.route('/lumbergh', methods=['POST']) def lumbergh(): text = request.form.get('text', '') if 'that would be great' in text.lower() and link not in text: return jsonify(text=link) return Response(), 200 if __name__ == '__main__': app.run(host='0.0.0.0')
Do you remember the token that Slack created for your outgoing webhook? You might notice that there is no token validation here. If you want, you can check if the call was made by Slack:
def lumbergh(): if request.form.get('token') == os.environ.get('WEBHOOK_TOKEN'): # process the message return Response(), 200
I decided however to skip the validation. That way one instance of a program can be used with multiple channels.
You can run the your new app (on a publicly available server):
and check if it works:
The problem is that you still need to have a public IP address. Let's solve this problem with Heroku.
I'm going to assume that you already have a Heroku account and that you installed Heroku CLI. Create a new app and give it a nice name (I picked lumbergh). Go to Settings, check the git URL and configure the git remote accordingly:
git init git remote add heroku https://git.heroku.com/lumbergh.git
To run the program you will need a server, for example
pip install gunicorn
For a program to work with Heroku, you have to create an additional file called
web: gunicorn lumbergh:app
Also, since Python 2.x is legacy and Python 3.x is the present and future of the language, you should inform Heroku that you want to use the proper version of Python by creating a
Now you can deploy to Heroku (remember that first you need to use
git push heroku master
Check the Overview to see if the program is working:
Now you can change the URL for the Slack webhook to the one provided by Heroku (you will find it in Settings):
As it turns out, answering messages automatically on Slack is very easy. Bot users can be enabled for many channels, but they need an infinite loop to process events. Outgoing webhooks can be called for each message, but they need a public IP and have to be added to each channel separately. And both of these solutions can be implemented in less than 31 lines of code.
The source code for this Slack bot can be found here. The latest version contains the webhook integration, but the first commit shows the bot user program. If you don't have the time to configure the bot by yourself, you can use my Heroku instance (just add a new outgoing webhook with
If you find any problems with this tutorial, please let me know.