Alexaのカスタムスキルを作る（その1）

# はじめに
- 我が家にAmazon Echo Dotが届きました。日本版。
- 早速カスタムスキルで遊んでみようと思います。
- 2017年12月11日時点の記事になります。参考にさせて頂いた記事も時期により古かったりしたので参考になれば。
- なお筆者は**Node.jsもLambdaも未経験です。**がんばりましょー

# 作ろうとしているカスタムスキル
- 簡単なところからで、Alexaにビットコイン相場でも教えてもらおうと思います。
- APIはzaifのAPIを使用します。

# この記事でやること
- Amazon開発者登録
- カスタムスキル設定（Alexa skill kit）
- API作成（AWS Lambda）
- シミュレーターによるテスト

次回予定
- zaif APIをコールし価格取得
- 実機テスト

# Amazon開発者登録
https://developer.amazon.com/ja/
なんと2017年12月時点で日本アカウントで登録可能です。
私は日本のAmazonのアカウントで登録できました。

# カスタムスキル設定

まずは開発者サイトにログインします
<img width="1557" alt="スクリーンショット 2017-12-13 17.03.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/a33a2860-b356-abcf-7dba-b9891bfa6806.png">

Alexaを選択します

<img width="1563" alt="スクリーンショット 2017-12-13 17.04.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/95a6841b-106e-fe40-bf1f-910e8b5e5db6.png">

画面右上の「あなたのAlexaダッシュボード」をクリック

<img width="920" alt="スクリーンショット 2017-12-13 17.04.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/8b3e8210-076f-17cb-0a33-ddfc9fac9ec3.png">

Alexa Skill Kitの「始める」ボタンをクリック

<img width="1556" alt="スクリーンショット 2017-12-13 17.04.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/038bc279-21ee-3fed-ba4f-b035de7b0648.png">

既に作ったスキルが表示されていますが、最初は空です。
「新しいスキルを追加する」をクリック

## スキル情報

<img width="1057" alt="スクリーンショット 2017-12-13 17.05.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/64d030c5-dd7a-c5f5-5056-ec719666b291.png">

スキル情報はこんな感じでセット
言語は日本語でいきます。

次の「対話モデル」というところがAlexa Skill Builderのbeta版が使えるのでここではそれを使います。

## 対話モデル

<img width="1577" alt="スクリーンショット 2017-12-13 17.06.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/2a2436f8-81d7-1a0c-c29d-ba8fddd9a477.png">

こんな画面
Code Editorに生のJSONが出ますが直接触らなくても大丈夫。
IntentsとSlot Typesを触ります。

### Intents設定

<img width="1578" alt="スクリーンショット 2017-12-13 17.07.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/19c12926-726a-8fa7-50f3-6738c017f37f.png">

IntentsのところのADD+をクリックすると上記の画面に移ります。
既にbtcIntentは作ってしまった後なので、ここでは入力例としてxemIntentと入れています。

<img width="1580" alt="スクリーンショット 2017-12-14 14.08.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/4bd4e8e8-5b4c-bce0-29a7-a728fd186577.png">


#### 例文の正しい設定のしかた
samplesに入れる語句は、以下の太字の部分です。
「アレクサ、びっとこいんそうばで**びっとこいんをおしえて**」
で、ここの「びっとこいん」の部分をモナやNEMに変えたい場合には変数にします。

変数と言っているのは上記画面でいうIntent Slotsのことです。
Slotの型を次のところで設定します。

### Slot Types設定

<img width="1146" alt="スクリーンショット 2017-12-14 14.51.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/6f44e3e2-34ab-f606-d498-f2a7f0f6613d.png">


こんな感じで。
Synonym（同義語）のところに同義語を登録します。XEMはぜむでもねむでも反応してほしいところ。

### だいじなこと
ここまでの設定が終わってCode Editorで生JSONを見るとわかるのですが
"invocationName"に「びっとこいんそうば」、"btcIntent"の"samples"に呼び出し語句が書いてあります。
**「びっとこいんそうば」でmy_zaif_chartスキルを使うというところまで特定され、**
**「びっとこいんをおしえて」でbtcIntentを使うというところが特定されます。**
後のテストでどうにもIntentが呼ばれないという場合、大抵ここの設定でミスってます。

