How I’m learning about APIs by building a Slackbot — Part 2
Tags: beginners, python, wecoded, slackbot
Published: 2020-09-27 12:52:10

Continuing on my [HelloWorld](https://dev.to/totorosyd/how-i-m-learning-about-apis-by-building-a-slackbot-part-1-2hmf) version, I completed my HelloGalaxy version. This journal article is to share the development process of heybot’s HelloGalaxy version, and one of my favourite challenges which there was no official documentation and I figured out my own solution (using private_metadata).

My full code is available [here](https://github.com/TotoroSyd/heybot/blob/master/heybot_v2.py).

----
# The Roadmap
##### [>>Project Overview](https://dev.to/totorosyd/how-i-m-learning-about-apis-by-building-a-slackbot-14h2)
##### [>> Part 1: Hello World version](https://dev.to/totorosyd/how-i-m-learning-about-apis-by-building-a-slackbot-part-1-2hmf)
##### >> Part 3: Final version (coming up next)
Recalling from my original roadmap, this is where we stand.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/11rqjc5c5syeyn3a2asn.jpg)
# Demo

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2720l916xkqgdak0sw20.gif)

# Objectives
In this version, my goals were:
1. To connect to BambooHR API by creating a trial account and Token key.
2. When I say “Hello”, heybot replies with action options (buttons).
3. When I click on a button to choose the service, heybot askes for employee ID via a `Modal`.
Heybot returns answers to me.

#Resources
#### BambooHR API
The [Documentation](https://documentation.bamboohr.com/docs) is clear and well organized; APIs are grouped by activities (Report, Login, Employees).
I wanted to build heybot for one single customer, so all I needed was **(1)** an account (**NOTE: a trial account is valid in 7 days only**), **(2)** subdomain is “*mycompany*” which I created when I opened my trial account (My company is **monsterinc** :D), **(3)** an API key which I made from my trial account “*Settings*”.
How I create an API key:
![How I create an API key](https://dev-to-uploads.s3.amazonaws.com/i/b0t1heniptc0x05vvl33.gif)
I recommend copying the API key and saving it in a note because we have to use it frequently.
One more important thing that you should note is the AUTHORIZATION_TOKEN (**"Basic NDJim********** **) in *headers*.

>Why do we need this?

We can't use the API key to send an HTTP Request to BambooHR directly. An API key will have to be converted into an authorization token.
Before running the Flask app, we have to "`export AUTHORIZATION_TOKEN= "NDJim**********`" to be an environment variable (similar to what I did to `SLACK_TOKEN` and `SLACK_SIGNING_SECRET` which I explained in [Step 3.1 here](https://dev.to/totorosyd/how-i-m-learning-about-apis-by-building-a-slackbot-part-1-2hmf)).

>How to use BambooHR API Key to find `AUTHORIZATION TOKEN`?

Go to the [API Reference](https://documentation.bamboohr.com/reference#get-employee) page and choose an API and "*Try It*". 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/udj3cfdnlgfa4veacwlb.gif)

# Challenges and Solutions
#### Find the right documentations on Slack API
Slack has been upgrading its API, and because of that, some features or methods were either discontinued or were still in use, but their support resources were deprecated.
In HelloGalaxy version, I needed to build:
1. rich-text messages containing buttons/ menu/ date picker
2. a modal.
For **#1**, Slack introduced [`Blocks`](https://api.slack.com/block-kit) (supported by **Block Kit Builder** to replace `attachments` according to this [post](https://api.slack.com/messaging/attachments-to-blocks). The purpose of this upgrade is to support more interactive features (vote, buttons, menu) or rich contents (video, link, image).

This is my action message built with `Blocks` and sent to the user by `chat_postMessage()` method. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/x011vjutzmaukih2536m.png)

You can see my [Block payload here](https://github.com/TotoroSyd/heybot/blob/master/messages.py) under `def understood_greeting(self, user)`.

For **#2**, `Modal` replaced `Dialogs` according to this [post](https://api.slack.com/block-kit/dialogs-to-modals). This change led to the retirement of [`dialog.open()`](https://api.slack.com/methods/dialog.open) method and the introduction of [`views.open()`](https://api.slack.com/reference/surfaces/views) method to open Modal (we can't use `chat_postMessage()` or other regular methods to open a `Modal`).
![A pop-up modal](https://dev-to-uploads.s3.amazonaws.com/i/7uc3mo47s34qqbbks631.gif)

#### Get content from request body with `Content-type = application/x-www-form-urlencoded`
Normally, working with `Content-type = application/JSON` is very easy and straightforward. We can use `request.data`, `request.get_data`, `request.json`, `request.get_json`.

`application/x-www-form-urlencoded` is a bytestring type file. So, if we use **`request.data`** (the common solution), we will get an empty string.
This [StackOverflow post](https://stackoverflow.com/questions/10434599/get-the-data-received-in-a-flask-request) is a comprehensive answer. My full answer with details was in here too. Below is my solution for your quick look.
```
@ app.route('/slack/request_handler', methods=['POST'])
def request_handler():
   # return an ImmutableMultiDict with 1 pair
   payload = request.form 

   # get value of key 'payload'
   a1 = payload['payload']  

   # Note that if you have single quotes as a part
   # of your keys or values, this will fail due to
   # improper character replacement.
   # Convert a string (representation of a Dict))
   # to a Dict
   a2 = json.loads(a1)

   # Get value of key "channel"
   channel = a2["channel"] 
   print(channel) 
   # {'id': 'D01ACC2E8S3', 'name': 'directmessage'}
```

#### Using private_metadata to solve view_submission payload's limitations.
**NOTE**: When a button clicked, it sends back a `block_actions` payload8 containing `channel_id`, `action_id`. When a modal is closed (made submission), a `view_submission` payload is sent.
I save samples of `block_actions` payload and `view_submission` payload [here](https://github.com/TotoroSyd/heybot/blob/master/payload.py).
> Context:
* A `Modal` appears to get Employee ID input from a user. Once the user provided the ID and clicked "*Submit*", a `view_submission` payload was sent to my server.

* I used that ID to get the answer from BambooHR.

* I wanted to reply to the user.

> Problem:

To call the correct function to send a `GET` request to BambooHR and build a right template message, need an [`action_id`](https://api.slack.com/messaging/attachments-to-blocks#callback_id_replacement) which I set when I created the `Block` message. `view_submission` payload **doesn't have** `action_id`.
In order to reply, a `channel_id` is a must. `view_submission` payload **doesn't have** `channel_id`.

> Solution:

There was no official documentation on this from Slack. However, I utilized [`private_metadata`](https://api.slack.com/block-kit/dialogs-to-modals#state) to store the `action_id` received from the `block_actions` payload.
This [Slack doc](https://api.slack.com/block-kit/dialogs-to-modals#responding) suggested storing `channel_id` in `private_metadata`.

> Pseudocode

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2nn14637n81fwtedceco.png)

> Code

```
# build view payload
def get_employee_id_modal(self, channel_id, action_id):
    private_metadata = {
       "channel_id": channel_id,
       "action_id": action_id
    }
    # jsonify a dict into a string 
    # (required type for private_metadata)
    json_private_metadata = 
    json.dumps(private_metadata)
    
    # return a block
    return ({
      "type": "modal",
      "private_metadata": json_private_metadata,
      "callback_id": "employee_id_modal",
      [....]
      })
```
-----
Thanks for reading!
Phoebe