AzureFunctionsを使ってAlexaのカスタムスキル作成備忘録

# はじめに
TechSummitのちょまどさんセッション([ちょまどさんのブログ](https://chomado.com/presentations/microsoft-tech-summit-2018/#more-3107))に触発されて、Alexaを購入しました。
が、特にカスタムすることなく、音楽を聴く置物と化していました。
話は変わりますが、私の上司にヨーグルトが大好きな方がいます。
そこで、Alexaを使っておススメされたこれまでのヨーグルト通知機能を作成してみることにしました。

# 環境
- Visual Studio 2017 Professional(Community Edtion以上)
 - 言語：C#
- Azureサブスクリプション

なかなかうまくいかずに苦戦していましたが、
この方の記事を見ながらやったらあっさりできました。感謝です。
[Azure Functions + C# を利用してAlexaスキルを開発してみる](https://blog.intracker.net/archives/1524)

# AzureFunctions側の作成

## AzureFunctionsのソリューションを作成
新規作成→プロジェクトより、Azure Functionsを選択します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/201826/97ffc3de-0521-12d5-6647-52de1d55e4d6.png)

上記、記事の方もおっしゃっているように、AzureFunctions v2を選択します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/201826/af67a46f-caa9-fab2-e95f-8b832477ac5d.png)

OKを押せば、ソリューションの完成です。

## 処理
まず、NuGetパッケージマネージャーよりAlexa.NETを追加します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/201826/0d77c186-6473-b2d5-d4ac-3dff7127c6cb.png)


作成したソリューションに下記コードをかきました。

```C#:Function1.cs
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

using Alexa.NET.Request;
using Alexa.NET.Request.Type;
using Alexa.NET.Response;
using System.Linq;
using AlexaSkill.Models;

namespace AlexaSkill
{
    public static class Function1
    {
        private static string HelloMessage { get; } = "こんにちは！ヨーグルトスキルです！";
        private static string IntroductionMessage = "今日のヨーグルトスキルです！おすすめのヨーグルトを教えてと話しかけてください。";
        private static string ErrorMessage { get; } = "すみません、わかりませんでした！";

        [FunctionName("Function1")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            string requestJson = "";
            using (var sr = new StreamReader(req.Body))
            {
                requestJson = await sr.ReadToEndAsync();
            }
            SkillRequest skillRequest = JsonConvert.DeserializeObject<SkillRequest>(requestJson);
            var skillResponse = new SkillResponse
            {
                Version = "1.0",
                Response = new ResponseBody()
            };
            switch (skillRequest.Request)
            {
                case LaunchRequest lr:
                    skillResponse.Response.OutputSpeech = new PlainTextOutputSpeech
                    {
                        Text = IntroductionMessage,
                    };
                    break;
                // インテント使って返事されるやつ
                case IntentRequest ir:
                    {
                        var r = HandleIntent(ir.Intent.Name);
                        skillResponse.Response.OutputSpeech = new PlainTextOutputSpeech
                        {
                            Text = r.ResponseMessage,
                        };
                        skillResponse.Response.ShouldEndSession = r.ResponseFinishFlg;
                    }
                    break;

                default:
                    skillResponse.Response.OutputSpeech = new PlainTextOutputSpeech
                    {
                        Text = "すみません。わかりません",
                    };
                    break;
            }
            return new OkObjectResult(skillResponse);
        }


        //インテントの処理を記述
        private static HandleIntentResult HandleIntent(string intent)
        {
            switch (intent)
            {
                case "HelloIntent":
                    return new HandleIntentResult
                    {
                        ResponseMessage = HelloMessage,
                        ResponseFinishFlg = false,
                    };
                case "AskYogurutoIntent":
                    {
                        string[] yogurutWord = new[] { "ローソンの飲むりんごヨーグルト", "セブンの期間限定ヨーグルト", "ごろごろ果実のはっさく" };
                        // ランダムソート
                        string[] yogurutWords = yogurutWord.OrderBy(i => Guid.NewGuid()).ToArray();
                        return new HandleIntentResult
                        {
                            ResponseMessage = yogurutWords[0],
                            ResponseFinishFlg = false,
                        };
                    }
                case "AMAZON.StopIntent":
                    return new HandleIntentResult
                    {
                        ResponseMessage = "また明日",
                        ResponseFinishFlg = true,
                    };
                default:
                    return new HandleIntentResult
                    {
                        ResponseMessage = ErrorMessage,
                        ResponseFinishFlg = false,
                    };
            }

        }
    }
}
```

