GoogleHomeで領収書をスプレッドシートに記録するアプリを作ってみた

確定申告の時期です(投稿時期は3月)
私はもう終わったのですが手打ちで記録するのがまあツラくて非効率だなと感じました。

そこで音声入力だけで済むように今回のアプリを作りました。
※家計簿としても使えます。

#実際の動作様子
下記クリックすると動画再生されます。
[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/0vurp1MOIJw/0.jpg)](http://www.youtube.com/watch?v=0vurp1MOIJw)

簡単な流れ
私:タクシーで3800円交通費
→一時的にスプレッドシートに記入される

Googleアシ：タクシーで3800円交通費でよろしいですか？

私:はい
→スプレッドシートに登録される


#使用したサービス及びフロー図
●使用したサービス
・GoogleAppsScript
・AWS
　lambda
　API Gateway
・GoogleAssistant
・Dialogflow

こうも色々サービスを使ってるのは、そもそも
GoogleAssistant→DialogFlow→スプレッドシートはIntegrationsで設定すればできますが、GoogleAssistantへ**テキストを返すこと**ができません(要は会話形式ができない)

またAWSを使ってるのははじめGASでデプロイしたエンドポイントをDialogFlowののWebhookに設定したのですが、302リダイレクトをしてできなかったので仕方なくLambdaとAPI gatewayを使いました。


●フロー図
※ [google homeで子供の宿題管理をする](https://qiita.com/sho0211/items/6a88ffa473980a504956)
から拝借、一部追記
![フロー図.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595220/8efccdb5-99da-ec69-00b5-6ca3416173e5.jpeg)

#開発の流れ
##Dialogflowの設定例
<img width="776" alt="Intent例.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595220/a0f27e17-087a-f92e-8952-04c24519553d.png">
・失敗の例-Entityにて-
<img width="1070" alt="Entity_不要.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595220/07c2d726-449e-157d-0b83-cb72888ad7cf.png">
この設定だと例えば交通費と言っても"なに"と判別されるので消しました。

##Dialogflow~AWS(API Gateway＆Lambda）~
手順や注意点はこちらの記事に書いてあります
[GoogleAssistantからWebhookでLineの特定人物へPush通知](https://qiita.com/puraAsia/items/6d8cd90da7ca25f53bb3)

##~GoogleAppsScript⇄スプレットシート
LambdaからWebhookで流れてきたJSONデータをGASであれこれしてます。
以下コードです(V8)

```javascript:code.gs
function doPost(e) {
  try {
    let result;
    let ss = SpreadsheetApp.openById(sheetId);
    let sh = ss.getSheetByName(sheetName);

    let request = JSON.parse(e.postData.getDataAsString());
    intentName = request.queryResult.intent.displayName;
    
    //領収書のIntent
    if (intentName == INTENT_RECEIPT_CONTENT) {
      let where = request.queryResult.parameters.PlacePay
      let howmuch = request.queryResult.parameters.number
      let what = request.queryResult.parameters.WhatPay

      //スプレッドシートに一時保存
      sh.getRange(1, 5).setValue(where);//where
      sh.getRange(1, 6).setValue(howmuch);//howmuch
      sh.getRange(1, 7).setValue(what);//what
      let result = { "fulfillmentText": where + "で" + howmuch + "円," + what + "でよろしいでしょうか？" };
      return returnAsJSON(result);
    }

    //Yes/NOの場合
    if (intentName == INTENT_RECEIPT_YESNO) {
      let yesno = request.queryResult.parameters.YesNo
      switch (yesno) {
        case Yes:
          //一時保存から取り出す
          let temp_where = sh.getRange(1, 5).getValue();//where
          let temp_howmuch = sh.getRange(1, 6).getValue();//howmuch
          let temp_what = sh.getRange(1, 7).getValue();//what
          let lastrow = sh.getLastRow();
          sh.insertRowAfter(lastrow);
          lastrow++;

          let now = new Date();
          let date = Utilities.formatDate(now, 'Asia/Tokyo', 'yyyy-MM-dd');
          let time = Utilities.formatDate(now, 'Asia/Tokyo', 'HH:mm:ss');
          sh.getRange(lastrow, 1).setValue(date + " " + time);
          sh.getRange(lastrow, 2).setValue(temp_where);//where
          sh.getRange(lastrow, 3).setValue(temp_howmuch);//howmuch
          sh.getRange(lastrow, 4).setValue(temp_what);//what

          result = { "fulfillmentText": "登録しました" };//fulfillmentTextが大事！
          return returnAsJSON(result);

        case No:
          result = { "fulfillmentText": "キャンセルしました" };
          return returnAsJSON(result);

        default:
          return returnAsJSON({ "fulfillmentText": "もう一度最初から" });
      }
    }
    return returnAsJSON({ "fulfillmentText": "ごめんね。よくわからなかったよ" });

  } catch (ex) {
    console.log(ex)
    return returnAsJSON({ "fulfillmentText": "エラーが発生しました。ログで確認できます" });
  }
}

function returnAsJSON(obj) {
  return ContentService.createTextOutput(JSON.stringify(obj)).setMimeType(ContentService.MimeType.JSON);
}
```

軽く説明すると
request.queryResult.intent.displayName
の部分がDialogflowで設定したIntent名です。

また
request.queryResult.parameters.＊＊＊
の部分が各Intentで設定したパラメータ値($**)です。

※大文字の変数はDialogflowで設定したIntent名等を各々入れること

#スプレッドシートへ記入
記入および一時保存をスプレッドシートで行ってます。
![スプレッド.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595220/7a6b808d-1d1d-4a71-6aaf-1369c21762ec.png)
GASだと時間指定で実行するトリガーが簡単に設定できるので、月や年度が変わった時に新規シートへ移行とかもできます。

#最後に
このアプリで信号待ちなので短い隙間時間でも領収書を登録できるようになりました。
これを応用すれば料理の注文アプリとか作れそうです。

また今回GoogleHome系ですがアレクサでも作れそうですね。

それにしてもDialogflowの便利ですね〜


##参考：
[GoogleAssistantからWebhookでLineの特定人物へPush通知](https://qiita.com/puraAsia/items/6d8cd90da7ca25f53bb3)
[google homeで子供の宿題管理をする](https://qiita.com/sho0211/items/6a88ffa473980a504956)

あとDialogのIntent等の設定はこちらの本が分かりやすかった↓
[やさしくはじめる スマートスピーカープログラミング](https://www.amazon.co.jp/%E3%82%84%E3%81%95%E3%81%97%E3%81%8F%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B-%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%82%B9%E3%83%94%E3%83%BC%E3%82%AB%E3%83%BC%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E3%82%AF%E3%82%B8%E3%83%A9%E9%A3%9B%E8%A1%8C%E6%9C%BA/dp/4839967229/ref=sr_1_2?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%82%B9%E3%83%94%E3%83%BC%E3%82%AB%E3%83%BC&qid=1583909275&s=books&sr=1-2)