Google Calendar API の Push Notification 機能を使ってみた

Google Calendar API の Push Notification 機能を使うと、Google カレンダー側の更新をリアルタイムに自分のアプリケーションに反映することができるらしいので試してみました。

Push Notification 機能を使うためには、通知を受け取る URL を Google 側に事前に登録しておく必要があるのですが、手順はドキュメントに書いてあるため省略します。

今回、クライアント用のライブラリには google-api-client を使用します。

# Gemfile
gem 'google-api-client'

Channel を作成する

Push 通知を受け取るために、まず Channel と呼ばれるものを作成する必要があります。

変更の通知を受け取りたいカレンダーを指定して Channel を作成することで、そのカレンダー内のイベントが変更されるたびに、Google 側から HTTPS でリクエストが飛んでくるようになります。

ちなみに Channel は変更の通知を受け取りたいカレンダーごとに作成する必要があるようです。

require 'google/api_client'

client = Google::APIClient.new

client.authorization.client_id = '<CLIENT_ID>'
client.authorization.client_secret = '<CLIENT_SECRET>'
client.authorization.refresh_token = '<YOUR_REFRESH_TOKEN>'
client.authorization.fetch_access_token!

service = client.discovered_api('calendar', 'v3')

client.execute!(
  api_method: service.events.watch,
  parameters: { calendarId: '<YOUR_CALENDAR_ID>' },
  body_object: {
    id: '<CHANNEL_ID>',
    type: 'web_hook',
    address: '<YOUR_RECEIVING_URL>'
  }
)

<CLIENT_ID><CLIENT_SECRET>Google Cloud Console で確認できます。

<YOUR_REFRESH_TOKEN>OAuth 2.0 認証後に取得できる refresh_token を指定します。

<YOUR_CALENDAR_ID> に変更の通知を受け取りたいカレンダー ID を指定します。
ちなみに、ここで自分のカレンダー ID を調べることができます。

<CHANNEL_ID>には任意の文字列を指定します。
複数の Channel を作成する場合は Channel 間でユニークになっているほうがいいでしょう。
ちなみに、自分はSecureRandom.uuid()を使っています。

<YOUR_RECEIVING_URL> には通知を受け取りたい URL を指定しておきます。

リクエストに問題がなければ、以下のようなレスポンスが返ってきます。

{
  "kind": "api#channel",
  "id": "4a0d04ac-b5df-4a4d-ba03-65d121fbc100",
  "resourceId": "ROKWIDXHB6wV6KdPqFWmo_lTTLF",
  "resourceUri": "https://www.googleapis.com/calendar/v3/calendars/sei@me.com/events?alt=json",
  "expiration": "1384317818000"
}

ここで大事なのはidexpirationです。

idは先ほど指定した<CHANNEL_ID>の値が入ってきます。

expirationは Channel の有効期限で、これが切れると通知が飛んでこなくなります。
もし、引き続き通知を受け取りたい場合は、期限が切れる前に Channel を作成し直す必要があります。

Push Notification を受け取る

Channel 作成後、カレンダーに変更があると以下のようなヘッダー付きのリクエストが飛んできます。

HTTP_X_GOOG_CHANNEL_EXPIRATION: Sun, 10 Nov 2013 11:08:46 GMT
HTTP_X_GOOG_CHANNEL_ID: 4a0d04ac-b5df-4a4d-ba03-65d121fbc100
HTTP_X_GOOG_MESSAGE_NUMBER: 235226900
HTTP_X_GOOG_RESOURCE_ID: ROKWIDXHB6wV6KdPqFWmo_lTTLF
HTTP_X_GOOG_RESOURCE_STATE: exists
HTTP_X_GOOG_RESOURCE_URI: https://www.googleapis.com/calendar/v3/calendars/sei@me.com/events?alt=json

HTTP_X_GOOG_CHANNEL_IDは先ほど登録した<CHANNEL_ID>です。

通知では変更の内容までは教えてくれないので、通知が来る度に API を使って更新された内容を取得する必要があります。

更新されたイベントを取得する

<YOUR_CALENDAR_ID>にカレンダー ID を指定して更新があったイベントを取得します。
複数の Channel をもつアプリケーションを構築する場合は、通知内のHTTP_X_GOOG_CHANNEL_IDの値をもとにカレンダー ID を参照できるようにしておく必要があります。

require 'google/api_client'

client = Google::APIClient.new

client.authorization.client_id = '<CLIENT_ID>'
client.authorization.client_secret = '<CLIENT_SECRET>'
client.authorization.refresh_token = '<YOUR_REFRESH_TOKEN>'
client.authorization.fetch_access_token!

service = client.discovered_api('calendar', 'v3')

client.execute!(
  api_method: service.events.list,
  parameters: {
    calendarId: '<YOUR_CALENDAR_ID>',
    updatedMin: 1.minute.ago.to_datetime.rfc3339
  }
)

変更があったイベントをピンポイントで取得できるわけではないので、直近で変更があったイベントを検索する形になります。

ヘタすると取りこぼしの危険があるので、多めに 1 分前以降の変更を取得するようにしていますが、もっといい方法があればなぁ、という感じです。
(Google さん、変更があった時刻も教えてくれるとうれしいですっ!)