ヨガレッスンの予約完了メールから自動でカレンダー作成してみる

## はじめに
**まだまだ歴も浅く自己流でやってしまっているので、「ここはもっとこうした方がいい」「普通はこうやる！」とかあったら優しく教えてください。。。**

僕は男子大学生にしては珍しくヨガに通っているのですが、毎回のレッスンを予約した後にいちいち手動でGoogleカレンダーに追加するのが面倒だなと感じていました。
今回は予約時に届くメールを元に自動でGoogleカレンダーにイベントを作成するGASを書いてみました。

習い事など定期的に予約する用事がある時に活用できると思います。

## 全体像

```javascript:setYogaLesson.gs
//Gmailに届いた予約完了メールを元に、レッスンをカレンダーに追加します
function setYogaLesson() {
  var myMessages = getMessages('予約完了通知【yoga******.jp】');
  
  for(var i=0; i < myMessages[0].length; i++){
    var message = myMessages[0][i]
    var sendTime = message.getDate();
    var now = new Date();
    
    if(sendTime > now.setHours(now.getHours() - 1)){
      var lessonData = getLessonData(message);
      setCalendar(lessonData);
    }
  }
}
```
以下では各ステップについて説明します。

## 予約完了メールからレッスン情報を抜き出す
サイト上でレッスンの予約をすると次のようなメールが送られてきます。
![予約完了メール.png](https://qiita-image-store.s3.amazonaws.com/0/271701/08d83ec0-2896-1353-d371-59382716be69.png)


このメールからレッスン情報を取り出していきます。

### メッセージオブジェクトを取得する
`setYogaLesson()`2行目の`getMessages()`

```javascript:getMessages.gs
//指定したキーワードを持つGmailのスレッドから全メッセージを取り出します。
function getMessages(keyword) {
  var myThreads = GmailApp.search(keyword,0,1);
  var myMessages = GmailApp.getMessagesForThreads(myThreads);
  return myMessages;
}
```
`GmailApp.search(検索ワード, 開始位置, 最大スレッド数)`では自分(=このスクリプトを実行するGoogleアカウント)のGmailから、指定した検索ワードを含むスレッドを配列`[GmailThreadオブジェクト, ...]`に格納します。`開始位置 = 0`とすれば最新のスレッドから調べられます。
今回は予約時に送られるメールの件名を検索ワードとして、最新の1スレッドを取得しました。
`GmailApp.getMessagesForThreads(GmailThreadオブジェクト)`では取得したスレッドに含まれるメッセージを二次元配列`[[GmailMessageオブジェクト, ...], [...](←1スレッド分), ...]`に格納します。

### レッスン情報を取得する
`setYogaLesson()`4行目以降のfor文はなんのためかというと、短時間に複数のレッスンを予約したとき予約完了メールが勝手にスレッドを作ってしまうので、その場合でも全ての予約をカレンダーに反映できるようにするためです。
また9行目以降のif文ではスクリプトを実行する直近1hに受信したメッセージだけが内側に入るようになっています。これはトリガーを1時間ごとに設定しているためです。

`setYogaLesson()`10行目の`getLessonData()`

```javascript:getLessonData.gs
//メールの本文からカレンダーに記載するのに必要な情報を抽出します。
function getLessonData(message) {
  var body = message.getBody();
  
  var lessonDateStr = getInfo(body,'日付','時間'); 
  var lessonTimeStr = getInfo(body,'時間','スタジオ');
  
  var lessonDateTime = new Date(lessonDateStr.slice(0,4),    //yyyy
                                lessonDateStr.slice(5,7) - 1,//mm
                                lessonDateStr.slice(8,10),   //dd
                                lessonTimeStr.slice(-8,-6),  //hh 終了時刻を取得
                                lessonTimeStr.slice(-5,-3),  //mm
                                00);                         //ss
  
  var lessonName = getInfo(body,'ス名','講師');
  var instructor = getInfo(body,'講師','ヨガ***：');
  
  var lessonData = [lessonDateTime, lessonName, instructor];
  
  return lessonData;
}
```
GmailMessageオブジェクトを渡して、本文からレッスン情報(日時、レッスン名、講師名)を取得しています。

`getInfo()`は本文から開始位置と終了位置を指定してテキストを切り出します。

```javascript:getInfo.gs
//本文から始まりと終わりのキーワードを指定して情報を抽出します。
function getInfo(body, startKey, endKey) {
  var idxStart = body.indexOf(startKey);
  var idxEnd = body.indexOf(endKey);
  var info = body.slice(idxStart + 5, idxEnd);
  
  return info;
}
```
`slice()`の開始位置が`idxStart`からずれているのは、例えば「講師：**hoge**」となっている場合`startKey`は「講師」ですが、欲しいテキストはその後ろの「hoge」だからです。

`lessonDateTime`の時刻をなぜ終了時刻にしているかというと、10時より前に始まるレッスンの場合、「hh時mm分」表記ではなく「9時30分-」と表記されるため、切り出す位置が変わってしまい面倒だからです。10時より前に終わるレッスンは行かないので、必ず「hh時mm分」と表記される終了時刻を取ることにしました~~起きられるはずがない~~。



## Goolgeカレンダーにレッスンのイベントを作成する
`lessonData`に必要な情報が格納されたのでいよいよカレンダーにイベントを作成します。

`setYogaLesson()`11行目の`setCalendar()`

```javascript:setCalendar.gs
//レッスン情報を元にカレンダーイベントを作成します。
function setCalendar(lessonData) {
  var lessonDateTime = lessonData[0]; //終了時刻
  var title = lessonData[1];
  var endTime = new Date(lessonDate);
  var startTime = new Date(lessonDateTime.setHours(lessonDateTime.getHours() - 1)); //開始時刻 = 終了時刻の１時間前
  var options = {
    location: 'ヨガ*** ***スタジオ'
  };
  
  var myCalendar = CalendarApp.getDefaultCalendar();
  myCalendar.createEvent(title, startTime, endTime, options)
}
```
先ほど`lessonDateTime`を終了時刻としたのでそこから1h前を開始時刻とします(1レッスンは60分なので)。

`CalendarApp.getDefaultCalendar()`では特にいじってない限り、自分のGoogleカレンダー(Calendarオブジェクト)が取得できます。
`createEvent(題名, 開始時刻, 終了時刻, オプション)`でカレンダーにイベントを追加します。
`options`では「詳細説明、場所、ゲスト、ゲストに招待状を送るか」を設定できます。

## 実際に動かしてみる
それでは`setYogaLesson()`を実行してみます。
![イベント作成.png](https://qiita-image-store.s3.amazonaws.com/0/271701/9bd731e8-6c6f-ed00-a8a5-1e498ea2cb2d.png)

確かに追加できましたね。

## レッスンをキャンセルした場合
レッスンをキャンセルした場合も同じような完了メールが届きます。
![キャンセル完了メール.png](https://qiita-image-store.s3.amazonaws.com/0/271701/9b1684e2-4293-9425-263e-683ac2303281.png)

このメールを元にカレンダー上でキャンセルしたレッスンを特定して、そのイベントを削除します。

```javascript:deleteYogaLesson.gs
//Gmailに届いたキャンセルメールを元に、レッスンをカレンダーから削除します
function deleteYogaLesson() {
  var myMessages = getMessages('予約キャンセル通知【yoga******.jp】');
  
  for(var i=0; i < myMessages[0].length; i++){
    var message = myMessages[0][i]
    var sendTime = message.getDate();
    var now = new Date();
    
    if(sendTime > now.setHours(now.getHours() - 1)){
      var lessonData = getLessonData(message);
      deleteCalendar(lessonData);
    }
  }
}
```

`setCalendar()`が`deleteCalendar()`になっただけですね。

```javascript:deleteCalendar.gs
//レッスン情報を元にカレンダーイベントを削除します。
function deleteCalendar(lessonData) {
  var lessonDate = lessonData[0]; //終了時刻
  var title = lessonData[1];
  var endTime = new Date(lessonDate);
  var startTime = new Date(lessonDate.setHours(lessonDate.getHours() - 1)); //開始時刻 = 終了時刻の１時間前
  
  var myCalendar = CalendarApp.getDefaultCalendar();
  
  var events = myCalendar.getEvents(startTime, endTime);
  for(var i in events){
    if(events[i].getTitle() === title){
      events[i].deleteEvent();
    }
  }
}
```
途中までは`setCalendar()`と同じなので12行目から説明します。
`getEvents(開始時刻, 終了時刻)`では指定した時間帯に存在するイベントを全て取得し、配列`[CalendarEventオブジェクト, ...]`に格納します。
イベントを一つだけ取得するメソッドは`getEventById(iCalId)`があるのですが、イベントのIDは手元にないのでここでは使えません。(iCalIdって何ですか？？)
For文を回して`events`からレッスン名に一致するイベントを見つけて、`deleteEvent()`で削除します。![イベント削除.png](https://qiita-image-store.s3.amazonaws.com/0/271701/4911fe72-9a84-f75f-aafc-6eb1652fee7d.png)

確かに消えましたね。

## おわりに
やっぱGAS便利だ。
まだまだプログラミングを始めて日が浅いので、自分が書いたもので自動で何かが行われるという体験に感動します。
これからも色々作ってみたいと思います。

## リファレンス
- [GAS: Class CalendarApp](https://developers.google.com/apps-script/reference/calendar/calendar-app)
- [GAS: Class GmailApp](https://developers.google.com/apps-script/reference/gmail/gmail-app)