初めてのAlexa Skill開発 

# はじめに
本記事は、[アイレット株式会社 22新卒 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/iret-22)　18日目の記事です。

ブラックフライデーで初めてスマートスピーカーを購入したので色々触ってみたいと思います。

本記事は[Alexa Skills Kit Official](https://developer.amazon.com/ja-JP/alexa/alexa-skills-kit/get-deeper/tutorials-code-samples)にあるチュートリアルを参考にしています。

こちらもぜひ参考にしてください。

# 1.この記事を見てできること

- Alexa　スキルの概要を理解できる
- 簡単な Alexa スキルを作成できる

# 2.事前準備
- Amazon Developerアカウントの用意
- サンプルコードの確認
- テストをするためのマイクなどの音声入力機器（無くても可）

### Amazon Developerアカウントについて

[Amazon Developer](https://developer.amazon.com/dashboard)にログインするためのアカウントが必要です。
「Amazon.co.jp」 のアカウントを持っている人はそちらでログインし、
アカウント持っていない人は作成しましょう。

***ここで注意です！
アカウント作成時は必ず「Amazon.co.jp」で作成しましょう。***
誤って「Amazon.com 」のアカウントでログインし、スキルの作成を進めていくと、Alexaアプリで表示されないなどの現象があります。
![スクリーンショット 2022-12-11 040743.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/ec38b056-dfae-d21b-b882-8aa6920cd925.png)

## サンプルコード
Git hub 上で[日本語用のサンプルコード](https://github.com/alexa-samples/skill-sample-nodejs-fact/tree/ja-JP)があるので、それをお手本に「宇宙の豆知識」のAlexa skillを作成していきます。

## 音声入力機器
作成したスキルのテストにマイクなどの音声入力機器を使います。
今回自分はヘッドセットを使いました。
最悪なくてもテキストでテストは可能です。

# 3.Alexa スキル とは？
Alexaスキルは簡単に言うとAlexa向けのアプリのようなものです。
ユーザーの声を使って、ニュースの確認、音楽の再生、他のIoT機器を制御をすることができます。
たとえば、ユーザーはAlexaに、照明をつけたり、エアコンの温度を変更したりするよう頼むことができます。スキルは、AmazonのAmazon EchoやAmazon Fire TVを始め、ほかのメーカーのAlexa搭載デバイスでも利用できます。

仕組みなどは[公式サイト](https://developer.amazon.com/ja-JP/alexa/alexa-skills-kit/start)でわかりやすく解説されているので気になった方はぜひ確認してみてください。


# 4.Alexa スキル作成

ログインし、この画面が表示されたら、赤で囲んである Alexa skills kit を選択します。
![スクリーンショット 2022-12-11 211035.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/8dbb5543-6763-c5d4-ee97-554d18088ed0.png)


この時初めてアクセスする時はプロフィール情報等の入力が必要になります。
以下の情報を登録します。
- 国/リージョン（日本を選択してください）
- 名/性
- Eメールアドレス（今回作成したアカウントのメールアドレス）
- 電話番号
- 開発者名
- 開発者名（ふりがな）
- 住所

登録が完了すると以下のコンソール画面に遷移します。

では早速「スキルの作成」を選択し、作成に移りましょう！
![スクリーンショット 2022-12-11 042616.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/c7566cf4-c496-3d0d-3d35-c1b14f8ced02.png)

スキルの名前は以下のように「宇宙の豆知識」に、
プライマリロケーションは「日本語」にし、右上の「次へ」を押下
![スクリーンショット 2022-12-11 233029.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/c8b2d55f-ee0b-b527-2275-407c166cf00e.png)

エクスペリエンスのタイプは「ゲーム&トリビア」
モデルは「カスタム」
ホスティングサービスはサンプルコードがNode.jsのため「Alexa hosted(Node.js)」
ホスト地域は「米国東部（バージニア北部）」を選択し、「次へ」を押下


![スクリーンショット 2022-12-11 233112.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/e3a9474d-e319-3a9e-ce02-e8c99d2a016e.png)
![スクリーンショット 2022-12-11 233356.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/4451366a-e080-1ab3-88fc-768ab1d1ab26.png)

選択内容を確認し、「Create Skill」を押下
スキルの作成には数分かかります。

![スクリーンショット 2022-12-11 233628.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/ba0ec2c7-9832-c221-763e-04b7e54ab370.png)

![スクリーンショット 2022-12-11 233904.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/d2925f0a-4f5d-7f0e-303b-92eebe5c9039.png)

作成されるとこのように表示されます。
![スクリーンショット 2022-12-12 011215.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/32a4e4ab-dead-112b-4dd3-91840c4a4e94.png)



### 補足
Alexa スキルにはフロントエンドとバックエンドがあります。
フロントエンドでは、発話（ユーザーが言ったこと）をインテント（目的のアクション）にマッピングします。バックエンドでユーザーのインテントをどう処理するかを決定する必要があります。
AWS Lambda 関数やHTTPSエンドポイントを使って独自にスキルをホスティングするか、Alexaでスキルをホスティングするかのいずれかです。AWSの無料枠には制限があるため、スキルの使用頻度が上がってきたら、独自にホスティングするオプションに変更できます。今回は無料枠内でつくる簡単なスキルのため、Alexaでホスティングするオプションを選択します。

## フロントエンド
左側のメニューの「対話モデル」 > 「JSONエディター」を編集します。
![スクリーンショット 2022-12-12 012026.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/35e9e993-7ad4-904b-c00e-a6999e8c1160.png)
以下のサンプルコードに編集し、「モデルを保存」し、「モデルビルド」します。
```ja-JP.json
{
  "interactionModel": {
    "languageModel": {
      "invocationName": "宇宙の豆知識",
      "intents": [
        {
          "name": "AMAZON.CancelIntent",
          "samples": []
        },
        {
          "name": "AMAZON.HelpIntent",
          "samples": []
        },
        {
          "name": "AMAZON.StopIntent",
          "samples": []
        },
        {
          "name": "GetNewFactIntent",
          "samples": [
            "豆知識",
            "教えて",
            "話して",
            "聞かせて",
            "宇宙の豆知識",
            "豆知識を教えて",
            "宇宙の豆知識を教えて",
            "豆知識を聞かせて",
            "宇宙の豆知識を聞かせて",
            "トリビア",
            "宇宙のトリビア",
            "トリビアを教えて",
            "宇宙のトリビアを教えて",
            "何か教えて",
            "宇宙について教えて",
            "なんか教えて",
            "なんか言って"
          ],
          "slots": []
        }
      ]
    }
  }
}
```

![スクリーンショット 2022-12-12 014130.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/68fc17d8-0ce5-0de6-b596-833bf7fdfa0b.png)

補足でも簡単に記載していますが、こちらのJSONで書いている部分がフロントエンドになります。
詳しく知りたい方は[こちら](https://developer.amazon.com/ja-JP/alexa/alexa-skills-kit/get-deeper/tutorials-code-samples/build-an-engaging-alexa-skill/module-2)から確認できます。

## バックエンド
「コードエディタ」を押下し、「lambda」ファイルの「index.js」を編集します。

![スクリーンショット 2022-12-12 022923.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/0d259189-8a6e-8246-2bef-e3c95c1cfcf5.png)
![スクリーンショット 2022-12-12 023118.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/d08a3554-89c5-fd56-991a-da3e95ad9b0f.png)

以下のサンプルコードに置き換えてください

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

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

//=========================================================================================================================================
//TODO: このコメント行より下の項目に注目してください。
//=========================================================================================================================================

const SKILL_NAME = "宇宙の豆知識";
const GET_FACT_MESSAGE = "知ってましたか？";
const HELP_MESSAGE = "豆知識を聞きたい時は「宇宙の豆知識」と、終わりたい時は「おしまい」と言ってください。どうしますか？";
const HELP_REPROMPT = "どうしますか？";
const FALLBACK_MESSAGE = "";
const FALLBACK_REPROMPT = "";
const STOP_MESSAGE = "さようなら";

//=========================================================================================================================================
//「TODO: ここから下のデータを自分用にカスタマイズしてください。」
//=========================================================================================================================================

const data = [
    "水星の一年はたった88日です。",
    "金星は水星と比べて太陽より遠くにありますが、気温は水星よりも高いです。",
    "金星は反時計回りに自転しています。過去に起こった隕石の衝突が原因と言われています。",
    "火星上から見ると、太陽の大きさは地球から見た場合の約半分に見えます。",
    "木星の<sub alias='いちにち'>1日</sub>は全惑星の中で一番短いです。",
    "天の川銀河は約50億年後にアンドロメダ星雲と衝突します。",
    "太陽の質量は全太陽系の質量の99.86%を占めます。",
    "太陽はほぼ完璧な円形です。",
    "皆既日食は一年から二年に一度しか発生しない珍しい出来事です。",
    "土星は自身が太陽から受けるエネルギーの2.5倍のエネルギーを宇宙に放出しています。",
    "太陽の内部温度は摂氏1500万度にも達します。",
    "月は毎年3.8cm地球から離れていっています。"
];

//=========================================================================================================================================
//この行から下のコードに変更を加えると、スキルが動作しなくなるかもしれません。わかる人のみ変更を加えてください。  
//=========================================================================================================================================

const GetNewFactHandler = {
    canHandle(handlerInput) {
      const request = handlerInput.requestEnvelope.request;
      return request.type === 'LaunchRequest'
        || (request.type === 'IntentRequest'
          && request.intent.name === 'GetNewFactIntent');
    },
    handle(handlerInput) {
      const randomFact = data[Math.floor(Math.random() * data.length)];
      const speechOutput = GET_FACT_MESSAGE + randomFact;
  
      return handlerInput.responseBuilder
        .speak(speechOutput)
        .withSimpleCard(SKILL_NAME, randomFact)
        .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 FallbackHandler = {
    // 2018-May-01: AMAZON.FallackIntent は現在 en-US のみ対応しております。
    //              その他の地域・言語では呼び出されませんが、デプロイには問題
    //              ありません。    
    canHandle(handlerInput) {
      const request = handlerInput.requestEnvelope.request;
      return request.type === 'IntentRequest'
        && request.intent.name === 'AMAZON.FallbackIntent';
    },
    handle(handlerInput) {
      return handlerInput.responseBuilder
        .speak(FALLBACK_MESSAGE)
        .reprompt(FALLBACK_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 skillBuilder = Alexa.SkillBuilders.custom();
  
  exports.handler = skillBuilder
    .addRequestHandlers(
      GetNewFactHandler,
      HelpHandler,
      ExitHandler,
      FallbackHandler,
      SessionEndedRequestHandler
    )
    .addErrorHandlers(ErrorHandler)
    .lambda();
```


コードを書き換えたら、「保存」を押下し、続いて「デプロイ」を押下しましょう。

今回はDBを使っていないですが、Dynamo DBや、S3を使うこともできます。

コードに登場するハンドラーなどの意味を知りたい方は[こちら](https://developer.amazon.com/ja-JP/alexa/alexa-skills-kit/get-deeper/tutorials-code-samples/build-an-engaging-alexa-skill/module-3)をご覧ください。

---
デプロイが完了したら、最後に「テスト」です
![スクリーンショット 2022-12-12 023933.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/df895880-1227-75bd-1002-41c824f24dfc.png)

こちらの「非公開」->「開発中」に変更するとテストができます
![スクリーンショット 2022-12-12 024322.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/b8540e11-ff54-c9d8-0e04-f292c99d3161.png)


テストの仕方はマイクのマークを押しながら、「関数の呼び出し名」と「設定したフレーズ」を話す
または入力することでテストが出来ます。


![スクリーンショット 2022-12-12 025115.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/1c6c3123-1183-f154-5b82-265e3855027f.png)

今回は関数を作成してから呼び出し名は変更していないので「宇宙の豆知識」が設定されています。
以下の画像のように、まずは関数を呼び出します。
そのあとは設定していたフレーズで処理を動かします、今回のフレーズはJSONファイルの「GetNewFactIntent」に設定したものです。
また、今回のサンプルコードでは関数を呼び出された時とのデフォルトレスポンスと、フレーズを言ったときのレスポンスは同じなので少しわかりずらいかもしれません。

![スクリーンショット 2022-12-12 031929.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/081f550e-2168-fb38-6ca9-b3eb113a3f55.png)

![スクリーンショット 2022-12-12 032015.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2957407/3b8247ef-0413-1011-c52e-b3d619a268ca.png)

以上で初めてのAlexa Skill開発 を終わります！
この記事でAlexa skillに興味を持った方はぜひ[公式のチュートリアル](https://developer.amazon.com/ja-JP/alexa/alexa-skills-kit/get-deeper/tutorials-code-samples/build-an-engaging-alexa-skill)を見てみて下さい！
今回説明しきれなかったことが詳しく書かれています


# さいごに
本来はここからスキルの公開範囲を設定し、スキルを申請し、審査してもらいます。
今回はサンプルなのでしませんが、これから自分でスキルを作成したらぜひ審査もしてみたいです！