ServerlessFramework+TypeScriptで作るAlexaスキル

ついに日本でもAmazonEchoが発売されました！
ということで、ServerlessFrameworkでAlexaスキルを作って見ようと思います。
今回は[Alexa Skills Kit for Node.js](https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs)+TypeScriptで実装してみます。

## 前提
事前に[alexa開発者ポータル](https://developer.amazon.com/ja/alexa)に登録しておいてください。 (lambdaをデプロイするだけなら不要ですが
nodeのインストール方法やserverlessの利用方法詳細等は省略します。

## 環境
- node v8.9.1
- yarn v1.3.2
- serverless v1.24.1

## serverlessプロジェクトを作る
まずは、lambda側の実装からやっていきます
適当なディレクトリを作ります

``` bash
$ mkdir serverless-alexa-sample
$ cd serverless-alexa-sample
```

serverlessコマンドでプロジェクトを作成します。

``` bash
$ sls create --template aws-nodejs
```

## TypeScript環境を作る
続いてTypeScriptの環境を作っていきます
package.json作成後、typescript周りのライブラリをインストールします。

``` bash
$ yarn init -y
$ yarn add typescript tslint tslint-config-standard --dev
```

必要な型定義を追加します

``` bash
$ yarn add @types/node @types/alexa-sdk --dev
```

tsconfig.jsonを追加します

``` tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "moduleResolution": "node",
    "target": "es2015",
    "sourceMap": false,
    "outDir": "build",
    "removeComments": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "allowUnreachableCode": true,
    "allowUnusedLabels": true,
    "alwaysStrict": true,
    "noImplicitReturns": true,
    "suppressImplicitAnyIndexErrors": true,
    "lib": [
      "es2017"
    ],
    "types": [
      "node"
    ]
  },
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}
```

tslint.jsonを追加します

``` tsling.json
{
  "extends": "tslint-config-standard"
}
```

続いてtsファイル用のディレクトリを生成し、既存のhandler.jsをリネームして配置します。

``` bash
$ mkdir src
$ mv handler.js src/handler.ts
```

このままではTypeScriptのビルドエラーとなるためhandler.tsを以下のように修正します。

``` diff:src/handler.ts
+ module.exports.hello = (event: any, context: any, callback: any) => {
+   const response = {
+     statusCode: 200,
+     body: JSON.stringify({
+       message: 'Go Serverless v1.0! Your function executed successfully!',
+       input: event
+     })
+   }
+ 
+   callback(null, response)
+ 
+   // Use this code if you don't use the http event with the LAMBDA-PROXY integration
+   // callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event })
+ }
```

ビルドが通るか確認してみます。
package.jsonにビルド用のコマンドを追加します。

``` diff:pakage.json
{
  "name": "serverless-alexa-sample",
  "version": "1.0.0",
  "main": "index.js",
  "author": "YukiYonekura <yuki.yonekura@example.com>",
  "license": "MIT",
+  "scripts": {
+    "build": "tsc"
+  },
  "devDependencies": {
    "@types/alexa-sdk": "^1.0.10",
    "@types/node": "^8.0.53",
    "tslint": "^5.8.0",
    "tslint-config-standard": "^7.0.0",
    "typescript": "^2.6.1"
  }
}
```

ビルドしてみます。

``` bash
$ yarn run build
yarn run v1.3.2
$ tsc
Done in 1.06s.
```

これでTypeScriptでの開発環境が整いました！
ビルドしたファイルは./buildディレクトリに配置されています。(tsconfig.jsonで指定

```
build
└── handler.js
```

## 実装
ここからlambdaの実装に入ります
今回は「開いて」と発話すると「こんにちはアレクサ」、「終了」と発話すると「さようならアレクサ」、
それ以外なら「よく分かりません」と返すだけの簡単なスキルを実装してみます。

[Alexa Skills Kit for Node.js](https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs)を追加します

``` bash
$ yarn add alexa-sdk
```

handler.tsを以下のように修正します

``` src/handler.ts
import * as Alexa from 'alexa-sdk'

module.exports.hello = (event: Alexa.RequestBody<any>, context: Alexa.Context) => {
  const alexa = Alexa.handler(event, context)
  alexa.registerHandlers({
    'Unhandled': function () {
      this.emit(':tell', 'よく分かりません')
    },
    'LaunchRequest': function () {
      this.emit(':tell', 'こんにちはアレクサ')
    },
    'AMAZON.StopIntent': function () {
      this.emit(':tell', 'さようならアレクサ')
    }
  })
  alexa.execute()
}
```

実際の実装ではstate毎にhandlerを実装していくことになると思うので、
handlerは別ファイルに分けたほうがいいでしょう


serverless.ymlを以下のように編集します
handlerにビルドしたファイルを指定し、eventsにAlexaスキル用のイベントを追記します

``` diff:serverless.yml
 functions:
   hello:
+    handler: build/handler.hello
+    events:
+      - alexaSkill
```

スキルの実装は以上になります
tsファイルをビルド後にslsコマンドでデプロイします

``` bash
$ yarn run build
$ sls deploy
```

### 参考
- [Alexa Skills Kit for Node.js はじめの一歩](https://dev.classmethod.jp/cloud/alexa-skills-kit-for-node-js-first/)
- [serverless - Alexa Skill](https://serverless.com/framework/docs/providers/aws/events/alexa-skill/)


## Alexaスキルの登録
ここからは、開発者コンソールからAlesaスキルの登録を行って行きます

（スキルの登録方法については、[公式](https://developer.amazon.com/ja/blogs/alexa/post/6e716e5c-55b0-445b-b936-9cfac4712e7b/training-1)に詳しくのっているで、雑に説明しています

まずは、[ALEXAのホーム](https://developer.amazon.com/edw/home.html#/)にアクセスし、
「Alexa Skills Kit」の「始める」ボタンをクリックします

![alexa_01.png](https://qiita-image-store.s3.amazonaws.com/0/148095/6b4769fe-092a-f05f-507c-1297f0525e86.png "alexa_01.png")

登録済みのスキル一覧が表示されます
まだスキルを登録していないので何も表示されていません。
「新しいスキルを追加する」ボタンをクリックします。

![alexa_02.png](https://qiita-image-store.s3.amazonaws.com/0/148095/092cb8ba-ebc5-3e84-f5ef-e1292ddfe7b4.png "alexa_02.png")

スキルの作成画面に遷移します。
説明に従って項目を埋めていきます。

![alexa_03.png](https://qiita-image-store.s3.amazonaws.com/0/148095/4302d8c4-0b55-3341-0083-021af96318fa.png "alexa_03.png")

※ALEXAではなくカナで「アレクサ」の方がいいかも

ちゃんと日本語化されているので特に困ることはないと思います。
全て載せると ~~面倒くさい~~ 長くなるのでデフォルト値から変更した箇所だけ載せます

### スキル情報
#### 言語
もちろん「Japanese」を選択します

#### スキル名
任意の名前を入力

#### 呼び出し名
画像では「ハローALEXAサンプル」となっていますが、カタカナの方がいい気がします

### 対話モデル
#### インテントスキーマ
AMAZON.StopIntentだけ登録します。
LaunchRequestとUnhandleは不要です

```
{
  "intents": [
    {
      "intent": "AMAZON.StopIntent"
    }
  ]
}
```

#### サンプル発話
AMAZON.StopIntentと「終了」、「おわり」という単語を紐づけます
組み込みインテントなので「ストップ」、「停止」等の単語は登録しなくても認識されます

```
AMAZON.StopIntent 終了
AMAZON.StopIntent おわり
```

### 設定
#### サービスエンドポイントのタイプ
「AWS Lambda の ARN (Amazonリソースネーム）」を選択

#### デフォルト
デプロイしたlambdaのARNを入力


例 `arn:aws:lambda:ap-northeast-1:123456789012:function:serverless-alexa-sample-dev-hello`

### テスト
ここまで項目を入力するとテスト実行が可能になります
サービスシミュレーターから以下のように動作を確認できます

LaunchRequest
![alexa_04.png](https://qiita-image-store.s3.amazonaws.com/0/148095/301a52cf-41e0-df69-f9ea-d74903d13988.png "alexa_04.png")

AMAZON.StopIntent
![alexa_06.png](https://qiita-image-store.s3.amazonaws.com/0/148095/d58fdaa2-b248-701a-3233-42e661796aed.png "alexa_06.png")

Unhandled
![alexa_05.png](https://qiita-image-store.s3.amazonaws.com/0/148095/44ce877c-09b6-14e3-8890-ebc9b9db7318.png "alexa_05.png")

## 追記: アプリケーションIDの設定
Alexa SDKではApplicationIDを設定してあげないと、ログに以下のようなWarningが出力されます

```
Warning: Application ID is not set
```

ApplicationIDはAlexaの開発者コンソールで確認できます。
「スキルIDの表示」をクリックするとApplicationIDが表示されます。

![alexa_07.png](https://qiita-image-store.s3.amazonaws.com/0/148095/70973950-5935-154b-037a-c8f791f20dea.png)

ApplicationIDを確認したらhandler.tsを以下のように編集します
今回はApplicationIDをLambdaの環境変数から取得するようにしています

``` diff:src/handler.ts
import * as Alexa from 'alexa-sdk'

module.exports.hello = (event: Alexa.RequestBody<any>, context: Alexa.Context) => {
  const alexa = Alexa.handler(event, context)
+  alexa.appId = process.env.APPLICATION_ID
  alexa.registerHandlers({
    'Unhandled': function () {
      this.emit(':tell', 'よく分かりません')
    },
    'LaunchRequest': function () {
      this.emit(':tell', 'こんにちはアレクサ')
    },
    'AMAZON.StopIntent': function () {
      this.emit(':tell', 'さようならアレクサ')
    }
  })
  alexa.execute()
}
```

続いてserverless.ymlに環境変数の設定を追記します

``` diff:serverless.yml
functions:
  hello:
    handler: build/handler.hello
+    environment:
+      # 開発者コンソールで取得したApplicationIDを設定
+      APPLICATION_ID: amzn1.ask.skill.12345678-1234-1234-1234-123456789012
    events:
      - alexaSkill
```

これでWarningは表示されなくなりました

## コード
今回作成したコードは以下においています
https://github.com/YukiYonekura/serverless-alexa-sample

## まとめ
**招待メールはよ**


申請までの流れはEchoが届いたら書くかも