Building PokéSlacker: A Slack Bot Tutorial

Jimmy
Insight
Published in
10 min readSep 9, 2016

--

For my Insight Data Science project, I built a linear regression model to predict nightly tap room attendance for New Republic Brewing Co. Using this model, New Republic will be able to schedule the appropriate number of bartenders for any given night. When it came time to decide how I would deliver this model to New Republic, I decided to eschew the typical dashboard in favor of building a Slack bot. New Republic had recently switched from communicating through Google Hangouts to Slack because they wanted to take advantage of Slack’s integrations, so I took this opportunity to explore Slack integration creation.

Example of Slack Bot output built for New Republic Brewing Company

Building a Slack bot to interact with the model has many advantages. It provides a user friendly interface to the model that allows anyone at New Republic to interact with it. It also allows employees to access model predictions and inform conversations about tap room staffing without having to break their workflow.

The best part is, building a bot for Slack is easy to do! With only a few lines of code and a simple web server, you can build one yourself. I’m going to walk you through the creation of a very simple Slack bot that will listen for a user’s commands, parse the arguments provided, retrieve information using an external API, and then return a formatted answer to the user. For the purposes of this tutorial, we’re going to keep everything real simple, but there is great potential to expand and explore on your own.

What’s the best type to fight this Snorlax?

Deciding on a Bot

Of course, we’re going to need to give our bot a purpose, so let’s pick a simple problem to tackle. Let’s say you, like many people, have recently begun playing Pokémon Go. You may even have a Pokémon Go Gym that you can access from your office. But some punks from another team keep coming by and putting Snorlax on it. Now it may very well have been many years since you last played Pokémon, so you may not remember exactly which Pokémon type is most effective against that pesky Snorlax. So instead of having to search for a Pokémon type chart, let’s build a Slack bot that will easily tell us. We’ll call it PokéSlacker!

Information Retrieval via API

We could build a database ourselves of all the Pokémon types and what they’re most effective against, but first let’s see if someone has already done that work for us. Enter Pokéapi, a database of Pokémon information with a publicly accessible RESTful API. We can query this database to get all the answers we need.

We are going to build this Slack bot in Python, so we’ll use Python to query the API. To accomplish this, we’ll import the json and request packages that come standard with Python. A simple query to the Pokéapi database would look something like this:

import json
import requests
#Perform an HTTP GET request, grab the text portion of the response.
url = ‘http://pokeapi.co/api/v2/type/normal/'
requests_text = requests.get(url).text
#Load the GET request text into a Python dictionary.
json_returned = json.loads(requests_text)

Now we have everything we need to know about Normal type Pokémon like Snorlax! But if you print out the dictionary we created, you’ll find that we have more information than we need! Let’s define a function to extract only Pokémon type information from the JSON that is relevant to battling. This code can be called to find out what types will be super effective or not very effective, depending on the input string used for the relation variable.

def extract_types(json_in, relation):
types_list = []
#relation can be something like double_damage_to
#to find what type it is super effective against
for pkmn_type in json_in[‘damage_relations’][relation]:
types_list.append(pkmn_type[‘name’])
#Check that the query produced an output
if len(types_list) > 0:
return types_list
#If no types were found, return None string to the user
else:
return [‘None’]

Sending messages to Slack

That’s essentially all of the Pokémon information retrieval work we’ll need for this portion of the code. Now that we’ve figured out how to get the information we need for our bot, let’s build the server that will host it. For this we will utilize Flask as the web server, and to make life easier, we will use the SlackClient Python package to interact with the Slack API. A very simple skeleton looks something like this:

from flask import Flask, request, Response
from slackclient import SlackClient
SLACK_DEV_TOKEN = <SLACK TOKEN> #Put your API dev token here
SLACK_WEBHOOK_TOKEN = <WEBHOOK TOKEN> #Put your webhook token here
slack_client = SlackClient(SLACK_DEV_TOKEN)
app = Flask(__name__)#A simple function to handle posting the message
def send_message(channel_id, message):
slack_client.api_call(
“chat.postMessage”,
channel=channel_id,
text=message,
username=’PokeSlacker’,
icon_emoji=’:joystick:’
)
#This is the server listening for posts to the root directory
@app.route(‘/’, methods=[‘POST’])
def inbound():
#This if ensures that outside sources won't spam the channel
if request.form.get(‘token’) == SLACK_WEBHOOK_TOKEN:
#Channel the request came from
channel_id = request.form.get(‘channel_id’)
#Text sent by the user
text = request.form.get(‘text’).lower()
message = <MESSAGE HERE> #We'll fill this in later
send_message(channel_id, message)
return Response(), 200
if __name__ == “__main__”:
app.run()

