SlackAPPを使ったインタラクティブBOT作成

# はじめに
この記事は**「Zoomライセンスの切り替えをSlackでやってみた」**の一環で投稿した記事です。

SlackAPPの作成から、APPからのメッセージ送信、スラッシュコマンドの利用、Interactiveメッセージを使ったAppの対話と、SlackAPPで使う上での基本的な部分を説明します。

https://qiita.com/matsutogen/items/9152ad13bf26337d1b57


# この記事で知れること
+ SlackAPPの作成方法
+ SlackAppからのメッセージ送信方法
+ スラッシュコマンドど実装方法
+ Interactiveメッセージ（Blockメッセージ）の実装方法
+ Interactiveメッセージを使ったAPPとの対話方法


# １．Slack APPの作成
## ワークスペースにAppを追加

https://api.slack.com/apps/

にアクセスし、**Create New App** からAPPを登録します。

+ AppName：任意
+ WorkSpace：Appを登録したいワークスペース

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/5f0850a8-00b6-c2af-1f70-ce6ad9aa5278.png" width=70%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/5a2f2e58-ce2f-27ce-d4ce-972efe5ad768.png" width=70%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/0b3acfea-45bc-7573-f801-b27edf0aeffe.png" width=70%>


# APPに権限の追加
APPの設定ページよりAPPの権限を設定します。

+ 「OAuth & Permissions」を選択
+ 「Scopes － Bot Token Scopes　－　Add an OAuth Scope」より以下の権限を追加
    + chat:write
    + commands
    + im:history
    + im:write

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/b58fe3c9-aad6-9bce-b36e-bd7cd4e6867c.png" width=70%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/c4caf61e-455d-3410-d986-b7b4b7e2eab0.png" width=70%>

### Interactivity & Shortcuts の設定
Interactiveメッセージを使用するための初期設定をします。

+ 「Interactivity & Shortcuts」を選択。InteractivityをONにする
+ 「Request URL」にはInteractiveityイベントを受信する用のURLを設定する。<br/>ここではFQDN ＋`/slack/interactive` とする
<br/>（例）`https://******.herokuapp.com/slack/interactive`

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/5d7bfd19-cd9b-0dcb-848f-320b51bbb103.png" width=70%>

## Slash Commands の設定
スラッシュコマンドを使用するための初期設定をします。

+ Slash Commandsを選択する
+ 「Create New Command」をクリックし、コマンドの情報を入力後Saveする
    + Command：スラッシュコマンド名（`/zoom`など）
    + Request URL：スラッシュコマンドが実行された際にPOSTイベントを受信する用のURLを設定
    + Short Description：任意
    + Usage Hint：任意

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/48737526-4604-8d4f-49f2-eed58d9fb82a.png" width=70%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/3a4e1160-6e36-aeb8-0d6e-d5bab64da078.png" width=70%>


## APPのインストール
各種設定が完了したらワークスペースにAPPをインストールします。

+ 「OAuth & Permissions」を選択し、「Install to Workspace」をクリックしてAPPをインストールする
+ 「＊＊＊権限をリクエストしています」の画面がでたら「許可する」をクリック
+ **Bot User OAuth Token**が表示されればOk

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/8163f69e-7b5e-c28a-ffc1-b2ba6e849fb7.png" width=70%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/023c0588-1fb1-27cd-5f41-a891964bf1b5.png" width=70%>



# ２．channelにメッセージを送信してみる
APPのインストールが完了したら、試しにAPPからチャネルにメッセージを送信してみます。

+ インストールしたAPPをSlackの任意のチャネル参加させる<br/>
Slcakのチャネル設定 － インテグレーション － アプリの追加より追加する
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/89dfe84b-71b5-5f34-2c5d-cb63e693226d.png" width=70%>
+ SlackAPPを使うのに必要なライブラリ（`slack_sdk`）をインストール

```
pip install slack_sdk
```

+ 以下のように`chat_postMessage`APIを使ってチャネルにメッセージを送信する
    + SLACK_TOKEN：APPのインストール時に「OAuth & Permissions」に表示された`Bot User OAuth Token`
    + CHANNELID：APPを追加したChannelのID

```python:slack_mdule.py
from slack_sdk import WebClient

SLACK_TOKEN = "xoxb-xxxxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
CHANNELID = "Cxxxxxxxxxx"

#SLACK にメッセージ送信
def dm_send( send_message):
    client = WebClient(SLACK_TOKEN)

    # DMを送信する
    client.chat_postMessage(channel=CHANNELID, text=send_message)

dm_send("テストメッセージ")
```

