Google App Script 自動化

【GAS】DMM英会話 予約情報をカレンダー登録

はじめに

DMM英会話はとても気に入っているのですが、予約情報がGoogleカレンダーと同期できないことだけは気に入りません。レッスン予約したときにメールが来るので、この情報をもとにGoogle App Scriptを使いGoogleカレンダーに自動登録する仕組みを考えました。

Google App Scriptについては以下の記事を参照ください。

ここで紹介する内容は、以下の条件を満たす方が対象となります。

対象読者

  • DMM英会話を契約している
  • DMM英会話からの予約確認メールにGmailを使用している
  • DMM英会話の予約確認メールをGoogleカレンダーに自動登録させたい

では、どのように実装するのかを見ていきましょう。

DMM英会話のレッスン予約情報をカレンダー登録

DMM英会話のレッスン予約情報に、予約時間と予約した先生の情報が含まれるので、この情報をGoogleカレンダーに登録します。

  • Gmailで未処理のDMM英会話のレッスン予約メールを抽出する
  • レッスン予約メール内容を解析する
  • カレンダーのタイトルは予約した先生の名前とする

次より処理内容を解説します。

処理概要

以下のような処理を実装することを考えます。

  • DMM英会話からレッスン予約メールを受信
  • 未処理のメールを抽出
  • レッスン予約日時と予約した先生の情報を抽出する
  • Googleカレンダーに抽出したレッスン予約日時のイベントとして登録する
  • イベントのタイトルは予約した先生の名前とする
  • レッスン終了時間は、開始時間の30分後とする
  • レッスン予約メールに処理済みラベルを付ける

そして、この処理を定期的に行わせます。

未処理メールの抽出

DMM英会話からの予約確認メールで、未処理のメールの抽出は以下の検索文字列で実現します。

メールの抽出条件の構文は、Gmailの抽出条件に設定できる構文と同じです。

// 処理済み後に付けるラベル名
var LABEL = '自動処理済';

// GMail検索文字列(DMM英会話からのレッスン予約/キャンセル情報)
var SEARCH_QUERY = 'is:unread -label:' + LABEL + ' from:noreply@eikaiwa.dmm.com subject:{レッスン予約 レッスンのキャンセル}';

処理済みを識別するために、処理完了後に該当メールのラベルに「自動処理済」を付けます。

そのため、抽出条件は以下の4つとなります。

  • 未読であること(is:unread)
  • 「自動処理済」のラベルが付いていないこと(-label:自動処理済)
  • 楽天市場からのメールであること(from:noreply@eikaiwa.dmm.com)
  • レッスン予約メールが確認できること(subject:{レッスン予約 レッスンのキャンセル})

Gmailへのアクセス方法については以下の記事を参照ください。

イベント内容説明

イベントの詳細を表示させると、以下のようにレッスンページに移動するためのリンクを表示するように登録します。

以下に処理概要とコードの抜粋を示します。

  • メールのプレーンテキストをmessage.getPlainBody()で取得してbodyに格納し、そこから改行で区切った本文をrowsに格納し、その後1行ごとに処理します。
  • メール件名の「レッスン予約」か「レッスンのキャンセル」をの文字列により、予約と予約キャンセルの処理を切り替えます。
  • メール本文の内容から、開始日時(startTime)、先生名(teacher)を取得します。
  • レッスン終了日時(endTime)は、開始日時の30分後とします。
  • レッスンページに移動するためのURLが記載されているので、イベントメモに追加するために取得します。
  • startTime, endTime, teacher, contentsの内容を取得したら、registerCalendar()でカレンダーに登録します。
  • カレンダーに登録後、メールに処理が完了したことを示すラベル「自動処理済」をputLabel()で付けます。

// 検索文字列
var TYPE_RESERVE = 'レッスン予約';
var TYPE_CANCEL = 'レッスンのキャンセル';

var startTime = null;     // レッスン開始時間
var endTime = null;       // レッスン終了時間
var teacher = null;       // 先生名
var tmp = null;
var lesson = null;
var lessonUrl = null;     // レッスンURL

var subject = message.getSubject();   // 件名取得
var body = message.getPlainBody();    // HtmlメールからPlain textの本文を取得する
var rows = body.split('\n');          // 1行ごとに処理するために改行で分割する

