Microsoft Bot Framework v3.0 からはじめる BOT 開発: 天気予報 BOT～カード表示で分かりやすく - Adaptive Cards

最近 SNS でよく見かける 画像を利用した "カード" と呼ばれるフォーマット。Bot Framework で利用可能になった Adaptive Cards を使って、情報をグラフィカルに表示する方法を紹介します。
[Livedoor 天気予報 Weather Hacks](http://weather.livedoor.com/weather_hacks/) の天気予報 API を利用してデータを取得、日付と気温と共に天気アイコンを表示するカードを作成、表示する BOT を作成します。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_16.png)


##手順
1. 天気予報 BOT - Adaptive Card : 天気情報を取得して表示する (※このページ)
2. [天気予報 BOT - Cognitive Services LUIS(1): 自然言語(文章)を解釈して、場所や日時を取得する](http://qiita.com/annie/items/c55101720aee1c6d7ad6)
3. [天気予報 BOT - Cognitive Services LUIS(2): 自然言語判定結果をBOTに組み込む](http://qiita.com/annie/items/23b821108f9d32a0832e)

##開発環境
Windows 10 + Visual Studio 2017 Enterprise, Bot Framework v3.8 (C#) で作成を行っています。

無償の Visual Studio 2017 Community or 2015 Community でOKなので、既存の環境がない場合は、ダウンロードしてインストールします。
[Visual Studio 2017 Community ダウンロードサイト](https://www.visualstudio.com/ja-jp/products/visual-studio-community-vs.aspx)
[Visual Studio 用の Bot Framework C# テンプレート](http://aka.ms/bf-bc-vstemplate)
[Bot Framework Channel Emulator (Windows版)](https://aka.ms/bf-bc-emulator) ※Mac/Linux は [Console版](https://aka.ms/bfemulator) をご利用ください
>Bot Framework 開発環境の作り方は、[Microsoft Bot Framework v3.0 からはじめる BOT 開発: Bot Framework を使うための開発環境](http://qiita.com/annie/items/edc26c0ee9603e84a2e4#bot-framework-%E3%82%92%E4%BD%BF%E3%81%86%E3%81%9F%E3%82%81%E3%81%AE%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83) をご覧ください。

#天気予報 BOT - Adaptive Card : 天気情報を取得して表示する
##Visual Studio テンプレートから新規 BOT アプリケーションの作成～下準備
[Visual Studio テンプレートから Bot アプリケーションの作成](http://qiita.com/annie/items/edc26c0ee9603e84a2e4#visual-studio-%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%81%8B%E3%82%89-bot-%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E4%BD%9C%E6%88%90) と同じ手順で、Visual Studio テンプレートから Bot アプリケーションの作成 と同じプロセスで新規アプリケーションを作成します。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_01.png)

今回は *WeatherBot* という名称で作成しています。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_02.png)

ソリューションエクスプローラーからプロジェクトの配下にある [参照] を右クリックして [NuGet パッケージの管理] をクリックします。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_03.png)

上部バーに *このソリューションに一部のNuGetパッケージが見つかりません．．．* と表示される場合は、[復元] をクリックして、必要なパッケージをインストールします。(表示されない場合は次に進みます)
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_04.png)

[参照] タブをクリックし、検索バーに *adaptive cards* と入力して検索、Microsoft.Adaptive.Cards をクリックします。すぐ右側に表示される詳細画面の [インストール] をクリックしてインストールします。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_04-02.png)

*インストールの確認* が表示されたら [OK]　、*ライセンスへの同意* は [同意する] をクリックして、インストールします。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_05_06-e1502072396256.png)

## 天気予報データを格納するクラスを作成
個人の範囲で無料で利用できる Weather Hack の お天気 Webサービス を使うと、天気と気温の予報データがJSON 形式で取得できます。 (→[サービス仕様](http://weather.livedoor.com/weather_hacks/webservice))
[例] 横浜(cityid=140010)の天気予報を取得
http://weather.livedoor.com/forecast/webservice/json/v1?city=140010

このような JSON データを格納して利用しやすくするためのクラスを作成します。
プロジェクトを右クリックして [追加]＞[新しいフォルダー] をクリック、*Models* というフォルダーを作成します。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_07.png)

作成した *Models* フォルダーを右クリックして、[追加]＞[クラス]をクリックします。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_08.png)

名前に *WeatherModel.cs* と入力して、[追加] をクリックして新規クラスファイルを作成します。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_09.png)

WeatherModel.cs を開き、デフォルトで作成されている WeatherModel クラスを一旦削除します。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_10.png)

API のフォーマットを取得するため、ブラウザーを起動して API にアクセスを行います。
http://weather.livedoor.com/forecast/webservice/json/v1?city=140010

表示される結果をすべて選択して、コピーします。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_11.png)

Visual Studio に戻り、*namespace <projectname>.Models* 内をクリックして選択してから、上部ツールバーから [編集]＞[形式を選択して貼り付け]＞[JSON をクラスとして貼り付ける] を選択して、コピーした内容を貼り付けます。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_12.png)
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_13.png)

