DialogFlowで会話をし、MobileNetで画像認識を行う自家製チャットサイトの構築

# はじめに
　[第一回](https://qiita.com/nstshirotays/items/cb06863dc7288c0fed25)  LINE風チャットサイトの構築（基本編）
　[第二回](https://qiita.com/nstshirotays/items/e47374a8c35241853280)  LINE風チャットサイトの構築（完成編）
　ということで、第三回目はこの自家製チャットサイトにボット機能と画像認識機能を加えたいと思います。

| ボット機能(DialogFlow) | 画像認識機能(MobileNet) |
|:------------:|:------------:|
| <img width="300" alt="10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/634e7442-c16e-b008-ddd3-5b4eaf19c9cd.png">     | <img width="300" alt="07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/8a100086-e34b-063a-3be1-2b259300158a.png">       | 




# Dialogflow
　googleのdialogflowを使います。会話の設定などはあらかじめ作成したものがあるので、それを読み込んで使います。
　ここではDialogflowの詳しい解説は行いませんが、サンプルエージェント（英語版）を手軽に読み込めますので学習の手始めにいいと思います。なお今回のエージェントはそのサンプルエージェント coffee-shopを大雑把に日本語翻訳したものです。

## エージェント構築手順

まずは私の方で作成したエクスポートzipファイルを、ご自分のローカルマシンにダウンロードしてください。

[CafeMaster.zip](https://drive.google.com/file/d/1vch4RQ40L4ObhAn6wJx5EJtaXWCUSqv-/view?usp=sharing)

ファイル名はそのままでOKです。

そしてDialogflowコンソールへアクセスします
https://dialogflow.cloud.google.com/?hl=ja#/login

中央の「Sign-in with Google」をクリックしてログインします。

<img width="600" alt="1Dialogflow-login.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/4b48196f-e224-7880-8d98-cf61d9dcd76e.png">

IDの指定と連携確認が行われた後、下記の画面になります。
上部のお知らせは消してしまいましょう
サービス規約を確認します

<img width="600" alt="2Dialogflow-login2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/57ea355e-40fe-1eda-d242-176b05efc2fd.png">
ACCEPTをクリックするとWelcome画面になります
早速エージェント作成ボタンを押します
<img width="600" alt="3Welcome.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/c4eb1631-43b0-8a8f-613c-d3ac07e58b07.png">
エージェント作成画面になりますので
1. エージェント名に「CafeMaster」
2. 言語設定に「Japanese-ja」を選択
3. CREATEをクリック
<img width="600" alt="4CreateAgent.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/73d61186-0ba7-c738-86a4-1884fb02a473.png">
しばらくするとインテント作成画面になりますが、ここではあらかじめ作成しておいたエージェントを読み込むために左上の三本線メニューを選択します
<img width="600" alt="5CreateAgent2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/45c8bae2-311d-0b43-f16d-48a5cdf05a90.png">
左のメニューが開いたら、CafeMasterと書かれている右横の歯車マークをクリックしてエージェント設定画面に移ります
<img width="600" alt="6ClickDialog.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/03120be0-1079-9b84-7e8d-4737cfa0b16a.png">
エージェント設定画面が開いたら、真ん中あたりの「Export and Import」タブを選択します
<img width="600" alt="7MasterEntry.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/1924c011-0013-881f-ab11-d41e6e43212c.png">
メニューから「RESTORE FROM ZIP」を選択します
<img width="600" alt="8selectMenu.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/dc8dff72-a56c-0d5e-03e7-5d123d0e9f05.png">
アップロードするファイルの選択画面になりますので、先ほどダウンロードしたCafeMaster.zipをドラッグ＆ドロップしてください
<img width="600" alt="9selectFile.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/5eaa6925-9e53-e70a-c874-be4377adfbae.png">
ファイルが選択されると下記の画面になります。ここで①の部分に「RESTORE」と打ち込み、②のRESTOREボタンを押します
<img width="600" alt="10RestoreFile.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/c777e06e-1ace-305b-b2a4-fcb9b6db05ef.png">
zipファイルが読み込まれ学習が実行されます。しばらくすると完了するので「DONE」を押します
<img width="600" alt="11FinishRestore.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/d83f4224-1adf-ba25-52fb-54c3c7d5c801.png">
これで、設定は完了です
<img width="600" alt="12CreatedAgent.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/2f035780-79d8-fdab-5654-5064b35c6668.png">

## 参考にさせていただいたサイト
[Qiita:Dialogflow入門](https://qiita.com/kenz_firespeed/items/0979ceb05e4e3299f313)
[Geekfeed:DialogFlowのFulfillmentを使ったチャットボット作成](https://www.geekfeed.co.jp/geekblog/dialgflow_Fulfillment)
[Qiita:サルにもわかる Dialogflow FAQ](https://qiita.com/miyasita1958/items/dbf74125f68533f19dba)
[ゆたかみわーく:【Dialogflowの使い方】Fulfillmentを使ってみる(超基礎編)](https://yutakami.work/?p=447)

## キーファイルの作成
　このAgentをプログラムから利用するため GCPのサービス アカウントを作成し、秘密鍵ファイルをダウンロードします。
　公式の案内　　　https://cloud.google.com/dialogflow/docs/quick/setup

GCPからのログインして作成することもできますが、ここではDialogflowコンソールから作成してみたいと思います。

まずはエージェントの設定画面のところにあるGoogle Projectの欄を見てください。ここに自動発行されたProject IDとService Accountが表示されています。ここで、このService Accountをクリックします。


<img width="895" alt="51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/6e03b098-12fe-36ea-479b-6596c0cc70c2.png">
初回時は規約への同意画面を経てGCPのコンソール画面へと遷移します。
この画面から、先ほどの自動設定されたService Accountの右横にある操作欄の…ボタンから「編集」を選択します
<img width="762" alt="52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/a46434fe-859d-8ba0-49af-be63d7c2e800.png">

サービスアカウントの設定画面になりますので「＋キーを作成」を選択します
<img width="762" alt="53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/32d96009-bb9c-fdbe-b60a-57be2be1dd58.png">
秘密鍵の作成ダイアログに代わりますので、作成タイプがJSONであることを確認して「作成」ボタンを押します
<img width="707" alt="54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/25afb098-bf1c-0ae2-cac7-475c1816eba9.png">
秘密鍵ファイルが作成されると「秘密鍵がパソコンに保存されました」メッセージが出て、cafemaster-xxxxxxxxx～.jsonがローカルのパソコンに自動でダウンロードされます。

このファイルを後ほどチャットサーバーにアップロードし、プログラムから参照させるため、ファイル名を **dialogflow.json** に変更してください。

ファイル名を変更したら保存ボタンを押してください。

<img width="707" alt="55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/b46c3fab-6373-7fe4-aefb-3176f3ee84a1.png">

次にこの画面からDialogflow APIの利用許可の設定を確認します。

GCPコンソールのメニューから　「APIとサービス」を選び「ライブラリ」を選択します
<img width="707" alt="56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/5e3994b7-a744-72b0-1314-ca08d0481ac6.png">
APIようこそ画面に遷移しますので、中央の検索ボックスに「dialogflow」と入力します。
<img width="749" alt="57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/31bfe3ea-49b5-86c5-e03e-ba75e59b05da.png">

検索の結果からDialogflow APIを選択します
<img width="749" alt="58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/6fad7915-f2f5-238c-a1a4-557eb1346ca9.png">

下記の画面になっていればOKですのでチャットサーバーの構築に手順を移してください。


<img width="879" alt="追加01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/003f9528-7112-8ac9-e661-233866b8a958.png">


---
### もしもAPIの有効化設定がされていない場合

プロジェクトを作成と書かれているドロップダウンリストから「CafeMaster」を選択します
<img width="749" alt="59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/23c858c5-6e14-a9d9-3917-86b28ce5ce06.png">

「続行」をクリック
<img width="749" alt="60.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/f2e135e4-cc5a-4769-9594-1bf45d70836d.png">

画面にAPIは有効になってます。と表示がでればOKです
<img width="749" alt="61.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/40175ee3-7a0c-c3b7-675e-a05865c58434.png">

そのまま×で閉じます
<img width="749" alt="62.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/1decdf66-d685-93ab-2f6c-26972d0e1ad9.png">
---



以上でDialogflowの秘密鍵の作成とAPIの有効化が完了です。



# サーバー環境構築
前回に引き続きお手軽なクラウドサービスを使って環境構築を行います。
いつものようにPaizaを使います。

##Paiza Cloud
![paiza表紙.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/5b97fa0d-b848-5865-4fe1-80d913697068.png)
[Paiza Cloude](https://paiza.cloud/ja/)にアクセスしてメールアドレスを登録すると、すぐに環境構築ができるようになります。

リンク先
https://paiza.cloud/ja/


## サーバー作成
アカウントを作成したらサーバー作成ボタンを押しましょう
<img width="404" alt="まだサーバーはありません.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/f4945a25-c1d3-b887-0e67-69f82f5ce9eb.png">
新規サーバー作成のポップアップで、Node.jsとMongoDBを選択してください。
![サーバー設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/086ad6fe-309e-317d-c685-5b6a8ba9b0ba.png)
数秒間待っているとサーバー環境ができあがります。
ちなみに無料プランの場合は
* サーバーの最長利用時間は24時間
* サービスは外部へ公開されない
逆に言うと練習にはもってこいという事でしょうか。

## アプリケーション構築
次に各種インストールを行い、アプリケーションの実行環境を構築します。
まずは画面からターミナルのアイコンをクリックしてください。

![s_ターミナル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/4b41e42b-9ef5-d818-f897-7b63bf1a354e.png)

起動したターミナルに下記のコマンドを入れます
①　git clone によるファイル展開

```
git clone https://github.com/nstshirotays/chatapp-shot3.git
```
② ディレクトリを移動し
③　npmによるパッケージのインストール
これにより実行に必要なモジュールなどがpackage.jsonに従って自動的にインストールされます。

```
cd chatapp-shot3
npm install
```

<img width="936" alt="01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/0db66995-66b1-5410-91a7-1aa3a2a56c57.png">

次に、先ほど作成したDialogflowに接続するための秘密鍵ファイルをアップロードします。
/home/ubuntuと書かれているところを右クリックするとメニューが表示されますので、そこから前の工程で作成したdialogflow.jsonファイルをアップロードします。

<img width="936" alt="02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/f67d8304-b1bd-3bed-b6d1-b6b2a8ba8b7e.png">

ファイル名は必ずdialogflow.jsonとして/home/ubuntu/に配置されていることを確認してください


<img width="936" alt="03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/58c98819-dc0a-09d9-df6b-5e4a9e2a3f73.png">

以上で必要な準備が整いましたので、あとはnodejsを起動してアプリを立ち上げます。

```
npm start
```

エラーがでなければ、左側に緑色のブラウザアイコンが新しく点滅し始めます。
![s_ブラウザ3000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/b7d9b59b-035b-4d8b-9863-a8846b252506.png)


このアイコンをクリックするとチャットアプリが起動します。
# アプリ実行
## ログイン画面
まずはログイン画面です。
![login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/e45c5bac-a99d-19e3-0b3f-d136107d09fb.png)
初回は誰も登録されていないので、Create an Account を押してユーザー登録画面に移ります。

## ユーザー登録画面
NickNameとPassCodeを入れてユーザーを登録しましょう。
NickNameは英文字で4から12文字。PassCodeは数字で6から12文字です。
![regisit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/ab70c461-2be8-9cc2-a5aa-9127ff5ace1b.png)

お好みでFaceIconを変更（png 32kbまで）できます。

ユーザーを登録したら実際にログインしてみましょう。

## 友達選択画面
<img width="369" alt="04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/f8e00e84-4e6a-60e1-64a1-0bf3a6b115bc.png">

　[前回](https://qiita.com/nstshirotays/items/e47374a8c35241853280)はEchoさんだけでしたが、今回はCafeMasterが追加されています。
　このCafeMasterさんの実体がDialogflowとなっています。

## DialogFlowによる会話生成
　それでは、実際にCafeMasterさんと会話してみましょう。

<img width="403" alt="05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/3c448e9d-0930-8f66-dab8-31ebe3ae21a7.png">

　最初の「いらっしゃいませ。コーヒーはいかがですか？」はチャットサイト側で出力しており、次の「アメリカンコーヒーを一杯お願いします」というTEXTからDialogflowにて処理をしています。
　Dialogflowでは渡されたTEXTから該当するインテントを割り出し処理をしていきます。ここではおそらくアメリカンコーヒーという単語に反応してorder.drinkインテントが発動されています。
　なので、店主の挨拶の後に「いつもの」などと入れるとorder.lastインテントが発動します。
　その他、Dialogflow側の処理については、DialogFlow Console　画面の右側にチャットボックスがあり自由にテストができますので、いろいろと試してみてください。

## プログラム解説

　それでは早速Dialogflowとの連動についてプログラムを見てみましょう。

### 起動時の処理
　Google APIを利用するためには秘密鍵による認証が必要です。先に配置したdialogflow.jsonファイルがそれです。
　この秘密鍵ファイルは環境変数の設定により必要とされるプログラムからアクセスされます。
　このため起動する前に当該環境変数の設定をする必要があります。ここでは、npm startで最初に参照されている package.jsonで設定を行っています。

``` json:package.json
{
  "name": "chatapp2",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "GOOGLE_APPLICATION_CREDENTIALS='/home/ubuntu/dialogflow.json' node ./bin/www"
  },
  "dependencies": {
    "@tensorflow-models/mobilenet": "^2.0.4",
    "@tensorflow/tfjs": "^1.5.2",
    "@tensorflow/tfjs-node": "^1.5.2",
    "basic-auth-connect": "^1.0.0",
    "bcryptjs": "^2.4.3",
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "dialogflow": "^1.2.0",
    "ejs": "~2.6.1",
    "express": "~4.16.1",
    "express-validator": "^6.2.0",
    "http-errors": "~1.6.3",
    "jsonwebtoken": "^8.5.1",
    "mongo-sanitize": "^1.0.1",
    "mongoose": "^5.7.6",
    "mongoose-sequence": "^5.2.2",
    "morgan": "~1.9.1",
    "pb-util": "^0.1.2",
    "randomstring": "^1.1.5",
    "redis": "^2.8.0",
    "save": "^2.4.0",
    "uuid": "^3.4.0"
  }
}
```

Startの値に環境変数の設定を追加しています。
> GOOGLE_APPLICATION_CREDENTIALS='/home/ubuntu/dialogflow.json' 

### 会話の送信と受信
チャットサーバーとDialogFlowとのやりとりは helper/bot.cafe.jsにて実装してあります。

``` javascript:helper/bot.cafe.js
// CafeMasterの処理
var db = require('../helper/db');
var Chat = db.Chat;
const chatsv = require('../models/chat.service');

//DialogFlowの設定
const dialogflow = require('dialogflow');
const uuid = require('uuid');
var sessionId = "";

//console.log(process.env.GOOGLE_APPLICATION_CREDENTIALS);
var fs = require("fs");
var use_readFile_json = JSON.parse(fs.readFileSync(process.env.GOOGLE_APPLICATION_CREDENTIALS, 'utf8'));
const project_id = use_readFile_json.project_id;
//console.log(project_id);



exports.start = function(){
	//  最初の挨拶を登録
	if(FrName == 'CafeMaster') {
		// CafeMaster
        sessionId = uuid.v4();
        console.log('Session ID = ' + sessionId);

		chatsv.create({
            fromAddress : FrID,
            toAddress : MyID,
            message : 'いらっしゃいませ。コーヒーはいかがですか？'
		});
        // 2秒ごとに新しいメッセージを検索する
		botTimer = setInterval(function(){
		    serachNewMessages();
		},2000);
	}
};


var resentMsg ="";

async function serachNewMessages() {
	if(FrName == 'CafeMaster') {
        // CafeMasterの最後の発言を取得する
        var query = { "fromAddress": FrID ,"toAddress": MyID};
        
        Chat.find(query,{},{sort:{timeStamp: -1},limit:1}, function(err, data){
            if(err){
                console.log("serachNewMessages err",err);
            }
            if(data.length > 0){
                // CafeMasterの最後の発言以降の自分の発言を取得する.
                var lastMessageTime = data[0].timeStamp;
                query = { "fromAddress": MyID ,"toAddress": FrID,"timeStamp": {$gt : lastMessageTime}};
                Chat.find(query,{},{sort:{timeStamp: -1},limit:1}, async function(err, data){
                    if(err){
                        console.log("serachNewMessages err",err);
                    }
                    if(data.length > 0){
                        // 応答メッセージの設定
                        var resMessage="";
                        
                        const {struct} = require('pb-util');
                        // Create a new session
                        const sessionClient = new dialogflow.SessionsClient();
                        const sessionPath = sessionClient.sessionPath(project_id, sessionId);
                        // The text query request.
                        const request = {
                            session: sessionPath,
                            queryInput: {
                                text: {
                                    // The query to send to the dialogflow agent
                                    text: data[0].message,
                                    // The language used by the client (en-US)
                                    languageCode: 'ja-JP', //'ja'
                                },
                            },
                        };


                        //DialogFlowへ会話を送って返事を待つ
                        const responses = await sessionClient.detectIntent(request);
                        const result = responses[0].queryResult;
                        
                        const parameters = JSON.stringify(struct.decode(result.parameters));
                        var obj = JSON.parse(parameters);	 
                        console.log(`  Query: ${result.queryText}`);
                        console.log(`  Response: ${result.fulfillmentText}`);
                        resMessage = result.fulfillmentText;

                        // 退避メッセージと異なってれば発話する
                        if ( resMessage !== "" && resentMsg != data[0].timeStamp + data[0].message) {
                            resentMsg = data[0].timeStamp + data[0].message;
                    		chatsv.create({
                                fromAddress : FrID,
                                toAddress : MyID,
                                message : resMessage
                    		});
                        }
                    }
                });
            }
        });
    }
}
```

　この処理は友達選択でCafeMasterが呼ばれると起動されます。
　初期処理にて、先ほどの環境変数で示された鍵ファイルからproject_idを取得し、ランダムに得られた番号をセッション番号としてDialogflowに接続します。
　最初の挨拶として「いらっしゃいませ。コーヒーはいかがですか？」を発話したのち、serachNewMessage関数を2秒ごとに実行しユーザーの会話を待ちます。

　新しい会話を検知すると、その内容をDialogFlowに渡して返事を待ちます。そして返事がきたらそのTEXTをmongoDBに書き込みます。

　以上によりチャットサイトとDialogFlowが連動して会話処理を行うことができます。


## Fulfillmentの設定
　さて、このdorder.drinkインテントですが、ここでは「飲み物」「サイズ」「デリバリー」の３要素を必須入力としています。この３要素が揃うと次のorder.drink-yesというインテントが発動し、いつもの支払い方法でよいか聞かれます。ここで「はい」と答えるとorder.drink.same_cardに移動し最終確認が行われます。
　初期段階ではFulfillmentが設定されていませんので、単純にこのインテントに設定されたResponse「この度はご注文ありがとうございました。またのご来店をお待ちしております。」というメッセージのみが戻されます。

<img width="472" alt="08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/7fd8d022-dc14-d524-2e52-91bdc1994130.png">

　本来であれば実際の注文処理が動くと思いますので、ここでFulfillmentによる外部サーバーの呼び出しを行ってみます。
　ここでは注文処理用の外部サーバー処理を先ほどのチャットサーバー上に同居させています。このためまずは呼び出すサーバーアドレスを取得します。
　なお、この設定を行うためには本チャットサーバーがインターネットに公開されていることが前提となります（Paiza.cloudの無料枠では利用できません）。
　まず公開されているアドレスを控えておきます。

<img width="484" alt="09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/0a8eb583-7754-d002-feee-a5baab53175b.png">

次にDialogFlow Consoleの左側のメニューからFulfillmentを選択します

webhookスイッチをオンにして、呼び出すサーバーの設定を行います。
①　先ほど控えたURLの末尾に cafeを追加して設定
②　IDに「userid」英字6文字を設定
③　PWDに「passcode」英字8文字を設定
④　最後に「SAVE」ボタンをクリック

<img width="1064" alt="01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/bd9806a4-e075-aebf-0377-e8eb8c58914a.png">

　そして、設定されたWebhookを呼び出すタイミングをインテントに指定します。

　Intentsを選択し、　order.drink -> order.drink - yes を展開し order.drink.samecardインテントを選択します。

<img width="1071" alt="02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/ca057abf-6805-d2cc-f5ad-08ef60c801f7.png">

　そして、①画面の下方にあるfuifillmentのタブを開いて②、「Enable webhook call for this intent」のスイッチをオン③にします。
　それから最後に④のSAVEを押して内容を反映させます。


<img width="770" alt="03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/73e47682-cdcc-aaae-011f-1787c7265a45.png">

　これでFulfillmentによるwebhookが設定できましたので、先ほどのCafeMasterさんを再度よびだしてみると、最終の挙動が変わっているのがわかると思います。

<img width="485" alt="10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/832ff67b-188a-a18a-0be5-367ef85740ca.png">

### webhookプログラムの解説

　それでは、受け取り側のwebhookプログラムについて解説します。実装はroutes/cafe.jsです。

``` javascript:routes/cafe.js　
var express = require('express');
var router = express.Router();
var db = require('../helper/db');
var User = db.User;
var Chat = db.Chat;
const verifyToken = require('../helper/VerifyToken');
var { check, validationResult } = require('express-validator');
var fs = require('fs');
const chatsv = require('../models/chat.service');
var basicAuth = require('basic-auth-connect');


//DialogFlowからのコールバックを処理する
//ベーシック認証
router.post('/', basicAuth('userid','passcode'), function (req, res, next) {

    //console.log("req.body.session "+ req.body.session);   
    //console.log(req.body.queryResult.outputContexts[1].parameters.drink);
    //console.log(req.body.queryResult.outputContexts[1].parameters.size);
    //console.log(req.body.queryResult.outputContexts[1].parameters.delivery);

    var cafeImageFile = req.body.queryResult.outputContexts[1].parameters.drink + ".png";
    var baseStr = "data:image/png;base64,";
    var cafeImage;

    fs.readFile('./public/files/' + cafeImageFile, function (err, data) {
        if (err) throw err;
        cafeImage = baseStr.concat(new Buffer(data).toString('base64'));
        chatsv.create({
            fromAddress : FrID,
            toAddress : MyID,
            image : cafeImage,
        })
    });
    
    let responseObj={
     "fulfillmentText":'おまたせいたしました。'
    }
    return res.json(responseObj);

});

module.exports = router;
```

　ここでDialogFlowからのpost要求を受け付けます。ここではbasic認証を入れてあります。本来であればmTLSによるサーバーの相互認証（これにより接続してきたDialogflow側の証明書を認証することができる）をすべきなのですが、paiza.cloudが仮想環境による実装のため、サーバー証明書を入れることができませんので実装は試せませんでした。
　処理的にはシンプルでpostされたデータにより画像ファイルを選択してmongoDBに格納しています。そしてDialogFlowへの返却値として「おまたせしました」というTEXTを送り返しています。

　以上がDialogFlowとの連動プログラムです。プログラム自体は単純なのですが、Dialogflow側の設定は慣れないと自分が何をしているかわからなくなります。このあたりはDialogflowのサンプルエージェントを眺めてみるるとその実装形態の思想が見えてくると思います。




## MobileNetによる画像識別
　2017年に誕生した畳み込み画像認識のmobileNetは、小型軽量でモバイル等での利用も盛んです。今回はこれをnode.jsに組み込んでいます。
　友達選択からEchoさんを選択し、画像を送ると１０００個の分類から識別してくれます。

<img width="472" alt="11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/66a1defd-f426-5d2b-793b-15ac6a4c5013.png">

　このように識別してくれていますが、分類名称が英語ですので、これを日本語ファイルで置き換えます。日本語ファイルはいろいろなサイトを参考にして作成しました。

<img width="514" alt="06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/7345a47a-17dc-33da-1c16-1f943a02dbda.png">

　chatapp-shot3/imagenet_classes.jsが和訳した内容ですので、node_module/@tensorflow-modules/mobilenet/distにある英文内容と差し替えます。
　差し替えたのちに　npm startで再度起動すると和訳文が読み込まれます。

<img width="473" alt="12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/dacefd79-81ea-fb35-66d0-c96be3e17004.png">

　余談ですが、この１０００分類を見ていただくとわかりますが、昆虫と魚と犬が多いなと思いました。
犬種に関してはかなりマニアックな感じがしました。


<img width="403" alt="07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/e1c01eb9-64fd-c9ba-0dba-67c5f3b176f8.png">

### プログラム解説

　実装は helper/bot.echo.jsにあります。

``` javascript:helper/bot.echo.js
// Echoさんの処理
var db = require('../helper/db');
var Chat = db.Chat;
const chatsv = require('../models/chat.service');

//tensorflow.jsの設定
var randomstring = require("randomstring");
const tf = require('@tensorflow/tfjs');
const mobilenet = require('@tensorflow-models/mobilenet');
const tfnode = require('@tensorflow/tfjs-node');
var fs = require('fs');


exports.start = function(){
	//  最初の挨拶を登録
	if(FrName == 'Echo') {
		// echo
		chatsv.create({
            fromAddress : FrID,
            toAddress : MyID,
            message : 'こんにちは'
		});
        // 1秒ごとに新しいメッセージを検索する
		botTimer = setInterval(function(){
		    serachNewMessages();
		},1000);
	}
};


var resentMsg ="";

function serachNewMessages() {
	if(FrName == 'Echo') {
        // Echo さんの最後の発言を取得する
        var query = { "fromAddress": FrID ,"toAddress": MyID};
        
        Chat.find(query,{},{sort:{timeStamp: -1},limit:1}, function(err, data){
            if(err){
                console.log("serachNewMessages err",err);
            }
            if(data.length > 0){
                // Echoさん最後の発言以降の自分の発言を取得する.
                var lastMessageTime = data[0].timeStamp;
                query = { "fromAddress": MyID ,"toAddress": FrID,"timeStamp": {$gt : lastMessageTime}};
                Chat.find(query,{},{sort:{timeStamp: -1},limit:1}, async function(err, data){
                    if(err){
                        console.log("serachNewMessages err",err);
                    }
                    if(data.length > 0){
                        var resMessage="";
                        // 応答メッセージの設定
                        if (data[0].image.length > 0) {
                            // MobileNetの呼び出し
                            var matches = data[0].image.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/),
                            imageBuffer = {};
                            imageBuffer.type = matches[1];
                            imageBuffer.data = new Buffer(matches[2], 'base64');
                            
                            const mobilenetModel = await mobilenet.load({version: 2, alpha: 1.0});
                            const predictions = await mobilenetModel.classify(tfnode.node.decodeImage(imageBuffer.data));
    
                            resMessage = predictions[0].className + "ですね";

                        } else if (data[0].stampTitle !== undefined) {
                            resMessage = data[0].stampTitle + "ですね";
                        } else if(data[0].message.length > 0){
                            resMessage = data[0].message + "ですね";
                        } else {
                            resMessage =  "無言ですね";
                        }
                        // 退避メッセージと異なってれば発話する
                        if ( resMessage !== "" && resentMsg != data[0].timeStamp + data[0].message) {
                            resentMsg = data[0].timeStamp + data[0].message;
                    		chatsv.create({
                                fromAddress : FrID,
                                toAddress : MyID,
                                message : resMessage
                    		});
                        }
                    }
                });
            }
        });
    }
}
```
　このボットが1秒ごとにメッセージを検索し、TEXTであれば末尾に「ですね」を付け加え、スタンプであればそのファイル名を返却し、画像であればmobilenetで画像識別を行ってその結果を書き込んでいます。
コードにすればほんの数行で画像認識を行うことができます（モデルの読み込みはここでなくてもよいかもしれません）。

　その他にもいろいろと呼び出せるライブラリがあります。

　　[TensorFlow.js ] (https://www.tensorflow.org/js/)


# 最後に
　いかがだったでしょうか。相変わらず前回から間が空いてしまいましたが、Echoさんが画像の識別をしてくれるようになり、ボットサイトを使った会話もできるようになりました。
　今回はGoogleのDialogflowを利用する関係で手順がとても多くなってしまいました。Step by Stepで記載しましたが、半年後には画面も変わったりするので何時ごろまで有効かはわかりませんが、ひとまずはこの手順で動くと思います。
　いずれにせよ、外部のサイトと連携したり、開発されたライブラリを活用することで、簡単に機能追加ができるのが実感できました。


### 次回は...
　次回はこの画像識別をさらにパワーアップさせて、画像のキャプションを自動生成するロジックを追加したいと思っています。

### 記事一覧
https://qiita.com/nstshirotays/items/cb06863dc7288c0fed25

https://qiita.com/nstshirotays/items/e47374a8c35241853280

https://qiita.com/nstshirotays/items/174c46610e962a345d13

https://qiita.com/nstshirotays/items/7cf78fdb9be4c212fd31

https://qiita.com/nstshirotays/items/c113b62b791931303447

https://qiita.com/nstshirotays/items/075cffee06f3a50c01db

https://qiita.com/nstshirotays/items/d865c20c2a338110a73e