こんな簡単に!?Googleカレンダーの予定追加をLINEで見逃しゼロにできた方法


この記事は[Fringe81アドベントカレンダー2019](https://qiita.com/advent-calendar/2019/fringe81)の18日目の投稿です！


# カレンダーの予定をみんなに知らせたいのに！
Googleカレンダーを数名のグループで予定共有に使っていて、とっても便利なのですがひとつだけ大きく不満になっている部分がありました。それはカレンダーに予定を追加した時に、スマホにお知らせが来ない事です！グループで予定を設定したけど、予定作成時に通知が来ないから見逃してしまうという事がおきていました。

LINEとGoogleカレンダーを使って予定を立てているグループなので、Googleカレンダーで予定を作った時にそれがグループの全員に伝わる良い方法はないかと考えていました :thinking: 

# Googleカレンダーで登録した予定をLINEのグループに通知する
そこで思いついたのが、カレンダー登録の通知をLINEグループに自動投稿してくれるような感じであれば、LINEの通知を媒介にやりたいことが実現できるのではと思い計画をコネコネしていきました。

イメージがこんな感じです！
## 全体の流れ
![zentai.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540446/39f248f5-c5a4-8782-5a5b-9d7719281294.png)


さっそくしたごしらえから入っていきましょう！

## 準備
### LINEで対象のグループチャット用Token発行

今回はLINEにメッセージが流れれば良いので「LINE Notify」というWEB APIを通じて簡単にグループチャットに投稿できる機能を利用します！
<img width="1132" alt="LINE_Notify.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540446/3113938b-e114-4983-506a-b2e703cb1801.png">

<img width="533" alt="LINE_Notify.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540446/5768885d-0017-30ae-f387-f61d692b22f8.png">

発行したToken(LINE_BOT_TOKEN)をメモします。

連携中のサービス一覧にトークルーム名が並ぶはずです

### 通知用のユーザをグループに招待します。
LINE NotifyユーザーもLINE友だちに追加されているので通知をしたいLINEグループチャットに招待します。


### Googleカレンダーのトリガーを設定

通知対象のカレンダーの設定から下記の項目を見つけて「カレンダーのメールアドレス」をメモします！
Googleカレンダーはカレンダー毎にメールアドレス(=ID)が振り分けられているみたいです。

<img width="665" alt="Stack.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540446/03382394-2111-eba7-1063-147ceae5a9c6.png">


次に、Google Apps Scriptで新規Scriptを作成します。
※プロジェクト名を「ifAddCalendarThenLineNotify」にしてます

編集 -> 現在のプロジェクトのトリガー -> トリガーを追加

下記の通りに内容を登録します。最後の「カレンダーのオーナーのメールアドレス」には、先程メモしたカレンダーのメールアドレスを入力します。

<img width="705" alt="ifAddCalenderThenLineNotify_-_プロジェクトのトリガー_-_Apps_Script.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540446/dcbad4c4-eb78-fe6e-b6fe-75242ab37406.png">
↓
<img width="710" alt="ifAddCalenderThenLineNotify_-_プロジェクトのトリガー_-_Apps_Script.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540446/0c7d9518-ab7e-1816-672a-318cf96bcca5.png">

### Bitly(URL短縮サービス)でTokenを取得する
Bitly.com( https://bitly.com )にてURL短縮用のTokenを取得してメモします。
※URLの短縮は、見た目の改善なので取得しなくても問題ありません。

## 実装

### カレンダー用のAPI利用設定
リソース -> Googleの拡張サービス... -> 「Calendar API」をオンに


### 環境変数の設定
ファイル -> プロジェクトのプロパティ -> 「スクリプトのプロパティ」を開いて 
「行を追加」から新規にプロパティを登録します。

1. LINE Notify用
プロパティ名 →「LINE_BOT_TOKEN」
値 → LINE Notifyで取得後にメモしておいたTokenをペースト

2. Bitly用
プロパティ名 →「BITLY_TOKEN」
値 → Bitlyで取得後にメモしておいたTokenをペースト


3. カレンダーToken用
また後ほど利用するカレンダーTockenの保存用に「SYNC_TOKEN」を利用します。
こちらにプロパティ登録の必要はありません。

Googleカレンダーは、なにか編集があったりすると、その時々の状況ごとにTokenが発行します。
過去のTokenと現在のTokenを比較することで差分を元に登録(もしくは更新)したカレンダーの内容を取得します。
予定を登録した時のイベントに情報が盛り込まれていると最初は思っていたので正直やや面倒です。

````
[現在のTokenから取得したカレンダー] - [以前のTokenから取得したカレンダー] = 新たに登録した予定
```

準備ができたら先程、作成したGASに処理を書いていきます。

### Script
```javascript
function myFunction(event) {
  
  // カレンダーIDのの取得
  var calendarId = event.calendarId;
  
  // 前回実行時に取得したカレンダーTokenの取得
  var token = getSyncToken(calendarId)
  
  // カレンダーは更新される毎にTokenを発行している
  // 引数に渡したToken以降に更新された予定を取得
  // イベント毎に実行されるScriptなので最新の1件のイベントが取得される想定
  var events = Calendar.Events.list(calendarId, {'syncToken': token});
  
  // ステータスを見て登録、もしくは更新の予定のみにフィルタリング
  var filteredItems = events.items.filter(function(e){return e.status == "confirmed"})
  
  // 予定をLINEに通知する
  filteredItems.forEach(function(e){postMessage(e)});
  
  // 今回のTokenを保存する(次回のScript実行時に利用)
  saveSyncToken(events.nextSyncToken);
}


/**
 * 受け取ったイベントからメッセージを出力する
 * メッセージ内容によって、出力する日付などの加工が必要
 * 
**/
function postMessage(event){
  // 表示用のペンの絵文字
  const emoticonPen = "\uDBC0\uDC41"
  // 表示用の置き時計の絵文字
  const emoticonClock = "\uDBC0\uDC71"
  
  // 通知用のメッセージを生成
  // 時間指定の予定と終日予定の情報差を無くしている
  // カレンダーのURLが長いので短縮している
  var message = "\n"
  + ""
  + emoticonPen + event.summary + "\n"
  + emoticonClock + calcStartDate(event) + " 〜 " + calcEndDate(event) + "\n"
  + "カレンダーで見る(" + getShortUrl(event.htmlLink) + ")"
  
  // LINE Notifyを利用するにあたって、発行したTokenをHeaderにセットして指定のエンドポイントにPOSTする
  const url = "https://notify-api.line.me/api/notify";
  var headers = {
    'Authorization': "Bearer " + PropertiesService.getScriptProperties().getProperty('LINE_BOT_TOKEN')
  };
  var payload = {"message" : message};
  var params = {
    "headers" : headers,
    "method" : "post",
    "payload" : payload
  };
  UrlFetchApp.fetch(url, params);
}

// 前回保存したカレンダーのSyncTokenを取り出す、前回保存分が無い場合は今回のSyncTokenを利用する
function getSyncToken(calendarId){
  var token = PropertiesService.getScriptProperties().getProperty('SYNC_TOKEN');
  if(token){
    return token;
  }
  var events = Calendar.Events.list(calendarId, {'timeMin': (new Date()).toISOString()});
  return events.nextSyncToken;
}

// SyncTokenをプロパティに保存する
function saveSyncToken(token){
  PropertiesService.getScriptProperties().setProperty('SYNC_TOKEN', token);
}

// 開始日時を取得 (予定が終日の場合は開始日時を0時0分とする)
function calcStartDate(e){
  var str
  if('dateTime' in e.start){
    str = e.start.dateTime
  }
  if('date' in e.start){
    str = e.start.date + 'T00:00:00+09:00'
  }
  return Utilities.formatDate(new Date(str), 'Asia/Tokyo', 'MM/dd HH:mm')
}

// 終了日を取得 (予定が終日の場合は終了日時を当日の23時59分とする)
function calcEndDate(e){
  var str
  if('dateTime' in e.end){
    str = e.end.dateTime
  }
  if('date' in e.end){
    str = e.end.date + 'T00:00:00+09:00'
  }
  var datetime = new Date(str)
  datetime.setMinutes(-1)
  return Utilities.formatDate(datetime, 'Asia/Tokyo', 'MM/dd HH:mm')
}

// 短縮URLの取得
function getShortUrl(url){
  // 単に return url とすると短縮しないで、そのままURLを返却する
  var token = PropertiesService.getScriptProperties().getProperty('BITLY_TOKEN');
  var res = UrlFetchApp.fetch('https://api-ssl.bitly.com/v3/shorten?access_token=' + token + '&longUrl=' + url);
  return JSON.parse(res.getContentText()).data.url
}
```

# デモです。

**カレンダーから予定を登録**

Googleカレンダーを開いて指定したカレンダーのメールアドレス(個人、共有)を使って予定を登録します。

<img width="444" alt="Stack.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540446/a0b9fb26-441c-2629-3f54-a621ba901820.png">



**LINEのグループにメッセージが届きます**

- LINEグループの通知機能を利用してユーザは通知を受け取ることができる
- 予定の概要がメッセージされ、Googleカレンダーへのリンクも付いています
- カレンダーへのリンクをタップするとGoogleカレンダーアプリが起動して詳細を確認する事ができます (短縮URLを活用するとリンクが短くなって見やすくなります)

![Screenshot_20191218-093343.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540446/e533213d-b7d9-70d0-ba6f-a0fa62e31a14.jpeg)



# 感想
共有のカレンダーから誰が予定を登録しても、LINEに通知が来るので見逃し防止になりました。