【TypeScript】【2019年9月更新】 DMM英会話の予約をGoogleカレンダーに自動登録するスクリプト

プログラミング
この記事は約11分で読めます。

DMM英会話のレッスンを続けています。今のところレッスンやサービス内容に特に不満は無く楽しく学習できているのですが、唯一不満があるとすればGoogleカレンダーなどのカレンダーアプリとの連携が無いことでしょうか。
だいたいいつも同じ時間帯にレッスンを予約しているとはいえ、空いている先生の都合などで普段とは違う時間帯に予約を入れることもあるわけです。
DMM英会話のサイトにログインすれば直近の予定はすぐ確認できますが、それも面倒です。
ですので、結局予約完了メールを見ながら手作業でGoogleカレンダーに登録することになります。面倒です。

というわけで、これを解消するためのツールを用意し、Googleカレンダーへのレッスン予定登録を自動化しましたので、紹介します。
なお、ツールはGAS (Google Apps Script)です。仕事でGASを書くときはTypeScriptで書いているので、今回もそうしました。

前提

  • DMMアカウントのメールアドレスにGmailアドレスを設定していること
    • 別のメールアドレスからGmailに転送設定していても大丈夫です
  • Node.js、yarnを使用できること
    • node: 10.10.0
    • yarn: 1.16.0
      • npmを使う場合はコマンドを適宜読み替えてください

プロジェクトのセットアップ

dmm-eikaiwa-calendarという名前のプロジェクトを作成し、依存ライブラリをセットアップします。
今回は、ローカル環境でGAS (Google Apps Script) を開発できるCLIツール claspを使います。
なお、最近のバージョンではデフォルトでTypeScriptをサポートしています。
トランスパイラの設定などが必要無くなり、セットアップがすごく簡単になりました。

$ mkdir dmm-eikaiwa-calendar                                                                                                                        
$ cd dmm-eikaiwa-calendar                                          
$ yarn init -y
$ yarn add @google/clasp tslint --dev
$ yarn add @types/google-apps-script
$ yarn run tslint --init

認証

claspでGoogleアカウントにアクセスできるよう、まずは認証を行います。
yarn経由でコマンドを実行します。

$ yarn run clasp login

ブラウザが開き、claspでのアカウントアクセスを許可する画面が表示されます。

許可すると、以下のメッセージが表示されますので、ウインドウを閉じて終了です。

Logged in! You may close this page.

なおこの認証情報は ~/.slasprc.jsonに書き込まれていますので、これ以降、毎回ログインする必要はありません。

スクリプト作成

それではスクリプトを作成していきましょう。
clasp createでスクリプトの種類を選びます。今回は単体で動作させるスクリプトなので、standaloneを選択しました。

$ yarn run clasp create dmm-eikaiwa-calendar                                                                       
yarn run v1.16.0
? Clone which script? (Use arrow keys)
❯ standalone
  docs
  sheets
  slides
  forms
  webapp
  api

Google Apps Script APIが無効になっていると、次のようなエラーが発生します。

Error: Permission denied. Enable the Apps Script API:
https://script.google.com/home/usersettings

その場合は以下のサイトを参考に設定してみてください。

動作確認

まずは簡単なコードで、claspでのコード反映・実行が正しく行われるかを確認します。
hello.tsを新規作成し、次の処理を書きましょう。

const greeter = (person: string) => {
  return `Hello, ${person}!`;
};

function testGreeter() {
  const user = "Grant";
  Logger.log(greeter(user));
}

ちなみにこれは公式サイトに書いてあるコードそのままです。

.claspignoreの準備

GASのサーバーApps Script serverにコードを反映するためにclasp pushコマンドを使います。
ただこのコマンドはカレントディレクトリにあるファイルをすべてサーバーにアップロードしようとするため、今のままだと、node_modulesのような不要なファイルやディレクトリまでアップロードしてしまいます。

これを避けるため.claspignoreというファイルに次の内容を記述しておきましょう。こうすると、TypeScriptファイルとスクリプトの設定情報が定義されたappsscript.jsonファイルだけがアップロードされるようになります。

