Google Calendar for Team Eventsを"使わずに"GoogleカレンダーとSlackを連携する

# はじめに
- Google Calendar for Team EventsとSlackの連携アプリがチーム内の事情で使えないので、SlackのWebhookを利用してSlackの公式連携アプリ+αの機能をGoogleスプレッドシートとGASの連携で実現しました。
- 完全に公式アプリのコピーだとなんだか寂しいので、備考欄も出力できるようにしました。

- ~~正直、GoogleカレンダーとSlackの公式連携が使えるのであればそっちを使ったほうがはるかにラクです。~~(2022/12/24追記)なんとGoogle Calendar for Team Eventsが停止になるとのこと・・・。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/6726b133-0e37-75d6-19cf-cef3ab472676.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/e731c748-7720-2c4c-557d-1ac949e5ffde.png)
- 過去の記事ですが今なら需要がある気がするので、2年前の記事ですが、Google Apps Script Advent Calendar 2022 17日目の投稿として再掲します。

https://qiita.com/advent-calendar/2022/google-apps-script

# コード
Githubを参照してください。
https://github.com/iamakawa/GCal-GAS-Slack

# 実現した機能
予定追加、予定変更/削除、まもなく開始(10分前)に、 
- タイトル
- 場所
- 備考
を通知します。

# 実現できていない機能
- 繰り返し予定(events IDの発行ルールが複雑でエラーになりやすいです)
- 毎週/毎日の予定通知(コードをいじればなんとでもなりますが)

## Googleスプレッドシート
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/dd916f58-662b-69cd-dc36-5b6af8d22df0.png)

## 予定追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/0b731e31-1514-7129-2310-6745d1b30642.png)

## 予定変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/59989de9-a3e9-eecf-1e5f-079c0777f874.png)

## まもなく開始
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/b5479a50-67fc-b3ea-1084-30b92d1a739b.png)

# コードについての解説
## 初期設定(Slack、Googleカレンダー)
### GoogleCalendarIDを取得
連携したいGoogleカレンダーを選択→「設定と共有」から「カレンダーID」を取得してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/9b0c7ad6-c3cd-488e-8b02-3c66b2e10d57.png)

### Slack Webhookを取得
詳細な設定方法については割愛。
- Slack-botの作り方(https://qiita.com/namutaka/items/233a83100c94af033575 )などを参照してください。
- WebhookURLを取得してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/64bc424b-9d1b-71f1-9511-fb2eb1b90be2.png)


### コード
取得したコードは下記のように設定します。

```javascript

var properties = PropertiesService.getScriptProperties();
var calendarId = properties.getProperty("calendarID");
var url = properties.getProperty("slackWebhook");
```

## Googleカレンダーの差分情報取得・カレンダー変更通知
- Googleカレンダーの現在の全カレンダー情報をpropertiesに保存
- onCalendarEditをGoogleカレンダーの編集をhookに起動
- 同じように全カレンダー情報を取得し、その差分→つまり、今回編集分のカレンダー情報 を取得

### コード(抜粋)

```javascript
//カレンダー初期起動用(差分取得用)
function initialSync() {
  var events = Calendar.Events.list(calendarId,
   {
     timeMin: Moment.moment().toISOString(),
     timeMax: Moment.moment().add(3, 'months').local().toISOString(),
   });
  var nextSyncToken = events.nextSyncToken;
  Logger.log("Sync:"+ nextSyncToken);
  properties.setProperty("nextSyncToken", nextSyncToken);
}

//カレンダー更新時に、前回差分から取得できるように
function onCalendarEdit(e) {  
  var nextSyncToken = properties.getProperty("nextSyncToken");
  Logger.log("pre_Sync:"+nextSyncToken);
  var events = Calendar.Events.list(calendarId, {
    syncToken: nextSyncToken,
  });

//以下割愛
```
### GASのトリガー設定
カレンダーのオーナーのメールアドレスには、「カレンダーID」を入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/a32d0468-eee8-2d15-02ee-b2ff3500e4dd.png)