// 予約完了メール処理
if(subject.indexOf(TYPE_RESERVE) >= 0){
  for (var i in rows) {
    var row = rows[i];

    if(row.indexOf('とのレッスン予約が完了しました。') >= 0){
      // 開始日時と先生名取得
      // 以下、レッスン情報の記載されたメール内容
      //「xxx様、2021/05/27 22:00のBenzとのレッスン予約が完了しました。レッスン開始の数分前にレッスンに参加してください。」
      var start = row.indexOf('様、');
      var end = row.indexOf('とのレッスン予約が完了しました。');
      tmp = row.substring(start+2, end);
      lesson = tmp.split('の');
      startTime = new Date(lesson[0]);  // 開始時刻取得
      endTime = new Date(startTime.getTime() + 30 * 60000); // 終了時刻取得
      teacher = lesson[1];              // 先生名取得
    }
    else if(row.indexOf('https://eikaiwa.dmm.com/app/lesson-booking') >= 0){
      lessonUrl = row;
    }
  }

  // Googleカレンダーに登録
  registerCalendar(calendar, startTime, endTime, teacher, lessonUrl);

  // 処理済みメールとしてラベルを付ける
  putLabel(thread); 
}

// 予約キャンセルメール処理
else if(subject.indexOf(TYPE_CANCEL) >= 0){
  for (var i in rows) {
    var row = rows[i];

     if(row.indexOf('講師とのレッスンは、キャンセルとさせていただきました。') >= 0){
      // 開始日時と先生名取得
      // 以下、レッスンキャンセル情報の記載されたメール内容
      //「xxx様、2021/05/27 22:00のBenz講師とのレッスンは、キャンセルとさせていただきました。」
      var start = row.indexOf('様、');
      var end = row.indexOf('講師とのレッスンは、キャンセルとさせていただきました。');
      tmp = row.substring(start+2, end);
      lesson = tmp.split('の');
      startTime = new Date(lesson[0]);  // 開始時刻取得
      endTime = new Date(startTime.getTime() + 30 * 60000); // 終了時刻取得
      teacher = lesson[1];              // 先生取得
    }
  }

  // Googleカレンダーから削除
  deleteCalendar(calendar, startTime, endTime, teacher);

  // 処理済みメールとしてラベルを付ける
  putLabel(thread);
}

上記コードは説明のために抜粋したものなので、このままでは動作しません。

コードの全体については、「すべてのコード」を確認ください。

カレンダー登録

カレンダーのタイトルは予約した先生の名前とします。※好みで変えてください。

また、イベントの色は橙色(CalendarApp.EventColor.ORANGE)にしていますが、ここも好きな色を設定してください。

function registerCalendar(calendar, startTime, endTime, teacher, url){
  if (startTime && endTime && teacher && url) {
    var title = teacher + "先生";

    // カレンダーに追加
    if(checkExist(calendar, startTime, endTime, title) == false){
      var options = {
        description : url
      }
      var event = calendar.createEvent(title, startTime, endTime, options);
      event.setColor(CalendarApp.EventColor.ORANGE);
      //event.removeAllReminders();   // 通知は鬱陶しいのでしない
      Logger.log('【登録】\n講師:'+teacher + '\n開始:'+startTime + '\n終了:'+endTime + '\nURL:'+url);
    }
  }  
}

カレンダー削除

予約をキャンセルした際には、カレンダーに登録した情報を削除します。

2021/10/26 予約をキャンセルした際にメールが来なくなったので、仕様が変わったかもしれません。

function deleteCalendar(calendar, startTime, endTime, teacher){
  if (startTime && endTime && teacher) {
    var title = teacher + "先生";

    // カレンダー削除
    if(checkExist(calendar, startTime, endTime, title) == true){

      var events = calendar.getEvents(startTime, endTime);
      for (var i in events) {
        var event = events[i];
        var eventTitle = event.getTitle();
        if(eventTitle.indexOf(title) >= 0){
          event.deleteEvent();
          Logger.log('【削除】\n講師:'+teacher + '\n開始:'+startTime + '\n終了:'+endTime);
        }
      }
    }
  }  
}

登録済み情報チェック

カレンダーに追加する前に、同じ時間の同じタイトルで、既にカレンダーへの登録があるかチェックしています。

そして、同じイベントの登録がない場合に、カレンダーに登録します。

function checkExist(calendar, registStart, registEnd, registTitle){

  // イベントリスト取得
  var events = calendar.getEvents(registStart, registEnd);
  for(const event of events){
    var title = event.getTitle();
    //Logger.log('title:' + title);
    if(title.indexOf(registTitle) >= 0){
      return true;    // イベント登録済み
    }
  }
  // イベント未登録
  return false;
}

