bot-expressを使ってピザ注文受付Botを60分で作ってみる

# 概要

この記事ではBotの利用例でよく引き合いに出されるピザ注文受付Botを開発する方法をチュートリアル形式で解説します。Bot本体はNode.jsベースで開発し、コンシェルジュ型Botの開発フレームワーク：[bot-express](https://github.com/nkjm/bot-express)を使ってどのくらい開発が単純化されるかを体験することを目的としています。bot-expressはNPMでインストールできます。

今回作成するBotはLINEとFacebook Messengerの両方で利用することができます。

# 所要時間

- 60分

# 必要なスキル

- Node.jsの基本的な知識
- LINEまたはFacebook MessengerでBotを作成した経験

# 手順

## LINEまたはFacebookで最低限必要な設定をおこなう

利用するメッセージプラットフォームに応じて、必要な設定をおこないます。

LINEの場合はまず[LINE Business Center](https://business.line.me/ja/)にアクセスし、今回のBot用に新しいMessaging APIのビジネスアカウントを作成し、Wehookの有効化、Channel Access Tokenの生成などをおこないます。
参考：[AIが入ったBotの作り方を学ぼう - Part2 LINEでビジネスアカウントを作成する](http://qiita.com/nkjm/items/daa4e34b26ef937446c6)

Facebook Messengerの場合はまず[Facebook for Developers](https://developers.facebook.com)にアクセスし、Facebookアプリの作成・設定をおこないます。公式サイトに良い[Getting Startedドキュメント](https://developers.facebook.com/docs/messenger-platform/guides/quick-start)があるのでありますのでそちらを参照ください。

いずれに場合もWebhookのURLは後述のBot本体を開発した後に設定しますので現時点では設定不要です。


## dialogflowでピザ注文を認識できるagentを作成する

dialogflowは自然言語処理のサービスです。ユーザーが発話した文章が何を意図しているのかを特定するために利用します。また、その文章の中からパラメーターを抽出する機能も備えています。2018年5月現在、基本的な機能は無償で利用できますのでまずは下記dialogflowのサイトにアクセスしてアカウントを作成してください。

[dialogflow](https://dialogflow.com/)

アカウントが作成できたらdialogflowにログインし、Agentを新規に作成します。
![スクリーンショット 2018-05-04 9.27.39.png](https://qiita-image-store.s3.amazonaws.com/0/26079/185ba6ba-9381-41f7-ee26-29b41f72533a.png "スクリーンショット 2018-05-04 9.27.39.png")



最初にEntityを登録します。これはユーザーが発話する文章の中でBotが抽出したいパラメータになります。今回はピザの種類（pizza）とサイズ（size）という2つのパラメーターをEntityとして登録します。
![スクリーンショット 2018-05-04 9.29.56.png](https://qiita-image-store.s3.amazonaws.com/0/26079/95ea13f6-ac5a-06bc-41a1-01d528d7f53a.png "スクリーンショット 2018-05-04 9.29.56.png")
![スクリーンショット 2018-05-04 9.30.14.png](https://qiita-image-store.s3.amazonaws.com/0/26079/45ab9b54-43a6-2a43-a6ea-80c702471f24.png "スクリーンショット 2018-05-04 9.30.14.png")



次にIntentを登録します。Intentはユーザーが何を求めているのかを表すものです。今回は「ピザの宅配をお願いします」というIntentを作成します。このIntentに複数の例文を登録することでdialogflowはそのIntentについて学習をおこない、次第に少々の表記揺れがあってもユーザーの意図を認識できるようになります。

例文には全くパラメーターが入っていないパターン（ex.ピザを注文したいのですが）と、パラメーターが入っているパターン（マリナーラのLサイズをお願いたいのですが）をいくつか登録します。パラメーターが入っているパターンの例文を登録すると、自動的にパラメーター部分が黄色くマークされるはずです。これによってdialogflowは文章からパラメーターを抽出してくれるようになります。
![スクリーンショット 2018-05-04 9.31.22.png](https://qiita-image-store.s3.amazonaws.com/0/26079/e993a86d-35f9-c859-232a-4fac345986b2.png "スクリーンショット 2018-05-04 9.31.22.png")


そしてこのIntentのActionを **handle-pizza-order** と設定してください。

このActionで指定した値をもとにbot-expressはどのスキルを利用するかを決定します。後ほどスキルスクリプトを **handle-pizza-order.js** というファイル名で作成しますが、そのファイル名とこのActionが一致している必要があります。



## Bot本体の開発準備をおこなう

ソースコードを入れるディレクトリを作成し、gitレポジトリを初期化しておきます。

```
$ mkdir pizza-bot
$ cd pizza-bot/
$ git init
```

.gitignoreファイルを作成してgitの管理対象外とするファイルを記述しておきます。

```/.gitignore
.DS_Store
.gitignore
node_modules
npm-debug.log
```

npm initコマンドでpackage.jsonファイルを作成し、bot-expressをインストール
します。

```
$ npm init --yes
$ npm install --save express bot-express
```

インストール中にskillディレクトリとindex.jsスクリプトを自動生成するか聞いてきますので「y」と入力して自動生成させます。

```
May I create skill directory and index.js for you? (y/n): y
```

これでカレントディレクトリ配下には下記の4つのファイル・ディレクトリが存在しているはずです。

```
index.js	node_modules	package.json	skill
```

自動生成されたindex.jsスクリプトにはbot-expressの初期設定が含まれています。今回はこれ以上変更する必要はありませんが、詳細な設定オプションについては[bot-expressのドキュメント](https://nkjm.github.io/bot-express/)を参照してみてください。

また、skillディレクトリにはBotのスキルを1スキル1ファイルという形で作成していくことになります。

## Bot本体で「スキル」を作成する

早速skillディレクトリ直下にピザの注文を受け付けるスキル「handle-pizza-order.js」を下記コードの通り作成します。このファイル名はdialogflowで設定したIntentのactionと同一である必要があります。

```javascript:/skill/handle-pizza-order.js
'use strict';

module.exports = class HandlePizzaOrder {

	// コンストラクター。このスキルで必要とする、または指定することができるパラメータを設定します。
    constructor() {
        this.required_parameter = {
            pizza: {
                message_to_confirm: {
                    type: "template",
                    altText: "ご注文のピザはお決まりでしょうか？ マルゲリータ、マリナーラからお選びください。",
                    template: {
                        type: "buttons",
                        text: "ご注文のピザはお決まりでしょうか？",
                        actions: [
                            {type:"postback",label:"マルゲリータ",data:"マルゲリータ"},
                            {type:"postback",label:"マリナーラ",data:"マリナーラ"}
                        ]
                    }
                }
            },
            size: {
                message_to_confirm: {
                    type: "template",
                    altText: "サイズはいかがいたしましょうか？ S、M、Lからお選びください。",
                    template: {
                        type: "buttons",
                        text: "サイズはいかがいたしましょうか？",
                        actions: [
                            {type:"postback",label:"S",data:"S"},
                            {type:"postback",label:"M",data:"M"},
                            {type:"postback",label:"L",data:"L"}
                        ]
                    }
                }
            },
            address: {
                message_to_confirm: {
                    type: "text",
                    text: "お届け先の住所を教えていただけますか？"
                }
            },
            name: {
                message_to_confirm: {
                    type: "text",
                    text: "最後に、お客様のお名前を教えていただけますか？"
                }
            }
        };
    }

    // パラメーターが全部揃ったら実行する処理を記述します。
    async finish(bot, event, context){
        let message = {
            text: `${context.confirmed.name} 様、ご注文ありがとうございました！${context.confirmed.pizza}の${context.confirmed.size}サイズを30分以内にご指定の${context.confirmed.address}までお届けに上がります。`
        };
        
        await bot.reply(message);
    }
};

```

まずコンストラクターにこのスキルで必要となるパラメーターをthis.required_parameterのプロパティとして設定しています。ここに設定したパラメーターはすべて集まるまでbot-expressがユーザーにメッセージを送信して確認（質問）します。

そのときに送信するメッセージを各パラメーターごとにmessage_to_confirmプロパティで設定できます。このプロパティはサポートされているメッセージプラットフォーム（LINEとFacebook Messenger）のいずれかのフォーマットで設定します。今回はLINEのメッセージフォーマットで記述しています。

それぞれのメッセージフォーマットについては下記の公式APIリファレンスを参照ください。

- LINE: https://devdocs.line.me/ja/#send-message-object
- Facebook Messenger: https://developers.facebook.com/docs/messenger-platform/send-api-reference


次にfinishメソッドを実装しています。このメソッドは必要なパラメーターが全部揃ったら実行されます。このメソッドには5つの引数が与えられており、中でも特に重要なのが第三引数のcontextです。このオブジェクトには確定したパラメーターの値が収められています。今回の例だとpizza, size, address, nameの情報がcontext.confirmedに入っています。開発者はこの情報を利用して最終処理を記述することができます。

また、第一引数のbotにはreply()メソッドが用意されておりLINEやFacebook Messengerといったメッセージプラットフォームの違いを意識することなく返信ができるようになっています。

## Bot本体をデプロイする

作成したBot本体のコードをクラウドへデプロイします。この記事ではクラウドにherokuを利用します。まずheroku上に新しいアプリケーションを作成します。

```
$ heroku apps:create
Creating app... done, ⬢ powerful-reaches-12345
https://powerful-reaches-12345.herokuapp.com/ | https://git.heroku.com/powerful-reaches-12345.git
```

今回、私の環境で作成されたアプリケーション名は **powerful-reaches-12345** ですが、このアプリケーション名はherokuの全アプリケーションに渡りユニークな値になります。以降はこのアプリケーション名をご自身の値に置き換えて進めてください。

このアプリのgitレポジトリを登録します。

```
$ heroku git:remote -a powerful-reaches-12345
set git remote heroku to https://git.heroku.com/powerful-reaches-12345.git
```

herokuでBot本体を起動するための設定ファイルである **Procfile**を作成し、書きの通りに記述します。

```text:/Procfile
web: node index.js
```

次にBot本体の環境変数に必要なパラメーターを設定します。

LINEやFacebook Messenger、そしてdialogflowと連携するにあたり、それぞれのサービスのアプリケーションIDやトークンをBot本体に設定する必要があるためです。これらはメインスクリプトの中で環境変数を通じて設定できるようになっています。

```
$ heroku config:set GOOGLE_PROJECT_ID=あなたの値
$ heroku config:set GOOGLE_CLIENT_EMAIL=あなたの値
$ heroku config:set GOOGLE_PRIVATE_KEY=あなたの値
$ heroku config:set LINE_CHANNEL_ID=あなたの値
$ heroku config:set LINE_CHANNEL_SECRET=あなたの値
```

GOOGLE_PROJECT_IDとGOOGLE_CLIENT_EMAILの値はDialogflowのAgent設定画面のGeneralタブで確認できます。下図のProject IDがGOOGLE_PROJECT_ID、Service AccountがGOOGLE_CLIENT_EMAILです。
![スクリーンショット_2018-05-04_9_42_22-2.png](https://qiita-image-store.s3.amazonaws.com/0/26079/abe6e3fe-3dc9-c24f-2c02-7a9542e49bd6.png "スクリーンショット_2018-05-04_9_42_22-2.png")

GOOGLE_PRIVATE_KEYは、Dialogflowの同設定画面からService Accountのリンクをクリックし、Google Cloud Platformのサービスアカウントの設定画面から生成します。

Dialogflow Integrationsの右端になるメニューからキー生成を選択します。
![スクリーンショット_2018-05-04_9_48_55.png](https://qiita-image-store.s3.amazonaws.com/0/26079/c9c9f2c5-6313-802f-4f81-4f9e7c2265a5.png "スクリーンショット_2018-05-04_9_48_55.png")

JSONを選択して作成をクリックします。
![スクリーンショット_2018-05-04_9_49_14.png](https://qiita-image-store.s3.amazonaws.com/0/26079/c7001ce9-f7d1-7291-f911-8722a15aa36b.png "スクリーンショット_2018-05-04_9_49_14.png")

JSON形式の鍵ファイルがダウンロードされます。このJSONファイルを開くと、中にprivate_keyの項目がありますのでその値をGOOGLE_PRIVATE_KEYに設定してください。


gitコマンドでコードをherokuにデプロイします。

```
$ git add .
$ git commit -m "first commit"
$ git push heroku master
```

これでBot本体はクラウド上で稼働し始めました。

## メッセンジャー側にWebhook URLを設定する

Webhook URLとはBot本体がメッセンジャーからのイベント通知を受け取るためのURLです。このURLは自由に設定できますが、本記事の設定では下記のフォーマットになります。

https://あなたのアプリケーション名.herokuapp.com/webhook

このWebhookのURLをLINEまたはFacebook Messenger側に設定します。

### LINEの場合

下記のチュートリアルを参照し、Messaging APIのChannelを作成します。そのChannelの設定に、WebhookのURLを設定します。
https://qiita.com/nkjm/items/38808bbc97d6927837cd#channel%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B

### Facebook Messengerの場合

[Facebookで自分が管理するアプリ一覧](https://developers.facebook.com/apps/)からBot用のアプリを選択します。左サイドバーのプロダクトからMessengerを選択し、右側のメインウィンドウの中からWebhooksセクションまでスクロールし、Webhookの設定ボタンをクリックします。
![スクリーンショット_2017-04-12_15_21_41.png](https://qiita-image-store.s3.amazonaws.com/0/26079/5b3fd035-89de-bb99-1597-3d56329f4e98.png "スクリーンショット_2017-04-12_15_21_41.png")

コールバックURLにWebhook URLを入力し、トークンにはFacebook App Secretを入力します。さらにmessagesとmessaging_postbacksにチェックして「確認して保存」ボタンをクリックします。
![スクリーンショット 2017-04-12 15.26.45.png](https://qiita-image-store.s3.amazonaws.com/0/26079/02d5d00b-f1b2-8516-0fa9-51811bfecfdc.png "スクリーンショット 2017-04-12 15.26.45.png")

Bot本体側にテストアクセスが行われ、トークンが正しく検証できればWebhookが追加されるはずです。トークンの検証はbot-expressがおこなってくれます。


## テストしよう

まず、「ピザを注文したいのですが」と話しかけてみます。するとBotは必要なパラメータを隈なく確認した上で注文を受付ます。
![IMG_0477.PNG](https://qiita-image-store.s3.amazonaws.com/0/26079/96036b9d-a7fd-ed08-9ab6-9dbc3d24e295.png "IMG_0477.PNG")

次に、「マリナーラのMサイズをお願いしたいのですが」と話しかけてみます。すると今回はピザの種類とサイズは確認されません。届け先と名前だけ確認され、それで注文が完了します。
![IMG_0478.PNG](https://qiita-image-store.s3.amazonaws.com/0/26079/52029168-ebfa-6a20-243b-c0bca4eedd76.png "IMG_0478.PNG")


これはdialogflowが自然言語処理の段階でpizza:マリナーラ, size:Mというパラメーターを抽出してくれ、それを受けたbot-expressがパラメーターを文脈情報にすでにセットしているのであらためてユーザーに確認する必要なしと判断されたためです。

## DEBUG

うまく動かない、という場合はDEBUGしましょう。
環境変数にDEBUGを追加して下記のように設定します。

```
$ heroku config:set DEBUG=bot-express:*
```

そしてHeroku上のログをheroku logsコマンドにてリアルタイムに眺めます。

```
$ heroku logs --tail
```

この状態でBotにメッセージを送信してみてください。どこかでエラーになっている可能性が高いと思います。
よくある設定ミスを下記に挙げておきます。

- 環境変数が正しくセットされていない => heroku configコマンドで環境変数名とその値をチェックしてください。
- ソースコードがShift-JISになっている => Windowsの方は要注意。すべてUTF8で保存してください。
- ソースのファイル名がまちがっている => 拡張子、大文字小文字、ファイルの場所をチェックしてください。

# 次のステップ

今回は最小限構成のスキルファイルを作成しましたが、実際にはfinish()メソッドに注文をデータベースに保存する処理を追加したり、ユーザーが返信した内容が各パラメーターに適しているか検査する処理が必要になるでしょう。

後者は各パラメータごとにparserを作成することで実装できるようになっています。parserを含むスキルファイルがbot-expressの[sample_skill/handle_pizza_order.js](https://github.com/nkjm/bot-express/blob/master/sample_skill/handle-pizza-order.js)にありますので見てみてください。

スキルファイルの仕様について詳しくは[bot-expressのドキュメント](https://nkjm.github.io/bot-express/)を参照してみてください。