This will not run without some minor modifications. First we’ll need to get a Slack test API token. The API token can be obtained from the Slack API test tokens page. This test API token will be used to authenticate to the Slack servers in order to send messages into your team’s channels. It is unique to each user on each team.

One developer may want to create multiple bots however, so we’ll also need to use a Slack Webhook token to identify each bot. Using this system of identification, you could actually run multiple different bots using the same Flask server. The Slack Webhook token will be generated when you create a new Slash Command. For this tutorial, we’re going to implement PokéSlacker as a slash command, but it could easily be called by listening for a keyword within any message if desired. We’re doing a slash command to ensure that the bot doesn’t get summoned accidentally.

Filling out the Integration Settings should be pretty straightforward, and it will look something like this when completed. We haven’t yet set up a server, so we don’t have the IP address to fill in, and the Webhook Token field will already be generated, so you won’t need to modify that, but do be sure to make note of it because it will be needed for the final script.

Example Slack Integration Settings

Final Product

Before we set up a virtual server to host PokéSlacker, we’ll first finish integrating the Pokéapi code with the Flask server code to get these two servers talking to each other. The final combined code looks like this.

from flask import Flask, request, Response
from slackclient import SlackClient
import json
import requests
SLACK_DEV_TOKEN = <SLACK TOKEN> #Put your API dev token here
SLACK_WEBHOOK_TOKEN = <WEBHOOK TOKEN> #Put your webhook token here
slack_client = SlackClient(SLACK_DEV_TOKEN)def extract_types(json_in, relation):
types_list = []
#relation can be something like double_damage_to
#to find what type it is super effective against
for pkmn_type in json_in[‘damage_relations’][relation]:
types_list.append(pkmn_type[‘name’])
#Check that the query produced an output
if len(types_list) > 0:
return types_list
#If no types were found, return None string to the user
else:
return [‘None’]
def get_relations(input_type):
#Perform an HTTP GET request,
#grab the text portion of the response.
url = ‘http://pokeapi.co/api/v2/type/'+input_type+'/'
requests_text = requests.get(url).text
#Load the GET request text into a Python dictionary.
json_returned = json.loads(requests_text)
#Pull out all the relevant information to report to the user.
half_damage_from = extract_types(json_returned,
'half_damage_to')
no_damage_from = extract_types(json_returned, 'no_damage_from')
double_damage_from = extract_types(json_returned, \
'double_damage_from')
half_damage_to = extract_types(json_returned, 'half_damage_to')
double_damage_to = extract_types(json_returned, \
'double_damage_to')
no_damage_to = extract_types(json_returned, 'no_damage_to')
#Format the output in the return statement.
return (input_type + ' is super effective against: ' + \
', '.join(double_damage_to) + '\n') + \
(input_type + ' is not very effective against: ' + \
', '.join(half_damage_to) + '\n') + \
(input_type + ' is weak to: ' + \
', '.join(double_damage_from) + '\n') + \
(input_type + ' is resistant to: ' + \
', '.join(half_damage_from) + '\n') + \
(input_type + ' takes no damage from: ' + \
', '.join(no_damage_from) + '\n') + \
(input_type + ' deals no damage to: ' + \
', '.join(no_damage_to) + '\n')

pokemon_types_list = ['bug', 'dark', 'dragon', 'electric', 'fairy',
'fighting', 'fire', 'flying', 'ghost', 'grass', 'ground',
'ice', 'normal', 'poison', 'psychic', 'rock', 'steel', 'water']
cached_dictionary = {}
for type_to_cache in pokemon_types_list:
cached_dictionary[type_to_cache] = get_relations(type_to_cache)
#Status update because it could seem like nothing's happening
print(type_to_cache+' added to cache.')
print('Caching completed.')
app = Flask(__name__)#A simple function to handle posting the message
def send_message(channel_id, message):
slack_client.api_call(
'chat.postMessage',
channel=channel_id,
text=message,
username=’PokeSlacker’,
icon_emoji=’:joystick:’
)
#This is the server listening for posts to the root directory
@app.route(‘/’, methods=[‘POST’])
def inbound():
#if statement ensures outside sources won't spam the channel
if request.form.get(‘token’) == SLACK_WEBHOOK_TOKEN:
#Channel the request came from
channel_id = request.form.get(‘channel_id’)
#Text sent by the user
input_text = request.form.get(‘text’).lower()
#Using a dictionary get, we can also handle errors elegantly
message = cached_dictionary.get(input_text, 'Type '+ \
input_text + ' not found, please try again.')
send_message(channel_id, message)
return Response(), 200
if __name__ == '__main__':
app.run(host='0.0.0.0')

