Google Calendar API を Ruby から使う: Push Notification

前回 の続き

前の記事の下の方で参考として貼ったリンクを読んだ前提で話します。

注意: 個人的に必要そうな情報のみに特化してますmm

ドメインの登録

まず google developer consoleドメインの確認 タブから「ドメインの追加」を行っておく必要があります。

わざわざ heroku でアプリケーション作って、SSL Endpoint 使ったりちょっとめんどくさいですが、そんなに難しくないのでサクッとやっちゃいましょう。 Let’s Encrypt の証明書使えば証明書自体はタダなので。(SSL Endpoint は有料。$20/month)

Subscribe Calendar

カレンダーの購読をするために Channel というものを作成する必要があります。

前回の記事の GoogleCalendar#watch_events というメソッドで Google::Apis::CalendarV3::CalendarService#watch_event というメソッドを呼び, channel の作成を行っています。

Google::Apis::CalendarV3::Channel の initialize 時に id, address, type の3つを渡してますが、最低限これらが必要になります。

Channel ID に関してはカレンダー毎に unique である必要があるため、 SecureRandom.uuid とかで生成してます。DBに保存しておいた方がいい値です。unsubscribe 時に使います

Unsubscribe Calendar

Channel には有効期限があるため、定期的に unsubscribe -> 再度 subscribe を行う必要があります。 Google::Apis::CalendarV3::CalendarService#watch_event の返り値が Google::Apis::CalendarV3::Channel のインスタンスですが、expiration という attribute に値が入っているので念のため, これも DB に保存しておきましょう。

ActiveJob とかに「期限が切れる 1h 前に unsubscribe -> subscribe して!」とかって渡しておくとかでもいいでしょう。

この際 Unix time (millisec 込み) が string として入っているので, datetime 型のカラムに保存する場合は Time.zone.at(channel.expiration.to_i / 1000.0) のようにする必要があります :sweat:

一つのカレンダーを何回も subscribe しちゃうとカレンダーに何かしら予定を入れたりした際に、 channel の数分 callback が飛んでくるので注意してくださいmm

Channel に限らないんですが、APIで利用する様々なオブジェクトにはそれぞれ resource_id というものが割り振られているので、それも一緒に保存しておき、subscribe する際にはそれも一緒に使います。

DB に保存しとくといいもの

また Calendar の ID とかも色々と使い回すことが多いので保存しておくといいかと思います。

また Event 通知が来た際にはどの resource に変更があったか、ぐらいの情報しかないため、

  1. callback が飛んでくる
  2. resource を特定して、どういった予定が入力/更新/削除されたか, 取得して確かめる

ということが必要になってきます。その際に特に parameter を付けずにあるカレンダーの events を取得する API を叩いてしまうと、今までの event が全て返ってきたりするので、 event list に含まれる next_sync_token という値を必ず保存し, callback から event を取得する際には毎回 next_token parameter をつけて event 一覧を取得するようにしましょう。

ざっくりまとめると

  • Channel: channel_id, resource_id, expiration
  • User: refresh_token
  • Calendar: calendar_id, next_sync_token

といったデータは保持しておくとよさそうです。

どういうテーブル構成で持たせるかはアプリケーションの要件にもよると思うので、それに合わせて持っておくといいでしょう。

Recurrence Event

iCalendar の仕様が RFC5545 とかで定められているんですが、その中に Recurrence Rule というものがあります。

なにかというと, Google Calendar に予定を入れるときに「繰り返し」というオプションがあって以下のような感じで登録できますよね?

  • 毎週水曜日, 2016.07.10 まで
  • 毎週火, 木, 3回繰り返す
  • など

こういった情報が登録された場合、その予定の数分データが取得できるわけではなく, 開始日時 + Rule といった情報が返ってきます。つらい。

でどんな内容かというと, Google::Apis::CalendarV3::Event#recurrence に含まれおり,

  • "RRULE:FREQ=WEEKLY;UNTIL=20160802T190000Z;BYDAY=WE"]
  • "RRULE:FREQ=WEEKLY;COUNT=3;BYDAY=TU,WE"
  • RRULE:FREQ=DAILY;COUNT=3;INTERVAL=3"

みたいなデータが Array[String] という形で入っています。おぅふ。

最初、「これ毎回 parse しないといけないのか…」とハゲそうになりましたが、安心してください、 ice_cube というgemがあります。

以下のような使い方すればいい感じで予定が取得できます

# recurrence じゃなければ event.recurrence は nil なのでチェックする必要はあります
# `RRULE:` より後ろの部分を渡さないとエラーになる
rule_str = event.recurrence.first.split("RRULE:")[-1]
s = IceCube::Schedule.new event.start.date_time # 終日の予定の場合 event.start.date を使う
rule = IceCube::Rule.from_ical rule_str
s.add_recurrence_rule rule
occurrences =
  # COUNT(何回繰り返すか) や UNTIL(いつまで) といった情報がなければ無限に繰り返すため
  # ある程度ケツを決めて予定を取得するようにする
  if rule.occurrence_count.nil? || rule.until_time.nil?
    end_time = 1.year.since # とりあえず 1年後 まで取得
    s.occurrences(end_time)
  else
    s.all_occurrences
  end
time_diff = event.end.date_time - event.start.date_time
occurrences.map(&:to_time).map do |start|
  { start_at: start, end_at: start + time_diff }
end
#=> [ {start_at: Time, end_at: Time }, { ... }, ... ]

間間に色々チェックする必要がありますが、これでなんとか予定一覧をとってくることができそうです。

削除された予定

今のところ, event.status == "canceled" のものは削除された予定と思ってよさそうです。

おわり

今から実装するのが億劫ですが、無事終わることを期待してます…

Contents