Node.js
Firebase
FirebaseCloudFunctions
GoogleHome
dialogflow

GoogleHomeに話しかけて検索結果をブラウザに表示させる ❷FirebaseのFunctionsでボットに返事をさせる(Dialogflow V2 API)

GoogleHome(mini)を購入したので、聞き取ったキーワードから
PCのブラウザにGoogleの検索結果1位のサイトを表示して、そのサイトのタイトルを読み上げるアプリを作成しました。検索したキーワードがDBに無かった場合はキーワードとURLとタイトルを登録します。

最終的に出来たアプリ

1_柳家も大好き💛.png

ワタシがやったこと

極力Googleさんのサービスだけで完結したいなーということで、DialogflowとFirebaseを使用
Firebaseがホスティングサーバーとデータベースと関数の管理と色々サービスを担ってくれているので
全部お任せしました。
2_ワタシがやったことjpg.png

:one: Dialogflowを使用して簡単なチャットボットを作成 (前回の記事)
:two: FirebaseのFunctions(cloud functions)を使用して、:one:で作ったボットが適切な返事するように設定
:three:  :two: を実行する際にFirebaseのDatabaseにデータを追加するように設定
:four:  :three: のデータ追加をトリガーにFirebaseのHostingで作成したページ(htmlファイル)にパラメータを渡し、
   windowopenで対象のページを開くように設定