## 設定

<img width="1030" alt="スクリーンショット 2017-12-13 17.13.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/671dbb86-6304-1d2a-0337-313ed852b313.png">

今回はLambdaを使うことにしたのですが、ここでARNの入力を求められるので、一旦Lambdaに移ります。

# LambdaでAPI作成

<img width="1575" alt="スクリーンショット 2017-12-13 17.17.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/83c65d3d-56b7-6946-c19c-4fc4670f23b2.png">


Lambdaは設計図の中からalexaのサンプルを探してテンプレートを入れました。
alexa-skill-kit-sdk-howtoskillを選択しました。

```js:index.js
/* eslint-disable  func-names */
/* eslint quote-props: ["error", "consistent"]*/
/**
 * This sample demonstrates a sample skill built with Amazon Alexa Skills nodejs
 * skill development kit.
 * This sample supports multiple languages (en-US, en-GB, de-GB).
 * The Intent Schema, Custom Slot and Sample Utterances for this skill, as well
 * as testing instructions are located at https://github.com/alexa/skill-sample-nodejs-howto
 **/

'use strict';

const Alexa = require('alexa-sdk');
const recipes = require('./recipes');

const APP_ID = 'amzn1.ask.skill.****************************'; // TODO replace with your app ID (OPTIONAL).

const languageStrings = {
    'en': {
        translation: {
            RECIPES: recipes.RECIPE_EN_US,
            SKILL_NAME: 'Minecraft Helper',
            WELCOME_MESSAGE: "Welcome to %s. You can ask a question like, what\'s the recipe for a chest? ... Now, what can I help you with?",
            WELCOME_REPROMPT: 'For instructions on what you can say, please say help me.',
            DISPLAY_CARD_TITLE: '%s  - Recipe for %s.',
            HELP_MESSAGE: "You can ask questions such as, what\'s the recipe, or, you can say exit...Now, what can I help you with?",
            HELP_REPROMPT: "You can say things like, what\'s the recipe, or you can say exit...Now, what can I help you with?",
            STOP_MESSAGE: 'Goodbye!',
            RECIPE_REPEAT_MESSAGE: 'Try saying repeat.',
            RECIPE_NOT_FOUND_MESSAGE: "I\'m sorry, I currently do not know ",
            RECIPE_NOT_FOUND_WITH_ITEM_NAME: 'the recipe for %s. ',
            RECIPE_NOT_FOUND_WITHOUT_ITEM_NAME: 'that recipe. ',
            RECIPE_NOT_FOUND_REPROMPT: 'What else can I help with?',
        },
    },
    'en-US': {
        translation: {
            RECIPES: recipes.RECIPE_EN_US,
            SKILL_NAME: 'American Minecraft Helper',
        },
    },
    'en-GB': {
        translation: {
            RECIPES: recipes.RECIPE_EN_GB,
            SKILL_NAME: 'British Minecraft Helper',
        },
    },
    'de': {
        translation: {
            RECIPES: recipes.RECIPE_DE_DE,
            SKILL_NAME: 'Assistent für Minecraft in Deutsch',
            WELCOME_MESSAGE: 'Willkommen bei %s. Du kannst beispielsweise die Frage stellen: Welche Rezepte gibt es für eine Truhe? ... Nun, womit kann ich dir helfen?',
            WELCOME_REPROMPT: 'Wenn du wissen möchtest, was du sagen kannst, sag einfach „Hilf mir“.',
            DISPLAY_CARD_TITLE: '%s - Rezept für %s.',
            HELP_MESSAGE: 'Du kannst beispielsweise Fragen stellen wie „Wie geht das Rezept für“ oder du kannst „Beenden“ sagen ... Wie kann ich dir helfen?',
            HELP_REPROMPT: 'Du kannst beispielsweise Sachen sagen wie „Wie geht das Rezept für“ oder du kannst „Beenden“ sagen ... Wie kann ich dir helfen?',
            STOP_MESSAGE: 'Auf Wiedersehen!',
            RECIPE_REPEAT_MESSAGE: 'Sage einfach „Wiederholen“.',
            RECIPE_NOT_FOUND_MESSAGE: 'Tut mir leid, ich kenne derzeit ',
            RECIPE_NOT_FOUND_WITH_ITEM_NAME: 'das Rezept für %s nicht. ',
            RECIPE_NOT_FOUND_WITHOUT_ITEM_NAME: 'dieses Rezept nicht. ',
            RECIPE_NOT_FOUND_REPROMPT: 'Womit kann ich dir sonst helfen?',
        },
    },
};

const handlers = {
    'btcIntent': function () {
        
        this.emit(':tell', '100.0えんです');
    },
    'Unhandled': function () {
        this.attributes.speechOutput = this.t('HELP_MESSAGE');
        this.attributes.repromptSpeech = this.t('HELP_REPROMPT');
        this.emit(':ask', this.attributes.speechOutput, this.attributes.repromptSpeech);
    },
};

exports.handler = function (event, context) {
    console.log(event.request.intent.slots);
    //console.log(context);
    const alexa = Alexa.handler(event, context);
    alexa.APP_ID = APP_ID;
    // To enable string internationalization (i18n) features, set a resources object.
    alexa.resources = languageStrings;
    alexa.registerHandlers(handlers);
    alexa.execute();
};
```