That’s it! That’s all the Python we need to run a simple Slack bot. You’ll still need to fill in the SLACK_WEBHOOK_TOKEN variable (from the slash command creation page) and SLACK_DEV_TOKEN variable (from the Slack API page) with your own information. Be sure to leave these out of your commit to GitHub! Making these environmental variables is a good way to not accidentally share your secrets with the world. The careful reader will also notice that beyond the combining the code snippets from above, there have been a couple changes and additions to the script.

First is the new get_relations function that loads the input, formats the results, and returns the formatted text. Secondly, you’ll notice the addition of the cached_dictionary. Pokéapi is a free service, but sometimes it can get a little overloaded, and the maintainers ask that users locally cache information whenever possible. Therefore, when the script starts up, we will load in the results for every current Pokémon type and store them in a dictionary. This will cause startup to take a bit longer, but will significantly improve response times to the user when the bot is summoned. We’ve also made it so Flask will accept outside connections in the app.run() routine.

Deployment

Now that the bot script is complete, we simply need to install it on a web server. A free tier Amazon EC2 instance will be sufficient for most users. We’ll choose an Ubuntu server for this install. When configuring your new EC2 instance, make sure that port 5000 is open for Flask, and keep SSH so that you can administer the server remotely.

Example Security Settings for your EC2 Server

Once the virtual server has been initiated, this would be a good time to go back into your Slack Integration settings and edit the IP address to reflect the new virtual server. Back on the Amazon EC2 setup page you will be prompted to downloaded the authentication token. Be sure to set the access permissions on it and then log in. From your local Mac or Linux machine, run the following commands with the appropriate paths and IP address.

$ chmod 600 ~/Downloads/pokeslacker.pem 
$ ssh -i ~/Downloads/pokeslacker.pem ubuntu@<IP-ADDRESS>

Now that you’ve logged into the EC2 server, it’s time to install Flask, pip, and SlackClient.

$ sudo apt-get update
$ sudo apt-get install python-pip
$ sudo pip install Flask
$ sudo pip install SlackClient

With everything set up, place the final Python script featured above on your server. I’ve placed the Python script in a github repo, so you can simply use wget to download it to your server, edit the file to contain your unique keys, and then execute it.

$ wget https://raw.githubusercontent.com/gnatman/Pokeslacker/master/pokeslacker.py
$ nano ./pokeslacker.py
$ python ./pokeslacker.py

Once you see the “Caching completed” message appear in your terminal window, you can now go to Slack and test the command in any channel within your team.

/pokeslacker normal

The above Slack command should produce the following output:

Sample output from PokéSlacker

So now we know to use a fighting type Pokémon against that pesky Snorlax! Once you have everything up and running, you may want to utilize a tool like Gunicorn to run your bot in daemon mode so that you can safely exit the shell and keep your Slack bot running.

This is just a small example of what Slack Bots can do. This bot could be extended to recommend specific Pokémon to use given the Pokémon you will be battling. If Pokémon isn’t your thing, you could easily write a bot to fetch the weather in your area, or pipe upcoming events into your channel. Another bot I’ve written for our Insight Slack team is a quiz bot that posts random questions at a random interval for Insight Fellows to keep their skills sharp. Hopefully this tutorial has given you the resources and inspiration to make something awesome for your own team!

Interested in transitioning to a career in data science? Find out more about the Insight Data Science Fellows Program in New York and Silicon Valley, apply today, or sign up for program updates.

Already a data scientist or engineer? Find out more about our Advanced Workshops for Data Professionals. Register for two-day workshops in Apache Spark and Data Visualization, or sign up for workshop updates.

--

--