LoginSignup
6
4

More than 5 years have passed since last update.

GoogleHomeに話しかけて検索結果をブラウザに表示させる ❹Databaseへのデータ追加をトリガーに特定のページへ遷移させる

Last updated at Posted at 2018-05-18

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)


3_stage4.png

あ、その前にそろそろ 朝の挨拶intentでは無理出てくるので・・・今回から新しいIntentを使用します。
今までの流れをバッサリいっちゃってすみません。:cold_sweat:

復習と機能の追記がてら、一から設定の流れをやってみます。

1. Dialogflowで新しいEntitieを登録(今回はEntitie名をkeywordにしました)

:pushpin: 機能追記 ☑Allow automated expansionについて

このAllow automated expansionというのは、自動拡張機能を許可するかのチェックです。ここがONでないと
Entitiesに完全一致しない言葉は弾かれて Default Fallback Intent に飛ばされてしまいますが、
拡張を許可することで登録していない言葉も受け取ってくれるようになります。
4_新しくEntities登録しました。.png

2.Dialogflowで新しいIntentを登録(今回はIntent名をspeakにしました)

:pushpin: 機能追記 Intent の優先度設定方法

Intent名の入力欄の左側の水色の中点をクリックすると重要度の選択が出来ます。
このspeakはとにかく優先度を高めにしたいのでHighestに設定します。
5_優先度設定.png

:pushpin: 機能追記 REQUIRED(必須) の項目設定 

Action and parameters にある REQUIRED をONにするとそのパラメータは必須項目だよと設定されます。

REQUIREDをONにすると出現するPROMPTS項目の Define promptsをクリックする。
立ち上がってきた画面でPROMPTSの項目に必須項目を聞き出す質問を入力し、Closeを押す。
6_必須項目.png

これで、もし必須項目が入っていない時でも、質問を返して必須項目を取得するようになります。

:pushpin: 機能追記 IS LIST の設定 

IS LIST を使うとパラメータを複数持つことが出来ます。
例えば❶Dialogflowで簡単なチャットボットを作成の記事冒頭のイラストで載せている
一番町にある黒毛和牛専門店を教えて」という聞き方をしたい場合 3つのパラメータが必要になります。
今のままの設定だと「黒毛和牛を表示して」ぐらいなら「黒毛和牛」というパラメータを認識してくれますが
「黒毛和牛の専門店を表示して」だと、:robot: ワーカリマセーンって言われちゃうんです。
7_いよいよList.png

これをパラメータ3つで受け取ってもらうために Action and parameters にある IS LIST をONにします。
8_IS LIST.png

トレーニングフレーズに入力し、パラメータにする箇所を選択する
9_トレーニングフレーズに追加.png

SAVEしたらテストしてみます。
3つのパラメータが認識されてデフォルトの応答 かしこまりました。が返ってきました:v_tone2:
10_Intentはこんな感じ.png

3.FirebaseのFunctionsを設定

Firebaseのログインとかそのあたりの設定はこちらを参照してください。
今回は、index.jsの中身をサラっとおさらいです。Entitieがkeywordになったので修正しています。

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

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

    response.setHeader("Content-Type", "application/json"); //Dialogflowに情報を返す
    response.send(
        JSON.stringify({
            "fulfillmentText": "えー、どうしようかなー"
        })
    );
});

deployしたらDialogflowに戻って設定をします。
Fullfillmentとwebhookの設定はこちらを参照して行ってください。

テストすると、パラメータが3つとFunctionsで設定した応答「えー、どうしようかなー」が表示されました。
11_はい無事につながりました.png

4.Function実行時にRealtime Databaseにデータを追加

コードの解説等はこちらを参照してください。index.jsの中身をサラっとおさらいです。
admin SDKの記述を追加しています。今回はhoyapathとmusubipathという2通りパスにデータを登録しました。
 hoyapathはpushで、musubipathはsetでデータを登録しています。

