Hangouts Chat のチャットボットを、Python on Google App Engineを使って実装してみた（２）

#はじめに
[前回のやまびこBOT](https://qiita.com/nagase/items/97ae8d7ca22c0b840017) を DialogFlow と連携させて、より「BOTらしい」アプリケーションを実装してみたいと思います。

#DialogFlow の準備
今回は[こちらの投稿](https://qiita.com/kenz_firespeed/items/0979ceb05e4e3299f313)にある例（前半「Intentを作る」まで）に倣って、DialogFlow 側のテスト環境を準備しました。
尚、DialogFlow の詳しい設定方法等は、紹介した投稿内で丁寧に説明されていますので、ここでは割愛します。

#DialogFlow Python SDK のインストール

[DialogFlow Python SDK](https://github.com/dialogflow/dialogflow-python-client![screencapture-chat-google-dm-jNN4pAAAAAE-2018-04-12-10_46_31.png](https://qiita-image-store.s3.amazonaws.com/0/94476/ca051d38-4e5f-f5ad-a602-cac3c8c01278.png)
) をインストールします。

```bash
$ pip install apiai -t lib
```

尚、Python on GAE では、C言語で開発されたモジュールをアップロードしても利用することが出来ない（仮にアップロードしても動かない）為、GAE側で用意された[組み込みサードパーティ ライブラリ](https://cloud.google.com/appengine/docs/standard/python/tools/built-in-libraries-27?hl=ja)を使う必要があります。
そこで、`DialogFlow Python SDK` をインストールした際、依存関係でインストールされた `numpy` がC言語で開発されている為、これをディレクトリごと削除し、Python仮想環境へ `numpy` をインストールし直します。
また、インストールする `numpy` のバージョンは GAE 組み込みモジュールのバージョンに合わせます。

```bash
$ rm -rf lib/numpy*
$ pip install numpy==1.6.1
```

`app.yaml` に GAE の組み込みモジュール `numpy` を利用する設定を追加します。

```bash
$ cat <<EOF >>app.yaml
libraries:
- name: numpy
  version: "1.6.1"
EOF
```

#DialogFlow のアクセストークンを取得

DialogFlow を API 経由で利用する場合、`Client access token` が必要なため、次の手順でアクセストークンを取得します。

DialogFlow コンソールを開き、エージェント名の右側に表示されている歯車マークをクリックします。
![screencapture-console-dialogflow-api-client-2018-04-11-16_04_57 - コピー.png](https://qiita-image-store.s3.amazonaws.com/0/94476/41b9cce8-bb53-8869-c9a0-98507ff3c99b.png)

表示されたページに `Client access token` が表示されます。
![screencapture-console-dialogflow-api-client-2018-04-11-16_04_57.png](https://qiita-image-store.s3.amazonaws.com/0/94476/e7bc9be6-af99-4e94-889e-6a3148b6aece.png)

#アクセストークンを環境変数として設定

`app.yaml` 内にアクセストークンを環境変数として設定することもできますが、トークンはその性質上秘匿したい情報にあたり、GitHub に誤ってコミットされないよう別ファイルに定義します。

秘匿したい環境変数を定義する `secret.yaml` を作成し、アクセストークンを記述します。

```bash
$ cat <<EOF >secure.yaml
env_variables:
  CLIENT_ACCESS_TOKEN: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
EOF
```

`app.yaml` に、`secret.yaml` をインクルードする設定を追加します。

```bash
$ cat <<EOF >>app.yaml
includes:
- secret.yaml
EOF
```

`.gitignore` へ `secret.yaml` を追加します。

```bash
$ cat <<EOF >>.gitignore
secret.yaml
EOF
```

#BOTアプリケーションの変更
Hangouts Chat からのリクエストメッセージを DialogFlow に渡し、DialogFlow からのレスポンスメッセージを Hangouts Chat へ返すよう、前回作成した `bot.py` を変更します。



```bash
$ vi bot.py
```

```python:bot.py
#!/usr/bin/env python
# coding: utf-8
"""Example bot that returns a synchronous response."""

from flask import Flask, request, json

app = Flask(__name__)

import os
import re
from apiai import ApiAI
from uuid import uuid4

@app.route('/', methods=['POST'])
def on_event():
  """Handles an event from Hangouts Chat."""
  event = request.get_json()
  if event['type'] == 'ADDED_TO_SPACE' and event['space']['type'] == 'ROOM':
    text = 'Thanks for adding me to "%s"!' % event['space']['displayName']
  elif event['type'] == 'MESSAGE':
    ''' 元のコードをコメントアウト
    text = 'You said: `%s`' % event['message']['text']
    '''

    ''' 追加（ここから） '''
    ai = ApiAI(os.environ['CLIENT_ACCESS_TOKEN'])
    req = ai.text_request()                         #リクエストはテキストデータ
    req.lang = 'ja'                                 #リクエストメッセージは日本語(ja)
    req.session_id = str(uuid4())                   #DialogFlow間のセッションIDをランダムなUUID型でセット
    req.query = re.sub(r'^@.* ', '', event['message']['text'])  #メッセージからメンション("@ボット名 ")を削除
    res = req.getresponse()                         #DialogFlowへリクエスト、レスポンスを取得
    dic = json.loads(res.read())                    #レスポンスボディを辞書型に変換
    text = dic['result']['fulfillment']['speech']   #レスポンスボディからテキストメッセージを取得
    ''' 追加（ここまで） '''

  else:
    return
  return json.jsonify({'text': text})

if __name__ == '__main__':
  app.run(port=8080, debug=True)
```

#ローカルテスト
テストデータ（`event.json`）のメッセージ部分（['message']['text']）を、DialogFlow にリクエストする日本語メッセージ（`@Demo １日の東京を予約` 等）へ変更します。

```json:event.json
{
  "type": "MESSAGE",
  "eventTime": "2017-03-02T19:02:59.910959Z",
  "space": {
    "name": "spaces/AAAAAAAAAAA",
    "displayName": "Chuck Norris Discussion Room",
    "type": "ROOM"
  },
  "message": {
    "name": "spaces/AAAAAAAAAAA/messages/CCCCCCCCCCC",
    "sender": {
      "name": "users/12345678901234567890",
      "displayName": "Chuck Norris",
      "avatarUrl": "https://lh3.googleusercontent.com/.../photo.jpg",
      "email": "chuck@example.com"
    },
    "createTime": "2017-03-02T19:02:59.910959Z",
    "text": "@Demo １日の東京を予約",
    "thread": {
      "name": "spaces/AAAAAAAAAAA/threads/BBBBBBBBBBB"
    },
    "annotations": [
      {
        "length": 8,
        "startIndex": 0,
        "userMention": {
          "type": "MENTION",
          "user": {
            "avatarUrl": "https://.../avatar.png",
            "displayName": "TestBot",
            "name": "users/1234567890987654321",
            "type": "BOT"
          }
        },
        "type": "USER_MENTION"
      }
    ]
  },
  "user": {
    "name": "users/12345678901234567890",
    "displayName": "Chuck Norris",
    "avatarUrl": "https://lh3.googleusercontent.com/.../photo.jpg",
    "email": "chuck@example.com"
  }
}
```

ローカル開発サーバを起動します。

```bash
$ dev_appserver.py .
```

別ターミナルから、BOTへテストデータをリクエストしてみます。

```bash
$ curl http://localhost:8080 -X POST H "Content-Type: application/json" -d @event.json
{
  "text": "2018-05-01 \u306e \u6771\u4eac\u30bf\u30a6\u30f3\u30db\u30c6\u30eb \u3092\u4e88\u7d04\u3057\u307e\u3057\u305f\u3002"
}
```

上記のようにレスポンスメッセージにマルチバイト文字が含まれていると、マルチバイト文字が Unicode にエンコードされて表示されます。
そういう場合は、レスポンスを `jq` にパイプすることで Unicode がデコードされて正しく表示されます。
もし `jq` がインストールされていない場合は、EPEL からインストールしましょう。（インストール方法は割愛します）

```bash
$ curl http://localhost:8080 -X POST H "Content-Type: application/json" -d @event.json | jq
{
  "text": "2018-05-01 の 東京タウンホテル を予約しました。"
}
```

#BOTアプリケーションのデプロイ
ローカルテストで問題がなければ、BOTアプリケーションを GAE へデプロイします。

```bash
$ gcloud app deploy
```

#Hangouts Chat からテスト

Hangouts Chat から BOT にメッセージを送り、動作を確認します。

BOTへダイレクトメッセージを送ると、次のようなレスポンスが返ってくると思います。
![screencapture-chat-google-dm-jNN4pAAAAAE-2018-04-12-10_46_31.png](https://qiita-image-store.s3.amazonaws.com/0/94476/774081df-29f9-44ec-5ca2-165714d914b9.png)

#次回
Hangouts Chat ではテキストメッセージの他に、`カードメッセージ`と呼ばれる、より複雑なフォマットのメッセージをサポートしています。
次回は、そのカードメッセージを BOT に実装してみたいと思います。