## 「まもなく開始」機能の実装
- 取得したスプレッドシートから、15分以内に開始する予定をSlackに投稿する
- 投稿されたあとはリストから削除し、通知しない


### コード

```javascript
function noticeCalendarWillStart() {
  var dat = sheet.getDataRange().getValues();
  for(var i=1;i<dat.length;i++){
    var starttime = dat[i][3];
    var diff = Moment.moment(starttime).diff(Moment.moment(), "minutes")
    
    if(diff < 15) {
      var des_row = findRow(dat[i][0], CALENDAR_ID);
      if(des_row != 0){
        sendToSlack("まもなく開始する予定があります", getCalendarInfo(des_row),"#e8bccc");
        deleteCalendarInfo(des_row)
      }
      else {
        sendToSlack("予定通知でエラー発生", dat[i],"#e80055");
      }   
    }
  }
}

```
### GASのトリガー設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/26a9acf5-876b-f18b-73c3-63f517a2a1b2.png)

## 毎日のリスト更新 
- Googleスプレッドシートの定期的な更新を行う。(定期な予定を取得するため)

### コード

```javascript

function setCalendarList() {
  /* 時刻情報取得 */
  var date_0 = Moment.moment().local().startOf('day');
  var getRangeDate_0 = Moment.moment().add(3, 'months').local().startOf('day');
  var myCal =CalendarApp.getCalendarById(calendarId);
  var myEvents = myCal.getEvents(date_0.toDate(),getRangeDate_0.toDate());
  var to_row = 2;
  
  sheet.getRange(2,1,100).clear();
  myEvents.forEach(function(evt){
    　sheet.getRange(to_row, 1).setValue(evt.getId());  
    　sheet.getRange(to_row, 2).setValue(evt.getTitle());  
    　sheet.getRange(to_row, 3).setValue(evt.getLocation());  
    　sheet.getRange(to_row, 4).setValue(evt.getStartTime());  
    　sheet.getRange(to_row, 5).setValue(evt.getEndTime());
    　sheet.getRange(to_row, 6).setValue(evt.getDescription().replace("<br>","\n").replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'').replace(/&nbsp;/g,''));
    　to_row++;    
  });
}

```

### GASのトリガー設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/5df72bc7-ab1d-7e6d-e362-605999c39953.png)

# 実行
## GoogleCalendarAPIの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/c30f875e-f6de-0fd2-1dc3-3ce885233b23.png)

## Momentライブラリの追加
Googleスプレッドシートでの時間計算には、Momentライブラリを使用しています。
詳細はhttps://tonari-it.com/gas-moment-js-moment/ を参照してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/0bfb7812-5873-e8dd-d17f-d20281674563.png)

## 実行時に、GASからGoogleカレンダーが編集できるように設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/4b97ddf2-cd7b-c4b8-64a5-6286f0484a38.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/8c913086-293a-281a-e5d5-c8bfdd7a41b4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/e638dd5e-5ab2-1a6b-8bfb-c5a26fb72908.png)

 実行時、詳細を表示→安全ではないページに移動
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90313/203acb90-28f7-e059-3dbd-0173ac755dda.png)

# おわりに
## 感想
- Googleカレンダーの変更差分取得は非常にミニマムな設計がされており、半日くらいうまく設定ができず苦戦した。

## 残課題
- CalendarApp.getCalendarByIdとCalendarを併用しており、同じ機能を呼び出しているのに別のAPIを使用しており、可読性に欠ける。

## 参考リンク
- [【GAS】Googleカレンダー編集時に発動するトリガーを使いこなす | 初心者でもわかるGoogle Apps Script活用のススメ](https://for-dummies.net/gas-noobs/how-to-use-oncalendar-change-triggers-for-gas/)
- [Googleカレンダーの編集をトリガーにして、スプレッドシートの予定も更新したい時の準備メモ - Qiita](https://qiita.com/cazimayaa/items/5fdfbc060dff7a11ee15)