:five:  Google検索で一番上に表示されてるページを一発で表示させてみる(I'm feeling luckey!)
:six:  データ登録時にページタイトルとサイトURLはオリジナルのものを保存する
   (webスクレイピングにはcheerio-httpcliを使用しました )
:seven:  フリーのMP3データをFirebaseのStorageに入れてレスポンスに音声データを返す(SSML)


:two: FirebaseのFunctionsでチャットボットに返事をさせる

3_stage2.png

Googleが提供している「Firebase」はアプリのバックエンド機能を提供するクラウドサービスです。
これを使えばワタシのようなバックエンド構築に慣れていない開発初心者でも思ったことが実現できます。

前回で、GoogleHomeに話しかけたらそれっぽいことを言ってくれるチャットボットは作成できました。
でも、これではIntentsで登録した言葉しか返せません。ゆくゆくは難しいことを返してもらいますが
今回はとりあえずFirebaseのCloudFunctionsの関数で応答してもらうというのがゴールです。

最初に新しくディレクトリ(フォルダ)を作っておきます。
ワタシはデスクトップにHOYAという名前で用意しました。

では、Actions on googleの画面でBackend serviceタブを選び、
その中のCloud FunctionsにあるGET STARTEDをクリック
4_Backend_service.png

Functions画面が開くのでスタートガイドをクリック
5Functionsの画面 .png

FunctionsはNode.jsを使用します。だからコマンドでfirebase-toolsをインストールしてねと言ってます。
6Functions の設定.png

npm:Node Package Managerの略 
NodeのPackage Manager に firebase で必要なツールをインストールするのね :slight_smile:
という訳で先ほど作ったHOYAの中で実行します。

npm install -g firebase-tools

HOYAの中で実行するというのがピンとこない方はこちらを読んでください
cmd.png
コマンドプロンプトで先ほど作成したディレクトリに移動して(cdコマンド)、npm installを実行して下さい。

はい。色々インストールされました。
では、ブラウザに戻って先ほどのFunctionsの設定の画面の次へをクリックします。

この画面は、コマンドを教えてくれている画面なので終了ボタンを押します。
    - プロジェクトを開始(初期化)する時のコマンドはfirebase initと入力するよ。
    - 関数をアップロードして使えるようにする時のコマンドはfirebase deployと入力してね。という内容
8_デプロイの仕方.png

すると最初のデプロイを待機していますと表示されます。さあ、いつでもOKよ:ok_woman_tone1:ってことですね。
9_最初のデプロイを待機しています.png

さて、ではプロジェクトを始める前にFirebaseにログインします。

firebase login

? Allow Firebase to collect anonymous CLI usage and error reporting information? (Y/n)
Firebaseが匿名で使用法とエラー報告情報を収集できるようにしますか?と聞いてます
Yでもnでもお好みで入力して下さい。そしてEnterすると‼‼

ブラウザがパーンと切り替わりました。アカウントを選んで許可ボタンを押します。
10_firebaselogin.png

ログインに成功するとこの画面が表示されます。
٩(ˊᗜˋ*)وヤッタネー!これですぐに始められるよ~と言ってます。
11_ログインできたー!.png

では、いよいよプロジェクトを始めます:grinning:

firebase init

準備はいいですか?にはY
どの機能を設定しますか?にはFunctionsを選択します。
12_firebase_init.png

このディレクトリをFirebaseプロジェクトに関連づけましょうと言われてるので
Dialogflowで作成したFirstAPPを選びます。
13_firebase_init.png

何の言語を使用したいですか? →  JavaScript
ESLintを使用してバグを見つけ、スタイルを強制しますか?  → No
npmで依存関係をインストールしますか?  → Yes  と答えています。
14_firebase_init.png

また色々とインストールされて・・・+ Firebase initialization complete!が出れば初期化完了です。
15_firebase_init.png

そうするとHOYAの中にはこの3つが出来ています。
16_HOYAの中.png

functionsの中はこのようになっていて、この中にあるindex.jsにコードを書いていきます。
17_functionsの中.png

とりあえずindex.jsに書いてあるコードのまま、fuctionsが使えるか試してみます。
requireはjavaとかでいうところのimport文みたいな感じです。
helloWorldというのが関数名、"Hello from Firebase!"というリクエストを送信してねという関数です。

index.js
const functions = require('firebase-functions');

// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions

 exports.helloWorld = functions.https.onRequest((request, response) => {
  response.send("Hello from Firebase!");
 });

このままdeployします。

firebase deploy

Function URL(helloWorld)の後に書いてあるURLをブラウザに表示するとリクエストが確認できます。
  https://us-central1-ここにプロジェクトIDが入る.cloudfunctions.net/ここは関数名が入る
18_Function_URL出ました.png

実際に確認してみます。出ました~!:v_tone2:
19_ Hello_from_Firebase!出ました.png

という訳で、コードを書き換えます。
前回作ったIntents 朝の挨拶に返事をさせます。(おはようって話しかけるやつ)
 -関数名はhoyatalkに変更しました。
 -request.body.queryResult.parameters.の後ろにパラメータ名を指定することで
  Dialogflowから送られてきたパラメータが受け取れます。
 -fulfillmentTextに返事してほしい言葉を入力します。

index.js
const functions = require("firebase-functions");//クラウド関数を作成してトリガを設定するためのFirebase SDKのクラウド関数。

exports.hoyatalk = functions.https.onRequest((request, response) => {
    const dialogflow_param = request.body.queryResult.parameters.Greeting_in_the_morning;  //Dialogflowから来てるパラメータを受けとる 

//ログの3行は実行時に不要なので消して下さいね。(解説のために入れてるだけです)
    console.log(request.body);  
    console.log(request.body.queryResult.parameters.Greeting_in_the_morning);
    console.log(request.body.queryResult.queryText);

    response.setHeader("Content-Type", "application/json"); //Dialogflowに情報を返す
    response.send(
        JSON.stringify({
            "fulfillmentText": "おはようございます"
        })
    );
});

このままdeployします。関数名を変えるとFunction URLも変わります。
※このFunction URLは今からDialogflowで連携させるのに使います。
20_hoyatalkURL.png

dialogflow V1 APIとV2 APIの違い

余談ですが:slight_smile:ワタシはこのアプリが完成して、Qiitaに書いてみよう!ってこの記事を始めました。
でも、色々試行錯誤して完成したのでどの手順が無駄だったのか不明

というわけで、もう1つgoogleさんのアカウント作って 一からアプリを作成しながら書いてます。
前のを作ったときは V2 がベータ版とか書いてあったのでデフォルトのV1 APIで作ったのですが
今後はV2 APIがデフォルトになるみたいで、まぁいいかとV2でそのまま作成していたら
ここのコード書くところで違いがハッキリしてまして、見事に躓きました。

<変更点1> parameters が result → queryResult の中に変更
<変更点2> response の speech → fulfillmentText に変更

(V1_APIで作成した当時の)index.js
const functions = require("firebase-functions");//クラウド関数を作成してトリガを設定するためのFirebase SDKのクラウド関数。

exports.hoyatalk = functions.https.onRequest((request, response) => {
    const dialogflow_param = request.body.result.parameters.Greeting_in_the_morning;  //Dialogflowから来てるパラメータを受けとる

    response.setHeader("Content-Type", "application/json");  //Dialogflowに情報を返す
    response.send(
        JSON.stringify({
            "speech": "おはようございます"
        })
    );
});
(V2_APIで作成する場合の)index.js
const functions = require("firebase-functions");//クラウド関数を作成してトリガを設定するためのFirebase SDKのクラウド関数。

exports.hoyatalk = functions.https.onRequest((request, response) => {
    const dialogflow_param = request.body.queryResult.parameters.Greeting_in_the_morning;  //Dialogflowから来てるパラメータを受けとる 

    response.setHeader("Content-Type", "application/json"); //Dialogflowに情報を返す
    response.send(
        JSON.stringify({
            "fulfillmentText": "おはようございます"
        })
    );
});

他にも変更になっているフィールドが沢山あるのでV1とV2の比較ページで確認して下さい。

Dialogflowへ戻って仕上げていきます!

左側のFullfillmentを選んでWebhookを  DISABLED → ENABLEDにします。
21_Fullfillment画面.png

URLのところにFunction URLを貼り、下にあるボタンでSAVE
22_webhookに貼り付けます.png

では、Intents(朝の挨拶)を手直しします。Training phrasesの「おはよう」を選択
すると@sys.colorとか@sys.emailとか色々出てきました。この中から自分で登録したEntitiesの
@Greeting_in_the_morningを選択します。すると「おはよう」の下とAction and parametersの欄に

   PARAMETER NAME          ENTITY             VALUE
  Greeting_in_the_morning   @Greeting_in_the_morning  $Greeting_in_the_morning

 が表示されます。これでGreeting_in_the_morningというパラメータを送る設定が出来ました。
23_パラメータを設定.png

@sys.colorとか@sys.emailとかいっぱい出てきてるのは何?
これはデフォルトで登録されているEntitiesです。
デフォルトで「色だよ」とか「メアドだよ」という定義はしてくれているのです。
だから、日付とか時間とか汎用なものはいちいちEntitiesに登録しなくてもよくて、
このTraining phrasesでこの言葉は日付だよとか指定すればいいんです。
※ワタシのアプリでは使ってないのでこの辺の説明は飛ばします。

続いて、Responsesに入れてあったText responseの「おはようございます」を消します。
Fulfillmentの右側にある を押して、表示された Enable webhook call for this intentをオンにしてSAVE
これでこのIntentsが呼ばれたときにFunction URLを貼ったwebhookを呼びますよの設定が出来ました。
24_Webhookを適用.png

さてー!シミュレータでテストしてみます!おー!出ました:v_tone2:

25_出来ました.png

では、どうなってるか せっかくログ出したので確認します。
Firebaseに戻りFunctionsタブでログを選択します。おー出てますね。
26_ログを確認.png

最初に出した赤枠のところはrequest.bodyの中身です。これを見れば中の構造が分かりますよね。
だから2番目のログではqueryResult.parameters.Greeting_in_the_morningで おはよう が
   3番目のログではqueryResult.queryTextで おはよー が取れています。
27_ログの中身.png

今回も長々と書きましたが、最後まで読んで下さりありがとうございました。


       
        次の記事:❸FirebaseのFunctions実行時 Databaseにデータを追加する
        前の記事:❶Dialogflowで簡単なチャットボットを作成