*class Rootobject* は *class WeatherModel* に変更します。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_13-02.png)


適時ファイルの保存を行っておきます。



## 天気予報データの取得テスト
Dialogs フォルダ を開き、自動生成されている RootDialog.cs を編集します。

RootDialog.cs の冒頭に System.Net.Http, Newtonsoft.Json, Adaptive Card への参照を追加します。
上記で作成した WeatherModel を利用するために、*Models* フォルダー への参照も追加します。

```RootDialog.cs
using System.Net.Http;
using Newtonsoft.Json;
using AdaptiveCards;
using WeatherBot.Models; // <projectname>.Models になります
```

ユーザーからメッセージが送信された時の動作を MessageReceivedAsync に記載していきます。
まず、MessageReceivedAsync 内のコードを下記に書き換えます。

```RootDialog.cs
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
    var activity = await result as Activity;

    // 返答メッセージを作成
    var message = context.MakeMessage();
    // 天気を取得
    WeatherModel weather = await GetWeatherAsync();

    // 天気をメッセージにセット
    message.Text = $"今日の天気は {weather.forecasts[0].telop.ToString()} です";

    //　返答メッセージをPost
    await context.PostAsync(message);
    context.Wait(MessageReceivedAsync);
}
```

天気を取得する GetWeatherAsync を作成します。
下記のコードでは、横浜 (city=1400100) を指定して天気情報を取得しています。

```RootDialog.cs
private async Task<WeatherModel> GetWeatherAsync()
{
    // API から天気情報を取得 (都市コード 140010 (横浜) の場合)
    var client = new HttpClient();
    var weatherResult = await client.GetStringAsync("http://weather.livedoor.com/forecast/webservice/json/v1?city=140010");

    // API 取得したデータをデコードして WeatherModel に取得
    weatherResult = Uri.UnescapeDataString(weatherResult);
    var weatherModel = JsonConvert.DeserializeObject<WeatherModel>(weatherResult);
    return weatherModel;
}
```

ここで一旦 BOT の動作確認を行います。、F5 または デバック＞デバックの開始 をクリックして、プロジェクトのビルドおよび起動を行います。ブラウザが起動して Bot Framework のデフォルト画面が表示されたら、Bot Framework Channel Emulator を起動してアクセスを行います。
Bot Framework Channel Emulator の上部中央にある Bot Url に、起動しているブラウザと同じ URL (デフォルトでは http://localhost:xxxx) に /api/messages を追加したアドレス (http://localhost:xxxx/api/messages) を指定します。

何か文字を入力して送信すると、天気予報が返されるのを確認してください。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_14.png)


## 天気予報データを Adaptive Cards で表示する
### (1) 単純なカードの作成
次に Adaptive Cards を使って、カード形式で情報を表示します。
MessageReceivedAsync を以下のように変更します。*message.text* の代わりに Card を作成、Attachment として返答にセットします。

```RootDialog.cs
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
    var activity = await result as Activity;

    // 返答メッセージを作成
    var message = context.MakeMessage();
    // 天気を取得
    WeatherModel weather = await GetWeatherAsync();

    //// 天気をメッセージにセット
    //message.Text = $"今日の天気は {weather.forecasts[0].telop.ToString()} です";

    // 取得した天気情報をカードにセット
    var weatherCard = GetCard(weather);
    var attachment = new Attachment()
    {
        Content = weatherCard,
        ContentType = "application/vnd.microsoft.card.adaptive",
        Name = "Weather Forecast"
    };
    message.Attachments.Add(attachment);

    //　返答メッセージをPost
    await context.PostAsync(message);
    context.Wait(MessageReceivedAsync);
}
```

Card を作成する *GetCard*, その中で天気情報をセットする *AddCurrentWeather* を以下のように作成します。

