Google Apps Script でカレンダーの予定をGoogle Chat, Chatwork, LINE などに送りつける

## この記事を読むとできるようになること
Google Calendar に登録されている本日の予定を、Google Chat, Chatwork, LINE に毎朝リマインド通知できるようになる。

全部やるのに必要になるアカウントは、

- Google
- Chatwork
- LINE
- LINE Developer

の4つで、開発に必要なのは Web Browser だけ。利用するのに料金はかからない。
GoogleやChatworkは、Admin権限等不要で、一般ユーザでできる。

## Google Apps Script とは

Google が提供している JavaScript ベースなプログラミング言語で、色んなことができますが、Google の提供しているサービスとの連携が簡単にできます。

昨今、非エンジニアであっても、面倒くさいことは全部 Python にやらせよう！という風潮がありますが、Python の場合、あれこれインストールしたりしないといけないという意味では、ちょっとハードルが高いです。一方で、Google App Script（GAS）の場合、必要なのは、ブラウザと Google のアカウントだけなので、非エンジニアであっても簡単に始められます。更に、GSuite を利用している会社も多いことと思いますので、面倒くさいことは、割と Google 絡みだったりすることも多いのではないでしょうか。

言語も基本的には JavaScript なので、困ったらググれば大抵のことは解決します。

という訳で、今回は、Googleカレンダーのイベントを取得して Chat ツールに送るリマインダー的な Bot を作ってみたいと思います。

## Hello, World

まずは Google Drive に行って、適当なところにファイルを作成します。
今回は、My Drive の下に bot というディレクトリを作って、そこで作業したいと思います。

bot ディレクトリに行ったら、左上の ”+ New” ボタンか 右クリックメニューから more > Google Apps Script を選択します。

![Screen Shot 2020-10-15 at 17.43.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/b5f2f74c-53c6-09e9-f6f5-d7a60bf1ff74.png)

そうすると、こんな感じのページに遷移します。

![Screen Shot 2020-10-15 at 18.10.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/a45f37be-49d7-beba-2ad0-600adca8d1a6.png)

Project 名に、helloWolrd などとつけて、myFunction の中身を以下のように書き換えます。

```javascript:Code.gs
function myFunction() {
  console.log('Hello, World');
}
```

保存は Ctrl+s でできます。保存出来たら、メニューの ▶ ボタンをクリックすると実行することができます。今は、実行しても何も起こっていないように見えますが、メニューの View > Logs と選択すると、console.log で出力した内容が確認できます。

![Screen Shot 2020-10-15 at 18.17.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/ef94f1aa-0cd9-cd71-a7a3-8e0f587bc595.png)

はい。というわけで、Hello, World できました。基本的な流れはこんな感じです。プログラムが完成したら、自動実行するための Trigger を設定しますが、一番最後にやります。


