SlackからHubotを使ってTrelloのカードを閉じる

[以前の記事](http://qiita.com/ryotahirano/items/be5df095c9859007524a)で、SlackにHubotを導入し、リマインド機能を実装するところまで行ったので、今回はTrelloによってタスク管理をしている場合に、Slackからタスクのリマインドをさせ、そのタスクが完了したらSlackからカードをクローズできるようにします。

## 1. Trello APIを使えるようにする
まずはTrelloのAPIの使用に必要なAPIKeyを取得します。
[https://trello.com/1/appKey/generate](https://trello.com/1/appKey/generate)にアクセスすると、APIKeyが確認できます。
`Key:`で表示されている部分がAPIKeyになるのでメモしておきます。

続いて、TrelloのAPIを実行するためのトークンを取得します。

~~~
https://trello.com/1/authorize?key=<取得したAPIKey>&name=&expiration=never&response_type=token&scope=read,write
~~~
上記のURLの*<取得したAPIKey>*を書き換えてアクセスします。
トークンが表示されたらメモしておきます。

APIKeyとトークンが分かったら、目的のカードが登録されているボードのIDを取得します。

~~~
https://trello.com/1/members/<Trelloでの自分のID>/boards?key=<取得したAPIKey>&token=<取得したToken>&fields=name
~~~
上記にアクセスすると、自分がメンバーになっているボードの名前とIDがjson形式で表示されるので、対象のボードIDをメモっときます。

次に、リマインドさせたいカードが置かれているListのIDを取得します。

~~~
https://trello.com/1/boards/<取得したボードのID>/lists?key=<取得したAPIKey>&token=<取得したToken>&fields=name
~~~
ListのIDが分かると、そのリストの含まれるカードの情報を取得できるようになります。

~~~
https://trello.com/1/lists/<取得したリストのID>/cards?key=<取得したAPIKey>&token=<取得したToken>
~~~
上記にアクセスすると、リストに含まれるカードの情報が、json形式で取得できます。
その他のAPIは、[API Reference](https://developers.trello.com/advanced-reference/list)を確認してください。

## 2. HubotにTrello APIを実行させる
Trello APIが使えるようになったら、HubotがAPI実行してcard情報が取得できるようにコードを書いていきます。

まずは、Node.jsでTrelloのAPIを扱うのに必要な、`node-trello`をインストールします。

~~~
$ npm i node-trello --save
~~~

ここでは、List内のカードのDue Dateが過ぎているものをHubotにリマインドさせてみたいと思います。
コードの全体は以下のようになります。

~~~
# Description:
#  Trello Task Remind

cronJob = require('cron').CronJob
Trello = require('node-trello')
moment = require('moment')

createMsg = (data) ->
  msg = "\n"
  msg = "以下のタスクの期限が過ぎています！\n"
  msg += "Trello ID：" + data.id + "\n"
  msg += "タイトル：" + data.name + "\n"
  msg += "期限：" + data.due + "\n"
  msg += "URL：" + data.url + "\n"
  return msg

module.exports = (robot) ->
  trello = new Trello(
    process.env.HUBOT_TRELLO_KEY,
    process.env.HUBOT_TRELLO_TOKEN
  )
  trelloListID = process.env.HUBOT_TRELLO_JOB_LIST_ID

  cronJob = new cronJob('00 00 11 * * *', () ->
    now = moment()
    envelope = room: "#general"
    mention = "@yourname: "

    trello.get "/1/lists/#{trelloListID}/cards", {}, (err, data) ->
      if err
        robot.send(err)
        return

      for card in data
        if !(card.due == null)
          due = moment(card.due)
          diff = now.diff(due, 'minutes')

          if diff >= 0
            remindData = {}
            remindData.id = card.id
            remindData.name = card.name
            remindData.due = due.format("YYYY/MM/DD h:mm A")
            remindData.url = card.url

            msg = createMsg(remindData)

            robot.send(envelope, mention + msg)
  )
  cronJob.start()
~~~

APIによってcard情報を取得している部分が、

~~~
trello.get "/1/lists/#{trelloListID}/cards", {}, (err, data) ->
~~~
から始まる部分になります。コードの前半で、

~~~
trello = new Trello(
    process.env.HUBOT_TRELLO_KEY,
    process.env.HUBOT_TRELLO_TOKEN
  )
~~~
と、APIKeyとトークンを渡して`trello`インスタンスを作成していて、その`trello`インスタンスgetメソッドの第一引数に実行したいAPIのURLを渡すことで、APIを実行したときのデータが`data`に入ってきます。
インスタンス作成時に、APIKeyとトークンを渡しているので、getメソッドの実行時には渡さなくてもデータを取得できます。

あとの部分は、毎朝11時に現在の時間とカードのDue Dateを比較して、Due Dateが過ぎているものをリマインドさせているコードになります。

## 3. SlackからTrelloのカードをcloseする
最後に、タスクが完了したらカードをcloseできるようにしたいと思います。

ここでは、 「close card [closeしたいカードのID]」とSlack上で発言すると、指定したカードIDのものがcloseされるようにします。
以下がカードclose用コードの全体になります。

~~~
# Description:
#  close Trello Card

Trello = require('node-trello')

module.exports = (robot) ->
  trello = new Trello(
    process.env.HUBOT_TRELLO_KEY,
    process.env.HUBOT_TRELLO_TOKEN
  )

  robot.hear /\bclose card\s+(\S+)/i, (msg) ->
    closeCardID = msg.match[1]

    trello.put "/1/cards/#{closeCardID}/closed", {value: true}, (err, data) ->
      if err
        msg.reply(err)
        return

      trello.get "/1/cards/#{closeCardID}", {}, (error, _data) ->
        if error
          msg.reply(error)
          return
        successMsg = "#{_data.name} is closed."

        msg.reply(successMsg)

~~~

コードの内容としては、Slackで入力した内容を、

~~~
robot.hear /\bclose card\s+(\S+)/i, (msg) ->
~~~
で、受け取っています。
`robot.hear`でチャンネル上の発言に反応するようにし、削除対象のカードIDを渡せるように、正規表現の中で`()`を使い、`msg.match`に配列で入ってくるようにしています。
そのあとは、`trello.put`を使い、close用のAPIが成功したら、`trello.get`でcardの名前を取得し、入力した相手に完了のメッセージを送るようにしています。