※チャネルIDはチャネル情報から確認できます
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/9c18467c-4ccf-44ea-b69f-17b7b29982a4.png" width=30%>


# ３．スラッシュコマンドを使ってみる
スラッシュコマンドの使用イメージは以下です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/4890c876-4f75-8b76-062a-d157a644ec71.png" width=100%>

スラッシュコマンドを実行したらSlackAPPから「[Slash Commands の設定](#slash-commands-の設定)」で設定したRequest URLにPOSTメッセージが送信されます。


今回は、スラッシュコマンドである「/zoom」を実行したらチャネルにテストメッセージを送信するコードを実装してみます。

###スラッシュコマンド受信部分

```python:webservice.py
from bottle import debug, route, run, request ,Response
import slack_module as slackm

##############################
# スラッシュコマンド受信route
##############################
@route("/slashcommand", method=['POST'])
def slash_zoom():
    print("slash")
    print(vars(request.params))

    channel_id = request.params["channel_id"]
    slackm.dm_send("スラッシュコマンド受信しました", channel_id)
    return Response(message="Ok")

##############################
# 初期起動処理：Webサービス起動
##############################
if __name__ == "__main__":
    print('starting  webdyno')
    debug(True)
    run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000)),)
```

### メッセージ送信モジュール部分

```python:slack_mdule.py
from slack_sdk import WebClient

SLACK_TOKEN = "xoxb-xxxxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"

#SLACK にメッセージ送信
def dm_send( send_message, channel_id):
    client = WebClient(SLACK_TOKEN)

    # DMを送信する
    client.chat_postMessage(channel=channel_id, text=send_message)
```

これをHerokuにデプロイし、APPを追加したチャネルで`/zoom`を実行してAPPから「スラッシュコマンド受信しました」が返信されればOKです。


# ４．インタラクティブメッセージを送信してみる

Interactive Messageは、SlackAPPと対話形式でやり取りできるようにボタンやプルダウンメニューなどを含んだメッセージのことです。

Interactive Messageを使うにはBlockKitという仕組みを使い、APPの送信メッセージ内にボタンやプルダウンメニューメッセージを含めることで、Slack上でボタンやプルダウンUIを表現することができます。
ボタンやメニュー以外にも結構色々なことを表現できるので、BlockKitを使うことでSlackでできることが広がると思います。

詳細については、以下の公式ドキュメントを参照してください。とても細かく記載してれています。

https://api.slack.com/block-kit



ここでは、サンプルとしてプルダウンメニューをAPPからチャネルに送ってみます。

### Block メッセージJson
BlockメッセージはJson形式で作成します。

どんなjson形式を送ればいいか？はSlackさんが「[Block Kitビルダー](https://app.slack.com/block-kit-builder/)」という素晴らしいツールを用意してくれているので、自身で送りたいイメージを作ってjsonイメージを確認してみてください。


以下はブルダウンメニューのjsonです。

```json:pulldown.json
{
    "type": "section",
    "text": {
        "type": "mrkdwn",
        "text": "■プルダウンから選んでね"
    },
    "accessory": {
        "type": "static_select",
        "placeholder": {
            "type": "plain_text",
            "text": "＜メニューを選択＞",
            "emoji": true
        },
        "options": [
            {
                "text": {
                    "type": "plain_text",
                    "text": "●朝ごはん",
                    "emoji": true
                },
                "value": "breakfast"
            },
            {
                "text": {
                    "type": "plain_text",
                    "text": "●昼ごはん",
                    "emoji": true
                },
                "value": "lunch"
            },
            {
                "text": {
                    "type": "plain_text",
                    "text": "●晩ごはん",
                    "emoji": true
                },
                "value": "dinner"
            }
        ],
        "action_id": "eatmenu"
    }
}
```

### メッセージ送信API部分

```python:slack_module.py
from slack_sdk import WebClient
import json

SLACK_TOKEN = "xoxb-xxxxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
CHANNELID = "Cxxxxxxxxxx"

with open('./pulldown.json' ,encoding="utf-8_sig") as f:
    pulldownmenu = json.load(f)

#SLACK にメッセージ送信(blockメッセージ送信)
def dm_send_block ( send_message, channel_id, block_template ):
    print(send_message)
    print(json.dumps(block_template))
    client = WebClient(SLACK_TOKEN)

    # DMを送信する
    client.chat_postMessage(channel=channel_id, text=send_message, blocks=block_template)


dm_send_block("スラッシュコマンド受信しました", CHANNELID, pulldownmenu)
```

blockメッセージを送信する場合はchat_postMessageAPIの`blocks`にjsonを渡してあげます。
なお、`text`パラメータに文字列を設定してもチャットメッセージとしては表示されません（Slackでメッセージ通知される際のnotificationにはtextに設定した文字列が表示されるようです）。
　
Slackにメッセージが送信されると以下のように表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/c5e0e276-f28f-a3ee-47d6-d751c71a61cf.png)


# ５．対話形式でインタラクティブメッセージを使ってみる
先程、Blockメッセージを使用しプルダウンメニューを送信しましたが、ユーザがプルダウンメニューを選択した際に決められたアクションを起こすことができれば、対話形式で会話することが可能になります。

例えば

1. ユーザ：スラッシュコマンドを実行
1. APP：ユーザにプルダウンメニューメッセージを送信
1. ユーザ：プルダウンメニューから項目を選択
1. APP：項目に応じた処理を実行しユーザに通知

といった感じでメッセージのやり取りができます。

ここではサンプルとして、上記のやり取りを実施するAPPを作ってみます。

＜必要な処理＞

+ プルダウンメニュー用Block メッセージJsonの作成
+ インタラクティブイベント受信ルート＆イベント受信処理の作成

### プルダウンメニュー用Block メッセージJsonの作成

これは[「４．インタラクティブメッセージを使ってみる」](#block-メッセージjson)で作ったjsonをそのまま使います。

### インタラクティブイベント受信ルート＆イベント受信処理の作成
```python:webservice.py
from bottle import debug, route, run, request ,Response
import os
import slack_module as slackm
import json

# Block用jsonの読み込み
with open('./pulldown.json' ,encoding="utf-8_sig") as f:
    pulldownmenu = json.load(f)

##############################
# スラッシュコマンド受信route
##############################
@route("/slash/zoom", method=['POST'])
def slash_zoom():
    print("slash")
    print(vars(request.params))

    channel_id = request.params["channel_id"]
    # slackm.dm_send("スラッシュコマンド受信しました", channel_id)
    slackm.dm_send_block("スラッシュコマンド受信しました", channel_id, pulldownmenu)
    return Response(message="Ok")

##############################
# interactive Event受信route
##############################
@route("/slack/interactive", method=['POST'])
def interactivemessage():
    print("interactive")

    #payloadを取り出す
    param=request.params["payload"]
    param_dic = json.loads(param)
    print("param:" + param)

    #送信者のチャネルIDを取得
    channel_id = param_dic["container"]["channel_id"]

    #Blockメセージ情報を取り出す
    actions = param_dic["actions"]

    #スタートメニュー
    if actions[0]["action_id"]  == "eatmenu":
        #選択したプルダウン毎に処理を分岐
        #valueがbreakfastの場合
        if actions[0]["selected_option"]["value"] == "breakfast":
            sendmsg = "breakfast 食べましょう"

        #valueがlunchの場合
        elif actions[0]["selected_option"]["value"] == "lunch":
            sendmsg = "lunch 食べましょう"

        #valueがdinnerの場合
        elif actions[0]["selected_option"]["value"] == "dinner":
            sendmsg = "dinner 食べましょう"

        slackm.dm_send(sendmsg, channel_id)

    return Response(message="interactive resend")

##############################
# 初期起動処理：Webサービス起動
##############################
if __name__ == "__main__":
    print('starting  webdyno')
    debug(True)
    run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000)),)
```

インタラクティブイベントを受信するというのは、BlockKitメッセージの応答のことを指します。
例えばプルダウンメニューを送信し、ユーザがプルダウンメニューを選択した際に選択した情報が送信されますが、これがインタラクティブイベントになります。

インタラクティブイベントの受信パスは[「Interactivity & Shortcuts の設定」](#interactivity--shortcuts-の設定)で設定した「Request URL」になります。

なので、Routeのパス指定も同様に`@route("/slack/interactive", method=['POST'])`とします。
ユーザから送信された情報は`param`にjson形式で設定されているので、その中から受信処理に必要な情報を抽出して処理を作ります。

※どんな構造で情報が設定されているかは、paramをダンプして確認してください。

これをHerokuにデプロイし、APPを追加したチャネルでスラッシュコマンドを実行しすると以下のように双方向で対話できました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1955406/7c681438-38cb-b9b4-d791-853497d5bba4.gif" width=100%>