スマートスピーカープログラミング（１）

書籍[「やさしくはじめる　スマートスピーカープログラミング」](https://book.mynavi.jp/ec/products/detail/id=103751)は、とても親切に書いていてわかりやすいのですが、2019年5月に出版したにも関わらず、すでに現在の内容と相違があります。
それだけ、Googleのバージョンアップが早くてお気の毒なのですけれども。

それで、現在の内容を調べつつ、自分なりに実現出来た事を書きます。
但し、この記事でさえ、すぐに陳腐化する恐れがありますが、いちいちついていくのは面倒なので、たぶん直しません。
あくまでも2020年2月8日の情報です。

ここでは、Capter4-4「どっちの手ゲームを作ろう」を取り上げます。
なので、詳しい内容は、上記書籍を見比べてください。
この記事は、あくまでも差分です。

## 新規プロジェクトを作成しよう
[Action Console](https://console.actions.google.com/)を開いて新規プロジェクトを作成します。
![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/3e08c3ff-ac46-47dd-28b5-a8b08c6e709c.jpeg)

プロジェクト名、言語と地域を設定し、CREATE PROJECTボタンを押します
![2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/cd889a5b-be81-18b8-2fd9-f170b881f94b.jpeg)

すると、色々出てきますが、「Conversational」を選びます
![3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/0aaadc85-71bc-be34-20df-3e83daa9e44d.jpeg)

本と違っていますが、めげちゃいけません。「Decide how your Action invoked」を選びます。
![4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/215c7ea2-badd-009e-f4dd-02639a308043.jpeg)

書籍ではDisplay nameは「どっちの手」になっていますが、これはすでにアプリとして存在しているようで、間違っていても、すでにある方が起動されますので、名前を変えて「どの手にあるかな」にしました。
Display nameを設定したら、SAVEを押します
![6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/7cbc96ed-7f18-d371-7452-e2168f2ab1be.jpeg)

「Don't forget to update sample invocations in the directory information page」とか言われるたら、左メニューの「Actions」を選びます

「ADD Your First Action」ボタンを押します
![7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/c6acee21-0826-9992-2c61-221c9690a30f.jpeg)

「Custom intent」が選択されているので、そのまま「BUILD」ボタンを押します
![8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/6b960c7a-c8f5-98b0-1037-165f043abe16.jpeg)

暫く時間がかかってから、Dialogflowの画面が出ます
DEFAULT LANGUAGEを「Japanese-ja」に変更して「CREATE」ボタンを押します。
![9.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/80d68b48-e1b4-bfc6-7153-5a8ae204a4b0.jpeg)

また暫く時間がかかります
「Default Welcome Intent」を選びます。
![10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/4a2abb6e-5f80-ad77-e586-c7389d01b24b.jpeg)

Text Responseに、ウェルカムメッセージを入れて「SAVE」ボタンを押します
![11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/05a3236f-724f-a5d5-2b7a-aee2b7f812dc.jpeg)

「Intent saved　OK」と出たら、左メニュの「Intents」ボタンを押し「CREATE INTENT」ボタンを押します
![12.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/e26fb4da-c16d-988b-fdab-82e71b43d78c.jpeg)

「Intent Name」を設定し「ADD TRAINING PHASES」を押します
![13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/ba81b562-0dec-a37c-aa02-5d4c2c5fee95.jpeg)

「Add user expression」に、受け付けるユーザーの答えのフレーズを入れていきます。
書籍では「右手」と「左手」だけですが、ここで色々入れておくと、言い方を変えても反応してくれる、、、はずです
![14.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/8015258c-23f8-755d-b046-1bd1f9305a6a.jpeg)

** ここで一回SAVEボタンを押しておきます **

SAVEされたら、左メニューからEntitiesを押します
![24.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/60e34259-18ff-b8b8-33c3-777b527c855b.jpeg)

まだエンティティはなにもないので、「CREATE ENTITY」ボタンを押します
![25.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/568ef993-5125-31e6-d046-3b96fbe1b47e.jpeg)

ここでは、エンティティ名をhandとします
![26.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/0ff34192-f45b-84f0-7417-c7e56202a833.jpeg)

Enter reference valueに「左手」Enter synonymに「左」と「左手」を登録します。
これで、言い方にブレが有っても、「左手」と認識するようになります
右手も同じ様に登録し「SAVE」ボタンを押します
![28.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/a3a714df-f91f-8f55-1037-3f62e8315de6.jpeg)
「Agent Training completed」が表示されたら、左メニューの「Intents」を選択してインテントに戻り、先程つくった「右手左手インテント」を選択します
![18.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/52649e5c-e6ec-91cb-5283-9b373a8d633c.jpeg)

Training phrasesの「左」「左手」「右」「右手」の部分を選択すると、エンティティ一覧が表示されます。今作成した「hand」は一番したに追加されていますので、選択します
![29.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/5b960e28-e924-c649-739f-891d5074e6be.jpeg)

全ての「左」「左手」「右」「右手」について、同様に処理しますが、２回目以降は「@hand:hand」を選べるようになっています
![30.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/1c1d3202-39e4-fc7c-580a-63de61b458a3.jpeg)
全て選択し終えたら「SAVE」ボタンを押します

次にFullfillmentを選んで、「Enable Fulfillment」ボタンを押します
![31.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/474e2361-afa4-80b2-4e75-da5879222c4e.jpeg)

「Enable　webhook call for this intent」のスイッチをオンにして、「SAVE]ボタンを押し、左メニューのFullfillmentを押します
![32.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/ed8ff868-c9f6-236c-7a97-31feb26d2255.jpeg)

「Inline Editor」のDISABLEスイッチを押して、編集可能にします
![33.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/05c1d361-ade0-03b6-6ff8-8d4bf4fdbc8b.jpeg)

予め設定されているプログラムは下記の通りです

```Javascript:index.js
// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';
 
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
 
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
 
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
  const agent = new WebhookClient({ request, response });
  console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
  console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
 
  function welcome(agent) {
    agent.add(`Welcome to my agent!`);
  }
 
  function fallback(agent) {
    agent.add(`I didn't understand`);
    agent.add(`I'm sorry, can you try again?`);
  }

  // // Uncomment and edit to make your own intent handler
  // // uncomment `intentMap.set('your intent name here', yourFunctionHandler);`
  // // below to get this function to be run when a Dialogflow intent is matched
  // function yourFunctionHandler(agent) {
  //   agent.add(`This message is from Dialogflow's Cloud Functions for Firebase editor!`);
  //   agent.add(new Card({
  //       title: `Title: this is a card title`,
  //       imageUrl: 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png',
  //       text: `This is the body text of a card.  You can even use line\n  breaks and emoji! 💁`,
  //       buttonText: 'This is a button',
  //       buttonUrl: 'https://assistant.google.com/'
  //     })
  //   );
  //   agent.add(new Suggestion(`Quick Reply`));
  //   agent.add(new Suggestion(`Suggestion`));
  //   agent.setContext({ name: 'weather', lifespan: 2, parameters: { city: 'Rome' }});
  // }

  // // Uncomment and edit to make your own Google Assistant intent handler
  // // uncomment `intentMap.set('your intent name here', googleAssistantHandler);`
  // // below to get this function to be run when a Dialogflow intent is matched
  // function googleAssistantHandler(agent) {
  //   let conv = agent.conv(); // Get Actions on Google library conv instance
  //   conv.ask('Hello from the Actions on Google client library!') // Use Actions on Google library
  //   agent.add(conv); // Add Actions on Google library responses to your agent's response
  // }
  // // See https://github.com/dialogflow/fulfillment-actions-library-nodejs
  // // for a complete Dialogflow fulfillment library Actions on Google client library v2 integration sample

  // Run the proper function handler based on the matched Dialogflow intent name
  let intentMap = new Map();
  intentMap.set('Default Welcome Intent', welcome);
  intentMap.set('Default Fallback Intent', fallback);
  // intentMap.set('your intent name here', yourFunctionHandler);
  // intentMap.set('your intent name here', googleAssistantHandler);
  agent.handleRequest(intentMap);
});

