DEV Community

Kauna Hassan
Kauna Hassan

Posted on

Building a Slack Bot in Python

Introduction

In this article, we will be building a bot in python using the Slack API. We'll go through setting up the development environment and also getting the slack API bot token.

Tools needed:

This bot as mentioned earlier will be requiring python and the Slack API but in order to make use of this, we will need certain tools which we will be making use of as follows:

Setting Up The Environment

While we are now aware of the tools and software needed to be able to get coding, we will also need to set up our development environment by going to the command line and changing the directory of where to keep the project folder. We will then need to create a virtual environment to set apart our application dependencies from other projects by inputing the following code to our command line.

virtualenv slackbot

Enter fullscreen mode Exit fullscreen mode

We will then need to activate the virtual environment by inputing the code below:

source slackbot/bin/activate
Enter fullscreen mode Exit fullscreen mode

In addition the slackclient API helper library by slack can then send and receive messages from a slack channel. By using the following code below, we will then need to install the slackclient library using the pip command below:

pip3 install slackclient
Enter fullscreen mode Exit fullscreen mode

The next step in the process is to create a Slack App to receive our API token for the bot.You will then need to pick an app name as shown below:

An image showing a workspace

The next step is choosing a workspace if you're already signed into one or create a new workspace as shown:

An image showing a slack workspace

APIs and Configuration

The whole idea of the slack bot is ensuring that it operates like any other user participating in conversations in DMs, channels and groups. This is called a bot user that can be set up by selecting "bot users" under the "features" section. Below are a couple of steps involved in adding a bot user.

  • The first step is clicking on the already created workspace
  • The next step is going to the App home in the navigation section and then clicking on the review scopes to add button.
  • After clicking on app home, the next thing is to go to the bots page to get a token and get started with configuring the API.

Following the steps above will lead to a page shown below:

A bot user

The bot user section

As shown above, the next step is generating a token and in the button shown in the bottom right corner of the screen. A usual practice is to export secret tokens as environment variables as shown below with the name SLACK_TOKEN:

export SLACK_TOKEN='your bot user access token here'
Enter fullscreen mode Exit fullscreen mode

We have now authorised the Slack RTM and Web API.

Bot Coding

In your new folder, create a new file named bot.py and input the code below:

import os
import time
import re
from slackclient import SlackClient
Enter fullscreen mode Exit fullscreen mode

Our dependencies have now been imported and we can now get the values associated with the environment variable and then represent the Slack client as shown in the code below:

# represent the Slack client
slack_client = SlackClient(os.environ.get('SLACK_TOKEN'))
# bot's user ID in Slack: value is assigned after the bot starts up
bot_id = None

# constants
RTM_READ_DELAY = 1 # 1 second delay between reading from RTM
EXAMPLE_COMMAND = "do"
MENTION_REGEX = "^<@(|[WU].+?)>(.*)"
Enter fullscreen mode Exit fullscreen mode

The code above, now represents the SlackClient with the SLACK_TOKEN which is then exported as an environment variable. This now communicates that we could use a variable to store our Slack user ID of the bot. We can then explain the constants which have been declared in the code below:

if __name__ == "__main__":
    if slack_client.rtm_connect(with_team_state=False):
        print("Bot connected and running!")
        # Read bot's user ID by calling Web API method `auth.test`
        starterbot_id = slack_client.api_call("auth.test")["user_id"]
        while True:
            command, channel = parse_bot_commands(slack_client.rtm_read())
            if command:
                handle_command(command, channel)
            time.sleep(RTM_READ_DELAY)
    else:
        print("Connection failed. Exception traceback printed above.")
Enter fullscreen mode Exit fullscreen mode

Our Slack client now connects to the Slack RTM API which now calls our Web API method(auth.test) to get the bot's ID.

The bot user now has a user ID for each workspace the Slack App is installed within. Another point to note is that the program now goes to an infinite loop where each time the loop runs the client, it receives any event that arrives from Slack's RTM API. With careful notice, we will see that before the loop ends, the program pauses for a second such as it doesn't loop so fast and not waste our CPU time.

For each event been read the parse_bot_comands() function now ensures the event contains a command for the bot. This will also ensure the command will contain a value while ensuring the handle command() function knows what to do with the command.

We now have the above explained in the code below:

def parse_bot_commands(slack_events):
    """
        Parses a list of events coming from the Slack RTM API to find bot commands.
        If a bot command is found, this function returns a tuple of command and channel.
        If its not found, then this function returns None, None.
    """
    for event in slack_events:
        if event["type"] == "message" and not "subtype" in event:
            user_id, message = parse_direct_mention(event["text"])
            if user_id == bot_id:
                return message, event["channel"]
    return None, None