この関数では、カレンダーへの登録があるかをチェックし、以下のように値を返します。

  • イベント登録済み:true
  • イベント未登録:false

処理済みラベル

処理済みを識別する方法としては、既読にする方法もあるのですが、それだとメールをチェックした時に気づくことができず見落とす可能性が高くなるため、処理済みメールの識別には「自動処理済」のラベルを付けることにします。

この「自動処理済」のラベルについては、好みで変更することができます。

var LABEL = '自動処理済';

function putLabel(thread){
  var label = GmailApp.getUserLabelByName(LABEL);
  if(label){
    thread.addLabel(label);
    Logger.log('既存ラベル使用');
  }
  else{
    var newlabel = GmailApp.createLabel(LABEL);
    thread.addLabel(newlabel);
    Logger.log('新規ラベル追加');
  }
}

「自動処理済」のラベルがない場合は、新規にラベルを作る仕組みとしています。

実行結果

Googleカレンダーには以下のように登録されます。

イベントをクリックすることで、詳細内容を確認することができます。

詳細内容のリンクをクリックすることで、レッスンページに移動することができます。

全てのコード

/*
 * DMM英会話からのレッスン予約/キャンセルメールを元にGoogle Calendarに登録する
 * 
 * Google Calenderに以下のフォーマットで終日イベントが作成される。
 * 「(教師名)先生」
 * 
 * 2021/09/14 Created by N.Sekiya
 */

// 処理済み後に付けるラベル名(ラベルが存在しなければ自動的に作られる)
var LABEL = '自動処理済';

// GMail検索文字列(DMM英会話からのレッスン予約/キャンセル情報)
var SEARCH_QUERY = 'is:unread -label:' + LABEL + ' from:noreply@eikaiwa.dmm.com subject:{レッスン予約 レッスンのキャンセル}';

// 検索文字列
var TYPE_RESERVE = 'レッスン予約';
var TYPE_CANCEL = 'レッスンのキャンセル';

function registDMMEnglishLessonInformationToCalendar() {

  // デフォルトカレンダーを取得
  var calendar = CalendarApp.getDefaultCalendar();

  // 条件に合うメール検索
  var threads = GmailApp.search(SEARCH_QUERY, 0, 1);
  if (threads.length === 0) {
    Logger.log('メールが見つかりません');
    return;
  }

  for(var k in threads){
    var thread = threads[k]
    var messages = thread.getMessages();
    for (var j in messages){
      var startTime = null;     // レッスン開始時間
      var endTime = null;       // レッスン終了時間
      var teacher = null;       // 先生名
      var tmp = null;
      var lesson = null;
      var lessonUrl = null;     // レッスンURL

      var message = messages[j];
      var subject = message.getSubject();   // 件名取得
      var body = message.getPlainBody();    // HtmlメールからPlain textの本文を取得する
      var rows = body.split('\n');          // 1行ごとに処理するために改行で分割する

      // 予約完了メール処理
      if(subject.indexOf(TYPE_RESERVE) >= 0){
        for (var i in rows) {
          var row = rows[i];

          if(row.indexOf('とのレッスン予約が完了しました。') >= 0){
            // 開始日時と先生名取得
            // 以下、レッスン情報の記載されたメール内容
            //「xxx様、2021/05/27 22:00のBenzとのレッスン予約が完了しました。レッスン開始の数分前にレッスンに参加してください。」
            var start = row.indexOf('様、');
            var end = row.indexOf('とのレッスン予約が完了しました。');
            tmp = row.substring(start+2, end);
            lesson = tmp.split('の');
            startTime = new Date(lesson[0]);  // 開始時刻取得
            endTime = new Date(startTime.getTime() + 30 * 60000); // 終了時刻取得
            teacher = lesson[1];              // 先生名取得
          }
          else if(row.indexOf('https://eikaiwa.dmm.com/app/lesson-booking') >= 0){
            lessonUrl = row;
          }
        }

        // Googleカレンダーに登録
        Logger.log('【メール】\n講師:'+teacher + '\n開始:'+startTime + '\n終了:'+endTime + '\nURL:'+lessonUrl);
        registerCalendar(calendar, startTime, endTime, teacher, lessonUrl);

        // 処理済みメールとしてラベルを付ける
        putLabel(thread); 
      }

      // 予約キャンセルメール処理
      else if(subject.indexOf(TYPE_CANCEL) >= 0){
        for (var i in rows) {
          var row = rows[i];

          if(row.indexOf('講師とのレッスンは、キャンセルとさせていただきました。') >= 0){
            // 開始日時と先生名取得
            // 以下、レッスン情報の記載されたメール内容
            //「xxx様、2021/05/27 22:00のBenz講師とのレッスンは、キャンセルとさせていただきました。」
            var start = row.indexOf('様、');
            var end = row.indexOf('講師とのレッスンは、キャンセルとさせていただきました。');
            tmp = row.substring(start+2, end);
            lesson = tmp.split('の');
            startTime = new Date(lesson[0]);  // 開始時刻取得
            endTime = new Date(startTime.getTime() + 30 * 60000); // 終了時刻取得
            teacher = lesson[1];              // 先生取得
          }
        }

        // Googleカレンダーから削除
        Logger.log('【メール】\n講師:'+teacher + '\n開始:'+startTime + '\n終了:'+endTime + '\nURL:'+lessonUrl);
        deleteCalendar(calendar, startTime, endTime, teacher);

        // 処理済みメールとしてラベルを付ける
        putLabel(thread);
      }
    }
  }
}

