hubotでタスクトラッキングbotを実装(と「コレどう書くんだっけ?」の逆引き集)


この記事に書いてあること

初めてHubotスクリプトを書いた時に調べたこと(Hubotのドキュメントに書いていなかったこと)をまとめました。

  • 発言者のnameの取得
  • 発言時刻の取得
  • KVSへ配列で要素追加/抽出したい時

CoffeeScriptなんて触ったことないけど何か作ってみたいなぁ、という方の背中をそっと押せれば幸いです。

※ほぼほぼ同内容を弊社ブログにも投稿してます

作ったものと、その動機

簡単なタスクトラッキングができるbotを作りました。

  • now hogehogeと発言すると、発言内容hogehogeと時刻と発言者を記録
  • hubot todayと発言すると、今日分の発言内容と時刻を全て出力

こんな仕様です。シンプルですね。

使用感はこんな感じ。一日中ブログしかやってないなこいつ

作った動機ですが、
弊社では日報作成時に、その日1日の作業内容と時刻を報告しています。

適当なタイムトラッキングツールなどを利用してもよかったのですが、

  • あんまり大したものでなくていいのに見つけられなかった
  • hubotスクリプト作ってみたいという自分のモチベーション

及び、

  • 誰が今何を手がけているか、をチームへゆるく共有できれば便利ではないか

という副次的効果も狙ってslack経由でタイムトラッキングさせることにしました。

というわけで実装です。

「このデータ欲しいときどう書くの」集

主にHubotのドキュメントを読めばそれなりのことはできるようになるのですが、
上記の仕様を実現する際に、ドキュメントだけでは足りなかった知識をまとめます。
「javascript,CoffeeScriptは普段ほとんど書かないがなんか作りたいなー」ぐらいの方向けです。

なお、環境構築周りの内容は含みません。

発言者のnameの取得

msg で受けた場合、 msg.message.user.name で取得できます。

  robot.hear /^now (.*)/i, (msg) ->
    user = msg.message.user.name

こんな感じ。
これに限らずですが、一度は console.logで中身を覗いてみることをオススメします。

発言時刻の取得

発言時刻ってどこに格納されてるんだろう?とずいぶん探してしまったのですが、普通にnew Dateすれば良いだけの話でした。

  robot.hear /^now (.*)/i, (msg) ->
    date = new Date

但し、未検証ですが恐らくサーバの内部時計の時刻が反映されてしまうので、日本時間で動いていない場合は別途調整が必要です。

KVSへ配列で要素追加/抽出したい時

これはhubot云々というよりKVSの使い方?寄りの話なのかなと思いますが、
発言内容の記録部分を実装するにあたり、当初は連想配列のような形でレコードを保存し、
発言日時をキーとしてレコードを引っ張ってくるような設計をイメージしていましたが、途中で行き詰まってしまいました。
なので、jsonで全部取得→末尾に要素追加→全部保存、という形で実装しました。

  # keyを設定
  key = "timeTracker"

  # 要素の保存
  tasks = robot.brain.get(key) ? [] # keyを元に全要素を持ってくる。なければ空配列をセット 
  task = { text:"文字列" } # 追加要素を作成
  tasks.push task # 全要素の末尾に追加
  robot.brain.set key, tasks # すべての要素を保存

特定の発言日時の要素を取得する場合はfilterで抽出すればOKでした。

  # keyを設定
  key = "timeTracker"

  tasks = robot.brain.get(key) ? [] # keyを元に全要素を持ってくる。なければ空配列をセット
  message = tasks.filter (task) ->
    task.date == '日時' # 日時で抽出
  .map (task) ->
      "#{task.text}" # 発言内容を組み立てる
  .join '\n'

できあがり

後は日時の成型用の関数とか発言内容の保存したい内容とか諸々を付け加えて、最終的に出来上がったのがこちらです。


# Description:
#   simple time tracker.
#
# Commands:
#   now <text> - save datetime, <text>
#   hubot today - export all todays datetime, <text>

# 時刻を受け取ってYYYY-mm-dd形式で返す
toYmdDate = (date) ->
  Y = date.getFullYear()
  m = ('0' + (date.getMonth() + 1)).slice(-2)
  d = ('0' + date.getDate()).slice(-2)
  return "#{Y}-#{m}-#{d}"

# 時刻を受け取ってhh:mm形式で返す
tohhmmTime = (date) ->
  hh = ('0' + date.getHours()).slice(-2)
  mm = ('0' + date.getMinutes()).slice(-2)
  return "#{hh}:#{mm}"

module.exports = (robot) ->
  # keyを設定
  key = "timeTracker"

  # hubot now <test> に反応させる
  robot.hear /^now (.*)/i, (msg) ->
    # 発言から内容を取得。date,text,userの3つ
    date = new Date
    text = msg.match[1]
    user = msg.message.user.name

    tasks = robot.brain.get(key) ? []
    task = { user: user, date: toYmdDate(date), time: tohhmmTime(date), task: text }
    tasks.push task
    robot.brain.set key, tasks
    msg.reply "task saved! #{tohhmmTime(date)} #{text}"

  robot.respond /today$/, (msg) ->
    date = new Date
    user = msg.message.user.name
    tasks = robot.brain.get(key) ? []
    message = tasks.filter (task) ->
      task.date == toYmdDate(date)
    .filter (task) ->
      task.user == user
    .map (task) ->
      "#{task.time} #{task.task}"
    .join '\n'
    msg.reply "#{message}"

雑感

  • 大雑把に1日を振り返るにはちょうど良い使用感
  • 自分で作ったものはやっぱり愛着が湧くので、三日坊主になってないです。今の所は。
  • ゆるく宣言してから作業開始するので、集中モードへ頭を切り替えるスイッチとして機能している嬉しい副作用もあった