AlexaとAWS Lambdaで遊ぶ

## はじめに

何かしらAWSのサービスを使ってみたいと考えていたところ、身内がビンゴ大会でゲットしたAmazon Echoがあることを思い出しました。
Amazon Echoと言えばAlexa。というわけで、Alexaで遊びつつAWSを使ってみることにしました。

## Alexaのチュートリアル

Amazonの公式ドキュメントに沿って進めると、チュートリアルができます。
アカウントの作成方法から実機のセットアップ方法まで書いてあり、とても親切でした。
https://developer.amazon.com/ja/blogs/alexa/post/31c9fd71-f34f-49fc-901f-d74f4f20e28d/alexatraining-firstskill

## AWSを使う

チュートリアルに従って、早速スキルが作れました。
しかし、バックエンドはAlexaにホストしてもらったため、自分ではAWSにログインすらしていません。
このままではAWSのサービスの使い方を分からずに終わってしまう……というわけで、次は自分のAWS Lambdaを使ってスキルを開発してみます。

### スキルの作成

[alexa developer console](https://developer.amazon.com/alexa/console/ask) の右上にある「スキルの作成」をクリックします。
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/c274331a-927f-d0f2-f58f-62117c7a1124.png)

スキル名を入力し、スキルに追加するモデルで「カスタム」を、バックエンドリソースをホスティングする方法で「ユーザ定義のプロビジョニング」を選択します。
せっかくなら楽しく開発したいので、自分の好きな道路関係のスキルを作ることにします。
![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/5c73426f-c36a-c974-4235-d7f6a95f1b81.png)

「スキルを作成」をクリックして少し待つと、以下の画面が表示されます。
右側のチェックリストにチェックマークが付くよう、設定していきます。
![02-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/7af2c69f-55b2-d87e-8109-502ef1b8cd44.png)

呼び出し名を設定します。
呼び出し名とは、「Alexa、○○を開いて××をして」の「○○」にあたる文言です。
Alexaへの呼びかけについては、[公式ブログ](https://developer.amazon.com/ja/blogs/alexa/post/93bad9f6-377f-4ada-887c-279e7346f729/alexatraining-customskill) に説明があります。
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/602f4fac-3007-1b6a-823a-3194d7b2e119.png)

インテントを追加します。
![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/c5bb859a-2f38-f802-f4e5-d80f1d421ea4.png)

「カスタムインテントを作成」をクリックすると以下の画面が表示されるので、サンプル発話を追加します。
サンプル発話とは、「Alexa、○○を開いて××をして」の「××をして」にあたる文言です。
各都道府県を走る国道を答えさせたいため、サンプルとして鳥取県を追加します。
![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/11769801-1b64-e7d8-dbb3-0e41dc711390.png)

ここまで終わったら、画面上部にある「モデルのビルド」をクリックします。

### Lambdaで関数の作成

ここで一旦alexa developer consoleからは離れ、AWS Lambdaでバックエンド側を作成していきます。
AWSからLambdaのページを開き、「関数の作成」をクリックします。
![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/10995275-a231-da3e-9f1d-6c9d3e52369c.png)

「Serverless Application Repositoryの参照」を選択し、下部に表示される一覧から「alexa-skills-kit-nodejs-factskill」を選択します。
![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/54707af2-1b10-8596-11f6-a4de3b611dd6.png)

アプリケーション名を入力し、「デプロイ」をクリックします。
![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/95f94c64-4138-11ae-e5e8-90123d420ebe.png)

デプロイ完了後、サイドメニューの「関数」をクリックし新たに作られた関数を選択します。
選択すると以下の画面が表示されるので、ここでコードを修正していきます。修正後は「保存」をクリックして保存します。
![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/3955d005-10dd-7fbb-55e6-d8ea42902394.png)

<details>
<summary>コード全容</summary>
<div>