/*
 * Google Calendarに登録
 */
function registerCalendar(calendar, startTime, endTime, teacher, url){
  if (startTime && endTime && teacher && url) {
    var title = teacher + "先生";

    // カレンダーに追加
    if(checkExist(calendar, startTime, endTime, title) == false){
      var options = {
        description : url
      }
      var event = calendar.createEvent(title, startTime, endTime, options);
      event.setColor(CalendarApp.EventColor.ORANGE);
      //event.removeAllReminders();   // 通知は鬱陶しいのでしない
      Logger.log('【登録】\n講師:'+teacher + '\n開始:'+startTime + '\n終了:'+endTime + '\nURL:'+url);
    }
  }  
}

/*
 * Google Calendarから削除
 */
function deleteCalendar(calendar, startTime, endTime, teacher){
  if (startTime && endTime && teacher) {
    var title = teacher + "先生";

    // カレンダー削除
    if(checkExist(calendar, startTime, endTime, title) == true){

      var events = calendar.getEvents(startTime, endTime);
      for (var i in events) {
        var event = events[i];
        var eventTitle = event.getTitle();
        if(eventTitle.indexOf(title) >= 0){
          event.deleteEvent();
          Logger.log('【削除】\n講師:'+teacher + '\n開始:'+startTime + '\n終了:'+endTime);
        }
      }
    }
  }  
}
/*
 * 登録済み情報があるかチェック
 */
function checkExist(calendar, registStart, registEnd, registTitle){

  // イベントリスト取得
  var events = calendar.getEvents(registStart, registEnd);
  for(const event of events){
    var title = event.getTitle();
    //Logger.log('title:' + title);
    if(title.indexOf(registTitle) >= 0){
      return true;    // イベント登録済み
    }
  }
  // イベント未登録
  return false;
}

/*
 * 処理済みとしてスレッドにラベルを付ける
 * LABELで指定されるラベルが存在しない場合は作成する
 */
function putLabel(thread){
  var label = GmailApp.getUserLabelByName(LABEL);
  if(label){
    thread.addLabel(label);
    Logger.log('既存ラベル使用');
  }
  else{
    var newlabel = GmailApp.createLabel(LABEL);
    thread.addLabel(newlabel);
    Logger.log('新規ラベル追加');
  }
}

繰返し実行

正しく動作することを確認したら、繰り返し実行できるよう設定します。

繰り返し実行には、トリガーの設定を行います。設定方法については、以下の記事を参照ください。

繰り返し実行する間隔は、急ぐ必要のない処理なので5分おきにします。

最後に

関谷さんの英会話力はDMM英会話によるところが大きいです。2016年の頃は月謝が5,000円以下だったので、お手軽と思いましたが、徐々に上がっていき、現在は6,480円となってしまいました。しかし、毎日都合の良い時間に25分間お話しできるので、とても便利に使わせてもらっています。iKnow!という単語練習アプリも無料で使えるようになるので、英会話レッスンと合わせて使うことにより、英会話を身に着けることができるようになります。

関谷さん的にはとても気に入っているので、ここで宣伝しておきます。

興味がわいたら、体験レッスンが2回できますのでお試しあれ。

では、今日も良い一日を。

-Google App Script, 自動化
-, , , , ,