DialogflowのfulfillmentをAzureで実装する

# はじめに

AzureFunctionsって料金どれくらいかかるんでしょうか......(恐怖)
すごーく簡単にできるように頑張ります！

## AzureFunction 側の設定・作成

[Azure FunctionsでPython3とpipを使う - Qiita](https://qiita.com/Akira-Isegawa/items/f3c8d14ee43049459264)
が参考になりますので，こちらを見てPython3が使えるようにします．

```AzureFunctions.py
import platform
print("Python '{0}'".format(platform.python_version())) 
```

>[Info] Python '3.6.1'

と出れば準備は完了です．

上部の ``関数のURLの取得`` からURLをコピーしておきます．


## Dialogflow の設定

### Fulfillment

左側のタブ ``Fulfillment`` を選択，Webhookを有効にします．

``URL`` に先ほどコピーしたAzureのURLをペーストし， ``DOMAINS`` を

> Enable webhook for all domains 

に変更し，SAVEします．

### intent

Azure側で処理したいintentの設定から， ``Enable webhook call for this intent`` をオン

**これで準備は完了です．プログラムを書いていきます．**

## Dialogflow -> AzureFunctions

以下のjsonに色々入っているのでいじって情報を得ます．

```Request.json
{
    "responseId": "ea3d77e8-ae27-41a4-9e1d-174bd461b68c",
    "session": "projects/your-agents-project-id/agent/sessions/88d13aa8-2999-4f71-b233-39cbf3a824a0",
    "queryResult": {
        "queryText": "user's original query to your agent",
        "parameters": {
            "param": "param value"
        },
        "allRequiredParamsPresent": true,
        "fulfillmentText": "Text defined in Dialogflow's console for the intent that was matched",
        "fulfillmentMessages": [
            {
                "text": {
                    "text": [
                        "Text defined in Dialogflow's console for the intent that was matched"
                    ]
                }
            }
        ],
        "outputContexts": [
            {
                "name": "projects/your-agents-project-id/agent/sessions/88d13aa8-2999-4f71-b233-39cbf3a824a0/contexts/generic",
                "lifespanCount": 5,
                "parameters": {
                    "param": "param value"
                }
            }
        ],
        "intent": {
            "name": "projects/your-agents-project-id/agent/intents/29bcd7f8-f717-4261-a8fd-2d3e451b8af8",
            "displayName": "Matched Intent Name"
        },
        "intentDetectionConfidence": 1,
        "diagnosticInfo": {},
        "languageCode": "en"
    },
    "originalDetectIntentRequest": {}
}
```

## AzureFunctions -> Dialogflow

以下のようにレスポンスを作成し，Dialogflowに返却します．
最低限 ``fulfillmentText`` があればGoogle homeくんが喋ってくれます．

```Response.json
{
    "fulfillmentText": "This is a text response",
    "fulfillmentMessages": [
        {
            "card": {
                "title": "card title",
                "subtitle": "card text",
                "imageUri": "https://assistant.google.com/static/images/molecule/Molecule-Formation-stop.png",
                "buttons": [
                    {
                        "text": "button text",
                        "postback": "https://assistant.google.com/"
                    }
                ]
            }
        }
    ],
    "source": "example.com",
    "payload": {
        "google": {
            "expectUserResponse": true,
            "richResponse": {
                "items": [
                    {
                        "simpleResponse": {
                            "textToSpeech": "this is a simple response"
                        }
                    }
                ]
            }
        },
        "facebook": {
            "text": "Hello, Facebook!"
        },
        "slack": {
            "text": "This is a text response for Slack."
        }
    },
    "outputContexts": [
        {
            "name": "projects/${PROJECT_ID}/agent/sessions/${SESSION_ID}/contexts/context name",
            "lifespanCount": 5,
            "parameters": {
                "param": "param value"
            }
        }
    ],
    "followupEventInput": {
        "name": "event name",
        "languageCode": "en-US",
        "parameters": {
            "param": "param value"
        }
    }
}
```

##　intent別に処理を実装する．

Request -> queryResult.intent.displayName にDialogflowが判定したインテント名が格納されているので，これを見て処理を分けていきます．

```python
intent_name = Result["queryResult"]["intent"]["displayName"]
if intent_name == "インテント名":
    処理
else:
    エラー処理など
```

## サンプル
``Recommend`` が来た時に現在放送中のラジオからランダムに返却して喋らせるやつ

```Sample.py
# -*- coding: utf-8 -*-
import io
import os
import json
import random
import requests
import xml.etree.ElementTree as ET
from html.parser import HTMLParser

# 番組情報[info]がhtmlタグ付きなので削除に使用するクラス
class MyHtmlStripper(HTMLParser):
    def __init__(self, s):
        super().__init__()
        self.sio = io.StringIO()
        self.feed(s)

    def handle_starttag(self, tag, attrs):
        pass

    def handle_endtag(self, tag):
        pass

    def handle_data(self, data):
        self.sio.write(data)

    @property
    def value(self):
        return self.sio.getvalue() 

# インテントで処理を分ける
def intent_check(Result):
    intent_name = Result["intent"]["displayName"]
    if intent_name == "Recommend":
        return intent_Recommend(Result)
    else:
        return Build_Response('NO')

# intent[Recommend]の時
def intent_Recommend(Result):
    bangumi = Create_RadioData(None)
    output = 'ただいま，' + bangumi['station'] + 'で，' + bangumi['title'] + 'が放送中です．'
    return Build_Response(output)

# ETそのままのStationを受け取って，使いやすい辞書型に変換する
def Build_bangumiData(bangumi):
    station = bangumi.findtext('name')
    prog = bangumi[1][1]
    title = prog.findtext('title')
    info = MyHtmlStripper(prog.findtext('info')).value
    return dict(station=station, title=title, info=info)

# APIアクセスとStation選択をする    
def Create_RadioData(TEXT=None):
    url_radiko = "http://radiko.jp/v3/program/now/JP13.xml"
    res = requests.get(url_radiko)
    res.encoding = res.apparent_encoding
    root = ET.fromstring(res.text)
    stations = root[2]
    count = -1
    for child in stations:
        if not TEXT is None:
            if TEXT == child.get('id'):
                return Build_bangumiData(child)
        count += 1
    return Build_bangumiData(stations[random.randint(0, count)])

# レスポンス作成
def Build_Response(output):
    tmp = {
        'fulfillmentText': output
    }
    return tmp

# webhookの受信と送信．ここが一番長かった（マジ）
data = json.loads(open(os.environ['req'], encoding='utf-8').read())
returnData = intent_check(data["queryResult"])
response = codecs.open(os.environ['res'], 'w')
json.dump(returnData, response)
response.close()
```

# おわり

Python3の日本語関連で詰まりすぎたので，次書く記事はそれにします......

# 参考文献
ありがとうございます．

[Azure FunctionsでPython3とpipを使う - Qiita](https://qiita.com/Akira-Isegawa/items/f3c8d14ee43049459264)

[python3でhtmlタグを除去 - Qiita](https://qiita.com/arc279/items/4c4d84824d4651084c95)

[Dialogflow Fulfillment Webhook JSON Sample - Github](https://github.com/dialogflow/fulfillment-webhook-json)