```js
// /* eslint-disable  func-names */
// /* eslint-disable  no-console */

const Alexa = require('ask-sdk');

const LaunchHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'LaunchRequest';
  },
  handle(handlerInput) {
    const launchMessage = 'ようこそ。知りたい都道府県を言ってみてください。';
    return handlerInput.responseBuilder
      .speak(launchMessage)
      .reprompt(launchMessage)
      .getResponse();
  },
};

const PrefectureIntentHandler = {
    canHandle(handlerInput) {
        const request = handlerInput.requestEnvelope.request;
        return request.type === 'IntentRequest'
          && request.intent.name === 'PrefectureIntent';
    },
    handle(handlerInput) {
        const routeNoMessage = '9号、29号、53号、178号、179号、180号、181号、183号、313号、373号、431号、482号です';
        return handlerInput.responseBuilder
            .speak(routeNoMessage)
            .getResponse();
    }
};

const HelpHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && request.intent.name === 'AMAZON.HelpIntent';
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak(HELP_MESSAGE)
      .reprompt(HELP_REPROMPT)
      .getResponse();
  },
};

const ExitHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && (request.intent.name === 'AMAZON.CancelIntent'
        || request.intent.name === 'AMAZON.StopIntent');
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak(STOP_MESSAGE)
      .getResponse();
  },
};

const SessionEndedRequestHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'SessionEndedRequest';
  },
  handle(handlerInput) {
    console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);

    return handlerInput.responseBuilder.getResponse();
  },
};

const ErrorHandler = {
  canHandle() {
    return true;
  },
  handle(handlerInput, error) {
    console.log(`Error handled: ${error.message}`);

    return handlerInput.responseBuilder
      .speak('Sorry, an error occurred.')
      .reprompt('Sorry, an error occurred.')
      .getResponse();
  },
};

const HELP_MESSAGE = 'You can say tell me a space fact, or, you can say exit... What can I help you with?';
const HELP_REPROMPT = 'What can I help you with?';
const STOP_MESSAGE = 'Goodbye!';

const skillBuilder = Alexa.SkillBuilders.standard();

exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchHandler,
    PrefectureIntentHandler,
    HelpHandler,
    ExitHandler,
    SessionEndedRequestHandler
  )
  .addErrorHandlers(ErrorHandler)
  .lambda();
```
</div>
</details>

この関数を使用できるスキルを制限するために、トリガーとして設定されている「Alexa Skills Kit」を一度削除します。
その後、「トリガーを追加」から改めて「Alexa Skills Kit」を選択します。
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/a0219346-43ba-1d53-fa3e-5b6f8026cadb.png)
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/8d263e27-4cb7-3939-c0d9-0d248459b9c5.png)
スキル検証を設定し、追加します。
![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/844a2953-5df3-02fc-827d-c0edfd43cb6e.png)
スキルIDは、alexa developer consoleの「エンドポイント」から確認できます。
![13-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/ad5f0bd7-6087-4236-4079-f49c4598c49e.png)

これでLambdaの関数の作成と設定は完了です。

### エンドポイントの設定

最後に、alexaが呼び出すエンドポイントを設定します。
スキルIDを確認した画面と同じ画面の「デフォルトの地域」で、エンドポイントを設定できます。
![13-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/31c90ac9-18a9-b6fd-d27d-634a2a5a7098.png)
Lambdaで作成した関数の画面にある赤枠の部分を、上記画面に設定します。
![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/7d0b2794-5ae3-f098-9ca5-046a3d9529cb.png)

「エンドポイントを保存」をクリックし、「テスト」タブから試してみると……
![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234341/f8994a9d-a0f9-ce6b-461d-2690128a5955.png)
答えてくれました:thumbsup:

ちなみに、コードのエラーはCloudWatchのログで確認できます。
Alexaのテストをしてうまく動かない場合は、こちらを確認すると何が起こっているのか分かります。

また、alexa developer consoleでテストをする際に、キャッシュが残っている(？)のかLambda側を修正しても反映されていないことがありました。

## おわりに

思っていたより手軽にできました！ブラウザでテストもできるので便利ですね。
LambdaやCloudWatchには無制限無料枠があり、こうやって遊ぶ程度では無料の範囲内に収まるはずなので気軽に試せました。
今は一覧と言っておきながら鳥取県を通る国道しか答えてくれないので、各都道府県を通る国道を答えてくれるようにしたいです。

## 参考

- Alexa公式ブログの[Alexaスキル開発トレーニングシリーズ](https://developer.amazon.com/ja/blogs/alexa/author/Kazushige+Yoshida)
- https://qiita.com/godan09/items/6dafc3da51b10532dbe1
- https://qiita.com/no-use-kuro/items/8511609488ce27e391aa