## Google Calendar のイベントを取得する
> [Calendar Service](https://developers.google.com/apps-script/reference/calendar)

Google Calendar の API でできることはここにまとまっているので、読んでやってみてください。今回は、Calendar から今日のイベントを取得してみたいと思います。

まずは、GAS ファイルを作り、notifyEvents とでも命名しておきます。

まずは、カレンダーに行き、左メニューのカレンダーリストからイベントを取得したカレンダーの ”︙” をクリックし、Settings and sharing を選択します。そうすると設定ページに移動するので、下の方の Integrate calendar のセクションに Calendar ID という項目があるので、それをコピーし、以下のコードの{Calender ID}の部分にペーストしてください。

```javascript:notifyEvent.gs
function notifyEvents() {
  const calendarId = '{Calendar ID}';
  const calendar = CalendarApp.getCalendarById(calendarId);
}
```

これを実行しても何も起きないので、カレンダー名を取得し、Log に吐き出してみます。

```javascript:notifyEvent.gs
function notifyEvents() {
  const calendarId = '{Calendar ID}';
  const calendar = CalendarApp.getCalendarById(calendarId);
  const calendarName = calendar.getName();

  console.log(calendarName);
}
```

これを実行すると、まずはカレンダーへのアクセスに対するレビューがあるので、画面の指示に従い、許可します。そして実行が完了するのを待って、ログを開くとカレンダー名が表示されます。ログがまだないよと言われた場合は、しばらく待ってみてください。

なお、CalendarApp というのは import したりする必要はなく、実際に自分で打ってみると分かりますが、method などを補完してくれます。便利。

では、続けてカレンダーに登録された本日のイベントを取得し、ログに出してみます。

```javascript:notifyEvent.gs
function notifyEvents() {
  const calendarId = '{Calendar ID}';
  const calendar = CalendarApp.getCalendarById(calendarId);
  const calendarName = calendar.getName();

  const today = new Date();
  const calendarEvents = calendar.getEventsForDay(today);
  
  console.log(calendarEvents);
}
```

実行すると、```[ {}, {}, {} ]``` と何やら3件のデータが取得されたらしいが、中身は見せてくれない感じになります。実際にこのカレンダーには、本日3件のイベントが登録されています。何もない場合は、```[]``` と空配列が返ってきます。イベントがあったら、それを表示し、なかったらないよと通知するようにしたいと思います。なお、本日だけじゃなくて、期間指定したい場合は、[getEvents](https://developers.google.com/apps-script/reference/calendar/calendar#getEvents(Date,Date,Object))メソッドを使います。

```javascript:notifyEvent.gs
function notifyEvents() {
  const calendarId = '{Calendar ID}';
  const calendar = CalendarApp.getCalendarById(calendarId);
  const calendarName = calendar.getName();

  const today = new Date();
  const calendarEvents = calendar.getEventsForDay(today);
  
  if (calendarEvents.length) {
    for (var event of calendarEvents) {
      var eventTitle = event.getTitle() ? event.getTitle() : '(no title)';
      console.log(eventTitle);
    }
  } else {
    console.log('ないよ');
  }
}
```

タイトルなしのイベントは、getTitle では何も返ってこないので、カレンダーの表示と同様、(no title) と入れることにしました。
カレンダーのイベントには、時間が指定されているものと、されていないものがあるので、時間指定のあるものは、それも表示してみましょう。と同時に、イベント名だけ送られてきても困るので、気の利いたメッセージも付け加えましょう。

```javascript:notifyEvent.gs
function notifyEvents() {
  const calendarId = '{Calendar ID}';
  const calendar = CalendarApp.getCalendarById(calendarId);
  const calendarName = calendar.getName();

  const today = new Date();
  const calendarEvents = calendar.getEventsForDay(today);
  
  var message = 'おはよう朝です。\n' +
                 Utilities.formatDate(today, 'GMT+0900', 'MM/dd') +
                'の' + calendarName + 'の予定を通知します\n' +
                '-----------------------------------------';
      
  if (calendarEvents.length) {
    for (var event of calendarEvents) {
      var eventTitle = event.getTitle() ? event.getTitle() : '(no title)';
      message = message + '\n- ' + eventTitle;
      if (!event.isAllDayEvent()) {
        message = message + ' at '+ 
                  Utilities.formatDate(event.getStartTime(), 'GMT+0900', 'HH:mm') + '~' + 
                  Utilities.formatDate(event.getEndTime(), 'GMT+0900', 'HH:mm') + '(JST)';
      }
    }
  } else {
    message = message + '\nないよ';
  }
  
  console.log(message);
}
```

はい。これでカレンダーのイベントを無事取得することができました。
今回は、イベントのタイトル、開始時刻、終了時刻だけを取得しましたが、他にも色々取れるので、getXX メソッドや isXX メソッドを見てみてください。

> [Class CalendarEvent](https://developers.google.com/apps-script/reference/calendar/calendar-event)


## Google Chat にポストする
Google Chat 使ってる人ってあんまりいないのかな？と思いますが、GSuite についてるんで、Google 内なので、まずはそこにポストしてみたいと思います。

下準備として、Chat ルームの Webhook を設定します。

![Screen Shot 2020-10-15 at 19.36.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/65cd51a8-2990-c299-861a-0feb68b46894.png)

メッセージを送信したいルームに行って、Manage webhooks というのを探してください。で、設定してください。そうすると URL が発行されるので、それをコピーし、以下の{Webhook URL}に貼り付け。

```javascript:notifyEvents.gs
function notifyEvents() {
  const calendarId = '{Calendar ID}';
  const calendar = CalendarApp.getCalendarById(calendarId);
  const calendarName = calendar.getName();

  const today = new Date();
  const calendarEvents = calendar.getEventsForDay(today);
  
  var message = 'おはよう朝です。\n' +
                 Utilities.formatDate(today, 'GMT+0900', 'MM/dd') +
                'の' + calendarName + 'の予定を通知します\n' +
                '-----------------------------------------';
      
  if (calendarEvents.length) {
    for (var event of calendarEvents) {
      var eventTitle = event.getTitle() ? event.getTitle() : '(no title)';
      message = message + '\n- ' + eventTitle;
      if (!event.isAllDayEvent()) {
        message = message + ' at '+ 
                  Utilities.formatDate(event.getStartTime(), 'GMT+0900', 'HH:mm') + '~' + 
                  Utilities.formatDate(event.getEndTime(), 'GMT+0900', 'HH:mm') + '(JST)';
      }
    }
  } else {
    message = message + '\nないよ';
  }
  
  postToChat(message);
}

function postToChat(message) {
  const chatWebhook = '{Webhook URL}';
  const messageText = { 'text': message };
  const options = {
    'method': 'POST',
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8'
    },
    'payload': JSON.stringify(messageText)
  };
  var result = UrlFetchApp.fetch(chatWebhook, options);
  Logger.log(result);
}
```

これまた Chat アクセスへのレビューを求められるので、よしなに。


## Chatwork にポストする
まずは、Chatwork API Token を取得します。
サービス連携>API Token と進んで発行し、それを控えておきます。
次に、ポストしたいチャットルームの ID を控えます。URLの #!ridxxxxxx の xxxxxx(数字)の部分です。

[Chatwork APIドキュメント](https://developer.chatwork.com/ja/)を見ながらせっせと実装しても良いのですが、GAS から Chatwork を利用する便利なライブラリが用意されているので、それを使います。
メニューのResources > Libraries で、```M6TcEyniCs1xb3sdXFF_FhI-MNonZQ_sT```を追加。Version は最新のもので、今回は18にしました。

![Screen Shot 2020-10-15 at 20.12.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/765a889c-7c1d-f806-e639-e83fe3fed30d.png)

以下コードに、Token と Room ID を設定して実行。

```javascript:notifyEvents.gs
function notifyEvents() {
  ...
  //postToChat(message);
  postToChatwork(message);
}

function postToChat(message) {...}

function postToChatwork(message) {
  const chatworkToken = '{Chatwork API Token}';
  const chatworkRoomId = '{Chatwork Room ID}';
  const chatworkClient = ChatWorkClient.factory({token: chatworkToken});
  chatworkClient.sendMessage({room_id: chatworkRoomId, body: message});
}
```
諸々省略しましたが、こんだけです。

## LINE にポストする

LINE はちょっと面倒くさいですが、ちょっとだけです。
今回は、自分にだけメッセージを一方的に送れれば良いので、メッセージの送り先は自分固定にします。
流れとしては、
1. LINE Developers にて、Provider を作成し、Messaging API Channel を作る
2. Messaging API の設定で、Channel access token (long-lived) を発行する→ Token 控える
3. Messaging API の設定で、Auto-reply messages, Greeting messages を Disabled にする（必要ないので）
4. Basic settings の Your user ID を控える
5. GAS を Web App として Publish し、URL を控える
6. Messaging API の設定で、Webhook を 5 で控えた URL に設定する
7. QR コードを読み込んでフレンド登録する
8. GAS を実行する

です。

LINE Developers の細かい説明は、他の記事などを見てもらうとして、GAS のコードは、以下のようになります。前述のステップ2と4で控えた Token と user ID を入れてください。

```javascript:notifyEvents.gs
...
function postToLine(message) {
  const lineToken = '{LINE Token}';
  const lineMessagePushUrl = 'https://api.line.me/v2/bot/message/push';
  const lineHeaders = {
    "Content-Type": "application/json; charset=UTF-8",
    "Authorization": "Bearer " + lineToken
  };
  const linePayload = {
    "to" : "{User ID}",
    "messages" : [{
      "type": "text",
      "text": message
    }]
  };
  const options = {
    "method": "POST",
    "headers": lineHeaders,
    "payload": JSON.stringify(linePayload)
  };
  var result = UrlFetchApp.fetch(lineMessagePushUrl, options);
  Logger.log(result);
}
```

## Trigger を設定し、毎朝実行されるようにする

最後に、自動実行の設定を行いましょう。メニューの ▶ の隣の時計なのか虫眼鏡なのかのボタンをクリックすると Trigger 設定のページに遷移するので、新しく Trigger を作成してください。

今回は、毎朝一度だけ通知するというようにしたいので、Time-driven, Day timer, 9am to 10am という設定にしました。お好きに設定してください。9時から10時という謎の幅についてですが、数日動かしてみたところ 9時46分とか、毎日バラバラです。気持ち悪い気がしなくもないですが、私の場合は、始業前に通知が来てれば良いので良しとしました。

![Screen Shot 2020-10-16 at 0.28.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245908/c6ca8184-3988-8888-6dc2-448c75da2431.png)

これでおしまいなんですが、そうするとですね、土日にも通知が来ることになって、せっかくの週末気分が台無しです。なので、土日にはメッセージを送らないように GAS に処理を追加します。祝日は…やれないこともないですが、今回は、まぁ良いでしょう。


```javascript:notifyEvents.gs
function notifyEvents() {
  const calendarId = 'kura@indigames.net';
  const calendar = CalendarApp.getCalendarById(calendarId);
  const calendarName = calendar.getName();

  const today = new Date();
  const dayOfWeek = today.getDay();
  if (dayOfWeek === 0 || dayOfWeek === 6) return;
  
  const calendarEvents = calendar.getEventsForDay(today);
  ...
}
...
```

## 終わりに

Google Apps Script でググると、Spreadsheet から…というものが多く、Excel の Macro 相当のものなのかなぁという印象もあり、誰もメンテできない秘伝の GAS みたいなものが頭を過りましたが、サーバレスで JavaScript が実行できる環境、しかも無料と考えると、非常に有用なサービスなのではないかと思います。
ちょっとしたものは、Google Apps Script でという選択肢は持っておいても良いかなと。


今回のサンプルは、メッセージが英語になってますが、github に置いてあります。

> [kurab/notifyEvents](https://github.com/kurab/notifyEvents)