Building a Slack Bot in Python
Tags: programming, python, tutorial
Published: 2023-07-29 00:57:28

##Introduction
In this article, we will be building a bot in python using the [Slack API](https://api.slack.com/). 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:

- Download [Python3](https://www.python.org/downloads/)
- [pip](https://pypi.org/project/pip/) and [virtualenv](https://virtualenv.pypa.io/en/latest/)
- A [Slack](https://slack.com/) account

##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.

```python
virtualenv slackbot

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

```python
source slackbot/bin/activate
```
 
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:

```python
pip3 install slackclient
```
The next step in the process is to [create a Slack App](https://api.slack.com/apps?new_app=1) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgsvdc34dasu84z5qy9i.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9buetiazi1km5u7zee0w.png)

##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](https://api.slack.com/bot-users) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nwhv4bgygl9p8wsmme33.png)


![The bot user section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xjd5jwxfhgl7eemywi5y.png)

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`:

```python
export SLACK_TOKEN='your bot user access token here'
```
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:

```python
import os
import time
import re
from slackclient import SlackClient
```
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:

```python
# 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].+?)>(.*)"
```
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:

```python
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.")
```
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:


```python
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
    )

```
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

```python
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.")
```

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

```python
python3 starterbot.py
```

The next step is creating a channel as shown below:

![an image showcasing how to create a slack channel](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zl8kgnd7sv51ysgytozs.png)


![an image showcasing how to create a slack channel](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tz591jf3maf9e0mifs2d.png)

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.