Slack APIとAWS (API Gateway + Lambda) でBotを作成してみた

# はじめに

Slack-botを本格的に作りたい！ということで触ってみた。
作成するBotは、メンションに付与する単語に応じて３パターンの応答を返す、とても簡単なBotです。

> ※これくらいの機能ならSlack標準のBotで充分実装できますが、
> 今後の拡張に向けた「穴通し」の位置づけとして、作成しています。

以下の記事を大変参考にさせて頂きました。図が豊富で分かりやすい。
https://nmmmk.hatenablog.com/entry/2018/10/10/001548


# できたもの
　![slackbot.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/b875cffd-de63-c2dc-5a46-e67d90c41da4.gif)


3パターンのBotです。

 1. 基本は、このbotを読んだユーザ名を返してくる。
 2. trelloと打つと、自分のTrelloダッシュボードのリンクが返す
 3. :+1:（いいねスタンプ）を打つと、花京院さん大好きな「チェリー」をくれます。

慣れてしまえば、20分くらいで作成できます。
※Lambda関数の実装部分が今後はキーとなってくる。


# システム構成

最低限の構成となってます。
![system.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/4563da7d-9b87-c9df-0c14-a0a59faacce9.png)

> draw.ioにslackのアイコンがないのは納得いかない。

# 手順 ①：関数作成
※１つずつのステップは[参考記事](https://nmmmk.hatenablog.com/entry/2018/10/10/001548)を参照。
Lambdaにて関数をPythonにて実装します。

## (1) 関数作成
![lambda.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/56a3adcc-47c6-df3a-c7a1-7280462b1bff.png)

## (2) Event APIの認証用ソースコードへ変更
Lambdaのソースコード編集画面にて、SlackのEvent APIの認証のため、以下に変更します。

```
def lambda_handler(event, context):
    
    # SlackのEvent APIの認証
    if "challenge" in event:
        return event["challenge"]
    
    return "OK"    
```
後述するSlack手順の「Request URL」を入力すると、Slack側との疎通が出来ているかを検証するのですが、そこでchallenge認証（Slackが"challenge"を送付するからそのまま"challenge"を返したらOK）をするために必要なコードです。


今回は、手順簡略化のため、ここで実際に動作させるソースコードに変更しちゃってください。

```
# -*- coding: utf-8 -*-
import os
import logging
import json
import urllib.request

# ログ設定
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    
    # 受信データをCloud Watchログに出力
    logging.info(json.dumps(event))

    # SlackのEvent APIの認証
    if "challenge" in event:
        return event["challenge"]
    
    # tokenのチェック
    if not is_verify_token(event):
        return "OK"    
    
    # ボットへのメンションでない場合
    if not is_app_mention(event):
        return "OK"    
    
    # Slackにメッセージを投稿する
    sgmes = event.get("event").get("text")
    sguser = event.get("event").get("user")

    if "trello" in sgmes:
        spmes = "https://datastudio.google.com/u/0/reporting/c62fdb64-5167-420b-a956-404d920834d7/page/VS0VB"
        
    elif ":+1:" in sgmes:
        spmes = ":cherries:"
        
    else:
        spmes = "君は、<@" + sguser + "> だね？"
    
    post_message_to_channel(event.get("event").get("channel"), spmes)

    return 'OK'


def post_message_to_channel(channel, message):
    url = "https://slack.com/api/chat.postMessage"
    headers = {
        "Content-Type": "application/json; charset=UTF-8",
        "Authorization": "Bearer {0}".format(os.environ["SLACK_BOT_USER_ACCESS_TOKEN"])
    }
    data = {
        "token": os.environ["SLACK_BOT_VERIFY_TOKEN"],
        "channel": channel,
        "text": message,
    }

    req = urllib.request.Request(url, data=json.dumps(data).encode("utf-8"), method="POST", headers=headers)
    urllib.request.urlopen(req)

def is_verify_token(event):

    # トークンをチェック    
    token = event.get("token")
    if token != os.environ["SLACK_BOT_VERIFY_TOKEN"]:
        return False

    return True
    

def is_app_mention(event):
    return event.get("event").get("type") == "app_mention"
```
[補足]
※1 ```if not is_app_mention(event):```
 これを記載しないと、Botの発言をBotが拾って「無限ループBot」の完成です。

※2 ```sgmes = event.get("event").get("text")```
　CloudWatchlogを参照すると、eventの構造が分かります。
　"@[Botuser名] あいうえお" と発言した時のログです。
　![cloudw.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/179e86cf-ea68-e218-cd61-06641da9c72e.png)



# 手順 ②：API作成
Lambda関数作成終わったら、そのトリガーとなるAPIを作成します。

## (1) トリガーの設定
API-Gatewayを選択して以下のように設定します。
ここでデプロイされるステージ（後の手順で利用する）を新規に作っておきましょう。

　![api-gate1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/f0658ca5-8daa-281a-ba1c-c33713741c04.png)


## (2) とりあえず大枠ができた。
　![api-gate2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/4fc0bd35-174d-18db-b83d-4bbc703d28d2.png)

なお、これはpost以外も許容している(`/*/*/`の部分)ので、後で削除します。


## (3) API-Gatewayでのメソッド作成
postメソッドを作成して、Lambda関数に先ほど作成した関数名を入力して保存します。
権限付与の確認メッセージが出たら承認してください。

　![api-gate3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/9fbed861-e331-0192-ee0c-18a7f0b52076.png)

Anyメソッドの削除を忘れずに。

## (4) デプロイ
メソッド作成完了したらこれをデプロイします。
デプロイ完了後に"post"の「URL呼出し」に記載のURLをメモしておきます。
※後述のslackのchallenge認証時にこれを貼り付けます。
　![api-gate4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/1a2d956b-4f11-c505-c7f5-4bba2777b23b.png)


## (5) Lambda画面に戻り、不要なAPI Gatewayを削除
上のAPI-Gatewayは`/*/*/`とpost以外も許容しちゃっているので、これは削除します。
下の`/*/post/`の方を残します。
　![lambd2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/ea1cc34b-d286-e01d-0253-e7f4cfcbb7bb.png)

# 手順 ③：APP作成
ここからはslack側の仕様変更で少し手順が異なるので、詳細に記載します。

## (1) slackAppページへGO
以下にアクセスする。
https://api.slack.com/apps
※ログインしてないと自分のワークスペースのURLなどを求められます。

## (2) Create New Appボタンを押下する。
　![sla1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/eb10ac90-1d97-df29-071b-933ce97bcebf.png)

## (3) Slack名とワークスペース設定
App NameとDevelopment Slack Workspaceを設定する。
　![sla2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/f718922d-15bb-eadc-b64a-4ec7ec4bccd6.png)

## (4) Event SubscriptionsでURL設定
Event Subscriptionsメニューを表示して、設定をOnにし、Request URLに、API Gateway設定時((4) デプロイ参照)に生成したURLをペーストする。
　![sla3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/6e6a9136-95f7-dc03-3336-e83689732e72.png)

認証されない場合は、"post"のURLでないかもしれません！
自分はここでずっと躓きました（笑）

## (5) Subscribe to Bot Eventsでメンション反応の設定
下の方にスクロールして、Subscribe to Bot Eventsの設定を行います。
Add Bot User Eventを押下し、「app_mention」を選択する。最後に、Save Changesボタンを押下する。
　![sla4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/8fcbb9cb-99f9-5a88-a158-b954e0054a8f.png)


## (6) OAuth & Permissionsでチャットの書き込み権限の付与
OAuth & Permissionsのページに移動し、下の方にスクロールし、Scopesの設定を行います。
Add an OAuth Scopeを押下し、「chat:write」を選択する。
　![sla5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/07d17e26-35f0-dad6-b40d-f951b09b0c29.png)

**参考**
slackの仕様変更で、chat:writeに一本化されたようです。
chat:write:userや:botという記事も見かけたので、ちょっと注意。そもそも選択肢にはないですが。
　![chatwrite.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/5f2980f7-485d-19e8-75fd-3d53af781996.png)

権限周りの設定はこれで以上です。

## (7) アイコンなどの設定
Bot作ってアイコン作らないとか、感性を疑われるので、設定しましょう。
Botの顔です！想いを乗せましょう。

Basic Informationに移動し、下の方にスクロールして、Display Informationで詳細な説明やアイコンを設定します。
　![sla6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/d9dcd1c0-0bea-2f8c-032d-06af03735d69.png)

## (8) インストール
最後にワークスペースにインストールします。
おっと、その前にどんな機能になったか見てみましょう。
Basic InformationのAdd features and functionalityを開いてみてください。
アクティブになってる機能を参照できます。なるほどー。

　![sla7-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/5e648dba-1039-4015-ca2a-ab6970ef387e.png)

※他にもSlash Commands機能とかもあります。

では、ワークスペースにインストールしましょう。
　![sla7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/d2b98beb-db12-3a52-de38-ffa6330de34e.png)
確認画面が出るので許可します。
※無料枠だと最大10個しか作成できないので注意。


# 手順 ④：環境変数設定
最後にLambdaに環境変数を設定しにいきます。
以下の通り設定します。

| 環境変数                    | 値                                     |
| --------------------------- | -------------------------------------- |
| SLACK_BOT_USER_ACCESS_TOKEN | SlackのBot User OAuth Access Tokenの値 |
| SLACK_BOT_VERIFY_TOKEN      | SlackのVerification Tokenの値          |

こんな感じ。
　![slaaws.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/497340ec-21e0-c043-b4b3-ab388b8cc013.png)

はい、以上で手順は終わりです。
実際にslack上で、メンション＋キーワードで投稿してみましょう。動きましたか？
うれしー！

# 感想
**やってよかった**

 - 普通にモノ作りを簡単に出来て、楽しい。
 - 記事書くことで、SlackのAPI仕様とかにも詳しくなれた。
    - といっても、まだ権限周りは100個以上あるので、『必要になった時』に学習していく必要あり。
 - AWSの一部もほんの少しだけ理解が進んだ。
  - なぜREST APIを利用したの？とか。
  - RESTfulってそもそもなんだ？とか。
  
> https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/services-apigateway.html
> - **HTTP API** – 軽量で低レイテンシーの RESTful API。
> - **REST API** – カスタマイズ可能で機能豊富な RESTful API。
> - **WebSocket API** – 全二重通信のためにクライアントとの永続的な接続を維持するウェブ API。

> https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html
![fin-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/a1b3c5b4-6ae4-c0cd-dc5b-482ac9aed31f.png)




**今後改善したいこと**

 - slackもAWSも学ぶのがちと遅い…アンテナ張るだけでなく、手を動かすところまでいかないと。


# 今後の展開
今回で基本的な"型"はできたので、あとはAWS側を発展させるのみ。そこが楽しく大変なんだけど！ｗ

それと、Slack側のUIもある程度豪華に出来るようなので、そちらも並行してやっていきたい。

 - [2020年のSlack App開発者が知るべき最新のSlack API仕様](https://tech.plaid.co.jp/slack_api_spec_trend_2020/)
 - [ここの日本語のサンプルとか良い。](https://qiita.com/seratch/items/628751be65de9eb23a80)
　　　 ![finish.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/384185/b877d263-21f9-44a0-d29e-64610d44dcac.png)