def parse_direct_mention(message_text):
    """
        Finds a direct mention (a mention that is at the beginning) in message text
        and returns the user ID which was mentioned. If there is no direct mention, returns None
    """
    matches = re.search(MENTION_REGEX, message_text)
    # the first group contains the username, the second group contains the remaining message
    return (matches.group(1), matches.group(2).strip()) if matches else (None, None)

def handle_command(command, channel):
    """
        Executes bot command if the command is known
    """
    # Default response is help text for the user
    default_response = "Not sure what you mean. Try *{}*.".format(EXAMPLE_COMMAND)

    # Finds and executes the given command, filling in response
    response = None
    # This is where you start to implement more commands!
    if command.startswith(EXAMPLE_COMMAND):
        response = "Sure...write some more code then I can do that!"

    # Sends the response back to the channel
    slack_client.api_call(
        "chat.postMessage",
        channel=channel,
        text=response or default_response
    )

Enter fullscreen mode Exit fullscreen mode

The parse_bot_command() function now takes these events and ensure that they are pointed to the Bot. While there are several event types which our bot will come across, we need to find the commands we want using the message events.

Message events generally have sub-types but in the commands we will find, sub-types won't be defined. These function now filters the not-so-great events by checking the properties.

The next step being taken into consideration is wanting to know if the event represents a message but also finding out if the Bot is mentioned.

Our parse_direct_mention() function will then take the task upon itself of figuring out if the text starts with a mention and then compare it to the user ID we stored in the Bot which if being the same, we know is a bot command and then returns the command text in the channel ID.

The parse_direct_mentions() function will take an expression to determine if a user being confirm if a user has been mentioned in the message. Using this, it then gives us back the user ID with the message none if there is no mention.

Our last function handle_command() is where for future reference adds what makes up the slack bot. It carries a known command to make use of by sending a response to Slack by using the chat.postMessage to call back our Web API.

The code below shows an explanation

import os
import time
import re
from slackclient import SlackClient


# instantiate Slack client
slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN'))
# slack user ID in Slack: value is assigned after the bot starts up
slack_id = None

# constants
RTM_READ_DELAY = 1 # 1 second delay between reading from RTM
EXAMPLE_COMMAND = "do"
MENTION_REGEX = "^<@(|[WU].+?)>(.*)"

def parse_bot_commands(slack_events):
    """
        Parses a list of events coming from the Slack RTM API to find bot commands.
        If a bot command is found, this function returns a tuple of command and channel.
        If its not found, then this function returns None, None.
    """
    for event in slack_events:
        if event["type"] == "message" and not "subtype" in event:
            user_id, message = parse_direct_mention(event["text"])
            if user_id == starterbot_id:
                return message, event["channel"]
    return None, None

def parse_direct_mention(message_text):
    """
        Finds a direct mention (a mention that is at the beginning) in message text
        and returns the user ID which was mentioned. If there is no direct mention, returns None
    """
    matches = re.search(MENTION_REGEX, message_text)
    # the first group contains the username, the second group contains the remaining message
    return (matches.group(1), matches.group(2).strip()) if matches else (None, None)

def handle_command(command, channel):
    """
        Executes bot command if the command is known
    """
    # Default response is help text for the user
    default_response = "Not sure what you mean. Try *{}*.".format(EXAMPLE_COMMAND)

    # Finds and executes the given command, filling in response
    response = None
    # This is where you start to implement more commands!
    if command.startswith(EXAMPLE_COMMAND):
        response = "Sure...write some more code then I can do that!"

    # Sends the response back to the channel
    slack_client.api_call(
        "chat.postMessage",
        channel=channel,
        text=response or default_response
    )

if __name__ == "__main__":
    if slack_client.rtm_connect(with_team_state=False):
        print("Starter Bot connected and running!")
        # Read bot's user ID by calling Web API method `auth.test`
        starterbot_id = slack_client.api_call("auth.test")["user_id"]
        while True:
            command, channel = parse_bot_commands(slack_client.rtm_read())
            if command:
                handle_command(command, channel)
            time.sleep(RTM_READ_DELAY)
    else:
        print("Connection failed. Exception traceback printed above.")
Enter fullscreen mode Exit fullscreen mode

We can now run the code by using the following code below:

python3 starterbot.py
Enter fullscreen mode Exit fullscreen mode

The next step is creating a channel as shown below:

an image showcasing how to create a slack channel

an image showcasing how to create a slack channel

You can now start giving commands to your bot.

Conclusion

In conclusion, we looked at creating a Slack bot in this article and proper implementation and usage of the Slack API. A common application of this bot could be in the creation of an onboarding bot to your Slack application. It is also important to integrate a database and also play around with other APIs to integrate to Slack.

Top comments (0)