MS BotFramework(Azure Bot Service) for Node.js で使えるTips

# はじめに
- [MS BotFramework](https://dev.botframework.com/)はMicrosoftが提供する多チャネル対応のChatBot開発Frameworkです。
- Node.js / C# に対応しています。
- ユーザーの状態、セッション管理やデータ管理(永続化)などが簡単に行なえます。
- 同MicrosoftのLUISと連携させたりすれば、イケてるChatBotが作れる気がします。
- サーバーは自前で用意する必要があります。

# BotFramework for Node.jsの基本
- npm packageの[BotBuilder](https://github.com/Microsoft/BotBuilder)によって実装されている。
- 基本はwaterflowモデルにて実行される。

## waterflowモデル
- 基本的に上のfunctionから実行されていく
- ```next(result)```  メソッドを呼び出すと、強制的に次のfunctionへ制御が移行する。<BR>resultはoptionalで指定すれば次のfunctionのargsで取得可能<BR>nextを使用する際はfunction自体の引数にnextを含める必要がある。(2つめのfunction)

```js
function(session, args, next) {
    session.send('次のfunctionに遷移します');
    next({'status': 'ok'});
},
function(session, result) {
    session.send('遷移しました');
    session.send('引数は' + result.status + 'です。');
}
```

## Prompt
- ユーザーからの入力値(選択肢)を取得するメソッド
- promptを出すと、BOTの制御は一旦そこで止まる
- ユーザーが何らかのアクション(文字入力、選択肢を選ぶなど)をすると、自動的に次Functionが開始する
- ユーザーの入力値は次functionの引数に設定されている
- テキスト入力を受け付けるには
    - ```builder.Prompts.text(session, messageObject)``` について
        - ユーザーの自由入力を待つ
        - 次functionの引数の['response']のキーに値が格納される
        - messageObjectにattachment等を設定すると[リッチなUI選択肢](https://github.com/Microsoft/BotBuilder-Samples/tree/master/Node/cards-RichCards)が表示可能
        
- 選択肢を提示してユーザーの選択を受け付けるには
    - ```builder.Prompts.choice(session, text, 'choice1|choice2|choice3', {retryPrompt: '正しい選択肢を選んで下さい'})```
        - choiceはユーザーに選択肢を与える
        - 次functionの引数の['response']['entity']に選択した値が格納される
        - 選択肢はArrayも設定可能
        - retryPromptを設定すると、ユーザーが選択肢を選ばなかった場合(直接文字入力など)に表示する文言を設定可能

## session
- session内にユーザー固有の情報や、BOTの接続チャネル、今までのメッセージ履歴など多くの情報を保持している
- 頻繁に使用するものは以下
    - ```session.endConversation()```
        - 会話を明示的に終了する。
    - ```session.dialogData```
        - そのDialog(後述)内でのみ有効な一時的なデータ保存領域。
        - functionを跨いでデータを保持したい場合などに使用する。
    - ```session.conversationData```
        - その会話内で有効なデータ領域(?)
        - ```session.endConversation()``` を呼ぶとクリアされる
    - ```session.userData```
        - ユーザー毎にデータを永続化できる保存領域。
        - どのようにしてユーザーを特定しているかは分からないが、各ユーザー毎にデータを保存しておける。
        - ```session.userData.hogehoge = 'hogehoge'```　のようにして値を格納する。
        - ```delete session.userData.hogehoge```　削除できる。
    - ```session.send(message)```
        - Botにmessageを発言させる。
    - ```session.sendTyping()```
        - Botにタイピングインジケータを表示させる
        - Botが次のアクションをしたタイミングで消える
        - プラットフォームに依る（と思う）が、一定時間経過でも自動的に消える
    - ```session.message.address```
        - このaddressをbotの発言時に付加すると、確実に対象の相手へsendできる
        - 例えば遅延処理などで自動でreplyする際などはaddress指定しないと別のユーザーと混線する

## Facebook Messenger連携
- Facebook Messenger上で表現できるUI(jsonで記述)は以下のようにして実装可能

```js
// FacebookAPIのQuickReplyボタン(画像カスタム)を表示
var reply = new builder.Message(session).text('カスタムボタンを出します。');

reply.sourceEvent({
    facebook: {
        quick_replies: [
            {
                content_type:"text",
                title:"はい",
                payload:"ANY STRINGS",
                image_url:"https://hogehoge.com/images/yes.png"
            },
            {
                content_type:"text",
                title:"いいえ",
                payload:"ANY_STRINGS",
                image_url:"https://hogehoge.com/images/no.png"
            }
        ]
    }
});

session.send(reply);
```

```js
// FacebookAPIのSendLocationボタンを表示
var reply = new builder.Message(session).text('現在地を送信するボタンを出します。');

reply.sourceEvent({
    facebook: {
        quick_replies: [{
            content_type:"location"
        }]
    }
});

session.send(reply);
```
## dialog
- Botの今処理している場所(状態)をdialogという表現で管理する
- 別のDialogを開始する
    - ```session.beginDialog('hogehoge/dialog_name', args)```
        - hogehoge/dialog_nameのdialogを開始する
        - argsは任意だが、指定すれば次dialogへ値を渡すことができる
        - dialogを開始しても後続のコードは実行されるので、以下のようなコードは注意

```js
function(session, args) {
    session.send('次のDialogに遷移します');
    session.beginDialog('/next/start', {'status': 'ok'});

    // beginDialogの後にreturnしていないので、
    // '/next/start'のDialogが開始するのとほぼ同時に以下のコードも実行される
    session.send('次のDialogが終わりました。');
}
```
- 別のDialogに置きかえる
    - ```session.replaceDialog('hogehoge/dialog_name', args)```
        - hogehoge/dialog_nameのdialogを開始する
        - beginDialogとは違い、replaceDialogは今までのdialogStackがすべて消える
        - つまり、hogehoge/dialog_nameが終了しても元のdialogへは戻らない

- Dialogを終了する
    - ```session.endDialog()```
        - 今現在のdialogを終了する
        - 引数は渡せない
        - endDialogにより、一つ前のdialogの次functionから始まる
- Dialogを終了し、戻り先に引数を渡す
    - ```session.endDialogWithResult(args)```
        - endDialogの引数あり版
        - 一つ前のdialogの次functionから始まり、そのfunctionの引数としてargsが渡される

# 終わりに
- 2018年3月頃にMS Bot Frameworkは ***Azure Bot Service*** に[統合されることがアナウンス](https://docs.microsoft.com/en-us/Bot-Framework/bot-service-migrate-bot)されています。
- 他にも実用的なTipsがあれば随時更新していきます。