index.js
const functions = require("firebase-functions");//クラウド関数を作成してトリガを設定するためのFirebase SDKのクラウド関数。
const admin = require("firebase-admin");//Firebase Realtime DatabaseにアクセスするFirebase Admin SDK
admin.initializeApp(functions.config().firebase);

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

    const nd = new Date();
    nd.setTime(nd.getTime() + 1000*60*60*9);//UTC→JSTに変換(9h追加)
    const year = nd.getFullYear(),month = nd.getMonth()+1,day =nd.getDate(),hour = nd.getHours(),minute = nd.getMinutes();
    const time = year + "" + month + "" + day + "" + hour +  "" + minute + "" ;

    const hoyapath = "/hoya/boya"
    const musubipath = "/musubi/maru"

    admin.database().ref(hoyapath).once("value", function(snapshot) { 
        response.setHeader("Content-Type", "application/json"); //Dialogflowに情報を返す
        response.send(JSON.stringify({"fulfillmentText": "えー、どうしようかなー"}));
        admin.database().ref(hoyapath).push({user_say:dialogflow_user_say, param_value:keyword  , timestamp: time });
        admin.database().ref(musubipath).set({say:"はい、tedkumaの記事を表示します", tx:dialogflow_param  , url: "https://qiita.com/tedkuma/items/9ceb17936c714a205a74"});
    });
});

deployしてDatabaseを確認します。パラメータ3つでも同じようにデータが入りましたー!:v_tone2:
普通にパラメータを登録した場合はmusubi/maruのtxで登録しているような配列になります。
hoya/boyaで登録しているparam_valueはjoin()を使ってこの配列を結合したものを登録しています。
12_はい、ちゃんとデータ追加されました。.png


はい。おさらいは以上です。ここからが今回の本題です

:four: Databaseへのデータ追加をトリガーに特定のページへ遷移する

まず、トリガーって??
ワタシの解釈では「きっかけ」と読み替えています。もしくは 〇〇したら の したらみたいな感じ
Databaseにデータ追加をされたのをきっかけに、特定のページに遷移する というのをやります。

前回まででGoogleアシスタントに話しかけたら、Databaseへのデータ追加することが出来ました。
今回は、FirebaseのHosting機能を使用し公開したページを介して、別ウィンドウで対象のページを開きます。
こうやってwebページを見ていても、どこかをクリックしたりしなければページを変えたりすることはできませんよね。そのクリックの代わりに

Googleアシスタントに話しかける → DBへのデータ追加 → ページ遷移 というのをやります。

FirebaseのHostingを追加する

では、プロジェクトのディレクトリでコマンドを入力してください。

firebase init

準備はいいですか?にはY
どの機能を設定しますか?にはHostingを選択します。
13_Hosting作成しますー.png

パブリックディレクトリとして何を使いたいですか?(デフォルトだとpublic)  → enter押下
単一ページとして設定しますか?(全てのURLを/index.htmlに書き換えます) → Yes と答えています。
Firebase initialization complete! が表示されたら設定はOKです。
14_Hosting作成します2.png

ではHOYAディレクトリを確認してみます。☟publicディレクトリが追加になっています。
15_publicが出来てます.png

publicディレクトリの中身を確認します。☟index.htmlが出来ています。
16_index.html出来てます.png

