【GAS】高度なサービス(Calendar API)を使用してイベントのURLを取得する

Google Apps ScriptCalendarAppから取得できるCalendarEventオブジェクトには、特定のイベントのURLを直接取得できる関数が用意されていません。特にGASを使ってカレンダーとSlack等を連携させたい時、URLが取得できないのは不便です。

そこでインターネットを検索すると、 CalendarEvent.getId() を叩き、取得したIDを弄ってURLを作成する方法が色々見つかります。

しかし紹介されているそれらの手法の多くには欠点があり、Google Calendarの多彩なURLパターンに全て対応しているコードは見つけられませんでした。繰り返しイベントや終日イベントを扱う時に不具合が出ることが多いのです。

先人の知恵を基に頑張って全パターン網羅した仕組みを作る事はできそうですが、そもそもGoogle CalendarのイベントURLは仕様が公開されておらず、将来的に変更されない保証もありません。

そこで今回は、Google公式が提供しているCalendar APIを使用して特定のイベントのURLを取得する方法を紹介します。

前提

  • 2023年10月時点の情報です。

  • 取得するURLは、そのカレンダーの閲覧権限があるメンバーがイベントを確認するためのURLです。誰でも見られるパブリックなURLではありません。

  • コード環境はclasp&TypeScriptで、環境のセットアップは公式のドキュメントを参考にしています。

GoogleカレンダーのイベントURL

Google公式はイベントURLの仕様を公開していませんが、確認できる限り、イベントURLは以下の様な仕様になっています。(値は全てダミーです)

https://www.google.com/calendar/event?eid=MjN3Mnk0MXB4NHk4bm1yZjdzcWN2cWxrZG9fMjAyMzEwMDRUMDIwMDAwWiB0YXJvdS55YW1hZGFAZ21haWwuY29tCg

URLに含まれている eid はbase64エンコードされた文字列です。

$echo MjN3Mnk0MXB4NHk4bm1yZjdzcWN2cWxrZG9fMjAyMzEwMDRUMDIwMDAwWiB0YXJvdS55YW1hZGFAZ21haWwuY29tCg | base64 --decode
23w2y41px4y8nmrf7sqcvqlkdo_20231004T020000Z tarou.yamada@gmail.com

eidは{基本イベントID}_{開始時間} {カレンダーID}の形式になっており(仕様が公開されていないので正式な名前ではありません)、ここに正しい値を入れることでイベントURLとして解釈されます。正しくない値が入っていた場合はアクセスしても400エラーや500エラーが帰ってきます。

eidに含まれている基本イベントIDは CalendarEvent.getId() で取得可能で、開始時間も CalendarEvent.getStartTime() で取得可能です。後ろのメールアドレスは CalendarEvent.getOriginalCalendarId() で取得できるので、頑張ればURLを構成できるのですが、イベントの種類によって微妙に仕様が異なってきます。

  • 繰り返しイベントは同じ基本イベントIDを持ち、後ろの開始時間で区別を付けている(らしい)
  • 終日イベントは開始時間に時分の情報が無く、付いているとエラーになる(っぽい)
  • イベントによっては開始時間のタイムゾーンの指定も異なる(みたい)

この辺りの試行錯誤を見ると分かるように、全てのパターンを網羅するのはかなり面倒です。

また、これらの仕様は前述の通り公開されている仕様ではないので、頑張って対応したところで突然の仕様変更に怯えながらメンテしなければなりません。

Google Calendar API

GASのプロジェクトを作成した際にデフォルトで使えるのは、Calendar Serviceとして定義されている高レイヤーなAPIですが、設定で高度なカレンダー サービスを有効にすることで、Calendar APIのクライアント ライブラリが使える様になります。

高度なカレンダーサービスで使える様になるCalendar APIのクライアント ライブラリは、同名のWebAPIを薄くラップした作りになっています。

これを利用して取得出来るイベント オブジェクトには htmlLink が含まれており、この値がGoogle公式APIから取得できる、目的のイベントへのリンクになっています。

実際に触る

環境のセットアップはこちらのドキュメントを参考にして下さい。

github.com

この環境ではtypescriptのトランスパイルの手間がありませんが、代わりにES Moduleが使用できないため注意が必要です。

高度なカレンダー サービスを有効にする

clasp環境でGoogle Calendar APIを使える様にするには appscript.jsondependencies に以下を追加します。

"dependencies": {
  "enabledAdvancedServices": [
    {
      "userSymbol": "Calendar",
      "version": "v3",
      "serviceId": "calendar"
    }
  ]
}

この記述はブラウザ上でサービスの追加を行うのと同様です。

@types/google-apps-scriptにはCalendar APIの型も定義されているので、clasp/typescriptの環境がセットアップ済みであればそのままIDEでヒントが使えます。ただし、型定義はCalendar 型であることを想定しており、 userSymbol(Web版の場合はID)を変更するとズレが生じるので気を付けて下さい。

Calendar APIにアクセスする

カレンダーはCalendarオブジェクトからアクセス可能です。 Calnedar API(WebAPI)でイベント一覧を取得する際には Events: list を使用するのですが、TypeScript上では Calendar.Events.list() でアクセスする事が出来ます。

// 明日の0時から24時までの間の予定を取得
const startTime = new Date();
startTime.setDate(startTime.getDate() + 1);
startTime.setHours(0, 0, 0, 0);
const endTime = new Date(startTime);
endTime.setDate(endTime.getDate() + 1);

const eventsListResponse = Calendar.Events.list(calendar.getId(),{
    singleEvents: true,
    orderBy: "startTime",
    timeMin: startTime.toISOString(),
    timeMax: endTime.toISOString()
});

上記コードで取得したeventListResponseオブジェクトの構造はWebAPIのレスポンスと同様になっており、以下の様に扱えます。

eventsListResponse.items.forEach((event) => {
    console.log(event.htmlLink);
});

まとめと感想

GASの高度なカレンダーサービス(Calendar API)で取得したURLはSlackのGoogle Calendar APP等で取得できるURLとほぼ同じになっており、そのまま有効なリンクとして活用可能です。GAS環境に簡単に導入でき、かつGoogle公式のAPIから取得したもので信頼出来るため、手動でURLを組み立てたり、生のWebAPIを叩いて取得していた箇所もこれに置き換えていいかもしれません。

GASの高度なサービスは、以前触ろうとした際はドキュメントがあまり深く整備されておらず触り辛かった記憶があるのですが、いつの間にかサンプルコードも増えており大分扱いやすくなっていました。手の届く範囲も広いので、カレンダーに限らず凝ったGASを作りたい時には積極的に使っていけそうな気がします。