Slackで動く当番ボットをLuaで作る(1)

[kiten.co](https://kiten.co) を使うと、比較的簡単なLuaのコードでHubot風にSlackボットを作成することができます。

この記事ではタイマーで動作する通知ボットを題材にして、kiten上でどのようなコードを書くのかご紹介します。

基本的なプログラミングの経験がある人を対象読者と想定しています。
Luaの知識があるとより理解しやすいです。

記事は長くなるので何回かに分けて掲載します。
第一回目の本記事では基礎的な部品の作成とテスト会話を行うところまでを解説します。

# 作るボットについて

エンジニアのチームではよくある話ですが、私のチームでも朝が弱いメンバーが多く、出社時間が一定していません。
こういう状態だと「誰かは出社しているだろう」とみなが思っていてその実誰も出社しておらず、お客様からの連絡がうけとれないという事態になりかねません。

そこで、週ごとに朝当番を決めることにしました。
朝当番になった人は担当の週の間は朝10時に出社する約束です。
朝当番はメンバー全員で持ち回りで担当することにしました。

朝当番がだれかということは座席近くのホワイトボードに書いておいてもいいのですが、出社前に確認することができるように、Slackに通知したほうが便利そうです。

週の初めの始業時間と、週の終わりの終業時間に、Slackに当番をお知らせするボットを作成することにしました。キーワードに反応して当番を教えてくれる機能も作ることにしました。

# ボットの仕様

ボットの仕様をまとめるとこのようになります。

* 週の初め(その週の休みでない最初の日の9:30)に特定のチャネルに今週の当番を発言する
* 週の終わり(その週の休みでない最後の日の18:30)に特定のチャネルに来週の当番を発言する
* 「今週」と話しかけられたら、今週の当番を発言する
* 「来週」と話しかけられたら、来週の当番を発言する
* 「当番」と話しかけられたら、今日の当番、今週の当番、来週の当番を発言する
* 休みはカレンダー通りの土日祝日+会社で定めた休日

# コード

では、コードを見ていきます。

kitenで提供されている機能のドキュメントについては https://docs.kiten.co/ から「リファレンス(Lua)」のメニューをたどってご参照ください。

## 休日判定

最初に、休日かどうか判定できるようにします。

下のような3つの関数をつくります。

簡単につくるために2017年の祝日と会社で定めた追加の休日はプログラム中に埋め込むことにします。

* `isHoliday(now)` nowが休日かどうかを返す
* `isWeekStart(now)` nowが週の初めの出勤日かどうかを返す
* `isWeekEnd(now)` nowが週の終わりの出勤日かどうかを返す

```lua:touban.lua
-- now はos.date("*t")が返す形式のテーブル
function isHoliday(now)
  -- 国民の祝日
  local holidays = {
    "20170109",
    "20170320",
    "20170503",
    "20170504",
    "20170505",
    "20170717",
    "20170811",
    "20170918",
    "20171009",
    "20171103",
    "20171123"
  }
  -- 会社の休み
  local additionalHolidays = {
    "20170102",
    "20170103"
  }

  local today = os.date("%Y%m%d", os.time(now))

  if now.wday == 1 or now.wday == 7 then
    return true
  end

  for _, h in ipairs(holidays) do
    if today == h then
      return true
    end
  end

  for _, h in ipairs(additionalHolidays) do
    if today == h then
      return true
    end
  end

  return false
end

function isWeekStart(now)
  for i = 1, (now.wday - 1) do
    local t = os.date("*t", os.time(now) - (now.wday - i) * 24 * 60 * 60)
    if not isHoliday(t) then
      return false;
    end
  end

  return not isHoliday(now)
end

function isWeekEnd(now)
  for i = (now.wday + 1), 7 do
    local t = os.date("*t", os.time(now) + (i - now.wday) * 24 * 60 * 60)
    if not isHoliday(t) then
      return false;
    end
  end

  return not isHoliday(now)
end
```

日付を8桁の数字で話かけると、isHoliday、isWeekStart、isWeekEndの結果を教えてくれるように設定して、テストしてみます。

パターンに反応して何かをするときには[`robot:respond()`](https://docs.kiten.co/luareference-robot/#respond)メソッドを使います。
この辺がHubot風味です。

発言は[`res:send()`](https://docs.kiten.co/luareference-response/#send)メソッドを使います。

```lua:touban.lua
-- テスト用
robot:respond("%d%d%d%d%d%d%d%d", function(res)
  local y, m, d = res.message.text:match("(%d%d%d%d)(%d%d)(%d%d)")
  d = {year = tonumber(y), month = tonumber(m), day = tonumber(d)}
  d = os.date("*t", os.time(d))
  local h = isHoliday(d)
  local s = isWeekStart(d)
  local e = isWeekEnd(d)
  res:send(string.format("isHoliday: %s,  isWeekStart: %s,  isWeekEnd: %s", tostring(h), tostring(s), tostring(e)))
end)
```

これで、作成したメソッドが期待通りに動くかテストできるようになりました。
2017/1/21と22の土日を含む前後の結果をテストしてみましょう。

<img width="400px" alt="bot.png" src="https://qiita-image-store.s3.amazonaws.com/0/530/4b5c5598-9e7e-1c98-2902-856141455925.png">

大丈夫そうです。

第一回はここまで。続きは明日(2017/1/18)投稿します。