```

ここに、右手や左手を発話された時の処理を書きます。
これについては、[Dialogflowのドキュメントのfulfillmentの章](https://cloud.google.com/dialogflow/docs/quick/fulfillment?hl=ja)を参考にしました。

インテントの追加はapp.intent()ではなく、
  intentMap.set(インテント名, 対応する処理関数);
で行います。

対応する処理関数でやることは、書籍の中の、app.intent()と内容は一緒ですが、方法が変わります。

先程、右手か左手かは、エンティティで指定するようにしましたので、
　agent.parameters.hand
で取得します。

また、ユーザーへの応答は
　agent.add(msg);
で返します。

それらを反映したソースは下記の通りです


```Javascript:index.js
// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';
 
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
 
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
 
// 定数の宣言 --- (*2)
const HAND_STR = ['右手', '左手'];

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
  const agent = new WebhookClient({ request, response });
  console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
  console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
 
  function welcome(agent) {
    agent.add(`Welcome to my agent!`);
  }
 
  function fallback(agent) {
    agent.add(`I didn't understand`);
    agent.add(`I'm sorry, can you try again?`);
  }
  function selectHand(agent) {
    // 隠した手を決める
    const	answer = Math.floor(Math.random()*2);
	// ユーザーの入力を得る
    var word = agent.parameters.hand;
    console.log( 'hand:', word);
	// 右手なら0、左手なら1とする
    const hand = (word ==='右手') ? 0 : 1;
    // 結果判定とメッセージの作成
    let msg = HAND_STR[hand] + 'を開きます。';
    if (hand === answer) {
        msg += 'すると、あめ玉が手のひらにありました。' +
          '当たりです！おめでとうございます。';
    } else {
        msg += 'すると、空っぽでした。残念。';
    }
    msg += '再び遊びましょう。' +
      '「右手」と「左手」どちらの手でしょうか？';
    agent.add(msg);
  }

  // // Uncomment and edit to make your own intent handler
  // // uncomment `intentMap.set('your intent name here', yourFunctionHandler);`
  // // below to get this function to be run when a Dialogflow intent is matched
  // function yourFunctionHandler(agent) {
  //   agent.add(`This message is from Dialogflow's Cloud Functions for Firebase editor!`);
  //   agent.add(new Card({
  //       title: `Title: this is a card title`,
  //       imageUrl: 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png',
  //       text: `This is the body text of a card.  You can even use line\n  breaks and emoji! 💁`,
  //       buttonText: 'This is a button',
  //       buttonUrl: 'https://assistant.google.com/'
  //     })
  //   );
  //   agent.add(new Suggestion(`Quick Reply`));
  //   agent.add(new Suggestion(`Suggestion`));
  //   agent.setContext({ name: 'weather', lifespan: 2, parameters: { city: 'Rome' }});
  // }

  // // Uncomment and edit to make your own Google Assistant intent handler
  // // uncomment `intentMap.set('your intent name here', googleAssistantHandler);`
  // // below to get this function to be run when a Dialogflow intent is matched
  // function googleAssistantHandler(agent) {
  //   let conv = agent.conv(); // Get Actions on Google library conv instance
  //   conv.ask('Hello from the Actions on Google client library!') // Use Actions on Google library
  //   agent.add(conv); // Add Actions on Google library responses to your agent's response
  // }
  // // See https://github.com/dialogflow/fulfillment-actions-library-nodejs
  // // for a complete Dialogflow fulfillment library Actions on Google client library v2 integration sample

  // Run the proper function handler based on the matched Dialogflow intent name
  let intentMap = new Map();
  intentMap.set('Default Welcome Intent', welcome);
  intentMap.set('Default Fallback Intent', fallback);
  intentMap.set('右手インテント', selectHand);
  
  // intentMap.set('your intent name here', yourFunctionHandler);
  // intentMap.set('your intent name here', googleAssistantHandler);
  agent.handleRequest(intentMap);
});
```

これらの変更が出来たら「DEPLOY」ボタンを押しますが、デプロイには時間がかかります。
デプロイ中の表示が出ている間は、待ちましょう。
![34.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/90864eb9-98d3-273c-28f6-a22f2816c7a1.jpeg)

終わったらテストします
Action　Consoleに戻ってテストタブを選択し、左下に、今回作ったものの起動ワードを入れます。
![35.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/fe33b0ed-63eb-6ea9-3814-8a2df5a14407.jpeg)

起動されたら「右手だよ」など、ユーザーの回答となる文章を入れます
![36.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99652/bb07cdfb-e7dd-9c20-f780-7d5d5aeab0b8.jpeg)

これで、終了インテントの手前までの処理を作成できました。

ちなみにインテント名を「右手左手インテント」にしなきゃいけないところを「右手インテント」と間違ってしまいました。ごめんなさい。