How I’m learning about APIs by building a Slackbot — Part 1
Tags: beginners, python, wecoded, slackbot
Published: 2020-09-21 05:07:45

This is my journal article for the development process of heybot’s Hello world version. I will write from a beginner’s perspective so it can be lengthy (a bit), but I hope other beginners will find it useful.

The project overview is [here](https://dev.to/totorosyd/how-i-m-learning-about-apis-by-building-a-slackbot-14h2).

[fullcode](https://github.com/TotoroSyd/heybot/blob/master/heybot_v1.py)

# Objective
In this version, my purposes were:
1. Connect heybot (the back-end is stored in my laptop) to Slack server
2. When I say “Hello”, heybot replies “Hello <my_slack_username>”

# Know the resources
These resources are all I need to build the Helloworld version and upgrade it to the final version.
#### General knowledge about API
<I will have a separated post for this>
#### Flask
I didn’t have a server, so using Flask I can create my webserver. With Flask I can handle web requests quickly (with no configuration).
#### ngrok
Thanks to Flask, I technically have a server on my Mac. But I am still invisible to other web services (servers) — in this case, they are Slack and BambooHR.

**Wait! Why? Don’t you usually still access those services via your browser?**
Ok! I can “talk” to them because I am one of their **clients**. They are visible in public web with a static address (domain). So, I can find them, ask them to give me the services whenever I want. They are going nowhere(unless they shut down their businesses).

Back to “*But I am still invisible to other web services (servers)*”, I’m having heybot as a service provider. According to the explanation above, if I want to “run my business”, I need to have a static address, too. This means I need to have a public address that can be found by other clients (can be another service provider (Slack, BambooHR) or a regular user).

In a nutshell, ngrok helps to create a tunnel so those web services can “see/ find” me. And, when they send me requests, thanks to ngrok’s tunnel, I can receive and respond to those requests.

#### Slack API
They have a few core APIs — Web API, Event API and other APIs.

#### Developer Kit for Python — slackclient
Slack prepares some tool kits for different programming languages to work with their server.

#### Slack Events API adapter for Python — python-slack-events-api
After hours of internet browsing and reading tutorials and docs, to build the Helloworld version, using Slack Events API adapter is a recommended and preferred approach. It is also easy to use too.
**Hint**: You might find some tutorials using RTM — Real Time Messaging API. According to this tutorial, _“you can stream events through a websocket connection with our RTM API. The RTM API is only recommended if you’re behind a firewall and cannot receive incoming web requests from Slack. ⚠️ The RTM API isn’t available for default Slack apps. If you need to use RTM (possibly due to corporate firewall limitations), you can do so by creating a classic Slack app”_.

# Step-by-step
There are plenty of materials to help you create a simple Slackbot. So, I won’t repeat what they said. Instead, I follow the same steps, link to what materials I found useful, and I only write what I figured out.

⚠️Before starting, remember to set up a virtual environment. It is a **MUST**.
#### 1. Create a Slack app
This step is straightforward on Slack API docs and also in [this step of this tutorial](https://github.com/slackapi/python-slackclient/blob/main/tutorial/01-creating-the-slack-app.md). 

#### 2. Build the message
In [step 2 of this tutorial](https://github.com/slackapi/python-slackclient/blob/main/tutorial/02-building-a-message.md), they prepared a complex message, but Helloworld version only needs heybot to say “*Hello <username>*”. So, I skipped that step.

#### 3. Prepare to pass URL verification challenge from Slack
The URL generated by ngrok must pass this test.
You should print the request to see what it has. Trust me, it helps a lot, knowing what is sending to you.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/uhcdlunt1q5qa0oi15r6.png)
```python
def reply():
 # print(request.headers)
 # print(request.data)
 # print(request.args)
 # print(request.form)
 # print(request.endpoint)
 # print(request.method)
 # print(request.remote_addr)
    
 # get ready to receive and respond HTTP POST
 # request from Slack to verify bot's endpoint URL
 if request.method == 'POST':
  challenge_parse = (json.loads(request.data)) 
                   ['challenge']
  # print(challenge_parse)
  # respond URL verification from Slack  
  # with 'challenge' value
  response = {"challenge": challenge_parse}
  return response, 200
```
#### 3.1 Respond to Slack Event
This step is the key to make heybot alive. It is in [step 3 of this tutorial] (https://github.com/slackapi/python-slackclient/blob/main/tutorial/03-responding-to-slack-events.md).
_Explanation:_
```python
# import dependencies to obtain the environment variable values
import os 
import slack
from slackeventsapi import SlackEventAdapter
import json
from flask import Flask, request, jsonify

# Import environment var by retrieving exported token https://slack.dev/python slackclient/auth.html
SLACK_BOT_TOKEN = os.environ['SLACK_BOT_TOKEN']
SLACK_SIGNING_SECRET = os.environ['SLACK_SIGNING_SECRET']
```
###### What is os?
The OS module in Python provides functions for interacting with the operating system. In this case, I had to use SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET. They are confidential, so we shouldn’t set them as variables in our code. I made them as _environment variables_ (use `export` command) => if I share the code, no one can see unless I gave them the tokens. So, os helps to call out _environment variables_.
###### What is json module?
Data in a request that a server sends/ receives is a JSON object. I need to parse JSON (in Python, parse JSON means `json.loads`)
                      ----------
```python
# create the Flask server
app = Flask(__name__)

# Initialize a Slack Event Adapter for receiving actions 
slack_events_adapter = SlackEventAdapter(SLACK_SIGNING_SECRET, '/slack/events', app)

# Instantiate a Web API client
slack_web_client = slack.WebClient(
token=os.environ['SLACK_BOT_TOKEN'])
```
slackclient v2 separated WebClient and RTMClient. You can read about the differences in this [Migration guide](https://github.com/slackapi/python-slackclient/wiki/Migrating-to-2.x).
                       ----------
```python
# Routing
@app.route('/', methods=['POST', 'GET'])
```
All functions go under this route.

In Flask docs, you see they create multiple _routes_, which is for building a website where you have multiple pages, and each page has its _path_ (_route_). We are building a chatbot, and we only need one end-point handling requests sent from Slack.
                      ----------
```python
# When a user sends a DM, the event type will be
# 'message'.
# Link the message callback to the 'message' event.
# Choose to use Event API (handled by
# SlackEventAdapter) instead of RTM API.

@slack_events_adapter.on("message")
def say_hello(event_data):
     message = event_data['event']

     # if the incoming message contains "hello" 
     # NOT CASE SENSITIVE, respond with a message
     # check if the message is from a bot or not.  
     # If Yes, do nothing. If No, reply.
     if message.get('bot_id') is None and 'hello' 
     in ((message.get('text')).lower()):
          channel_id = message['channel']
          user = message['user']
          message = "Hello <@%s>! :tada:" % user
          slack_web_client.chat_postMessage 
          (channel=channel_id, text=message)
     else:
          return

# Error events
@slack_events_adapter.on("error")
def error_handler(err):
     print("ERROR: " + str(err))

slack_events_adapter.start(port=5000)
```
The syntax for this code you can read from [this example](https://github.com/slackapi/python-slack-events-api/blob/main/example/example.py), [`chat_postmessage`](https://api.slack.com/methods/chat.postMessage) method.

# Challenges and Solutions
#### heybot can’t distinguish between its messages and user’s messages
Because of that, it replies non-stop.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1ugoeq4m480q7tgar2pc.png)

Solution on [this StackOverflow post](https://stackoverflow.com/questions/59489908/slack-events-api-events-are-posted-multiple-times-for-responses-of-own-bot-us/59519428#59519428) was the most relevant and well-described, but it mentioned a solution checking for `subtype` in the request’s `event`, which wasn’t there.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/81c6sdqpqpy7enpn8e6w.jpg)
My solution was to check for `bot_id` from `event`.
```python
# some code
if message.get('bot_id') is None .....:
# some code
```

#### [Errno 48] Address already in use
Occasionally, when you run ngrok, this error comes because sometimes the system didn’t kill the program process properly when you asked.

The solution is to use `kill -9`, which is [not very recommended](https://unix.stackexchange.com/questions/8916/when-should-i-not-kill-9-a-process) but it worked.
```bash
# list of processes using the port if any 
sudo lsof -i:5000   
# or 
ps -a 

# use the id on the PID column to terminate the process use 
kill (PID)

# if the above doesnt solve the problem, use this. Not very recommended :D. 
kill -9 (PID)
```

Phew! Thanks for reading this lengthy post. You are awesome! I explained the technical work with my own knowledge, so if they aren’t correct, please tell me.

Phoebe