Bots are a useful way to interact with chat services such as Slack. If you have never built a bot before, this post provides an easy starter tutorial for combining the Slack API with Python to create your first bot.
We will walk through setting up your development environment, obtaining a Slack API bot token and coding our simple bot in Python.
Tools We Need
Our bot, which we will name "StarterBot", requires Python and the Slack API. To run our Python code we need:
- Either Python 2 or 3
- pip and virtualenv to handle Python application dependencies
- Free Slack account with a team on which you have API access or sign up for the Slack Developer Hangout team
- Official Python slackclient code library built by the Slack team
- Slack API testing token
It is also useful to have the Slack API docs handy while you're building this tutorial.
All the code for this tutorial is available open source under the MIT license in the slack-starterbot public repository.
Establishing Our Environment
We now know what tools we need for our project so let's get our development environment set up. Go to the terminal (or Command Prompt on Windows) and change into the directory where you want to store this project. Within that directory, create a new virtualenv to isolate our application dependencies from other Python projects.
virtualenv starterbot
Activate the virtualenv:
source starterbot/bin/activate
Your prompt should now look like the one in this screenshot.
The official slackclient API helper library built by Slack can send and
receive messages from a Slack channel. Install the slackclient library with
the pip
command:
pip install slackclient
When pip
is finished you should see output like this and you'll be
back at the prompt.
We also need to obtain an access token for our Slack team so our bot can use it to connect to the Slack API.
Slack Real Time Messaging (RTM) API
Slack grants programmatic access to their messaging channels via a web API. Go to the Slack web API page and sign up to create your own Slack team. You can also sign into an existing account where you have administrative privileges.
After you have signed in go to the Bot Users page.
Name your bot "starterbot" then click the “Add bot integration” button.
The page will reload and you will see a newly-generated access token. You can also change the logo to a custom design. For example, I gave this bot the Full Stack Python logo.
Click the "Save Integration" button at the bottom of the page. Your bot is now ready to connect to Slack's API.
A common practice for Python developers is to export secret tokens as
environment variables. Export the Slack token with the name
SLACK_BOT_TOKEN
:
export SLACK_BOT_TOKEN='your slack token pasted here'
Nice, now we are authorized to use the Slack API as a bot.
There is one more piece of information we need to build our bot: our bot's ID. Next we will write a short script to obtain that ID from the Slack API.
Obtaining Our Bot’s ID
It is finally time to write some Python code! We'll get warmed up by coding a short Python script to obtain StarterBot's ID. The ID varies based on the Slack team.
We need the ID because it allows our application to determine if messages
parsed from the Slack RTM are directed at StarterBot. Our script also
tests that our SLACK_BOT_TOKEN
environment variable is properly set.
Create a new file named print_bot_id.py
and fill it with the following
code.
importosfromslackclientimportSlackClientBOT_NAME='starterbot'slack_client=SlackClient(os.environ.get('SLACK_BOT_TOKEN'))if__name__=="__main__":api_call=slack_client.api_call("users.list")ifapi_call.get('ok'):# retrieve all users so we can find our botusers=api_call.get('members')foruserinusers:if'name'inuseranduser.get('name')==BOT_NAME:print("Bot ID for '"+user['name']+"' is "+user.get('id'))else:print("could not find bot user with the name "+BOT_NAME)
Our code imports the SlackClient and instantiates it with our
SLACK_BOT_TOKEN
, which we set as an environment variable. When the
script is executed by the python
command we call the Slack API to list
all Slack users and get the ID for the one that matches the name "starterbot".
We only need to run this script once to obtain our bot’s ID.
python print_bot_id.py
The script prints a single line of output when it is run that provides us with our bot's ID.
Copy the unique ID that your script prints out. Export the ID as an
environment variable named BOT_ID
.
(starterbot)$ export BOT_ID='bot id returned by script'
The script only needs to be run once to get the bot ID. We can now use that ID in our Python application that will run StarterBot.
Coding Our StarterBot
We've got everything we need to write the StarterBot code. Create a new file
named starterbot.py
and include the following code in it.
importosimporttimefromslackclientimportSlackClient
The os
and SlackClient
imports will look familiar because we used them
in the print_bot_id.py
program.
With our dependencies imported we can use them to obtain the environment variable values and then instantiate the Slack client.
# starterbot's ID as an environment variableBOT_ID=os.environ.get("BOT_ID")# constantsAT_BOT="<@"+BOT_ID+">:"EXAMPLE_COMMAND="do"# instantiate Slack & Twilio clientsslack_client=SlackClient(os.environ.get('SLACK_BOT_TOKEN'))
The code instantiates the SlackClient
client with our SLACK_BOT_TOKEN
exported as an environment variable.
if __name__ == "__main__":
READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose
if slack_client.rtm_connect():
print("StarterBot connected and running!")
while True:
command, channel = parse_slack_output(slack_client.rtm_read())
if command and channel:
handle_command(command, channel)
time.sleep(READ_WEBSOCKET_DELAY)
else:
print("Connection failed. Invalid Slack token or bot ID?")
The Slack client connects to the Slack RTM API WebSocket then constantly
loops while parsing messages from the firehose. If any of those messages are
directed at StarterBot, a function named handle_command
determines what
to do.
Next add two new functions to parse Slack output and handle commands.
defhandle_command(command,channel):""" Receives commands directed at the bot and determines if they are valid commands. If so, then acts on the commands. If not, returns back what it needs for clarification."""response="Not sure what you mean. Use the *"+EXAMPLE_COMMAND+\"* command with numbers, delimited by spaces."ifcommand.startswith(EXAMPLE_COMMAND):response="Sure...write some more code then I can do that!"slack_client.api_call("chat.postMessage",channel=channel,text=response,as_user=True)defparse_slack_output(slack_rtm_output):""" The Slack Real Time Messaging API is an events firehose. this parsing function returns None unless a message is directed at the Bot, based on its ID."""output_list=slack_rtm_outputifoutput_listandlen(output_list)>0:foroutputinoutput_list:ifoutputand'text'inoutputandAT_BOTinoutput['text']:#returntextafterthe@mention,whitespaceremovedreturnoutput['text'].split(AT_BOT)[1].strip().lower(),\output['channel']returnNone,None
The parse_slack_output
function takes messages from Slack and determines
if they are directed at our StarterBot. Messages that start with a direct
command to our bot ID are then handled by our code - which is currently
just posts a message back in the Slack channel telling the user to write
some more Python code!
Here is how the entire program should look when it's all put together (you can also view the file in GitHub):
importosimporttimefromslackclientimportSlackClient# starterbot's ID as an environment variableBOT_ID=os.environ.get("BOT_ID")# constantsAT_BOT="<@"+BOT_ID+">:"EXAMPLE_COMMAND="do"# instantiate Slack & Twilio clientsslack_client=SlackClient(os.environ.get('SLACK_BOT_TOKEN'))defhandle_command(command,channel):""" Receives commands directed at the bot and determines if they are valid commands. If so, then acts on the commands. If not, returns back what it needs for clarification."""response="Not sure what you mean. Use the *"+EXAMPLE_COMMAND+ \
"* command with numbers, delimited by spaces."ifcommand.startswith(EXAMPLE_COMMAND):response="Sure...write some more code then I can do that!"slack_client.api_call("chat.postMessage",channel=channel,text=response,as_user=True)defparse_slack_output(slack_rtm_output):""" The Slack Real Time Messaging API is an events firehose. this parsing function returns None unless a message is directed at the Bot, based on its ID."""output_list=slack_rtm_outputifoutput_listandlen(output_list)>0:foroutputinoutput_list:ifoutputand'text'inoutputandAT_BOTinoutput['text']:# return text after the @ mention, whitespace removedreturnoutput['text'].split(AT_BOT)[1].strip().lower(), \
output['channel']returnNone,Noneif__name__=="__main__":READ_WEBSOCKET_DELAY=1# 1 second delay between reading from firehoseifslack_client.rtm_connect():print("StarterBot connected and running!")whileTrue:command,channel=parse_slack_output(slack_client.rtm_read())ifcommandandchannel:handle_command(command,channel)time.sleep(READ_WEBSOCKET_DELAY)else:print("Connection failed. Invalid Slack token or bot ID?")
Now that all of our code is in place we can run our StarterBot on the
command line with the python starterbot.py
command.
In Slack, create a new channel and invite StarterBot or invite it to an existing channel.
Now start giving StarterBot commands in your channel.
Wrapping Up
Alright, now you've got a simple StarterBot with a bunch of places in the code you can add whatever features you want to build.
There is a whole lot more that could be done using the Slack RTM API and Python. Check out these posts to learn what you could do:
- Attach a persistent relational database or NoSQL back-end such as PostgreSQL, MySQL or SQLite to save and retrieve user data
- Add another channel to interact with the bot via SMS or phone calls
- Integrate other web APIs such as GitHub, Twilio or api.ai
Questions? Contact me via Twitter @fullstackpython or @mattmakai. I'm also on GitHub with the username mattmakai.
Something wrong with this post? Fork this page's source on GitHub.