DEV Community

Cover image for How I’m learning about APIs by building a Slackbot — Part 1
Phoebe
Phoebe

Posted on • Updated on

How I’m learning about APIs by building a Slackbot — Part 1

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.

fullcode

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 ”

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

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.

2. Build the message

In step 2 of this tutorial, they prepared a complex message, but Helloworld version only needs heybot to say “Hello ”. 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

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
Enter fullscreen mode Exit fullscreen mode

3.1 Respond to Slack Event

This step is the key to make heybot alive. It is in step 3 of this tutorial.
Explanation:

# 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']
Enter fullscreen mode Exit fullscreen mode
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)
----------

# 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'])
Enter fullscreen mode Exit fullscreen mode

slackclient v2 separated WebClient and RTMClient. You can read about the differences in this Migration guide.
----------

# Routing
@app.route('/', methods=['POST', 'GET'])
Enter fullscreen mode Exit fullscreen mode

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

# 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)
Enter fullscreen mode Exit fullscreen mode

The syntax for this code you can read from this example, 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

Solution on this StackOverflow post 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
My solution was to check for bot_id from event.

# some code
if message.get('bot_id') is None .....:
# some code
Enter fullscreen mode Exit fullscreen mode

[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 but it worked.

# 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)
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)