Anycaの予約リクエストをGoogleカレンダーに自動登録する

# Anycaって何
DeNA SOMPO Mobilityが運営している個人間で車をシェアするカーシェアサービスです。
https://anyca.net/

興味のある人は下の招待URLから登録してくれると嬉しい。
https://anyca.net/invite?uid=103674&cpn_id=13&code=s660share

# 誰に宛てた記事か
こんな風に思っているオーナーさんに向けた記事です。

「予約状況を確認する時、**アプリでしか確認出来ないのがめんどくさい**」
「どうせなら**Googleカレンダーで他のスケジュールと一緒に管理したい**」

# 使用前提条件
Excelが使える人ならそんなに難しくはないです。

- Anycaからのお知らせをGmailで受け取っている
- Googleスプレッドシートの使い方を知ってるor調べられる
- GoogleスプレッドシートにGoogle Apps Scriptを埋め込む方法を知ってるor調べられる
- GoogleカレンダーのIDを知ってるor調べらえる

# Anycaのシェア予約の仕組み
CtoCのサービスなので、ユーザーとしてはオーナーとドライバーが存在します。
予約の流れとして、まずドライバー側がAnycaアプリ上でオーナーに対し予約リクエストをします。
<img width="250" alt="IMG_8026.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514873/af639ad8-08e7-6023-d41b-c1d2a6b88969.png"> <img width="250" alt="IMG_8027.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514873/7688bcad-3dc1-2fca-61b2-21e265294675.png">
するとオーナー側にはアプリでの通知とメールでお知らせが届きます。
<img width="350" alt="IMG_8048.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514873/ca1b498b-d25f-78c3-c388-953d133072a6.jpeg">
<img width="250" alt="IMG_8051.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514873/1bf11688-b065-3051-0f9f-bd5f7f20d693.jpeg">
今回はこのメールを自動で拾い上げる事で予約情報を取得し、Googleカレンダーに登録していくようにします。

GmailでAnycaから予約通知メールを受信
↓
スプレッドシートに仕込んだスクリプトで自動検出
↓
スプレッドシートに予約情報書き出し
↓
書き出された情報を基にGoogleカレンダーに追加

って感じです。

#スプレッドシートの下準備
一行目に下記の通りラベル付けをしておきます。※しなくても動きます

|メール受信日時|タイトル|ドライバー名|車名|予約開始|予約終了|カレンダー追加|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|   |   |   |   |   |   |   |
|   |   |   |   |   |   |   |
|   |   |   |   |   |   |   |
![spreadsheet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514873/188aea9c-e102-5b48-d28e-ac4189adc2e3.png)

# コードをドン
Anycaオーナーさんの役に立てば良いな、という記事なので詳しい解説は気が向いた時に加えるとして、コピペで使えるコードをさっさと貼ります。
コードはGmailから予約通知メールを抽出する関数と、スプレッドシートに記録されたデータからGoogleカレンダーに登録する関数の2つに分かれます。
`// ----- ここから書き換え -----`から`// ----- ここまで書き換え -----`までの部分は、各環境に合わせて書き換えて下さい。

```javascript:予約リクエスト情報取得.gs
function mailToSheet () {
  // ----- ここから書き換え -----
  // メールから取得した情報を書き出すスプレッドシートのURL
  var sheet_url = 'https://docs.google.com/spreadsheets/d/hogeeeeeeeeeeeeeee/edit';
  // 書き込むシート名
  var sheet_name = 'シート1';
  // Gmailの抽出条件
  var mail_query = 'subject:【Anyca】予約リクエストが届きました';
  // 存在チェック(既にシートに書き込み済のメールは処理しない(日付、件名の一致で判定))
  var existence_check = true;
  // ----- ここまで書き換え -----

  var ss = SpreadsheetApp.openByUrl(sheet_url);
  var sheet = ss.getSheetByName(sheet_name);
  var existence_keys = fetchExistenceKeys();
  var mail_data = fetchMailData();
  for (var i = 0; item = mail_data[i]; i++) {
    // 重複チェック 既存データと合致したらループ抜ける
    if (existence_check && existsData(item)) {
      continue;
    }
    // 重複なければ最終行に値を追加
    sheet.appendRow(itemToRow(item));
  }
  
  function fetchExistenceKeys () {
    var existence_keys = {};
    var sheet_data = sheet.getDataRange().getValues();
    for (var i = 0; row = sheet_data[i]; i++) {
      existence_keys[generateKey(rowToItem(row))] = true;
    }
    return existence_keys;
  }

  function existsData (item) {
    if (existence_keys[generateKey(item)]) {
      return true;
    }
    return false;
  }

  function generateKey (item) {
    var key = item['date'] + '_' + item['subject'];
    // Logger.log(key);
    return key;
  }

  function rowToItem (row) {
    var item = {};
    item['date'] = row[0];
    item['subject'] = row[1];
    item['driver'] = row[2];
    item['car'] = row[3];
    item['start'] = row[4];
    item['end'] = row[5];
    return item;
  }

  function itemToRow (item) {
    var row = [];
    row[0] = item['date'];
    row[1] = item['subject'];
    row[2] = item['driver'];
    row[3] = item['car'];
    row[4] = item['start'];
    row[5] = item['end'];
    return row;
  }

  function fetchMailData () {
    var result = [];
    var threads = GmailApp.search(mail_query);
    for (var i = 0; it = threads[i]; i++) {
      var messages = it.getMessages();
      for (var j = 0; message = messages[j]; j++) {
        var item = {};
        item['date'] = message.getDate();
        item['subject'] = message.getSubject();
        var body = message.getPlainBody();
        // 必要な行から余計な文字列を消去
        var bodys= body.replace('ドライバーニックネーム：','').replace('カーシェア期間：','').replace('に予約リクエストが届きました。','');
        // 改行を使って配列化する
        var ary = bodys.split("\n");
        // ドライバーの行を取得し文字を抽出
        item['driver'] = ary[5].substring(0).replace('\r','').replace('<br>','');
        // 車種の行を取得し文字を抽出
        ary[1] = ary[1].slice(ary[1].indexOf("さんのクルマ、")+7);
        item['car'] = ary[1].substring(0).replace('\r','').replace('<br>','');
        // 予約時間の行を取得し開始時間/終了時間に分けて配列として格納
        var reservation = ary[6].substring(0).replace('\r','').replace('<br>','').split("〜");
        // 日時の書式変換をして格納
        item['start'] = convertDateFormat(reservation[0]);
        item['end'] = convertDateFormat(reservation[1]);
        
        result.push(item);
      }
    }
    return result;
  }
  function convertDateFormat (date) {
    // 2018年08月11日(土)09:00　→　2018/08/11/09:00に書式変換して返すメソッド
    // "年月日"を"/"に置換
    date = date.replace("年","/").replace("月","/").replace("日","/");
    // 曜日の括弧書き部分だけ除いて結合(前半11文字と後半5文字を合体)
    var convertedDate = date.slice(0,11)+date.slice(-5);
    return convertedDate;
  }
}
```