**/**
!*.ts
!appsscript.json

コードの反映

clasp pushでコードをサーバーに反映します。

$ yarn clasp push

コードの確認

clasp openで、反映したコードをブラウザ上で確認できます

$ yarn clasp open

実行

ブラウザで表示したスクリプトエディタのメニューから「実行」を選択し、関数testGreeterを実行しましょう。

実行後、メニューの「表示」からログの出力内容を確認します。
次のように出力されていれば成功です。

ソース

それでは、最後にDMM英会話の予約メールからGoogleカレンダーの予定を自動登録するスクリプトを紹介します。

処理の流れ

プログラムの処理の流れはおよそこのようなかんじです。

  1. Gmailの直近7日間の未読メールから、送信元が「noreply@eikaiwa.dmm.com」で件名が「レッスン予約」のメールを探す
    • 2019年9月頃にメールの仕様が変わりました。以前は件名「【DMM英会話】レッスン予約完了のお知らせ」というメールでした。
  2. メール本文から先生の名前とレッスン開始日時を抽出する
  3. Googleカレンダー「english_lesson」に、新規イベントとしてレッスン予定を登録する
  4. メールを既読にする

code.ts

関数mainがエントリーポイントです。

import Calendar = GoogleAppsScript.Calendar.Calendar;
import GmailThread = GoogleAppsScript.Gmail.GmailThread;
import GmailMessage = GoogleAppsScript.Gmail.GmailMessage;

const CALENDAR_NAME = "english_lesson";
const QUERY = 
  `newer_than:7d is:unread from:noreply@eikaiwa.dmm.com subject:"レッスン予約"`;

interface ICalendarEvent {
  date: string;
  time: string;
  name: string;
}

function main() {
  const results =
    threads()
      .map(messages)
      .map(calendarEvents)
      .map(register);

  if (results.every(isSucceededAll)) {
    threads()
      .map(read);
  } else {
    Logger.log("カレンダー登録に失敗しました");
  }
}

function threads(): GmailThread[] {
  return GmailApp.search(QUERY);
}

function messages(thread: GmailThread): GmailMessage[] {
  return thread.getMessages();
}

function calendarEvents(gmailMessages: GmailMessage[]): ICalendarEvent[] {
  return gmailMessages.map((message) => {
    const e = {
      date: "",
      name: "",
      time: "",
    };
    const body = message.getBody();
    const body = message.getBody();
    const match = body.match(/(\d{4})\/(\d{1,2})\/(\d{1,2}) (\d{1,2}):(\d{1,2})の(.+)とのレッスン予約が完了しました。/);
    if (match !== null && match.length === 7) {
      e.name = `DMM英会話 ${match[6]} 先生`;
      e.date = `${match[1]}-${match[2]}-${match[3]}`;
      e.time = `${match[4]}:${match[5]}:00`;
    }
    Logger.log(`${e.name}, ${e.date}, ${e.time}`);
    return e;
  });
}

function register(events: ICalendarEvent[]): boolean[] {
  const result =
    calendars()
      .map((calendar) => {
        return events.map((e) => {
          const startTime = new Date(`${e.date}T${e.time}`);
          const endTime = new Date(startTime.getTime());
          endTime.setMinutes(endTime.getMinutes() + 25);

          Logger.log(`${e.name} (${startTime} - ${endTime})`);
          calendar.createEvent(e.name, startTime, endTime);
          return true;
        });
      });
  return [].concat.apply([], result);
}

function calendars(): Calendar[] {
  const result = CalendarApp.getCalendarsByName(CALENDAR_NAME);
  if (result.length === 0) {
    result.push(CalendarApp.createCalendar(CALENDAR_NAME));
  }
  return result;
}

function isSucceededAll(results: boolean[]): boolean {
  return results.every((n) => n);
}

function read(thread: GmailThread) {
  thread.markRead();
}

appsscript.json

正しい日時でカレンダーに予定が登録されるよう、appsscript.jsonに設定の追加/変更が必要です。
スクリプト実行時のタイムゾーンを、Asia/Tokyoにしておきましょう。
もしtimeZoneの設定値がなければ追加してください。

{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
  },
  "exceptionLogging": "STACKDRIVER"
}

実行

サーバーへの反映・実行は動作確認のときと同じ流れです。
clasp pushclasp openを実行し、スクリプトエディタから関数mainを実行してみましょう。

実行前に、条件に該当するメールがあることを確認してくださいね。

Gmailの直近7日間の未読メールから、件名が「【DMM英会話】レッスン予約完了のお知らせ」のメールを探す

実行時、以下のダイアログが表示された場合は許可を与えましょう。

処理が修了すると、カレンダーに予定が登録されているはずです。

トリガーの設定

このスクリプトが定期的に実行されるよう、トリガーを設定しておきましょう。
スクリプトエディタの「編集」メニューから「現在のプロジェクトのトリガー」を確認・設定することができます。

「新しいトリガーを作成」からトリガーを作成しましょう。

対応していないこと

とりあえず予定を登録できるところまでは実装していますが、例えば次のようなことは考慮していません。必要に応じて処理を追加・変更してみてください。

  • レッスンキャンセルに連動してGoogleカレンダーの予定を削除
  • レッスン画面へのリンクをGoogleカレンダーの予定に載せる
  • 先生のSkype名をGoogleカレンダーの予定に載せる
    • 【補足】2019年に入り、DMM英会話ではSkypeを使わずブラウザ上でレッスンを行う新システムが運用開始となっています

まとめ

  • GAS (Google Apps Script) の作成・サーバーへの反映は、claspを使ってCLIで
  • TypeScriptでの開発もclaspを使えば面倒な設定は不要
  • GASを活用するとGmailやGoogleカレンダーを操作してタスクを自動化できる。しかも少ないコードで

コメント

タイトルとURLをコピーしました