Microsoft Bot FrameworkのDirect Line APIの簡単まとめ

Microsoft Bot Frameworkには、ボットのインターフェースとしてSkype, Slack, Facebook, Teamsなどの他に、単純なREST APIとして呼べるDirect Lineが用意されています。

[Second Lifeのオブジェクトの周りで喋ったら反応するボットのスクリプトを書いた](https://github.com/Makopo/lsl-ms-bot-framework-bot-client/blob/master/msbot_connector.lsl)ついでに、現時点でのDirect Lineの使い方を整理してみました。

## サーバ側の準備

* Botを稼働させるサーバを用意
    * インターネットからアクセスできれば(理論上は)何でも良い
    * 特にこだわりがなければAzureにした方が楽
* Botサーバ部分の開発
    * [C#.NET](https://docs.microsoft.com/en-us/bot-framework/dotnet/bot-builder-dotnet-quickstart)か[Node.js](https://docs.microsoft.com/en-us/bot-framework/nodejs/bot-builder-nodejs-quickstart)にすると、SDKがあって楽
* ローカルでテスト
    * [Bot Emulator](https://github.com/Microsoft/BotFramework-Emulator/releases/latest)をインストール
    * [ここに書かれている通りに](https://docs.microsoft.com/en-us/bot-framework/debug-bots-emulator)、[ngrok](https://ngrok.com/)を連携させておく
        * これを怠るとサーバにデプロイした後の確認でハマる
* サーバにデプロイ
    * Visual StudioからだとAzureのリソースも自動で作られて楽([C#.NET](https://docs.microsoft.com/en-us/bot-framework/deploy-dotnet-bot-visual-studio)、[Node.js](https://docs.microsoft.com/en-us/bot-framework/deploy-nodejs-bot-visual-studio))
    * Bot Emulatorでローカルと同じように動作するか確認
* ボットとして登録
    * [Bot Framework Portal](https://dev.botframework.com/)にログインし、[こちらの手順の通り](https://docs.microsoft.com/en-us/bot-framework/portal-register-bot)に操作
        * Application InsightsやAnalytics(分析)の設定はスキップで良い
    * C#.NETやNode.jsで作っている人は[設定ファイルや環境変数の修正を忘れずに](https://docs.microsoft.com/en-us/bot-framework/portal-register-bot#updateConfigSettings)
* Direct Lineチャネルをボット定義に追加
    * [こちらの手順の通り](https://docs.microsoft.com/en-us/bot-framework/channel-connect-directline)に操作
    * 実はデフォルトで作られているWeb Chatでも良かったりする...

## REST呼び出しの準備

### マニュアル

`Build a client`が、ボットクライアントを作る際に必要なAPIの記事となります。その直前の`Build a bot`は、C#やNode.jsのSDKを使わずに、自力でチャネルやMicrosoftのボットサービスと連携させる際に必要となる記事ですので、大多数のライトユーザは読む必要がありません。

記事の場所がわかりづらいので、マニュアルのツリーの画面キャプチャを貼っておきます。

![tree](https://qiita-image-store.s3.amazonaws.com/0/10290/0d9881c0-6da4-b9e2-1922-7dfac173dcf2.png)

WebSocketを使わない単純なHTTP呼び出しなら、以下のマニュアルで事足ります。

* 認証の方法
    * https://docs.microsoft.com/en-us/bot-framework/rest-api/bot-framework-rest-direct-line-3-0-authentication
* 会話の開始
    * https://docs.microsoft.com/en-us/bot-framework/rest-api/bot-framework-rest-direct-line-3-0-start-conversation
* ボットにメッセージを送信
    * https://docs.microsoft.com/en-us/bot-framework/rest-api/bot-framework-rest-direct-line-3-0-send-activity
* ボットからのメッセージを受信
    * https://docs.microsoft.com/en-us/bot-framework/rest-api/bot-framework-rest-direct-line-3-0-receive-activities
* 会話の終了
    * https://docs.microsoft.com/en-us/bot-framework/rest-api/bot-framework-rest-direct-line-3-0-end-conversation
* APIリファレンス
    * https://docs.microsoft.com/en-us/bot-framework/rest-api/bot-framework-rest-direct-line-3-0-api-reference

以下、2017年10月現在のDirect Line 3.0を前提として説明します。

### Secret Keyの取得

[Bot Framework Portal](https://dev.botframework.com)で`CHANNELS`を開き、Direct Lineチャネルの行の`Edit`をクリックします。下のような画面が出てきた`Show`をクリックし、出てきた文字列をコピーして、どこかに保存しておきます。(以下、SECRET_KEYとします)

2個ありますが、どちらを使っても構いません(なぜ2個あるかは謎)。

![secretkey](https://qiita-image-store.s3.amazonaws.com/0/10290/2090915f-5168-a64f-7d26-8841bc357837.png)

## RESTの呼び出し方法

### 会話の開始

初回のみ実行します。

#### リクエスト

* メソッド: `POST`
* URL: `https://directline.botframework.com/v3/directline/conversations`
* ヘッダ:
    * `Authorization`: `Bearer SECRET_KEY`
* 本文(Body): なし
    
#### レスポンス

* ステータスコード: `201`
* 本文: 今後必要なのは以下のみ
    * `conversationId` (以下、CONVERSATION_IDとします)
    * `token` (以下、TOKEN_STRINGとします)
    * `expires_in`: トークンの期限が切れる秒数(2017年10月現在は`1800`)

```json:例
{
    "conversationId": "1BLDHwkJbAI9OaQ7kaawFy",
    "token": "QXnIh2YOvyA.dAA.MQBCAEwARABIAHcAawBKAGIAQQBJADkATwBhAFEANwBrAGEAYQB3AEYAeQA.0YpbcG5D0wE.W_6rw0NK2ug.3kmUW-UAyM6T2eXX_VyU8UM4IVM36fAlBP_Vo43nQfs",
    "expires_in": 1800,
    "streamUrl": "wss://directline.botframework.com/v3/directline/conversations/1BLDHwkJbAI9OaQ7kaawFy/stream?watermark=-&t=QXnIh2YOvyA.dAA.MQBCAEwARABIAHcAawBKAGIAQQBJADkATwBhAFEANwBrAGEAYQB3AEYAeQA.0Zw8Y2pD0wE.bzda8JskN9c.8lxihTYtXiuXh3drL_O8xa8Ml6atIH4p4J7VS2aUB6o",
    "referenceGrammarId": "1799972b-e9cb-5455-89fc-220485899d8a"
}
```

### ボットにメッセージを送信

#### リクエスト

* メソッド: `POST`
* URL: `https://directline.botframework.com/v3/directline/conversations/CONVERSATION_ID/activities`
* ヘッダ:
    * `Authorization`: `Bearer TOKEN_STRING`
    * `Content-Type`: `application/json`
* 本文(Body): 
    * `type`: `message`
    * `id`: クライアントの識別子(何でも良い？)
    * `text`: ボットに送信する文字列

```json:例
{
    "type": "message",
    "from": {
        "id": "user1"
    },
    "text": "hello"
}
```
    
#### レスポンス

* ステータスコード: `200`
* 本文: `id`のみ。今後の操作には不要な情報

### ボットからメッセージを受信

メッセージの送信と1対1の関係というわけではなく、このAPIを呼び出したら、(`watermark`以降)API呼び出し時点までの全ての会話ログを返してきます。会話ログには、ボットの発言だけでなく、こちらから送信したメッセージも含まれます。

#### リクエスト

* メソッド: `GET`
* URL: `https://directline.botframework.com/v3/directline/conversations/CONVERSATION_ID/activities?watermark=WATERMARK_STRING`
* ヘッダ:
    * `Authorization`: `Bearer TOKEN_STRING`

#### レスポンス

* ステータスコード: `200`
* 本文: (主に必要なもののみ抜粋)
    * `activities`
        * `from` - `id` : 発言者
        * `text`: 受信した文字列
    * `watermark`

前回取得した`watermark`をWATERMARK_STRINGの位置に指定すると、未読のアクティビティのみが取得できます。初回の呼び出しでは`?watermark=`の部分は不要です。`?watermark=0`でも良いです。

### 会話の終了

放っておいても`expires_in`の秒数が経過すると会話は終了しますし、ボットのサーバ側から会話を終了させることもできますが、こちらから会話を終了する手段も用意されています。

#### リクエスト

* メソッド: `POST`
* URL: `https://directline.botframework.com/v3/directline/conversations/CONVERSATION_ID/activities`
* ヘッダ:
    * `Authorization`: `Bearer TOKEN_STRING`
    * `Content-Type`: `application/json`
* 本文(Body): 
    * `type`: `endOfConversation`
    * `id`: クライアントの識別子(何でも良い？)

```json:例
{
    "type": "endOfConversation",
    "from": {
        "id": "user1"
    }
}
```

#### レスポンス

* ステータスコード: `200`
* 本文: `id`のみ。今後の操作には不要な情報

### 例外処理

ステータスコードが400番台と500番台は、エラー扱いとなります。

* 本文(Body): 
    * `error`
        * `code`: エラー種別
        * `message`: 詳細なメッセージ

```json:例
{
    "error": {
        "code": "BadArgument",
        "message": "Security token not valid for this conversation"
    }
}
```

### 会話の延長

`expires_in`の時間が経過すると、直前までどんなに激しい会話の応酬がされていても、ボットは会話を終了してしまいます。それを防ぐために、**会話が終了してしまう前に**トークンを更新する必要があります。

#### リクエスト

* メソッド: `POST`
* URL: `https://directline.botframework.com/v3/directline/tokens/refresh`
* ヘッダ:
    * `Authorization`: `Bearer TOKEN_STRING`
* 本文(Body): なし

#### レスポンス

* ステータスコード: `201`
* 本文: 今後必要なのは以下のみ
    * `conversationId`
    * `token`
    * `expires_in`

以後は、レスポンスに記載された`conversationId`と`token`を使うようにします。