```javascript:カレンダー追加.gs
function createEventFromSheet() {
  // ----- ここから書き換え -----
  var calendarId = "hugaaaaaaaaaaaaaaaa@group.calendar.google.com"
  // ----- ここまで書き換え -----
  var sheet, i, myevent, mystart, myend, added, driver, car;
  sheet = SpreadsheetApp.getActiveSheet();
  for(i = 1; i <= sheet.getLastRow(); i++) {
    car = sheet.getRange(i, 4).getValue();
    driver = sheet.getRange(i, 3).getValue();
    myevent = "シェア予約"+"("+car+"/"+driver+")";
    mystart = sheet.getRange(i, 5)
      .getValue();
    myend = sheet.getRange(i, 6)
      .getValue();
    added = sheet.getRange(i, 7).getValue();
    if(added == "") {
      thisevent = CalendarApp.getCalendarById(calendarId).createEvent(myevent, new Date(mystart), new Date(myend));
      sheet.getRange(i, 7).setValue("追加済み");
    }
  }
}
```

#関数のトリガー
関数のトリガーは時間ベースで15分間隔とかで設定してください。
![Anyca_trigger_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514873/df093b4d-9de5-ba9d-5664-86c37455018d.png)
`mailToSheet`と`createEventFromSheet`の2種類の関数があるので、忘れずどちらもトリガー設定しましょう。
![Anyca_trigger.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514873/2ee041a3-a319-4488-478f-7335436e1863.png)

# 注意点
- メールの内容に変更があるとちゃんと動きません。(特にタイトル)
- 変更があった際はアップデートし、たい、と思ってる。(するとは言ってない)
- 変更しないようAnycaさんの方に向かって祈りましょう。

__2022/5/13追記__
Anycaからのメールを転送していて、転送先でこのスクリプトを使おうとすると動きません。
原因は転送されたメールは各項目(ドライバー名や日時)の書かれている位置(行)がズレるためです。
どうしても転送先で動かしたい時は良い感じにコードを書き換えて下さい…。

# 更にもう一工夫
私は[IFTTT](https://ifttt.com)を使ってGoogleカレンダーに新規イベントが追加されると、[LINE Notify](https://notify-bot.line.me/ja/)で家族のグループLINEに通知されるようにしています。
![IMG_5175.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514873/d007b481-aec6-de0c-ea4a-5a5444f3b269.jpeg)
これだと毎回家族に「この日シェアあるから！」って言わなくて済むので楽ちんです。
ちなみにIFTTTを経由しなくても、GASで直接LINE Notifyで通知させる事も出来るみたいです。その辺はお好みで。
⇒[Google Apps ScriptからLINE NotifyでLINEにメッセージを送る](https://qiita.com/tadaken3/items/5f916a12587e42ece814)

あとは、Anycaと同じように予約情報はメールで来るけど、Googleカレンダーに自動登録したいよ～～～！！みたいなのがある場合、このスクリプトをベースに弄れば実現出来ます。
私の場合、同じ仕組みでオンライン英会話の予約管理もやってますね。
色々工夫して使ってみて下さい。Let's Anyca!!

# 参考記事
Gmailのメールをスプレッドシートに書き出すコードは下記記事を参考にさせて頂きました。
⇒[スプレッドシートにGmailの内容を自動で書き込む](https://qiita.com/naoqoo2/items/e7c4d1081276459fcb5f)