enebularで社畜感あふれるAlexaスキルを作った話

これを書いている時点で、12/24。クリスマスイブでございます。
本来は12/20の枠なのにだいぶ遅くなってしまい、申し訳ありませんでした！
ジャンピング土下座！！





# enebularとの出会い

- [【9/19 東京：女性限定】ノンプログラミングでClovaスキルハンズオン＆お茶会](https://kotodama.connpass.com/event/100102/)


LINEのテクニカルエバンジェリスト立花さんから突然、[丸山さん](https://qiita.com/hmaruyama/items/bb198f96dbf9f4b58369)と私に女子会運営のお誘いがあり、
さらに「スキル作成はenebularを使ってノンプログラミングでやりましょう」と。
そこで立花さんが召喚したのが[enebularマスターの田中さん](https://www.1ft-seabass.jp/memo/2018/12/02/enebular-infotype-with-chartjs-canvas-firststep/)でした。

- [enebular developer Meetup Vol.5 11/22](https://enebular.connpass.com/event/104535/)

9月のイベントで田中&丸山両氏がやってたように、httpsでJsonの受け渡しができるRestAPIが作れるなら、Alexaスキルも作れるのではないかと考え、[LT資料](https://docs.google.com/presentation/d/1IGabsG41KBxcLzau4SEBDZXA02lclScorwic8iF9QTY/edit?usp=sharing) p.19〜のようにチャレンジしたのですが、うまく行かず。。

ぐぬぬ。。
今日はこれをリベンジしたいと思います。(^o^)


# 結論：Alexaスキル、出来た！

田中さんからもらったClovaスキルハンズオンの資料
https://blog.enebular.com/line/line-cek-meets-enebular/
こちらからenebularのflowをforkし、Alexa用にちょこちょこっと書き換えたら、できました(^o^)


## 作ったもの

「元気ですか？」と聞くと「元気です」と答えるAlexaスキル。

- セッションの引き回し
- カスタムスロットからの発話取り出し

も、やってます。(^o^)

### システム構成


開発環境：

- Mac book air
- chrome
- enebular


プロダクト環境：
Amazon Echo <---> Alexa <---> HEROKU 


### 動くデモ

<a href="https://youtu.be/pRPGbXcj9Gk"><img src="https://qiita-image-store.s3.amazonaws.com/0/173392/ebd62bf2-b51c-16d1-0315-c7d5b77bc5b0.png"></a>

https://youtu.be/pRPGbXcj9Gk

**ものすごく真面目にやった結果、なぜか、ブラック企業の社畜コントのようになってしまいました。**

## 作りかた

### 1. Alexa developerサイトでスキル新規登録

https://developer.amazon.com/ja/alexa

- スキル名：(なんでも良い）
- 呼び出し名：なんでも良いですが、「サンプルのツール」にしました。
- カスタムスロット：GenkiSlot
- カスタムスロットの値：元気、げんき
- カスタムインテント：GenkiIntent
- カスタムインテントのサンプル発話：
 - {GenkiSlot}
 - {GenkiSlot} ですか
 - {GenkiSlot} かな
- インタフェース：　最初は空。あとで、Enebularのプロジェクトをデプロイ後に発行されるHEROKUのURLを記入。


|![Alexaスキル.png](https://qiita-image-store.s3.amazonaws.com/0/173392/989363ff-3d54-8459-e46d-305557608e50.png)|
|:--:|





### 2. enebular フローつくる

Alexaスキル・フロー図（クリックで拡大）

|![image.png](https://qiita-image-store.s3.amazonaws.com/0/173392/90f94712-136c-399b-88c6-39dbfe8a9a91.png)|
|:--:|


同じものをDiscoverに置いたので、よろしければImportして使ってみてください。
https://enebular.com/discover/flow/4d5a35ad-72d0-4823-8179-91cb7db0fb8c




### 3. enebular フロー、書き換え

[田中マスターのClovaスキルサンプル](https://enebular.com/discover/flow/626f9173-346a-4183-a8a9-bff8cb2a19db)に対して、Alexa向けに書き換えをしました。
clovaスキルと、若干文法が違いますのと、セッションの引き回しを追加しました。
ノードごとに詳しく書いていきます。

ノード名は、「Alexaスキル・フロー図」に対応しております。m(_ _)m


### httpレスポンスノード「POST」
URL: /clova
↓
URL: /


###　functionノード 「LaunchRequestセリフ」

Alexaの文法にあわせてかきなおし。

```
msg.payload =
{
  "version": "1.0",
  "sessionAttributes": {
    "count": 1
  },
  "response": {
    "outputSpeech": {
      "type": "PlainText",
      "text": "今日は新しいことを学ぶチャンスが訪れるでしょう。 それをやり通せば可能性は無限です。"
    },
    "card": {
      "type": "Simple",
      "title": "カードです",
      "content": "今日は新しいことを学ぶチャンスが訪れるでしょう。 それをやり通せば可能性は無限です。"
    },
    "reprompt": {
      "outputSpeech": {
        "type": "PlainText",
        "text": "他にも質問はありますか？"
      }
    },
    "shouldEndSession": false
  }
}
return msg;
```
参考） Alexa公式のJson応答例
https://developer.amazon.com/ja/docs/custom-skills/request-and-response-json-reference.html#standard-response-to-launchrequest-or-intentrequest-example


### switchノード 「Slots有無」

プロパティ：
payload.request.intent.slots.Genki
↓
payload.request.intent.slots.GenkiSlot を指定

プロパティの条件分岐は、以下のようになります。

|![image.png](https://qiita-image-store.s3.amazonaws.com/0/173392/1124b6a6-ea6c-563c-a2c2-6f1b274a89aa.png)|
|:--:|


「全ての条件を適用」になっている場合は、
「最初に合致した条件で終了」に切り替えておきます。


### switchノード「slots.Genki判定」

はい、ここはポイントです。

**プロパティに「payload.request.intent.slots.GenkiSlot.value」を指定すると、カスタムスロットから発話を取り出すことができるんですねー！すてき(^o^)**



プロパティ：
payload.request.intent.slots.GenkiSlot["value"]
を指定します。


|![image.png](https://qiita-image-store.s3.amazonaws.com/0/173392/91c59151-9a51-e326-0ffa-7e376b9ca617.png)|
|:--:|



→ 1 ：functionノード「発言がぬるぽでした」
→ 2 ：switchノード「session attributes count判定」
→ 3 ：functionノード「元気以外はききとれません」

最初3をつけてなかったので、Alexaスキルがタイムアウトをしてしまいました。
Slotの値が特定の単語以外だった場合の受け皿として設ける必要があります。


**※このプロパティを応用すると、Alexa developerサイトのほうで、カスタムスロットに「蠍座、射手座、」のように何種類かをセットしておけば、それを拾って会話の分岐ができます。夢が膨らみまくりんぐ！**




### function ノード「発言がぬるぽでした」

```
// GenkIntentに入ったが、GenkiSlot=null
// 「ぬるぽです」と返す
msg.payload =
{
  "version": "1.0",
  "sessionAttributes": {
    "count": 1
  },
  "response": {
    "outputSpeech": {
      "type": "PlainText",
      "text": "発言がぬるぽでした。もういちどおねがいします。"
    },
    "card": {
      "type": "Simple",
      "title": "GenkIntentに入ったけど、GenkiSlot=nullですぜって表示される",
      "content": "発言がぬるぽでした。もういちどおねがいします。"
    },
    "reprompt": {
      "outputSpeech": {
        "type": "PlainText",
        "text": "他にも質問はありますか？"
      }
    },
    "shouldEndSession": false
  }
}
return msg;
```

### switchノード 「session attributes count判定」

session attributesから、countを取得して、今何回めかを判定します。
**このように、コードを書かなくてもセッションから値を取り出せるのは、ありがたいです(^p^)**
プログラム苦手勢もこれならハードルが下がるのでは！(^o^)

|![スクリーンショット 2018-12-24 12.59.44.png](https://qiita-image-store.s3.amazonaws.com/0/173392/b3d3e99d-dbcb-c1dc-888f-d5fa386b3bbb.png)|
|:--:|

これだけですよ。。ちょっと信じられないです。
いままでなんだったんだ・・
書くのも楽だし可視化されるのありがたいですよね・・

ちなみにこのcountの値は、セリフのFunctionの中でセットしています。
上の「LaunchRequestセリフ」だと、4行目〜6行目です。

```
  "sessionAttributes": {
    "count": 1
  },
```
### function ノード「元気以外はききとれません」

冒頭のデモ動画に出てくるセリフです。
アレクサがブラック企業の鬼上司、私が社畜のようになっておりました。(^^;)


```
// GenkIntentに入ったが、GenkiSlot=「元気」以外の場合、
// 「元気以外はききとれません」と返す
msg.payload =
{
  "version": "1.0",
  "sessionAttributes": {
    "count": 1
  },
  "response": {
    "outputSpeech": {
      "type": "PlainText",
      "text": "元気以外はききとれません。もういちどおねがいします。"
    },
    "card": {
      "type": "Simple",
      "title": "GenkIntentに入ったけど、GenkiSlot=「元気」以外ですぜって表示される",
      "content": "元気以外はききとれません。もういちどおねがいします。"
    },
    "reprompt": {
      "outputSpeech": {
        "type": "PlainText",
        "text": "他にも質問はありますか？"
      }
    },
    "shouldEndSession": false
  }
}
return msg;
```

### Functionノード「1回目、元気です！返答（ここには来ない）」

受け皿として設けましたが、ロジック上ここへは来ません。ので飛ばします。


### Functionノード「n回目です。やたら元気です。と返答」

はい、ここがキモです。

**「msg.payload.session.attributes.count」には、
このスキルを呼び出してから「元気です」と繰り返した回数が格納されています。
自然に格納されているわけではなく、このノードの中でカウントアップのロジックを書いて、
"sessionAttributes"にセットし直しています。（★1、★2のところ）**


```
//sessionからcountを取得

var count = msg.payload.session.attributes.count;　　//セッションから「元気です」と繰り返した回数を取得
var next = count + 1; //次の呼び出しに向けてカウントアップ（★1）


msg.payload =
{
  "version": "1.0",
  "sessionAttributes": {
    "count": `${next}` 　//カウントアップしたものをセット（★2）
  },
  "response": {
    "outputSpeech": {
      "type": "PlainText",
      "text": `${count}`+" 回目です。やたら、元気です。" //「元気です」と繰り返した回数をセリフの中にぶちこむ（★3）
    },
    "card": {
      "type": "Simple",
      "title": "カードここに書く",
      "content": "今日は新しいことを学ぶチャンスが訪れるでしょう。 それをやり通せば可能性は無限です。"
    },
    "reprompt": {
      "outputSpeech": {
        "type": "PlainText",
        "text": "他にも質問はありますか？"
      }
    },
    "shouldEndSession": false
  }
}
return msg;
```
さらに、セリフのなかに「今何回目か」を喋るようにしました。（★3のところ）


### httpノード

これは応答を返す大事なノードです。ですが使い方は簡単で、
ファンクションの後ろに線で繋げるだけでOKでした。


### msg.payload

デバッグ用に置いてます。
デモ動画の中で、私が「元気です！」と返答すると、行がぴろぴろっと出てましたが、
このmsg.payloadの働きです。


|![genki.png](https://qiita-image-store.s3.amazonaws.com/0/173392/d5dd4bdb-33d5-b49a-4535-332cd6860bc7.png)|
|:--:|

いろんなところにつけたり剥がしたり。。
console.logが見たい時に使います。
なお、これが貼り付けられるノードは、右側に出力の端子が出てるものです。


## 4.デプロイ

enebularのコンソールから、Herokuにデプロイします。
そこで払い出されたURLをAlexaスキルのエンドポイントに指定することで、連結できます。

## 5.デバッグ

Alexa Developerサイトの「テスト」シミュレーターを使って一通りテストをします。

|![image.png](https://qiita-image-store.s3.amazonaws.com/0/173392/b4bbbf7d-17cf-4ea7-ee53-7b0b037328b4.png)|
|:--:|

うまくいったら、実機テストもしてみましょう！たのしい！

## 残課題

### AWS Lambdaへデプロイ


enebularは、Deploy時にとても簡単にHerokuにアップできるので、とりあえずそれで大丈夫なんですが、
作ったAlexaスキルを一般公開する場合は、AWS Lambdaにアップしておきたいです。
これはIAMの作成が必要なんで、またの機会に。


# 感想

これは**やばいです。**

つくってて圧倒的に楽しかったです。

スマートホーム向けNode-red対応のAlexaスキルとは違って、
会話型のスキルをnode-redで書くという奇特な人がいないのか、ググっても説明がでてこず、
また、残念ながら、node-red-contrib-alexaの一連のノードがうまく動かず、
基本的なhttpとfunctionでやってしまいましたが、それでも十分でした。

フローが可視化できるのは大変便利ですね。
AWS Lambdaにも対応しているということですし、サーバーサイドのnode-redも有りだな..と思った次第です。

いずれプログラマという職種は、こういうフローの、ファンクションブロックの中のコードを書く人、
そしてそれを共通化して売るという商売になるのかもしれません。
enebularとかtwillioとか、プラットフォームが違っても、言語が同じなら使いまわせると思うので。
あっ、そうなったら、コード公開は義務付けられるだろうから、商売にはならんでしょうね。
人類の進歩に拍車がかかるなぁ〜(^o^)

その時に私はどのような関数を書けるかしら。
技を磨いておきたいと思います。



以上っす！