google homeで子供の宿題管理をする

# ねんがんのgoogle homeをてにいれたぞ

[GAFA](http://studyhacker.net/vocabulary/gafa)[^1]の中でと言わず星の数ほどある企業の中でgoogleが大好きな私が、満を持してgoogle homeを買いました。

## 最初にぶつかった壁

[Play Musicは有料版を使わないと再生すらできない！](https://support.google.com/googlehome/answer/7030379?hl=ja)
アップロードしてあるのに・・・。

信者なのでお布施と割り切って、サインアップします。

## 第2の壁

妻が大好きなミスチルを再生しようと思うと、ラジオミックスとかMobile Melody Seriesに邪魔されて狙った曲が再生できない。
妻にお小言をいただく。おうふ。。。
プレイリストを作ってやりくりして許してもらいました。

## 第3の壁

chromecastにつないだ。
・・・ぶっちゃけスマホ操作の方が楽です。

## 閑話

使い道が見つからない。
子供がおもちゃにして遊んでるのでまあいいのだけど。
下の子がわざと「ねぇぐるぐる」と話しかけて反応を見て喜んでいる姿に癒やされる。

## 最大の壁

使い道を広げようとIRKitを買おうと思っても、時すでに遅し。
品薄どころか手に入らない。
うーん、このまま埃をかぶるのはもったいない。
まて、お前の本懐はなんだ！　と自問したところで、ガジェットをいじり倒すのが好きなことを思い出す。

# 本題

前置きが長くなったところで、本稿の本題です。
自分でアプリを作ると言ってもアイデア勝負なので苦手とするところ。
ピコーンと頭に浮かぶ、近づいてくる冬休み。
私に似て最終日に宿題をまとめて必死にやる娘のために、彼女にとって旬なgoogle homeで宿題管理アプリを作ればやる気を出すかも？

## 使うもの

- Actions on Google
- Dialogflow
- AWS API Gateway & Lambda
- Google Spreadsheet with GAS[^2]

## 構成
![000003.JPG](https://qiita-image-store.s3.amazonaws.com/0/152901/fedef21c-48d8-3b34-3e4f-960c20ef0e99.jpeg)
こんな感じ

## それぞれの内容
裏側から順に

### Spreadsheet with GAS
![000004.JPG](https://qiita-image-store.s3.amazonaws.com/0/152901/58b3bf11-061c-dc91-7e01-f14776622b85.jpeg)
スプレッドシートをDB代わりに使います。
くくカードとは九九を覚えるための単語帳みたいなカードです。
九九と書くと「きゅうじゅうきゅう」と発声してしまったのでひらがなで書いてます。


スクリプトはこんな感じ

```js
function doPost(e){
  var request = JSON.parse(e.postData.getDataAsString());
  switch(request.result.parameters.action){
    case 'list':
      var list = getList();
      var result = {"list": list, "speech": convertListToSpeech(list) }
      return returnAsJSON(result);
    case 'start':
      var target = request.result.parameters.target;
      return returnAsJSON(startTask(target));
    case 'end':
      var target = request.result.parameters.target;
      return returnAsJSON(endTask(target));
    default:
      return returnAsJSON({"returnCode":"no operation", "speech": "ごめんね。よくわからなかったよ"});
  }
}

function convertListToSpeech(list){
  return list.join("と") + "が残ってるよ";
}

function startTask(name){
  var remained = getRemainedTasks();
  var continued = remained.filter(function(e){ return (e[1] && !e[2]); });
  var targetTask = remained.filter(function(e){ return e[3] == name });
  var result = {}
  if (continued.length > 0) { 
    result.speech = continued[0][0] + "がまだ終わってないよ";
  }else if(targetTask.length == 0){
    result.speech = "あれ？　終わってるのかな？　その宿題はもうないよ";
  }else{
    setValue(name, 1, new Date());
    result.speech = "がんばってね！";
  }
  return result;
}

function endTask(name){
  var remained = getRemainedTasks();
  var continued = remained.filter(function(e){ return (e[1] && !e[2]); });
  var targetTask = remained.filter(function(e){ return e[3] == name });
  var result = {}
  if (continued.length > 0) { 
    setValue(name, 2, new Date());
    result.speech = "すごい！　よく頑張ったね";
  }else if(targetTask.length == 0){
    result.speech = "あれ？　終わってるのかな？　その宿題はもうないよ";
  }else{
    setValue(name, 2, new Date());
    result.speech = "すごい！　よく頑張ったね。始める前に教えてほしかったな。";
  }
  return result;
}

function getList() {
  var remained = getRemainedTasks();
  var result = remained.map(function(e){ return e[0]; });
  return result;
}

function getRemainedTasks() {
  var sheet = SpreadsheetApp.openById("XXXXXXXXXXXXXXX").getActiveSheet(); // IDはシートのID
  var range = sheet.getDataRange();
  var values = range.getValues();
  var result = values.filter(function(e){ return !e[2]; });
  return result;
}

function setValue(name, target, value){
  var sheet = SpreadsheetApp.openById("XXXXXXXXXXXXXXX").getActiveSheet(); // IDはシートのID
  var range = sheet.getDataRange();
  var values = range.getValues();
  for (var i = 0; i < values.length; i++){
    if (values[i][3] == name){
      sheet.getRange(i+1, target+1).setValue(value);
    }
  }
}

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

これを[Webサービスとしてデプロイ](https://qiita.com/kazuph/items/8fb8b9b04bcb6070fca8)しています。
このときにかなりはまったのが、修正したら都度新版でデプロイする必要があること。
どうしても修正が反映されずあほみたいな時間を過ごしました。

### AWS
API Gatewayには特別なことはやっていないので設定等は割愛。
ググれば腐るほど出てきますね。

肝心のLambdaの処理は、なんと・・・

```python
import urllib.request, json

def lambda_handler(event, context):

    url = "https://script.google.com/macros/s/XXXXXXXXXXXXXXXXX/exec" # GASのエンドポイント
    headers = {"Content-Type": "application/json"}
    body = json.dumps(json.loads(event["body"])).encode("utf-8")

    request = urllib.request.Request(url, data=body, method="POST", headers=headers)
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode("utf-8")
    result = {"statusCode": 200, "body": response_body }

    return result
```

右から左へ受け流す[^3]だけ。

なぜこんなことをしているのかというと、**GASでデプロイしたエンドポイントは、302リダイレクトをしてしまい、DialogflowのWebhookとして使えなかった**からです。
最初に記事を書いていたときには、「Google大好き！ GASとActions on GoogleでGoogle Homeをハックする」でした。。
Cloud functionsを使わなかったのは、単に慣れの問題です。

### Dialogflow～Assistant

[API gateway ～ Dialogflow ～ Assistantの設定も良い記事が多い](http://tech.unifa-e.com/entry/2017/10/16/103458)ので詳細は割愛します。
どうなったかというところですが
![000005.JPG](https://qiita-image-store.s3.amazonaws.com/0/152901/9c4a3077-6cf3-94a9-7ce0-cad7d8ffa6ff.jpeg)
こんな感じでインテント作って
![000006.JPG](https://qiita-image-store.s3.amazonaws.com/0/152901/ff48214d-6325-7fa4-6c72-c867edba2622.jpeg)
こんな風にインテグレーションを設定すれば意図した通りに動きました。
ポイントは、ほぼ何もしないWelcomeインテント（宿題管理）を作って、そこから先の詳細処理をAdditional triggersとして設定したことでしょうか。

## 結果
![000007.JPG](https://qiita-image-store.s3.amazonaws.com/0/152901/a80b55ce-87ea-d295-3187-f6b9afd0441d.jpeg)
![000008.JPG](https://qiita-image-store.s3.amazonaws.com/0/152901/8dabd059-f75e-979f-642f-4e8ad1567102.jpeg)

なんかラグがあったのでしょうか？　よくわからないけど、とりあえずできた！
冬休みなので虫取りはしません。
アプリ名のダサさが私の個性です。
マンと言いつつ女声です。

# まとめ

google homeのアプリを作るのはかなりお手軽でした。
子供が使って楽しめるように拡張していきたいと思います。
宿題のタスクを登録できるようにするとか、回答バリエーションを増やすとか、いろいろやれることがありますね。

# お知らせ
まだまだgoogle homeが仕事を肩代わりしてくれないので、エイチームブライズでは仕事をいっしょに楽しむマンを募集中です。
興味のある方はぜひとも[エイチームグループ採用ページ](http://www.a-tm.co.jp/recruit/)（[Webエンジニア詳細ページ](http://www.a-tm.co.jp/recruit/requirements/career/lifestylesupport-webengineer/)）よりお問い合わせ下さい。

# 明日は
デザイナーチームを華やかに彩るうちの1人@mamitasoが参戦です。
記事の企画を聞きましたがデザイナーならではの面白いものが期待できます。
お楽しみにしてください！

# 脚注
[^1]: MSやIBMがここに入らないということに時代の流れを感じる
[^2]: GASについては@rf_pが多くの記事を書いているのでそちらをご参考に
[^3]: ムーディ、意外とクセになりましたよね