```C#:HandleIntentResult.cs
namespace AlexaSkill.Models
{
    class HandleIntentResult
    {
        public string ResponseMessage { get; set; }
        public bool ResponseFinishFlg { get; set; }
    }
}
```

基本的にちょまどさんのコードに一部変更を加えているのみの実装です。
LaunchRequestが「Alexa,～開いて！」と言った後に走るみたいです。
一方、IntentRequestは、後ほど設定するAlexa Developer Consoleの
インテントに設定した言葉が読み取られると、走るみたいです。
今回は、とりあえずAlexaに言って欲しい言葉を配列にいれて、ランダムに返してくるようにしました。

## 発行
AzureFunctionsを先にAzureで作成しておくという手段もありますが、新規作成にて発行しました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/201826/ee3b7e55-9ab5-81ae-d8e4-2efca9105362.png)


# Alexa Developer Consoleにて設定の作成
公式の説明がわりとわかりやすいです。
[Alexaスキル開発トレーニング](https://developer.amazon.com/ja/alexa-skills-kit/training/building-a-skill)
ここでは軽く説明したいと思います。

1. [alexa developer console](https://developer.amazon.com/ja/alexa)にログインします。

2. 上の方に「あなたのAlexaダッシュボード」欄があると思うので、そちらからSkillsを選択。

3. スキルの作成を選択。

4. スキル名や呼出名を指示に従って入力していきます。

5. インテントを追加します。インテントを追加することによって、
先ほどコードにかいたIntentRequest部分が走ります。
追加したインテントはこちら
![image.png](https://qiita-image-store.s3.amazonaws.com/0/201826/f4421a09-3baf-db4f-3beb-22683ac78c38.png)
テストするとこんな感じでJsonに送られます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/201826/2e195403-170b-96db-586b-50dc91c034c0.png)


6. 追加が完了したら、モデルを保存し、モデルをビルドします。

7. 最後にエンドポイントの設定をします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/201826/c2f36936-d12a-c8ed-c664-5a394d0abb93.png)
デフォルトの地域の値は下記のように取得します。
    1. Azureポータルへ移動し、今回追加したAzureFunctionsを選択します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/201826/fb63cf3e-bdb0-8ff8-5079-fed2c71f37db.png)
    1. 関数のURLの取得をクリックします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/201826/f4d14635-e32b-14f8-2495-1af2f3c0c24b.png)
    1. URLをコピー  
    ![image.png](https://qiita-image-store.s3.amazonaws.com/0/201826/cd8ce8b7-b080-159d-e778-3028c88bbdf7.png)




終わったら再びモデルを保存し、ビルドします。

# テスト
テストタブを選択し、スキルテストが有効になっているステージを開発中とします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/201826/0fe4394f-c47a-74a0-93ad-e6543b234a41.png)




# はまったこと
AMAZON.StopIntent(もとからあるもの)に「また明日」というセリフを言って、
スキルを終了して欲しかったのですが、
スキルが終わらずに永遠に「こんにちは！」と言い続けていました。
調べたところ、スキルを終わらすには、shouldEndSessionをtrueにする必要があったそうですね。。
しばらくAlexaのコンセントを抜いてテストをしていました。
「[標準ビルドインテント](https://developer.amazon.com/ja/docs/custom-skills/standard-built-in-intents.html)」

# さいごに
Alexaが音楽を聴くただの置物から卒業できそうです。

# 参考
- [Azure Functions + C# を利用してAlexaスキルを開発してみる](https://blog.intracker.net/archives/1524)
- [Microsoft Tech Summit 2018 で登壇しました(Azure Functions でスマートスピーカー開発) #mstsjp18 #AC02](https://chomado.com/presentations/microsoft-tech-summit-2018/#more-3107)←ちょまどさんのブログ
- [ちょまどさんのGitHub](https://github.com/chomado/SmartSpeakerGetLatestArticle)