handlersの中身から、いらない（と思われるもの）を省いてとりあえず作成。
固定で100円と返ってきます。BTC、MONAなら激安ですね。
※なんかやることが多いのでzaif API接続は次にまわします。

<img width="1573" alt="スクリーンショット 2017-12-13 17.18.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/4a7dd1b4-44cd-c863-6c4b-60f8c1e26878.png">

画面右上にARNが表示されてるんで、コピーして先ほどの設定のところに貼り付けます。

# テスト

<img width="1109" alt="スクリーンショット 2017-12-14 14.31.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/214400/7742161a-717c-753d-21b1-1acaa23907c2.png">

こんな感じ

## common.commaが設定されていませんエラー
テストしようとしてもスイッチをONにできずエラーが出る場合があります。
Builderでビルドしていないことが原因。ビルドが必要なのでやりましょう。

## テスト実行

```json:request
{
  "session": {
    "new": false,
    "sessionId": "SessionId.*********************************",
    "application": {
      "applicationId": "amzn1.ask.skill.*********************************"
    },
    "attributes": {},
    "user": {
      "userId": "amzn1.ask.account.*********************************"
    }
  },
  "request": {
    "type": "IntentRequest",
    "requestId": "*********************************",
    "intent": {
      "name": "btcIntent",
      "slots": {
        "crypt": {
          "name": "crypt",
          "value": "btc"
        }
      }
    },
    "locale": "ja-JP",
    "timestamp": "2017-12-14T05:31:44Z"
  },
  "context": {
    "AudioPlayer": {
      "playerActivity": "IDLE"
    },
    "System": {
      "application": {
        "applicationId": "amzn1.ask.skill.*********************************"
      },
      "user": {
        "userId": "amzn1.ask.account.*********************************"
      },
      "device": {
        "supportedInterfaces": {}
      }
    }
  },
  "version": "1.0"
}
```

```json:response
{
  "version": "1.0",
  "response": {
    "outputSpeech": {
      "ssml": "<speak> 100.0えんです </speak>",
      "type": "SSML"
    },
    "speechletResponse": {
      "outputSpeech": {
        "ssml": "<speak> 100.0えんです </speak>"
      },
      "shouldEndSession": true
    }
  },
  "sessionAttributes": {}
}
```

## Lambda側のログ
CloudWatchで確認。

## Synonymがヒットしない問題
本来なら「びっとこいんそうばでびっとこいんをおしえて」でvalueにｂｔｃが入って欲しいところですが、
以下の文献によると、Service Simulatorではスロット解決をしてくれないようです。
https://dev.classmethod.jp/cloud/alexa-slots-synonyms/


つづく！
https://qiita.com/miutex/items/fbf8535786d07103f30f

# 参考
https://qiita.com/ysak/items/de3e10a00cce19c48076#amazon-echo%E6%8C%81%E3%81%A3%E3%81%A6%E3%81%AA%E3%81%84%E3%81%91%E3%81%A9
https://dev.classmethod.jp/cloud/alexa-slots-synonyms/
https://qiita.com/imajoriri/items/65159385749d463d88bb
https://dev.classmethod.jp/etc/first-step-of-making-alexa-custom-skills/