The new Slack Bot - Category: researchslack

28 Aug 2016 by Xavier Perarnau

Intro

First of all thanks to the excellent starter guide by FullStackPython, you can find his guide here FullStackPython SlackBot Guide

Index

Requirements

Setting up

Fist of all head up to the team admin page on Slack, select integrations -> custom integration -> bot, set the parameters and write down your api token.

I will assume that you are using virtualenv, so:

Enter on your working directorty with Terminal/Command Prompt and type:

virtualenv SlackBot

Before activation let’s modify the activation script so:

Windows

Add this to SlackBot/Scripts/activate.bat:

set "SLACK_BOT_TOKEN=your slack token pasted here"

And then on Command Prompt:

SlackBot/Scripts/activate.bat

Linux/OSX

Add this to SlackBot/bin/activate:

export SLACK_BOT_TOKEN='your slack token pasted here'

And then on Terminal:

source SlackBot/bin/activate

Continuation(same for Linux/OSX/Windows)

Now on Python:

pip install slackclient

Now, we will need the bot ID so we will use this code

print_bot_id.py by FullStackPython:

import os
from slackclient import SlackClient


BOT_NAME = 'your bot name here'

slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN'))


if __name__ == "__main__":
    api_call = slack_client.api_call("users.list")
    if api_call.get('ok'):
        # retrieve all users so we can find our bot
        users = api_call.get('members')
        for user in users:
            if 'name' in user and user.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)

From Terminal/Command Prompt:

python print_bot_id.py

When finished. type this on the same console(it will unload virtualenv to be able to modify activate script):

deactivate

Now, write down the bot_id and modify again the activate script

Windows

Add this to SlackBot/Scripts/activate.bat

set "BOT_ID=bot id returned by script"

And then on Command Prompt:

SlackBot/Scripts/activate.bat

Linux/OSX

Add this to SlackBot/bin/activate

export BOT_ID='bot id returned by script'

And then on Terminal:

source SlackBot/bin/activate

Demo example bot

Now let’s see this code, and analyze it, this demo code is again from FullStackPython:

Code

import os
import time
from slackclient import SlackClient


# starterbot's ID as an environment variable
BOT_ID = os.environ.get("BOT_ID")

# constants
AT_BOT = "<@" + BOT_ID + ">:"
EXAMPLE_COMMAND = "do"

# instantiate Slack & Twilio clients
slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN'))


def handle_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."
    if command.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)


def parse_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_output
    if output_list and len(output_list) > 0:
        for output in output_list:
            if output and 'text' in output and AT_BOT in output['text']:
                # return text after the @ mention, whitespace removed
                return output['text'].split(AT_BOT)[1].strip().lower(), \
                       output['channel']
    return None, None


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?")

Nice code, it works but how? Let’s look inside main:

Easy, yeah? Now let’s look deep and the interesting functions; firstly to parse_slack_output, and finally handle_command

NOTE: To simplify and make the code small I have removed the comments to explain it more. The original comments are in the demo previously shown

Parsing slack output

def parse_slack_output(slack_rtm_output):
    output_list = slack_rtm_output
    if output_list and len(output_list) > 0:
        for output in output_list:
            if output and 'text' in output and AT_BOT in output['text']:
                return output['text'].split(AT_BOT)[1].strip().lower(), \
                       output['channel']
    return None, None

So time for parse_slack_output analysis:

Handling commands

def handle_command(command, channel):
    response = "Not sure what you mean. Use the *" + EXAMPLE_COMMAND + \
               "* command with numbers, delimited by spaces."
    if command.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)

And finally we will analyze handle_command:

Improvement

My Proposals

Add the possibility to interact with the bot with other keywords than it’s name

On constants section replace AT_BOT = "<@" + BOT_ID + ">:" for:

AT_BOT = "<@" + BOT_ID + ">:"
AT_BOT_2 = "<@" + BOT_ID + ">"
NAME = "nncubie"     """ what ever you want, in my case the bot name 
                         (disable that nickname on Slack panel to disable overlap) """
SHORTCUT = "Nn"      """ same, but now to create a shortcut 
                         (disable that nickname on Slack panel to disable overlap) """
EVERYONE = "<!everyone>:"
EVERYONE_2 = "<!everyone>"
TRIGGERS = [AT_BOT, AT_BOT_2, NAME, SHORTCUT, EVERYONE, EVERYONE_2]

I know that I could put the strings directly on the array and get ride of some lines, but for testing purposes this is better

Finally modify parse_slack_output function:

def parse_slack_output(slack_rtm_output):
    output_list = slack_rtm_output
    if output_list and len(output_list) > 0:
        for output in output_list:
            if output and 'text' in output:
                for receiver in TRIGGERS:
                    if receiver in output['text']:
                        # return text after the @ mention, whitespace removed
                        return output['text'].split(receiver)[1].strip().lower(), \
                            output['channel']

What we have done?

Customize handle_command and add some examples

ToDo

ToDo List

Original author proposals