Googleカレンダーの予定が更新(作成、編集、削除)されたらをLINEへ通知

[LINE DC Advent Calendar 2021 (クリスマスプレゼント付き) Advent Calendar 2021](https://qiita.com/advent-calendar/2021/linedc) その2の21日の投稿となります。

二人の予定をGoogleカレンダーで管理しています。
予定が追加されたときや、変更があったときに通知がきてほしい。

というわけで、
カレンダーの更新を検知
↓
GAS起動
↓
LINEへ通知
を実装しました。

全体の流れとしては以下になります。
1. Googleカレンダーの更新(追加・編集・削除)を検知してGASを起動
2. カレンダーの予定の更新情報を取得する
3. LINEへメッセージを送る

GASの選定理由は、Googleカレンダーの更新をトリガーにして起動させるのが簡単にできそうだったからです。

## Googleカレンダーの更新(追加・編集・削除)を検知してGASを起動
Google Apps Scriptでプロジェクト作成後、トリガーを設定します。
![](https://storage.googleapis.com/zenn-user-upload/3e1c48956c71-20211123.png)

カレンダーのオーナーのメールアドレスに、更新を検知したいカレンダーのメールアドレスを設定します。
これだけで、対象のカレンダーに予定の追加・編集・削除があったときにGASを起動させることができます。

このトリガーによって起動した時に、どんな情報が取得できるのかというと以下になります。
・authMode
・calendarId
・triggerUid

https://developers.google.com/apps-script/guides/triggers/events#google_calendar_events

カレンダーに関するデータは`calendarId`のみで、更新のあった予定に関するデータは取れないようです。

## カレンダーの予定の更新情報を取得する
カレンダーに更新があった時に単に通知がくるだけでなく、どんな予定が更新されたかも通知内容に含むようにしたいです。

上述の通り、カレンダー更新のトリガーでGASが起動された時に、変更の内容までは取得できないです。
なので、自前で変更内容を取得する必要があります。

そこで使うのが、
- Google Calendar API
- syncTokenパラメータ

です。

```js
const events = Calendar.Events.list(calendarId, options);
```

https://developers.google.com/calendar/api/v3/reference/events/list


calendarIdを指定することでそのカレンダーの予定を取得することができます。
また、syncTokenというオプションのパラメータがあり、レスポンスから取得することができます。
リクエスト時にsyncTokenを付与することにより、付与したsyncTokenを取得したときのカレンダーの予定と、リクエストした現在時点でのカレンダーの予定の差分の予定のみを取得することができます。

例えばカレンダーに予定A、予定B、予定Cが登録されていたとします。
syncTokenパラメータなしでリクエストすると、

```json
{
  "nextSyncToken": "next_sync_token_hogehoge",
  "items": [
    {予定A},
    {予定B},
    {予定C}
  ],
  // その他項目省略
}
```
https://developers.google.com/calendar/api/v3/reference/events/list

という感じで予定が取得できます。
そのあと、グーグルカレンダー側で予定を編集して「予定B」の名前を「予定B編集」と変えたとします。
今度はsyncTokneパラメータを付与してリクエストします。

```js
const events = Calendar.Events.list(calendarId, {syncToken: "next_sync_token_hogehoge"});
```

レスポンスは

```json
{
  "nextSyncToken": "next_sync_token_fugafuga",
  "items": [
    {予定B編集},
  ],
  // その他項目省略
}
```
という感じになり、差分のみが取得できます。

これを利用して、予定の更新情報を取得してLINEへ通知します。

予定の更新部分取得の流れとしては、
1. トリガー設定前に手動実行してnextSyncTokenを取得して保存しておく
2. トリガーをセット
3. 保存しておいたnextSyncTokenを使って予定取得
4. 新たに取れたnextSyncTokenを保存しておく
となります。

### トリガー設定前に手動実行してnextSyncTokenを取得して保存しておく
このnextSyncToken、予定をすべて取得しきらないと取得できません。
どういうことかというと、

カレンダーに予定が300件登録されているとします。

```js
const events = Calendar.Events.list(calendarId);
```
上記のように特にオプションを指定せずリクエストすると予定を全件取得しようとします。
そして、1リクエストでの最大取得件数はデフォルトでは250件です。

```json
{
  "nextPageToken": string,
  "items": [
    events Resource
  ]
  // その他項目省略
}
```
`nextPageToken`というものが代わりに取得されます。
ページネーションのようなもので、次回リクエスト時にnextPageTokenを付与すると残りの30件が取得できます。

なので、nextSyncTokenを取得するには予定をすべて取得しきる必要があります。

いくつか方法は考えられます。

自分の場合は、`timeMin`というオプションを使いました。

```js
const events = Calendar.Events.list(calendarId, {timeMin: _now_rfc3339());

function _now_rfc3339() {
  // 現在日時返す 
}
```
予定の終了日時がtimeMinに指定した日時より未来の予定をすべて取得します。
自分の場合未来の予定がデフォルト取得件数の250件より少なかったので一度のリクエストで取得できました。

nextSyncTokenを保存するにはGASのプロパティサービスを使用します。
https://developers.google.com/apps-script/guides/properties

```js
scriptProperties.setProperty('NEXT_SYNC_TOKEN', events.nextSyncToken);
```
これでGAS上にデータを保存することができます。

### トリガーをセット
これは前述のようにGASの設定でセット

### 保存しておいたnextSyncTokenを使って予定取得
GASのプロパティサービスで取得。

```js
const nextSyncToken = scriptProperties.getProperty('NEXT_SYNC_TOKEN');
```

### 新たに取れたnextSyncTokenを保存しておく

```js
scriptProperties.setProperty('NEXT_SYNC_TOKEN', events.nextSyncToken);
```


## LINEへ通知
LINE Notify( https://notify-bot.line.me/ )で通知したいトークルーム・グループを選択してアクセストークンを発行します。

グループへ通知する場合はLINE側でLINE Notifyのアカウントをグループへ招待しておくことを忘れずに。

通知のAPIのドキュメントはこちら。
https://notify-bot.line.me/doc/ja/

`POST https://notify-api.line.me/api/notify` でメッセージを送信することができます。

```js
  const headers = {
    "Content-Type":  "application/x-www-form-urlencoded",
    'Authorization': 'Bearer ' + access_token,
  };
  const postData = {
    message: message
  };
  const options = {
    "method": "post",
    "headers": headers,
    "payload": postData
  };
 
  const url = 'https://notify-api.line.me/api/notify'
  UrlFetchApp.fetch(url, options);
```

ひとまず予定の名前と日時だけを返すようにしました。
![IMG_0979.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402333/0c882610-f5ff-abc4-9cbb-40290b318dc8.jpeg)

## 今後の改善
更新された予定が、新規追加なのか編集なのかを判別するようにしたい。
だれが予定を更新したかわかるようにしたい。