index.htmlの中身を確認してみます。うわうわ:no_mouth:何やら書かれてますが面倒なので一旦このままでOKです。

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Welcome to Firebase Hosting</title>

    <!-- update the version number as needed -->
    <script defer src="/__/firebase/5.0.3/firebase-app.js"></script>
    <!-- include only the Firebase features as you need -->
    <script defer src="/__/firebase/5.0.3/firebase-auth.js"></script>
    <script defer src="/__/firebase/5.0.3/firebase-database.js"></script>
    <script defer src="/__/firebase/5.0.3/firebase-messaging.js"></script>
    <script defer src="/__/firebase/5.0.3/firebase-storage.js"></script>
    <!-- initialize the SDK after all desired features are loaded -->
    <script defer src="/__/firebase/init.js"></script>

    <style media="screen">
      body { background: #ECEFF1; color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
      #message { background: white; max-width: 360px; margin: 100px auto 16px; padding: 32px 24px; border-radius: 3px; }
      #message h2 { color: #ffa100; font-weight: bold; font-size: 16px; margin: 0 0 8px; }
      #message h1 { font-size: 22px; font-weight: 300; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
      #message p { line-height: 140%; margin: 16px 0 24px; font-size: 14px; }
      #message a { display: block; text-align: center; background: #039be5; text-transform: uppercase; text-decoration: none; color: white; padding: 16px; border-radius: 4px; }
      #message, #message a { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
      #load { color: rgba(0,0,0,0.4); text-align: center; font-size: 13px; }
      @media (max-width: 600px) {
        body, #message { margin-top: 0; background: white; box-shadow: none; }
        body { border-top: 16px solid #ffa100; }
      }
    </style>
  </head>
  <body>
    <div id="message">
      <h2>Welcome</h2>
      <h1>Firebase Hosting Setup Complete</h1>
      <p>You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!</p>
      <a target="_blank" href="https://firebase.google.com/docs/hosting/">Open Hosting Documentation</a>
    </div>
    <p id="load">Firebase SDK Loading&hellip;</p>

    <script>
      document.addEventListener('DOMContentLoaded', function() {
        // // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥
        // // The Firebase SDK is initialized and available here!
        //
        // firebase.auth().onAuthStateChanged(user => { });
        // firebase.database().ref('/path/to/ref').on('value', snapshot => { });
        // firebase.messaging().requestPermission().then(() => { });
        // firebase.storage().ref('/path/to/ref').getDownloadURL().then(() => { });
        //
        // // 🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥

        try {
          let app = firebase.app();
          let features = ['auth', 'database', 'messaging', 'storage'].filter(feature => typeof app[feature] === 'function');
          document.getElementById('load').innerHTML = `Firebase SDK loaded with ${features.join(', ')}`;
        } catch (e) {
          console.error(e);
          document.getElementById('load').innerHTML = 'Error loading the Firebase SDK, check the console.';
        }
      });
    </script>
  </body>
</html>

このままdeployします。すると、Hosting URLというのが出ています。これをブラウザに表示させてみます。
17_HostingURL.png

はい。表示されました。という訳でindex.htmlにwebページを作ればこのURLで表示させられます。
18_setupが完了したからでてます.png

では、index.htmlでウェブページを作ります。

いらすとやさん にあった虫メガネの画像が可愛いかったので、こんな感じで作成しました。
19_ベースになるページを作りました.png

HTMLはこんな感じです。headは元々のを流用しています。
bodyにテキストボックスを配置してidをkeywordという名前にしています。

現在のindex.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>テストページ</title>

    <!-- update the version number as needed -->
    <script defer src="/__/firebase/5.0.3/firebase-app.js"></script>
    <!-- include only the Firebase features as you need -->
    <script defer src="/__/firebase/5.0.3/firebase-auth.js"></script>
    <script defer src="/__/firebase/5.0.3/firebase-database.js"></script>
    <script defer src="/__/firebase/5.0.3/firebase-messaging.js"></script>
    <script defer src="/__/firebase/5.0.3/firebase-storage.js"></script>
    <!-- initialize the SDK after all desired features are loaded -->
    <script defer src="/__/firebase/init.js"></script>

    <style media="screen">
      body { background: rgba(216, 226, 230, 0.904); color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
      #message { background: rgb(248, 248, 248); max-width: 350px; margin: 100px auto 16px; padding: 32px 24px; border-radius: 3px; }
      #message h1 { font-size: 18px; font-weight: 400; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
      #message { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
      #keyword { color: rgba(0,0,0,0.4); text-align: center; font-size: 15px; max-width: 300px;}
    </style> 
  </head>
  <body>
    <div id="message">
      <div align="center">
        <img src="../img/search_mushimegane.png" alt="虫眼鏡" width="105px" height="120px"/>
        <h1>テキストボックスにURLを表示します</h1>
        <input type="text" id="keyword"/>
      </div>
    </div>
  </body>
</html>

ウェブページに、作成したプロジェクトを統合します

先ほどのHosting URLの上にProject Consoleが出ていたと思います。そのページをブラウザに表示します。
20_プロジェクトコンソールに戻ります.png

</> ウェブアプリにFirebaseを追加 をクリックします。
21_ウェブアプリにfirebaseを追加を選びます.png

こういうのが立ち上がるので全部コピーしてHTMLのheadに貼り付けます。
22_スニペットをコピーします.png

そうしたら以下のscriptを追加します。
musubiRefにDBのmusubi/maruの下のurlを参照しています。(今回は私の記事のURLが入ってます)
onメソッドでイベントリスナーを追加していて、musubiRefにデータが入ったら
テキストボックスにmusubiRefの値が入って、新規ウィンドウでmusubiRefのURLページが開かれます。

    <script>
      var hoyawindow;
      const musubiRef = firebase.database().ref("/musubi/maru/url");//★DBの中のurlを読み込む
      musubiRef.on('value', function (data) { 
        document.getElementById("keyword").value = data.val();
        hoyawindow = window.open(document.getElementById("keyword").value,"test01");
      });
    </script>

最終的なコードはこのようになります。

index.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>テストページ</title>

    <!-- update the version number as needed -->
    <script defer src="/__/firebase/5.0.3/firebase-app.js"></script>
    <!-- include only the Firebase features as you need -->
    <script defer src="/__/firebase/5.0.3/firebase-auth.js"></script>
    <script defer src="/__/firebase/5.0.3/firebase-database.js"></script>
    <script defer src="/__/firebase/5.0.3/firebase-messaging.js"></script>
    <script defer src="/__/firebase/5.0.3/firebase-storage.js"></script>
    <!-- initialize the SDK after all desired features are loaded -->
    <script defer src="/__/firebase/init.js"></script>

    <style media="screen">
      body { background: rgba(216, 226, 230, 0.904); color: rgba(0,0,0,0.87); font-family: Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 0; }
      #message { background: rgb(248, 248, 248); max-width: 350px; margin: 100px auto 16px; padding: 32px 24px; border-radius: 3px; }
      #message h1 { font-size: 18px; font-weight: 400; color: rgba(0,0,0,0.6); margin: 0 0 16px;}
      #message { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); }
      #keyword { color: rgba(0,0,0,0.4); text-align: center; font-size: 15px; max-width: 600px;}
    </style> 
    <script src="https://www.gstatic.com/firebasejs/5.0.3/firebase.js"></script>
    <script>
      // Initialize Firebase
      var config = {
        apiKey: "*********************************",
        authDomain: "********ここには自分の********",
        databaseURL: "*****プロジェクトの情報を*****",
        projectId: "*********貼り付けます**********",
        storageBucket: "**************************",
        messagingSenderId: "******************"
      };
      firebase.initializeApp(config);
    </script>
    <script>
      var hoyawindow;
      const musubiRef = firebase.database().ref("/musubi/maru/url");//★DBの中のurlを読み込む
      musubiRef.on('value', function (data) { 
        document.getElementById("keyword").value = data.val();
        hoyawindow = window.open(document.getElementById("keyword").value,"test01");
      });
    </script>
  </head>
  <body>
    <div id="message">
      <div align="center">
        <img src="../img/search_mushimegane.png" alt="虫眼鏡" width="105px" height="120px"/>
        <h1>テキストボックスにURLを表示します</h1>
        <input type="text" id="keyword"/>
      </div>
    </div>
  </body>
</html>

ではdeployしてブラウザにHosting URLを表示した状態でテストしてみます。
おー!Databeseに追加していたurlのページ(ワタシの記事)が表示されました!!:grin:
23_出来ましたー!.png

今回もかなり長くなってしまいましたが、最後まで読んで頂きありがとうございました。


   次回の記事:❺Google検索で一番上のページを表示させる

   以前の記事:❶Dialogflowで簡単なチャットボットを作成
         ❷FirebaseのFunctionsでボットに返事をさせる(Dialogflow V2 API)
         ❸FirebaseのFunctions実行時 Databaseにデータを追加する

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4