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カレンダーの予定を自動登録するスクリプトを紹介します。
処理の流れ
プログラムの処理の流れはおよそこのようなかんじです。
- Gmailの直近7日間の未読メールから、送信元が「noreply@eikaiwa.dmm.com」で件名が「レッスン予約」のメールを探す
- 2019年9月頃にメールの仕様が変わりました。以前は件名「【DMM英会話】レッスン予約完了のお知らせ」というメールでした。
- メール本文から先生の名前とレッスン開始日時を抽出する
- Googleカレンダー「english_lesson」に、新規イベントとしてレッスン予定を登録する
- メールを既読にする
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 push
とclasp open
を実行し、スクリプトエディタから関数main
を実行してみましょう。
実行前に、条件に該当するメールがあることを確認してくださいね。
Gmailの直近7日間の未読メールから、件名が「【DMM英会話】レッスン予約完了のお知らせ」のメールを探す
実行時、以下のダイアログが表示された場合は許可を与えましょう。
処理が修了すると、カレンダーに予定が登録されているはずです。
トリガーの設定
このスクリプトが定期的に実行されるよう、トリガーを設定しておきましょう。
スクリプトエディタの「編集」メニューから「現在のプロジェクトのトリガー」を確認・設定することができます。
「新しいトリガーを作成」からトリガーを作成しましょう。
対応していないこと
とりあえず予定を登録できるところまでは実装していますが、例えば次のようなことは考慮していません。必要に応じて処理を追加・変更してみてください。
- レッスンキャンセルに連動してGoogleカレンダーの予定を削除
- レッスン画面へのリンクをGoogleカレンダーの予定に載せる
- 先生のSkype名をGoogleカレンダーの予定に載せる
- 【補足】2019年に入り、DMM英会話ではSkypeを使わずブラウザ上でレッスンを行う新システムが運用開始となっています
まとめ
- GAS (Google Apps Script) の作成・サーバーへの反映は、
clasp
を使ってCLIで - TypeScriptでの開発も
clasp
を使えば面倒な設定は不要 - GASを活用するとGmailやGoogleカレンダーを操作してタスクを自動化できる。しかも少ないコードで
コメント