私は毎日帰宅する際に「今から帰るボタン」を押して LINE メッセージを送っています。

最近このボタンに機能を追加しより便利にしてみました。

追加した機能

Google カレンダーと連携し、ボタンを押したタイミングからその日の 23 時 59 分までの間に始まるイベントがある場合 (飲み会とか)、イベントの内容を送るようにしてみました。
以下のような感じです。

IMG_6533

日を跨ぐ可能性も考慮して終わりの時間には日付も入れるようにしています。

Google カレンダーからイベントを取得する方法

これ調べるのが結構大変だった…。混乱ポイントだったのが、まず適当にググってみたところ旧 Google カレンダー API の情報とごっちゃになっておりそこが混乱の元でした。Google 公式ドキュメントでも旧 API の情報が残っていて?ややこしい感じでした。
現在信頼できる公式ソースは以下。

旧カレンダー API では API キーでアクセスできたみたいなのですが、最新の v3 では OAuth 認証が必須です。

Your application must use OAuth 2.0 to authorize requests. No other authorization protocols are supported. If your application uses Google Sign-In, some aspects of authorization are handled for you.

Authorizing Requests to the Google Calendar API  |  Google Developers

スコープは読み取り専用のスコープである https://www.googleapis.com/auth/calendar.readonly があるのでこれを使います。スコープの一覧も下記 URL にあります。

今回、カレンダー API を使用するのは私のみです。またサーバーサイドでカレンダーへアクセスし情報を得られれば良いので、この場合は GCP 的には「サービスアカウント」がアクセストークンをリクエストして取得し、それを使用してカレンダーへアクセスする形になります。

サービス アカウントは、ユーザーではなく、アプリケーションや仮想マシン(VM)インスタンスで使用される特別なアカウントです。アプリケーションは、サービス アカウントを使用して、承認された API 呼び出しを行います。

サービス アカウント  |  Cloud IAM のドキュメント  |  Google Cloud

サービスアカウントは GCP コンソールの IAM と管理 -> サービスアカウント から作成できます。作成時秘密鍵を含む JSON ファイルを作成できるのでダウンロードします。

次に IAM と管理 -> IAM でメンバーを新規作成します。追加するメンバーにはサービスアカウント作成時に設定されたメールアドレスを設定します。メンバーには役割を設定する必要があるのですがとりあえずオーナーを割り当てます。でもこれはカレンダー限定とかに絞れるのかも…。

次に Google カレンダーの設定で、作成した IAM メンバーとカレンダーを共有します。Google カレンダーを開き、右上の歯車アイコンから設定を開きます。設定から マイカレンダーの設定 -> <対象のカレンダー> -> 特定のユーザーと共有 の設定で、作成した IAM メンバーのメールアドレスを設定します。これでサービスアカウントとカレンダーを共有することができました。

また、カレンダーのイベントを取得する際にカレンダー ID が必要になりますがこれも上記カレンダーの設定の カレンダーの統合 にて確認できます。

で、動作するコード (多分) は以下ですが、これは AWS Lambda 前提のコードです。ローカルで動かすなら AWS SAM の sam local invoke で動かせます。以下のコードは OAuth 認証して JWT (JSON Web Token) を取得し JWT を使用してカレンダー API へアクセスしています。

const {google} = require('googleapis');

const SCOPE = ['https://www.googleapis.com/auth/calendar.readonly'];

exports.lambdaHandler = function(event, context) {
  const jwtClient = new google.auth.JWT(
    null,
    './<ダウンロードした GCP サービスアカウントの JSON ファイル>',
    null,
    SCOPE,
    '<GCP サービスアカウントのメールアドレス>',
  )

  jwtClient.authorize();

  const calendar = google.calendar({version: 'v3', auth: jwtClient});

  calendar.events.list({
    calendarId: '<Google カレンダーの ID>',
    timeMin: (new Date()).toISOString(),
    maxResults: 10,
    singleEvents: true,
    orderBy: 'startTime',
  }, (err, res) => {
    if (err) return console.log('The API returned an error: ' + err);
    const events = res.data.items;
    if (events.length) {
      console.log('Upcoming 10 events:');
      events.map((event, i) => {
        const start = event.start.dateTime || event.start.date;
        console.log(`${start} - ${event.summary}`);
      });
    } else {
      console.log('No upcoming events found.');
    }
  });
};

JSON ファイルやアカウント情報は環境変数で渡してあげれば良いと思います。

また JSON ファイルでなくてもオプションで必要な設定を渡してあげることもできるので、最終的にはそのようにしています。

正直 Google API たくさんあってよくわからん状態なのでもっときれいな方法はありそう…。

AWS SAM の Tips

ローカルで実行する場合環境変数は env.json で渡せます。

-n、--env-vars PATH Lambda 関数の環境変数の値を含む JSON ファイル。環境変数ファイルの詳細については、「環境変数ファイル」を参照してください。

sam local invoke

一方、sam deploy 時に現在 --env-vars オプションで環境変数を設定しデプロイすることはできません。従ってデプロイ時は sam のテンプレートファイルに SSM パラメータストアから値を取得し、環境変数として設定する形にすると良いです。この辺の話は以下の GitHub Issue で議論されているので参考になります。

感想

自画自賛ですがこれ結構便利です!朝にパートナーへ帰りが遅くなる場合は言っていますが忘れることもあるし相手が忘れる場合もあるので…。

また、今後の展望としては、Google カレンダーではなく TimeTree から予定を取得するように変更しようと思っています。というのもパートナーとは共通のカレンダーとして TimeTree を使っており、私も予定は基本的に TimeTree に追加しているからです。今から帰るボタンにカレンダー機能を追加した時にはまだ TimeTree に予定を取得する API が無かったのですが、最近予定取得 API が TimeTree に追加されたので、これを使わない手は無い!🎉