[入門]Clovaスキル(CEK)は作りながら覚えて行く（Lambda使ってコード実装編）

# この記事で書いてあること

先日CEKの一般公開を期に、`CEK + Lambda + API Gateway`を実践してみました。
この記事を一通りやると、簡単なClovaスキルが作成できるようになります。


| No|タイトル|
|------------------|--------------------------------------|
|1|[[入門]Clovaスキル(CEK)は作りながら覚えて行く（前提知識編）](https://qiita.com/imajoriri/items/8d511cdfde8cb731ff82)|
|2|[[入門]Clovaスキル(CEK)は作りながら覚えて行く（インテント、スロットなど登録編）](https://qiita.com/imajoriri/items/604ff74c4e81859771e2)|
|3|[[入門]Clovaスキル(CEK)は作りながら覚えて行く（Lambda使ってコード実装変）<<今ここ](https://qiita.com/imajoriri/items/357026af6b0de0dfe330)|
|4|[[入門]Clovaスキル(CEK)は作りながら覚えて行く（signature検証編）](https://qiita.com/imajoriri/items/92d8a2906e554f43b2a5)|
|番外編|[公式SDKを使ってLambdaで開発した記事を書いてみました](https://qiita.com/imajoriri/items/0ef7aba9fea1f2d6ea1c)|

Clova専用のSDKを作ったので、それを元にエンドポイント（今回はLambdaを使用）の実装について説明していく。
インテントやスロットはすでにCEKで定義している前提。

***公式SDKを使ったLambdaでの開発方法も書きました。***
[Clovaの公式SDKがLambdaで使えるようになっていたので試してみた。](https://qiita.com/imajoriri/items/b67318cfda04998255b8)

# 注意

~~今回は簡単化のために***「RSA公開鍵を用いてリクエストメッセージを検証する」***をしていません。
しなくても、動作はするのですがLINEさん公式では署名を検証することを推薦しています。
なので、この記事を拡張して一般公開用のスキルを作成する場合は検証機能を追加することをお勧めします。
詳しくは[こちら](https://clova-developers.line.me/guide/#/CEK/References/CEK_API.md#Signature)
(次回の記事で自分の方でも検証のやり方の記事を書こうかなと思います。)~~
書きました。
[[入門]Clovaスキル(CEK)は作りながら覚えて行く（signature検証編）](https://qiita.com/imajoriri/items/92d8a2906e554f43b2a5)

また、今回使用しているSDKはあくまで個人で開発したものであり、出来て間もないためバグなどがある可能性があります。ご了承ください。今後も修正を重ねて行く予定です。
なお、ask-sdkに似た様な書き方でかける様にしているのでおなじみの方にはやりやすいかと思います。

# 環境

* npm入ってる >> (npmなにそれ、って人向けにzipファイル用意しましたので必須ではないです。)
* node.js(8.10以上) >> (node.jsなにそれ、って人向けにzipファイル用意しましたので必須ではないです。)
* AWSのアカウント持ってる >> これは必須です。

# まずは、簡単にスキルの説明

今回は、星座で占いをしてくれる「ほげほげ占い」を作っていく。

## 会話例

```
ユーザー: 「Clova、ほげほげ占いを開いて」
Clova: 「あなたの星座を教えてください」
ユーザー: 「私の星座は魚座です。」
Clova: 「魚座ですね。魚座の今日の運勢は○○です。」（スキル終了）
```

## Slots

スロット名: `constellation`

値:

```
ふたご座
蟹座
魚座
乙女座
etc
```

## インテント

インテント名: `DivinationIntent`

発話例:

```
私の星座は魚座です
私の星座は蟹座だ
私の星座はふたご座​よ
```

# 開発の流れ

1. ローカル（自分のパソコン)にてNode.jsでコードを書いちゃう。
2. Lambdaにzipファイルとしてアップロードする。
3. Lambdaのテスト画面で試してみる。
4. 実機で試してみる。

# ローカルでの開発

***npmまたはNode.jsなんて知らないよ、という方にZIPファイル用意しました。[ここから](https://github.com/imajoriri/fortune-func/raw/Qiita_3/upload.zip)ダウンロードしてください。
そして、「AWSのコンソール画面で」まですっ飛ばして大丈夫です。***

## 初期設定

ディレクトリ作成

```
$ mkdir fortune-func
$ cd fortune-func
$ npm init #なんか色々聞かれるが全部エンター押していく
```

インストール

```
$ npm install --save love-clova
```

ファイル作成

```
$ vi index.js
```
***好きなエディタをご利用ください。***

## コードのコピペ

`index.js`に以下のコードをコピペする。
とりあえずの自作SDKなのでご了承を。

```js
var clova = require("love-clova");

const LaunchRequestHandler = {
  canHandle: function(handlerInput){
    return handlerInput.requestEnvelope.isMatch('LaunchRequest');
  },
  handle: function(handlerInput){
    var msg = "あなたの星座を教えてください";
    return handlerInput.responseBuilder.speak(msg).reprompt(msg).getResponse();
  }
}

const SessionEndedRequestHandler = {
  canHandle: function(handlerInput){
    return handlerInput.requestEnvelope.isMatch('SessionEndedRequest');
  },
  handle: function(handlerInput){
    var msg = "";
    return handlerInput.responseBuilder.speak(msg).reprompt(msg).getResponse();
  }
}

const ClovaGuideIntentHandler = {
  canHandle: function(handlerInput){
    return handlerInput.requestEnvelope.isMatch('Clova.GuideIntent');
  },
  handle: function(handlerInput){
    var msg = "このスキルは今日のあなたの運勢を占います。あなたの星座を教えてください。";
    return handlerInput.responseBuilder.speak(msg).reprompt(msg).getResponse();
  }
}

const DivinationIntentHandler = {
  canHandle: function(handlerInput){
    return handlerInput.requestEnvelope.isMatch('DivinationIntent');
  },
  handle: function(handlerInput){
    // 運勢を配列で。
    var fortunes = ["良い", "普通", "悪い"];
    // fortunesの中からランダムで
    var fortune = fortunes[Math.floor(Math.random() * fortunes.length)];

    // slotsを取得
    var constellation = handlerInput.requestEnvelope.request.intent.slots.constellation.value;

    // clovaに話す内容を作成。
    var msg = `${constellation}ですね。${constellation}の今日の運勢は${fortune}です。`;

    return handlerInput.responseBuilder.speak(msg).getResponse();
  }
}

const errorHandler = {
  canHandle: function(handlerInput){
    return true;
  },
  handle: function(handlerInput){
    var msg = "エラー発生";
    return handlerInput.responseBuilder.speak(msg).reprompt(msg).getResponse();
  }
}

exports.handler = clova.extensionBuilders
  .addRequestHandlers(LaunchRequestHandler,SessionEndedRequestHandler,ClovaGuideIntentHandler,DivinationIntentHandler)
  .addErrorHandlers(errorHandler)
  .lambda()
```

## zipファイル化

lambdaにコードをあげる方法はいくつかあるが、簡単なZIPファイルにしてあげる方法にする。
fortune-funcディレクトリ配下でとりあえず、存在しているファイルを確認。

```ls
$ ls 
index.js	package-lock.json	 node_modules		package.json		
```

全部を圧縮する

```
$ zip -r upload.zip *
```

再度、存在しているファイルを確認

```ls
$ ls 
index.js	package-lock.json	 node_modules		package.json	upload.zip	
```

`upload.zip`というファイルが作成されているのが確認できる。

# AWSのコンソール画面で

## Lambdaで関数作成

AWSのLambdaの画面に移動し、右上の「関数の作成」ボタンを押す。

<img width="1399" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3139353838322f30363864356333652d616463332d353663322d383432372d3836623965646463636630632e706e67.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/366fb395-3c71-fb01-980d-8436396b37a5.png">

「一から作成」を選択肢、`名前`、`ランタイム`、`ロール`を以下のように設定する

* 名前: FortuneFunc
* ランタイム: Node.js ***8.10***
* ロール: カスタムロールの作成(すでにある場合は既存のロールを選択する）

<img width="1324" alt="Lambda_Management_Console-2.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/221fcaf2-9b76-8678-b9ca-63fcf6030eb5.png">

カスタムロールの作成を選ぶと新規画面が勝手に出てくるので、右下の`許可`をクリック。

<img width="1406" alt="IAM_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/d4389e11-be8b-52ff-f7ab-cf39740c8c58.png">


すると、先ほどの関数の作成画面に戻るはず。
右下の`関数の作成`をクリック。
おそらく、以下のような画面になるはず。

***ご指摘いただいたのですが、人によってはLambdaの出力(画面右の方）がCloudWatchだけだったりしますが今回は動作上問題ありません。***

<img width="1325" alt="スクリーンショット 2018-07-05 18.44.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/6e0e3c95-70ce-d2bd-845e-c6e603e15635.png">


次に、画面少し下行くと`関数コード`という項目があるので、その中の`コードエントリタイプ`を`zip ファイルをアップロード`を選択する
そんで、その下の`アップロード`ボタンを押して先ほど作成下`upload.zip`を選択して、右上の`保存`ボタンで保存する。

![Lambda_Management_Console.png](https://qiita-image-store.s3.amazonaws.com/0/195882/ae1590ec-2f35-5bf9-ee7b-8ddd4c2030fc.png)


## とりあえず、テストしてみる。

まだ、ClovaとLambdaを連携させてないので実機でのテストはできないがLambda上でテストする方法がある。
Lambda画面右上の「テストイベントの設定」ボタンをクリックするとテストイベントの設定画面が出てくるので以下の様に入力してあげる。

<img width="1323" alt="Lambda_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/3e087c67-f910-6f7c-f890-27fe23c63a27.png">


イベント名

```
LaunchRequest
```

イベント名の下のJSONを入力するところ。

```json
{
  "version": "0.1.0",
  "session": {
    "new": true,
    "sessionAttributes": {},
    "sessionId": "sample",
    "user": {
      "userId": "V0qe",
      "accessToken": "sample"
    }
  },
  "context": {
    "System": {
      "application": {
        "applicationId": "sample"
      },
      "user": {
        "userId": "V0qe",
        "accessToken": "sample"
      },
      "device": {
        "deviceId": "sample",
        "display": {
          "size": "l100",
          "orientation": "landscape",
          "dpi": 96,
          "contentLayer": {
            "width": 640,
            "height": 360
          }
        }
      }
    }
  },
  "request": {
    "type": "LaunchRequest"
  }
}
```

<img width="800" alt="スクリーンショット 2018-07-11 16.45.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/04859ecf-2194-ad41-8eb9-0e3e6f744a05.png">


作成ボタンを押してから右上の「テスト」を押すとテストが実行される。

<img width="1304" alt="Lambda_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/fffa307d-db1c-5ef7-ecd3-40567beefb80.png">


テストが成功すると以下の様な画面が表示される。
赤く囲った部分が実際にClovaが喋るテキストの部分である。

<img width="1281" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3139353838322f65366230326138392d633038342d303539322d386439392d3663643931316361383466302e706e67.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/b6f3fd7a-9689-0902-3373-7d8ba5c8fb4a.png">



こんな感じだと、とりあえずちゃんと動いている。

## API Gateway

このままではClova側と繋ぐことができないので、API Gatewayを使用する。
API Gatewayとは外部サービスとLambdaを連携させる入り口的な役割。

AWSのサービス一覧からAPI Gatewayのページに行き「APIの作成」ボタンを押す。
次に、以下の様に`API名`と、`説明（なくても良い)`を入力し「APIの作成」ボタンを押す。

API名: CEKGateway
説明: for CEK Gateway

<img width="1223" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3139353838322f33353561303331322d393035632d646630662d373137622d3436353230393464323766352e706e67.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/c248372e-20a2-486d-ecb3-077dd155fb5b.png">


左上の「アクション」ボタンより「リソースの作成」を押し、以下の様にリソース名を入力し「リソースの作成」ボタンを押す。

リソース名: Fortune

<img width="1155" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3139353838322f66336663653332632d666164612d643932302d363435652d6335333463623065313332632e706e67.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/5016bd76-b40d-6adb-a9c0-20d2b5694cb2.png">


次に、また左上の「アクション」ボタンから「メソッドの作成」ボタンを押す。
すると、ドロップボタンができるので「POST」を選択しすぐ右のチェックボタンを押す。

<img width="1142" alt="API_Gateway.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/697fa9fb-71c9-b5a4-c551-7858accf5a1c.png">


セットアップ画面になるので、総合タイプを「Lambda関数」を選択し、Lambda関数項目には先ほど作成した「FortuneFunc」を入力する。

<img width="1168" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3139353838322f37346163336538312d333066332d373438662d333462612d6634623330393661633864372e706e67.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/f31fd09a-8cd5-1ff1-ef75-a16b4505a034.png">

権限の追加の許可を聞かれるので「OK」をクリック

<img width="935" alt="スクリーンショット_2018-07-14_12_14_36.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/e9da4bd0-ef48-7e5e-fb41-b252e11b95a5.png">


またまた、左上の「アクション」ボタンから「APIのデプロイ」をクリックする。

<img width="1158" alt="API_Gateway.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/aa25f40a-5c37-efed-438a-fb221e092387.png">


入力画面が出てくるので、以下の様に入力し「デプロイ」ボタンを押す。

デプロイされるステージ: 新しいステージ
ステージ名: prod
ステージの説明: production

<img width="625" alt="API_Gateway.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/484a4fdd-5231-c389-ac15-6187d1259dcd.png">

画面左のディレクトリ構成みたいなところから`prod/fortune/POST`を選択すると以下の様な画面になる。
URLの呼び出しってところのURLをメモっておく。
***URLの最後の部分が/prod/foutuneとなっているのを確認。***

<img width="1160" alt="API_Gateway.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/40fa1239-271b-74f9-9c10-a152da3592ab.png">


# CEK側でLambdaと連携させる

次にいよいよ、CEKとLambdaを連携させる。
先ほどの「スキル設定画面」より「サーバー設定」の欄に行く。

先ほどAPI GatewayでコピーしたURLを「ExtensionサーバーのURL」欄に入力する。
そして、保存。これで完成。

<img width="1310" alt="Banners_and_Alerts_と_Clova_Developer_Center_β.png" src="https://qiita-image-store.s3.amazonaws.com/0/195882/0aa85f60-5727-3437-b007-33f579a65d96.png">



# ブラウザでテスト

ブラウザでちゃんと動くかをテストしてみる。

画面左の、先ほどのビルドボタンのすぐ下あたりにある「テスト」ボタンをクリックする。
すると、ユーザーのサンプル発話をテストできるページが表示されるので私の星座は魚座です。を入力しエンターを押す。

すると、すぐ下にテスト結果が帰ってくる。

「サービスの応答」の項目にCLovaが喋る内容がテキストで帰ってきているのが確認できる。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3139353838322f37356239613661372d366666372d383030332d616630652d3463376133366438326130352e706e67-2.png](https://qiita-image-store.s3.amazonaws.com/0/195882/f0c73eee-49be-22bc-405f-92b6b0098374.png)

# 実機でテスト

いよいよ実機でテストをしてみましょう。
実機でテストするには「Clova friends」もしくは、「Clova friends mini」が必要です。
さらには、開発したのと同じアカウントでセットアップしてある必要があります。

実機でのテストは簡単。
「Clova, ほげほげ占いを開いて」と行って見てください。
すると先ほど開発したスキルが起動します。

# 実機でのテストがうまくいかない。

スキルがうまく起動しない場合、考えられる原因としてClovaが誤認識した可能性があります。
[ここで](https://clova-developers.line.me/guide/#/DevConsole/Guides/CEK/Device_Test_Extension.md)Clovaが認識した内容を確認する方法が書かれているので参考にして見てください。

# それでも、それでもうまくいかない場合

この記事か、SDKに問題がある場合も考えられるのでTwitterでここに[@imasirooo](https://twitter.com/imasirooo)ご連絡いただけると助かります。
もちろん、Clova以外のスマートスピーカー開発での質問も受け付けてます。（答えられるかはわからないですが）
皆さんでスマスピを盛り上げていきましょう!!!

# 最後に

長々としてしまいましたが、読んでいただきありがとうございます。
今回使用したSDKは、日々修正していこうと思います。
もしご要望***あれば***コードの中身だっったり、今回使ってない色々な機能を記事にしていこうかなと思います。

次は、公開には必須のsignature検証についてです。
[[入門]Clovaスキル(CEK)は作りながら覚えて行く（signature検証編）](https://qiita.com/imajoriri/items/92d8a2906e554f43b2a5)