```RootDialog.cs
private static AdaptiveCard GetCard(WeatherModel model)
{
    var card = new AdaptiveCard();
    AddCurrentWeather(model, card);
    return card;
}

private static void AddCurrentWeather(WeatherModel model, AdaptiveCard card)
{
    // タイトル作成
    var titleColumnSet = new ColumnSet();
    card.Body.Add(titleColumnSet);

    var titleColumn = new Column();
    titleColumnSet.Columns.Add(titleColumn);

    var locationText = new TextBlock()
    {
        Text = $"{model.location.city} の天気",
        Size = TextSize.ExtraLarge,
        HorizontalAlignment = HorizontalAlignment.Center
    };
    titleColumn.Items.Add(locationText);

    // 本文作成
    // 天気情報をセット
    var mainColumnSet = new ColumnSet();
    card.Body.Add(mainColumnSet);

    var mainColumn = new Column();
    mainColumnSet.Columns.Add(mainColumn);

    var mainText = new TextBlock()
    {
        Text = $"{model.publicTime.Date.ToString("M/d")}",
        Size = TextSize.Large,
        HorizontalAlignment = HorizontalAlignment.Center
    };
    mainColumn.Items.Add(mainText);

    // 天気アイコンをセット
    var mainImage = new AdaptiveCards.Image();
    mainImage.Url = model.forecasts[0].image.url;
    mainImage.HorizontalAlignment = HorizontalAlignment.Center;
    mainColumn.Items.Add(mainImage);
}

```

再度 BOT の動作確認を行います。
何か文字を入力して送信すると、今度は天気予報がカードで返されるのを確認してください。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_15.png)


### (2) 複数 Column を持ったカードの作成
今度は、Adaptive Cards で複数の Column を持つ形のカードを作成します。
何度か TextBlock と Image を追加する工程があるので、メソッドを作成して再利用できるようにします。
以下のように、RootDialog.cs に AddTextBlock と AddImage を追加します。

```RootDialog.cs
private static void AddTextBlock(Column column, string text, TextSize size, HorizontalAlignment alignment)
{
    column.Items.Add(new TextBlock()
    {
        Text = text,
        Size = size,
        HorizontalAlignment = alignment
    });
}
private static void AddImage(Column column, string url, ImageSize size, HorizontalAlignment alignment)
{
    column.Items.Add(new AdaptiveCards.Image()
    {
        Url = url,
        Size = size,
        HorizontalAlignment = alignment
    });
}
```

RootDialog.cs に AddWeather を下記のように追加して、MessageReceivedAsync からの呼び出しを AddCurrentWeather → AddWeather に変更します。

```RootDialog.cs

private static AdaptiveCard GetCard(WeatherModel model)
{
    var card = new AdaptiveCard();
    //AddCurrentWeather(model, card);
    AddWeather(model, card);
    return card;
}

    : (中略)

private static void AddWeather (WeatherModel model, AdaptiveCard card)
{
    // タイトル作成
    var titleColumnSet = new ColumnSet();
    card.Body.Add(titleColumnSet);

    var titleColumn = new Column();
    titleColumnSet.Columns.Add(titleColumn);
    AddTextBlock(titleColumn, $"{model.location.city} の天気", TextSize.ExtraLarge, HorizontalAlignment.Center);

    // 本文作成
    // 天気情報をセット
    var mainColumnSet = new ColumnSet();
    card.Body.Add(mainColumnSet);

    foreach (var item in model.forecasts)
    {
        var mainColumn = new Column();
        mainColumnSet.Columns.Add(mainColumn);

        // 天気データの取得と加工
        string day = item.dateLabel;
        string date = DateTime.Parse(item.date).Date.ToString("M/d");            

        // temperature が null の場合は "--" に変換
        string maxTemp, minTemp;
        try
        {
            maxTemp = item.temperature.max.celsius;
            minTemp = item.temperature.min.celsius;
        }
        catch
        {
            maxTemp = "--";
            minTemp = "--";
        }

        // データのセット
        AddTextBlock(mainColumn, $"{day}({ date})", TextSize.Large, HorizontalAlignment.Center);
        AddTextBlock(mainColumn, $"{maxTemp} / {minTemp} °C", TextSize.Medium, HorizontalAlignment.Center);
        AddImage(mainColumn, item.image.url, ImageSize.Medium, HorizontalAlignment.Center);
    }

}
```

再度 BOT の動作確認を行います。
何か文字を入力して送信すると、3日間の天気予報がカードで返されるのを確認してください。
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170804_16.png)

##Appendix
ご参考まで、ここまで作成したアプリはこちらからダウンロードできます
[Bot Framework × Adaptive Cards: WeatherBot アプリサンプル](https://msdnshared.blob.core.windows.net/media/2017/08/WeatherBot_AdaptiveCards.zip)

##Next Step
次回以降の[手順](http://qiita.com/annie/items/3416fcf1ec275940979a#%E6%89%8B%E9%A0%86)で、Cognitive Services LUIS を用いて、自然言語を判断して回答する BOT に仕立てていきます。
手順 3 まで行って作成できる 天気予報BOT↓
![](https://msdnshared.blob.core.windows.net/media/2017/08/bluesky_20170816_06.png)