Chienomi

明日の予定を通知しよう (CalDAV + Mattermost)

開発::noddy

MattermostとRadicaleは私が構築・運用してるシステムの中でも「やって正解だった」ものの筆頭だ。

軽く、制約や機密性を気にせず使うことができるカレンダーと、通知やメモに大活躍のMattermostは本当に便利。1

だが、そうして使っていると思うのだ。

「Mattermostに予定通知してくれんか」と。

なにせ私は予定管理という習慣がなく、曜日感覚のない生活をしてきたためにカレンダーは見ないし予定は立てないし予定を確認しない。 最近はようやく、「予定をカレンダーに入れる」という習慣がついてきたが、カレンダーを確認する習慣はいまいちついていない。

一方でメッセンジャーをチェックする習慣はあるから、「明日の予定をメッセンジャーに飛ばして欲しい」の気持ちが生えるわけだ。

当初Plannを使う方向で考えていたのだが、さっとうまく動かなかったため「作ったほうが早いのでは」となり、作ることにした。

CalDAVとソフトウェア

CalDAVはWebDAVとiCalファイルを組み合わせたものであり、オンラインカレンダーの標準的な形式である。

PlannはPythonのcaldavicalendarライブラリを使う仕組みになっており、当初Pythonで書くことも検討したが、Rubyにもcalendavというライブラリが結構アクティブに存在しているので、いつもどおりRubyで書くことにした。

今回のソフトウェアで必要なのは、「明日のイベントを一覧する」である。 そのため求められる機能はイベントの一覧だけなので、実装するのは簡単である。

「終日」イベントとDTEND

カレンダーイベントで多様するのが、時間指定のない「終日」イベントだ。

時間つきイベントの場合は

DTSTART;TZID=Asia/Tokyo:20241003T150000
DTEND;TZID=Asia/Tokyo:20241003T170000

のようにDateTimeになっているが、終日イベントは

DTSTART;VALUE=DATE:20201120
DTEND;VALUE=DATE:20201121

のようになっている。

ここで重要なのが、2020-11-20の終日イベントのDTEND20201121となっているということだ。

その関係上、calendav Gemではこのイベントは2020-11-20 00:00:00 - 2020-11-20 23:59:592020-11-21 00:00:00 - 2020-11-21 23:59:59の両方で出現してしまう。

「じゃあDTENDと一致する日が起点なら除外すればいいじゃないか」と思うかもしれないが、繰り返しになっているイベントだとDTSTARTDTENDはそのイベントの最初のタイミングになってしまうため、繰り返しを指定するRRULEと合わせて確認しないと判定できない。

そして、RRULEの判定は非常にめんどくさい。

この問題は「終端を含むべきでないのに含んでしまう」ということであり、終日イベントは必ず1日長く出現するというものである。

そこで解決策として、「明日の予定」と「明後日の予定」の両方を取得し、終日のイベントで「明後日の予定」に含まれないものは明日の予定から除外する、という手法をとった。 同一のイベントかどうかは、イベントに固有のURLが存在するため、これを使った。

Mattermostへのブリッジ

Mattermostへの通知は

  • tomorrow_events.rb コマンドを呼ぶ
  • 返ってきたJSONをパース
  • テキストに整形してwebhook用オブジェクト(JSON文字列)を作る
  • curl でwebhookを叩く

という単純なもの。

用意したものはMattermost用だが、ほとんど違いのないSlackは簡単に転用できるだろうし、他にも同じようなwebhookを使うものはちょっと改造すれば対応できるだろう。

ソフトウェアについて

このソフトウェアはそのまま使う用というよりは、簡単なプログラムなのでちゃちゃっと改変して使うことを想定している。