Google Home + IFTTT + GAS + LINEBOTでToDo管理

# はじめに
簡単なToDoをPCのテキストエディタで管理していたのですが、PCを起動するところから面倒になりました。
手間がかからないように管理をする方法を検討・調査、作成したのでこの記事でまとめています。

この記事の「利用」の項目から行ったことの結果がだいたいわかると思います。
# 行うこと
以下の記事を参考にToDo管理の実現方法を決めました。
[Google Home、IFTTT、Googleスプレッドシートを使って独自音声コマンドでログをとる（ついでにNode.jsやngrokやらも使ってLINEやGoogle Homeに通知する）](https://qiita.com/miso_develop/items/be562d8a823ad2639d94)

以下、まとめ
(以降、GoogleAppsScriptをGASと記載)

| 行いたいこと | 実現方法 | 備考 |
|-----------|------------|------------|
| ToDoを保持する | Google Sheets  | ToDoをどこかに持っておく。<br>サーバを用意したくなかったので、<br> Google Sheetsを代わりに利用する。|
| ToDo一覧を見る | GAS, LINEBOT | GASから結果をLINEに出力する。 <br> 個人的に目で一覧で見たいのでGoogle Homeには出力しない。 |
|定常的にToDoを表示| GAS, LINEBOT | 「ToDo一覧を見る」とやりたいことは同じ、<br> ただし定常的にToDoをリマインドするようにしたい。|
| ToDoを追加する | Google Home, <br> IFTTT, <br> GAS | 追加は音声入力が楽だと思い、Google Homeを利用する。 <br> IFTTTでGoogle Sheetsに出力し、 <br> 足りない部分をGASで補う。 |
| ToDoを削除する| GAS, LINEBOT | ToDo一覧を見て、完了したものを削除する。 <br> こちらもLINEから入力を行う。 |

Google Sheetsなどそれぞれの解説はたくさんあるのでここでは割愛します。
自身が参照したものは参考にまとめますので、そちらからご確認ください。

# IFTTT, Google Sheets, LINEBOTの設定
それぞれで連携を取るための設定をします。
ここではそれぞれの設定内容や引っかかった部分をまとめています。

## IFTTT設定
Google Homeへの音声入力をGoogle Sheetsに出力するための設定をします。
New Appletから以下の設定を行います。

* This設定
  1. Choose a serviceから「Google Assistant」を選択
  2. Choose triggerから「Say a phrase with a text ingredient」を選択
  3. Complete trigger fieldsを下記画像のように設定

![20180504_IFTTT_THIS.png](https://qiita-image-store.s3.amazonaws.com/0/254221/2d1e48f6-991c-00c1-39ed-1e754396046b.png)
ここで「What do you want to say?」はなるべく単純にして、かつリストという単語を使用しない方がいいかもしれません。
「リストに$を追加」のように設定すると入力そのものができなかったり、[ショッピングリスト](https://support.google.com/googlehome/answer/7029190?hl=ja)に追加されてしまうことがありました。

* That設定
  1. Choose action serviceから「Google Sheets」を選択
  2. Choose actionから「Add row to spreadsheet」を選択
  3. Complete action fieldsを下記画像のように設定
 
![20180504_IFTTT_THAT.png](https://qiita-image-store.s3.amazonaws.com/0/254221/ef25cd66-2ff4-4423-d8d1-1a016efd4d08.png)

これでGoogle Homeへの音声入力をGoogle Sheetsに追記していくことができるようになります。


## Google Sheets設定
Google Sheetsに対してLINEBOTからPostできるように設定します。
IFTTTのComplete action fieldsで設定したDrive folder path以下にSpread sheet nameのシートを事前に作成しておきます。
(一回Google Homeに「○○を追加」と音声入力してもシートが作成されます)

1. シートを開き、「ツール」から「スクリプトエディタ」を選択

![20180504_GoogleSheet_script.png](https://qiita-image-store.s3.amazonaws.com/0/254221/703e8855-78f6-b6f9-9f59-a258f6ccbddf.png)
2. 「公開」から「ウェブアプリケーションとして導入」を選択し、以下のように設定

| 項目 | 設定内容 | 備考 |
|-----------|------------|------------|
| 次のユーザとしてアプリケーションを実行 | 自分 | |
| プロジェクトバージョン | 任意のバージョン | GASを更新する際に適宜このバージョンも変更する必要がある点に注意 |
| アプリケーションにアクセスできるユーザー | 全員（匿名ユーザーを含む） | 匿名ユーザーを含まないとLINEからPostできない |
ウェブアプリケーションのURLは変更等しないで利用します。

## LINEBOT設定
Post先をGoogle Sheetsに設定します。
[LINEログインを利用するには](https://developers.line.me/ja/docs/messaging-api/getting-started/)などを参考に、事前にチャネル作成までを行っておきます。
(料金体系はDeveloper Trialを利用)

1.　Channel基本設定のメッセージ送受信設定を以下のように設定

| 項目 | 設定内容 |
|-----------|------------|
| アクセストークン | 発行してあればよい |
| Webhook送信 | 利用する |
| Webhook URL | Google SheetsのウェブアプリケーションURL |

2.　LINE@機能の利用から「自動応答メッセージ」を利用しないに設定

# GAS作成
ToDoの追加や一覧取得など、必要なGASを作成します。
## ToDoの追加(Google Homeの入力以外の情報追加)

Google Homeだけでは取得できない、ToDoのid番号(ToDoの削除用)と
ToDoの登録日(リスト一覧を見た時に古いものを判別する用)を補うようにします。
下記のコードと後述のトリガー設定で動作させます。

```
var sheet = SpreadsheetApp.getActiveSheet();

function addRowInfo() {
  // 空いている番号を見つける
  var data = sheet.getDataRange().getValues();
  var lastRow = sheet.getLastRow();
  var numArray = [];
  for (i = 0; i < lastRow; i++){
    numArray.push (data[i][0]);
  }
  numArray.sort(compareNumbers);
  addRowNum = 0;
  for (i = 0; i < numArray.length + 1; i++){
    addRowNum = i;
    //バグによる同値防止
    if (i != numArray[i] && i < numArray[i]) break;
  }
  SpreadsheetApp.getActiveSheet().getRange(lastRow, 1).setValue(addRowNum);
  //とりあえず見やすい形にフォーマットしておく
  SpreadsheetApp.getActiveSheet().getRange(lastRow, 3).setValue(Utilities.formatDate(new Date(), 'Asia/Tokyo', '/yyyy-MM-dd/'));
}

//ソート用
function compareNumbers(a, b) {
  return a - b;
}
```

（ToDo登録時に）シートを探索し、現在のid番号を昇順に並べた時に、存在しない一番小さい値をid番号として持つようにしています。
登録日時は適当にフォーマットして見やすい形にしておきます。1列目にid、3列目に時刻を入れるようにしています。

## ToDo一覧を見る

ToDo一覧を文字列にまとめて、LINEにメッセージを送信します。
コードは以下のようにしています。

* リスト作成

```
var sheet = SpreadsheetApp.getActiveSheet();

function createMessage(timecheck){
  var message = "";
  var data = sheet.getDataRange().getValues();
  
  for(var i = 0; i < data.length; i++) {
    tmpMessage = "";
    for (var j = 0; j < data[i].length; j++) {
      if (j != 0 && j != 1 && !timecheck) continue;
      tmpMessage += data[i][j] + ":";
    }
    tmpMessage = tmpMessage.substring(0, tmpMessage.length-1);
    message = message + tmpMessage + "\n";
  }
  message = message.substring(0, message.length-1);
  return message;
}
```
シートのセルの値を「：」、各行を改行コードで結合しています。
timecheckがtrueの場合に限り登録日をつけるようにしています。

* LINEに送信

```
var token = "[LINEのChannel基本設定にあるアクセストークンを指定]";
var sheet = SpreadsheetApp.getActiveSheet();

function sendMessage(message) {
  var to = "[LINEのChannel基本設定にあるYour user IDを指定]";
  var url = "https://api.line.me/v2/bot/message/push";
  var headers = {
    "Content-Type" : "application/json; charset=UTF-8",
    'Authorization': 'Bearer ' + token,
  };

  var postData = {
    "to" : to,
    "messages" : [
      {
        'type':'text',
        'text':message,
      }
    ]
  };

  var options = {
    "method" : "post",
    "headers" : headers,
    "payload" : JSON.stringify(postData)
  };

  return UrlFetchApp.fetch(url, options);  
}
```
[公式ページ](https://developers.line.me/ja/docs/messaging-api/building-bot/)や[LINE BOTからTodoistのタスク作成](https://qiita.com/mtokiwa/items/bdc5ac441697c4dc782e)を参考に作成

## ToDoの削除
与えられた番号のidを持つToDoを消去します。

```
function deleteSelectColumn(num) {
  var data = sheet.getDataRange().getValues();
  var lastRow = sheet.getLastRow();
  var numArray = [];
  for (i = 0; i < lastRow; i++){
    if (String(num) == data[i][0]){
      sheet.deleteRow(i+1);
      return true;
    }
  }
  return false;
}
```

## LINEからの入力を受け取る(ToDo削除やToDo一覧の表示)

[LINE BOTからTodoistのタスク作成](https://qiita.com/mtokiwa/items/bdc5ac441697c4dc782e)の記事を参考に作成

LINEからPostされた入力を受け取り、対応した処理を行います。
コードは以下の通りです(コード中の一部関数についてはここまでに記載しているものを利用)。

```
function doPost(e) {
  var userMessage = JSON.parse(e.postData.contents).events[0].message.text;
  if (userMessage == "リスト") { 
    sendMessage(createMessage(false));
  } else if (!isNaN(userMessage)) {
    if(deleteSelectColumn(userMessage)){
      sendMessage(createMessage(false));
    }else{
      sendMessage("対応ToDoなし");
    }
  } else if (userMessage == "タイムリスト"){
    sendMessage(createMessage(true));
  } else {
    sendMessage("受け付けていない入力です");
  }
}
```

* リストと入力されている場合はToDo一覧を返す。
* 入力が数値であればその数値のidを持つToDoを消して一覧を返す。
* タイムリストと入力されている場合は登録日も含めて返す。

## 定常的なToDoの表示
以下の記事を参考に作成

* [Google Apps Scriptの日毎のトリガーで時間をもっと細かく設定する](https://qiita.com/sumi-engraphia/items/465dd027e17f44da4d6a)
* [Google Home、IFTTT、Googleスプレッドシートを使って独自音声コマンドでログをとる（ついでにNode.jsやngrokやらも使ってLINEやGoogle Homeに通知する）](https://qiita.com/miso_develop/items/be562d8a823ad2639d94)

下記のコードと後述のトリガー設定で動作します。

```
// 実行時間の設定
function setNotifications() {
  setTrigger("Noti01", 8, 30);
}

//トリガーを作成
function setTrigger(funcName, h, m) {
  var triggerDay = new Date();
  triggerDay.setHours(h);
  triggerDay.setMinutes(m);
  ScriptApp.newTrigger(funcName).timeBased().at(triggerDay).create();
}

// 残ってしまうトリガーを削除する
function deleteTrigger() {
  var triggers = ScriptApp.getProjectTriggers();
  for(var i = 0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() == "Noti01") {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}

// 定常実行されるfunction
function Noti01(){
  deleteTrigger();
  sendMessage(createMessage());
}
```
GASのトリガーでは特定の時刻に定常実行を行うことができないので、特定時間に動作するトリガーを用意し、
毎日、用意したトリガーをセット・削除することで定常的に特定の時間にリマインドをします。


# トリガーの設定
定常実行とToDoのidなどを付与するためにGASのトリガーを設定します。
以下は設定内容です。
![20180504_Trigger.png](https://qiita-image-store.s3.amazonaws.com/0/254221/11756b94-d219-24f0-c11e-abb71b942605.png)

定常実行のためのトリガー作成自体を日タイマーのトリガーとして設定しています。
ToDoのidなどはスプレッドシートの更新時(Google Homeの入力がGoogle Sheetsに与えられた時)に付与されるようにしています。

# 利用
以下はテストとして機能の利用をした時の結果です。
(定常実行、音声入力の結果については記事に載せにくいので省略)

1. ToDo一覧の取得と登録日を含めた一覧の取得
![Screenshot_20180504-221145.jpg](https://qiita-image-store.s3.amazonaws.com/0/254221/7ef2df29-210c-0549-be37-0b10e8e4712c.jpeg)
リストやタイムリストといった決まった単語で一覧が返ってくることを確認しました。

2. idが10のToDoの削除
![Screenshot_20180504-221156.jpg](https://qiita-image-store.s3.amazonaws.com/0/254221/a86f842e-cae7-eba3-8e8d-b60a4fc9a13d.jpeg)
id番号が10のToDoが消えてリストを返したことを確認しました。

3. Google Homeに「追加　きーた用のテスト」と音声入力した後のリスト
![Screenshot_20180504-221206.jpg](https://qiita-image-store.s3.amazonaws.com/0/254221/8d9d5f82-b6ca-66d4-e635-b43d345616fc.jpeg)
固有名詞が入るのでうまく入力できていないようですが、リストに追加されること自体は確認できました。

# 参考リンク
* [Google Home で独自のコマンドを利用する（IFTTT）](https://qiita.com/minatomirai21/items/3fffec8b32ba25d0aa00)
* [LINE BOTの作り方を世界一わかりやすく解説（１）【アカウント準備編】](https://qiita.com/yoshizaki_kkgk/items/bd4277d3943200beab26)
* [[GAS]GoogleSpreadSheetとLineBotでTODO管理](https://qiita.com/hanahiro_aze/items/097ff003249b7dfa5d3a)
* [Google Home、IFTTT、Googleスプレッドシートを使って独自音声コマンドでログをとる（ついでにNode.jsやngrokやらも使ってLINEやGoogle Homeに通知する）](https://qiita.com/miso_develop/items/be562d8a823ad2639d94)
* [Google Apps ScriptからLINE NotifyでLINEにメッセージを送る](https://qiita.com/tadaken3/items/5f916a12587e42ece814)
* [Google Apps Scriptの日毎のトリガーで時間をもっと細かく設定する](https://qiita.com/sumi-engraphia/items/465dd027e17f44da4d6a)
* [LINE BOTからTodoistのタスク作成](https://qiita.com/mtokiwa/items/bdc5ac441697c4dc782e)