LINE WORKS トークBot をPythonで実装してみる 〜後編: チャットボット実装〜

# 前回
LINE WORKSのAPIを使うために必要な各種設定とTokenの発行までを行いました。
前回: https://qiita.com/mmclsntr/items/1d0f520f1df5dffea24b

今回は、実際のチャットボットの実装を行います。

# LINE WORKS トークBotの仕組み
ユーザーからのメッセージを受信し、それに応じた返答を返すという対話を行うために、LINE WORKSでは __トーク Bot API__ が用意されています。
トークBot APIの概要: https://developers.worksmobile.com/jp/document/3005001?lang=ja 

メッセージ送受信用Webアプリケーションを用意し、LINE WORKS Developer Console上でBotにコールバックURLとして紐付けを行うことで、LINE WORKS上にチャットボットを実装することができます。

今回は、PythonとAWS Lambdaを使って開発したいと思います。

# 検証環境
言語: Python 3.7
実行環境: AWS Lambda

今回は、AWS API Gateway + Lambdaの構成で作りました。

# 1. チャットボット開発
## 1.1. リクエスト検証
コールバックに設定したサーバーに送られて来たリクエストが正しいものであるか、その確認をする仕組みが用意されています。
https://developers.worksmobile.com/jp/document/1005009?lang=ja 

以下、コードサンプル。

```python
import os
from base64 import b64encode, b64decode
import hashlib
import hmac

API_ID = os.environ.get("API_ID")  # API ID

def validate_request(body, signature):
    """
    リクエスト検証
    """
    # API IDを秘密鍵に利用
    secretKey = API_ID.encode()
    payload = body.encode()

    # HMAC-SHA256 アルゴリズムでエンコード
    encoded_body = hmac.new(secretKey, payload, hashlib.sha256).digest()
    # BASE64 エンコード
    encoded_b64_body = b64encode(encoded_body).decode()

    # 比較
    return encoded_b64_body == signature
```

## 1.2. メッセージの受信
送られてくるメッセージのタイプは大きく分けて5つあります。

| タイプ | 説明 |
|:--|:--|
| message | 一般メッセージ。 |
| join | Bot を複数人トークルームに招待。 |
| leave | Bot が複数人トークルームから退室。 |
| joined | メンバーが Bot の属するトークルームに参加。 |
| left | メンバーが Bot の属するトークルームから退室。 |

特に、`message` には、下の4タイプあります。

| メッセージタイプ | 内容 |
|:--|:--|
| text | テキスト |
| location | 位置情報 |
| sticker | スタンプ |
| image | 画像 |

LINEと同じくスタンプメッセージにも対応してます。

メッセージ受信 - Callback 形式: https://developers.worksmobile.com/jp/document/1005009?lang=ja


## 1.3. 返答メッセージ返答

返答メッセージを送るために、メッセージ送信用のAPIを使います。

メッセージ送信: https://developers.worksmobile.com/jp/document/1005008?lang=ja

これは、リプライだけでなくプッシュ通知を行う際も使われます。
このAPIを利用する際に、前回発行したTokenを利用します。

送信できるメッセージのタイプは以下の通り。

| メッセージタイプ | 内容 |
|:--|:--|
| text | テキスト |
| image | 画像 |
| link | リンク |
| buttonTemplate | ボタンテンプレート |
| listTemplate | リストテンプレート |
| sticker | スタンプ |

以下、コードサンプル。

```python
import os
import json
import requests
import urllib

API_ID = os.environ.get("API_ID")  # API ID
SERVER_API_CONSUMER_KEY = os.environ.get("SERVER_API_CONSUMER_KEY")  # Server API Consumer Key
BOTNO = os.environ.get("BOTNO")  # Bot No

TOKEN = os.environ.get("LW_TOKEN")  # 発行したToken

def send_message(content, account_id):
    """
    送信
    """
    url = 'https://apis.worksmobile.com/' + API_ID + '/message/sendMessage/v2'
    headers = {
          'Content-Type' : 'application/json;charset=UTF-8',
          'consumerKey' : SERVER_API_CONSUMER_KEY,
          'Authorization' : "Bearer " + TOKEN
        }
    params = {
            "botNo" : int(BOTNO),
            "accountId" : account_id,
            "content" : content
        }

    form_data = json.dumps(params)

    r = requests.post(url=url, data=form_data, headers=headers)
    if r.status_code == 200:
        return True

    return False

```

