Google Homeを使用してTrelloにタスクを登録する

# この記事で説明すること
+ Google Homeでアプリを作る方法
+ Node.js(Express.js)でTrelloのAPIをたたいてカードを追加、削除する方法

# やりたいこと
冷蔵庫の中身と賞味期限が把握できないため、Trelloでリスト化したい。
冷蔵庫にしまうときにGoogle Homeに話しかけてリスト化したら良さそう。
食べ終わったらリストから消して、買い物リストに入れたい。
![WS000007.JPG](https://qiita-image-store.s3.amazonaws.com/0/319410/1a6702bd-d96c-934c-1ee7-9f9620c5e5e4.jpeg)
![WS000008.JPG](https://qiita-image-store.s3.amazonaws.com/0/319410/12911137-6404-98df-dd34-233a96350f7c.jpeg)
システム的には以下の手順になる。
1. Google Homeに話しかけてタスク登録したい内容を教える
2. 1のデータをサーバーに送信する
3. サーバーでTrelloのAPIを叩く
4. Trelloに登録される

# Google Homeの設定
ここではタスク追加のために必要なデータを集めるための会話を作れる。

## Actions on Googleにプロジェクトを作成する
[Actions on Google](https://console.actions.google.com/u/0/)にアクセスする。
Add/import projectをクリックする。
以下の画像のようにプロジェクト名と言語を設定して「CREATE PROJECT」をクリック。
![Add/import project](https://qiita-image-store.s3.amazonaws.com/0/319410/fb5f53ab-fe83-a461-93b6-109317fa6e6f.jpeg)

左側のメニューから「BUILD」>「Actions」を選択。
「ADD YOUR FIRST ACTION」をクリック。
「Custom intent」を選択して「BUILD」をクリック。
Dialogflowが起動する。
起動した画面でLanguageを日本語にして「SAVE」をクリック。
日本語に設定しそびれたら、左側のメニューの歯車アイコンをクリックして開いた画面で、上部のメニューの「Languages」から設定できる。

### Entitiesの設定
左メニューからEntitiesを選択。
ここで列挙型みたいなものが作れる。
以下を作成。

+ 買い物リストに入れる/入れないのフラグ
+ 冷蔵庫に入れる/取り出すのフラグ
![entity.png](https://qiita-image-store.s3.amazonaws.com/0/319410/48516ffc-ba70-81d3-2066-4d23e29901a1.png)

「CREATE ENTITY」をクリックして登録画面を開く。
以下のように設定する。
![buy.png](https://qiita-image-store.s3.amazonaws.com/0/319410/96beefab-f51e-51d2-11d6-72b67ab68fe2.png)
左側がパラメーターとして渡される値、右側が音声入力で認識する値。

冷蔵庫出し入れフラグは以下。
![inout.png](https://qiita-image-store.s3.amazonaws.com/0/319410/ed1605df-6425-9022-077e-af41ff1d0bf6.png)

### Intentsの設定
左メニューからIntentsを選択。
ここでは会話パターンが作れる。
デフォルトに以下の2つが用意されている。

+ Default Fallback Intent（聞き取れなかった場合の返事）
+ Default Welcome Intent（起動フレーズと返事）

今回は以下2つの会話パターンを作成する。

+ 冷蔵庫に入れる
+ 冷蔵庫から取り出す

冷蔵庫に入れるパターンは以下のように設定する。
![addtask.png](https://qiita-image-store.s3.amazonaws.com/0/319410/0af97a18-2a39-b600-05eb-9a34364341d2.png)

Training phrasesにユーザーが言う言葉のパターンを書く。
``` 賞味期限までの食品を登録 ```
「賞味期限」の部分をドラッグするとEntityの一覧が出てくるので@sys.dateを選択する。
同じく「食品」は@sys.any、「登録」は@in-outにする。
ここで選択した部分がwebサーバーに送信されるパラメーターになる。

そのパラメーターを設定するのがAction and parameters。
![param.png](https://qiita-image-store.s3.amazonaws.com/0/319410/ceab12cb-2f67-64e5-c070-880d56a9f607.png)

+ REQUIRED：ONにすると必須入力になる。分かるまで聞いてくれる。
+ PARAMATER NAME：サーバーに送信するときのパラメーターのキーになる。
+ ENTITY：型。
+ VALUE：自動で$パラメーター名になるので変更しない。
+ IS LIST：ONにするとパラメーターとしてサーバーに送信してくれる。
+ PROMPTS：必須にするとリンクが出てくるので、聞き返すときのメッセージを入力できる。
+ Default value：カーソルを当てると右側に出てくる設定のアイコンをクリックすると入力できる。
<BR>この会話パターンではin-outはinだけ入るので、デフォルトに設定する。

FulfillmentをONにする。
2つあるが、とりあえず上の方だけで大丈夫。

# Fulfillmentの設定
左メニューからFulfillmentを選択。
WebhookをEnabledにする。
URLにwebサーバーのアドレスを入力する。
そうすると先ほどの会話パターンからURLに会話で得たデータを送信してくれる。
※Inline Editorだとサーバーを立てなくていいが、外部のAPIを叩けない。

# Trelloの情報を取得する
## APIキーとトークンを取得する
Trelloにログインして以下にアクセスする。
https://trello.com/app-key
![trello.png](https://qiita-image-store.s3.amazonaws.com/0/319410/9284226b-94d3-bc04-90d7-f5b5cf0d6634.png)
APIキーは黒塗りにしているところに表示される。
文字リンクの「トークン」をクリックするとトークンが発行される。

## リストのIDを取得する
ブラウザ確認できないので、直接APIを叩いて調べる。
ユーザー名はTrelloのプロフィールから見れる。

以下のコマンドでボードIDを取得する。

```
$ curl "https://trello.com/1/members/ユーザー名/boards?key=キー&token=トークン&fields=name"

[{"name":"冷蔵庫","id":"XXXXXXXXXXXXXXXX"}]
```

ボードIDが分かったので、次はリストIDを取得する

```
$ curl "https://trello.com/1/boards/ボードID/lists?key=キー&token=トークン&fields=name"

[{"id":"XXXXXXXXXXXXXXXX","name":"冷蔵庫"},{"id":"XXXXXXXXXXXXXXXX","name":"買い物リスト"}
```

# タスク追加処理の実装
コードを全部載せると長くなるので、[こちら](https://github.com/teal-blue/fridge_list)に上げてあるので適宜見てほしい。
以下、各処理のポイントだけ載せておく。
node.jsとexpressを使用して実装している。
※Node.jsの初心者なのでおかしなところがあればマサカリ投げていただけると助かります！

### Google Homeのパラメータを取り出す処理

``` index.js
// 食品を取得する
var reqParam = req.body.queryResult.parameters;
var content= reqParam['content'].toString();
```

### Google Homeにしゃべらせたいメッセージを返却する

``` index.js
// Googleアシスタントにメッセージを返却する
res.json({
  fulfillmentText: '登録しました。'
});
```

### Trelloのリストからタスクを取得

``` index.js
/**
 * リストからカードを探す
 * @param {string} list_id リストID
 * @param {string} card_name カード名
 * @return {string} カードID
 */
function get_card_id (list_id, card_name) {
  return new Promise((resolve, reject) => {
    console.log('=====[get_card_id]====');

    //取得するURL
    var options = {
      uri: 'https://api.trello.com/1/lists/' + list_id + '/cards?key=' + key + '&token=' + token + '&fields=id,name',
    };
    
    // GETする
    request.get(options, function (error, response, body) {
      if (error) {
        console.log('=====[ERROR]====');
        console.log(error);
      } else {
        console.log('=====[BODY]====');
        var info = JSON.parse(body);
        for (var i in info) {
          //取得したカードリストから探す
          if (info[i].name.toString() === card_name) {
            var card_id = info[i].id.toString();
            console.log(card_id);
            resolve(card_id);
            return;
          }
        }
        // 見つからなかった場合は空文字を返す
        resolve('');
      }
    });
  });
}
```

### Trelloのリストにタスクを追加

``` index.js
/**
 * リストにカードを追加する
 * @param {string} list_id リストID
 * @param {string} card_name カード名
 * @param {string} due 期限(yyyy-mm-dd)
 * @return {bool} resolve レスポンスメッセージ
 */
function add_card (list_id, card_name, due) {
  return new Promise((resolve, reject) => {
    console.log('=====[add_card]====');
    
    var listName = list_id === list_fridge ? '冷蔵庫' : '買い物リスト';
    //リストに追加するURLとデータ
    var options = {
      uri: 'https://api.trello.com/1/cards',
      json: {
        'key' : key,
        'token' : token,
        'idList': list_id,
        'name': card_name,
        'due': due,
        'keepFromSource': 'all',
      }
    };
  
    // POSTする
    request.post(options, function (error, response, body) {
      // 返答内容
      if (error) {
        console.log('=====[ERROR]====');
        console.log(error);
        resolve(listName + 'に登録できませんでした。');
      } else {
        console.log('=====[BODY]====');
        console.log(body);
        resolve(listName + 'に' + card_name + 'を登録しました。期限は' + due + 'です。');
      }
    });
  });
}
```

### Trelloのタスクを削除

``` index.js
/**
 * カードを削除する
 * @param {string} card_id カードID
 * @return {bool} resolve レスポンスメッセージ
 */
function delete_card (card_id) {
  return new Promise((resolve, reject) => {
    console.log('=====[delete_card]====');
    if (!card_id) {
      resolve('見つかりませんでした。');
      return;
    }
    
    //削除URL
    var options = {
      uri: 'https://api.trello.com/1/cards/' + card_id +'?key=' + key + '&token=' + token,
    };
  
    // DELETEする
    request.delete(options, function (error, response, body) {
      // 返答内容
      if (error) {
        console.log('=====[ERROR]====');
        console.log(error);
        resolve('削除できませんでした。');
      } else {
        console.log('=====[BODY]====');
        resolve('削除しました。');
      }
    });
  });
}
```

# 動作確認
動作確認はActions on GoogleのTESTのところからできる。
Google Homeに「テスト用アプリにつないで」と言ったり、Intentsで設定した起動フレーズからでも使える。

# 参考文献
### Google Home
[GoogleHomeからTodoistにタスクを追加する](https://qiita.com/cyanoa0504/items/76053fb7cb1b12112b33)

### Trello
[Trello APIを利用してスクリプトからタスクを管理する](https://qiita.com/AKB428/items/a4a9ff2893affb20f99c)
[Trello API を叩いてカードを作成する方法（curl利用）](https://qiita.com/isseium/items/8eebac5b79ff6ed1a180)

### Javascript関連
[ゼロからはじめるExpress + Node.jsを使ったアプリ開発](https://qiita.com/nkjm/items/723990c518acfee6e473)
[Promiseと仲良くなって気持ち良く非同期処理を書こう](https://qiita.com/progre/items/03626b7f4655007d8cb2)
[とにかくJavascriptで同期処理](https://qiita.com/rawHam/items/838eefc80bc35a90e49a)