※ 注意
>メンバー数によってクォータ(最大送信数の制限)が設定されています。
メンバー数が 100 人以下 : 最大 20,000 回 / 日
メンバー数が 100 人超過 : 最大 20,000 回 + (超過するメンバー数)*100 回 / 日
クォータは ０ 時(GMT +9)にリセットされます。
クォータの 80%、90%、100% に到達すると、管理者に通知メールを送信します。

## 1.4. コード全体
サンプルとして、「こんにちは」と送られたら「こんにちは！」と返す超シンプルなボットを作れるコードを貼ります。

```python:handler.py
import os
import json
import requests
import urllib

from base64 import b64encode, b64decode
import hashlib
import hmac

from requests.structures import CaseInsensitiveDict

API_ID = os.environ.get("API_ID")  # API ID
SERVER_API_CONSUMER_KEY = os.environ.get("SERVER_API_CONSUMER_KEY")  # Server API Consumer Key
BOTNO = os.environ.get("BOTNO")  # Bot No

TOKEN = os.environ.get("LW_TOKEN")  # 発行したToken


def validate_request(body, signature):
    """
    リクエスト検証
    """
    # API IDを秘密鍵に利用
    secretKey = API_ID.encode()
    payload = body.encode()

    # HMAC-SHA256 アルゴリズムでエンコード
    encoded_body = hmac.new(secretKey, payload, hashlib.sha256).digest()
    # BASE64 エンコード
    encoded_b64_body = b64encode(encoded_body).decode()

    # 比較
    return encoded_b64_body == signature


def send_message(content, account_id):
    """
    送信
    """
    url = 'https://apis.worksmobile.com/' + API_ID + '/message/sendMessage/v2'
    headers = {
          'Content-Type' : 'application/json;charset=UTF-8',
          'consumerKey' : SERVER_API_CONSUMER_KEY,
          'Authorization' : "Bearer " + TOKEN
        }
    params = {
            "botNo" : int(BOTNO),
            "accountId" : account_id,
            "content" : content
        }

    form_data = json.dumps(params)

    r = requests.post(url=url, data=form_data, headers=headers)
    if r.status_code == 200:
        return True

    return False


def handler(event, context):
    """
    チャットボット処理
    """
    event = CaseInsensitiveDict(event)
    headers = event["headers"]
    body = event["body"]

    # リクエスト検証
    if not validate_request(body, headers.get("x-works-signature")):
        # 不正なリクエスト
        return

    # Jsonへパース
    request = json.loads(body)

    # 送信ユーザー取得
    account_id = request["source"]["accountId"]

    res_content = {
        "type" : "text",
        "text" : "テキストのみ対応"
    }

    # 受信したメッセージの中身を確認
    request_type = request["type"]
    ## Message
    if request_type == "message":
        content = request["content"]
        content_type = content["type"]
        ## Text
        if content_type == "text":
            text = content["text"]
            if text in "こんにちは":
                res_content = {
                    "type" : "text",
                    "text" : "こんにちは！"
                }
            else:
                res_content = {
                    "type" : "text",
                    "text" : "・・・"
                }

    # 送信
    rst = send_message(res_content, account_id)

    res_body = {
        "code": 200,
        "message": "OK"
    }
    response = {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json"
        },
        "body": json.dumps(res_body)
    }

    return response
```


# 2. デプロイ
AWS Lambdaへデプロイ (ランタイム: Python 3.7)
トリガーとしてAPI Gatewayを置く。

# 3. Botの登録
最後に、Botをテナントへ登録する。

1. LINE WORKS Developer Consoleで、Botの設定の「Callback URL」に、今回作成したAPI Gatewayのエンドポイントを設定する。
2. LINE WORKS 管理画面で、「サービス>Bot」から上記Botを追加する。

## 最後に
これで、LINE WORKS トークBotをPythonで実装することができました。
LINE WORKSには他にもいろいろAPIがあるので、社内システムとの連携も含め、多彩なチャットボットが実現できると思います。
今の所、SDKがないので自前でURL叩く必要があります。。SDK欲しいですねー

# 参考
https://qiita.com/comefigo/items/ce573e4ff5d86519eb16
https://qiita.com/tokotan/items/976d35ca56132e0bb5c1
https://utano.jp/entry/2018/01/hmac-sha-256-python/