Gmailで届いた会議、研究会のスケジュールを、メール転送するだけでGoogleカレンダーに登録する
1. やることのポイント
特定のサブアドレスに届いたメールをGASで処理
AIを用いた日時の判定
Googleカレンダーに、スケジュール日時、メールの件名、Gmailへのリンクを登録
※ この記事は、「仕事の依頼メールをGoogle ToDoリストへ自動登録」と同じようなことを、GooleTasks(Google ToDoリスト) ではなく、Googleカレンダーに対して行うものです。
2. 背景
- スケジュールを忘れがち
研究会とか会議とかの通知はメールで届く。しかし、仕事に集中していると、それらの開始時刻はだいたい思い出せない。うっかり欠席してしまう。 - Googleカレンダーに登録するにしても通知のメールの内容を全部コピペできない
Googleカレンダーに登録するとき、日時、場所など、いちいち入力するのは致し方ない(かも)。ただ、カレンダーには、メールの内容が全て登録できるわけではないことは致命的だ。メールに添付ファイルがある時点でアウト。だから、メールのままアクセスできるといい。ところが、Gmailでは、特定のメールを探そうとしてもうまくいかないことが多い。すぐには見つからないのはストレスだ。 - 対策案
そこで、メールをAIに判定させて、自動的に情報を引き出して、カレンダーに登録することを考えた。特定のサブアドレスにメールを送ると、OpenAIの(ChatGPTの)API を使って文面から時刻を含む日程を抽出、それを使ってGoogleカレンダーに登録させる。これをGASにやらせる。GASは、1分ごとに起動する。登録内容には、メールへのリンクが仕込んであるので、カレンダーからすぐにメールにアクセスできる!
登録されたカレンダーは、Amazon Alexa に喋らせる。
これで解決!(となるといいな)。
3. 準備
これらは、ToDoリストに登録するときに書いたこととほぼ同じ。
3-1. OpenAIのAPI
- 機密保持:OpenAI のAPIでは、機密は保たれることになっている
- 登録:OpenAIのAPIは、ChatGPTとは別に課金されるので、チャージしておく
- モデル:gpt-3.5-turbo を使う。
3-2. メールのサブアドレス
今回は、hoge@example.com に対して、hoge+cal@example.com を使う。このアドレスに届いたメールだけを処理する。
3-3. Googleスプレッドシート
あらかじめ、Googleスプレッドシートを作っておき、データが蓄積できるようにしておく。1行目まで入力しておく。IDを調べておく。
4. プログラム
4-1. メールから情報を抽出してスプレッドシートに書き込む
スタンドアロンのGASを毎分起動して、特定のサブアドレスに届いたメールから情報を抽出して、スプレッドシートに書き込む。時刻の読み取りはAIにやらせる。ポイントは、だいたい次の通り。
- ChatGPT3 は現実世界の今日の日付を答えられないので、メールのヘッダから日付を抽出して与える。
- Gmailへのリンクは、RFC822の Message ID を利用。送信者・同報で送られた人も同じURLで利用できる。
- GASはスタンドアロンで、トリガーで毎分起動するように設定する。
- ToDoリストへ自動登録でアイデアだけ書いた「転送されたメールなら、スレッドの一つ前のメールを処理する」を実現している。
メールを受信したら件名とスケジュールを書き出す
// サブアドレスに送られてきた仕事依頼のメールの以下の情報をスプレッドシートに書き出す。
// ・件名
// ・会合などの予定
// ・メッセージIDに基づくGmailの検索URL
// Ver.01 : 2024-02-08
// 左側の 「プロジェクトの設定」(歯車マーク)> スクリプト プロパティ で、次の2つを設定、入力する
// 1) スプレッドシートのID : SID
// 2) OpenAI の APIキー : ChatGPTAPI
// 3) あなたのメールアドレス: EMailAddress
// 関数本体
function processEmails() {
var spreadsheetId = PropertiesService.getScriptProperties().getProperty('SID'); // スクリプトプロパティによるシートのID
var subaddress = PropertiesService.getScriptProperties().getProperty('EMailAddress'); // スクリプトプロパティによるメールアドレス
subaddress = subaddress.replace('@', '+cal@'); // メールのサブアドレス hoge+cal@foo.com を設定。
var sheet = SpreadsheetApp.openById(spreadsheetId).getActiveSheet();
const ProcessedLabel = 'CalProcessed'; // 処理済みとわかるためのラベル名。任意。
var processedLabel = GmailApp.getUserLabelByName(ProcessedLabel);
if (!processedLabel) { // ラベルが存在しない場合は作成
processedLabel = GmailApp.createLabel(ProcessedLabel);
}
var query = 'to:' + subaddress + ' -in:draft -label:' + ProcessedLabel; // 未送信(下書き)、処理済ラベルがついたものは対象としない。
var threads = GmailApp.search(query);
for (var i = 0; i < threads.length; i++) {
var messages = threads[i].getMessages();
var messageIndex = messages.length - 1; // デフォルトではスレッドの最後のメッセージを使用
// スレッド内の最後のメッセージが 'Fwd:' で始まるかチェック
if (messages[messageIndex].getSubject().startsWith('Fwd:') && messages.length > 1) {
messageIndex--; // 'Fwd:' で始まる場合、一つ前のメッセージを使用
}
var message = messages[messageIndex]; // 対象のメッセージを取得
if (!message.isInTrash()) {
// メールから情報取得
var subject = message.getSubject();
var dateHeader = message.getDate();
var mailurl = genMailURL(message);
// AI で期限を取得
var dateHour = getDeadLineWithOpenAI(message.getPlainBody().slice(0,999), dateHeader);
var dateHourStart= dateHour.slice(6,24);
var dateHourEnd = dateHour.slice(30,46)
// スプレッドシートに追記(4列のデータ)
sheet.appendRow([
new Date(), // 登録日時
'', // シートのコンテナバインドGASの処理状況欄。最初は空欄。
dateHourStart, // 開始日時
dateHourEnd, // 終了日時
subject, // メールの件名
mailurl]); // メールのスレッドへのURL(検索)
threads[i].addLabel(processedLabel); // 処理済ラベルを付ける
}
}
}
// OpenAI APIを使用してメールから期限・期日を抽出する関数
// AI は、「今日」を認識しないので、メールから取り出したメールの日付を活用する。
function getDeadLineWithOpenAI(emailBody, emailDate) {
const apiKey = PropertiesService.getScriptProperties().getProperty('ChatGPTAPI'); // スクリプトプロパティによるAPIキー
const apiUrl = 'https://api.openai.com/v1/chat/completions'; //ChatGPTのAPIのエンドポイントを設定
//ChatGPTに投げるメッセージを定義(ユーザーロールの投稿文のみ)
const messages = [{'role': 'user', 'content': 'メールの内容を分析し、会合、研究会、会議などの開始時刻と終了時刻を、2つの19バイトの文字列を空白を挟んで連結して答えなさい。具体的には、"Start=yyyy-MM-dd HH:MM:SS End=yyyy-MM-dd HH:MM:SS" の形式で答えよ。答えは、1バイト文字だけ使い、39バイト分だけで答え、日本語を含まない。数字記号以外を含まない。理由などの記述も省く。終了時刻がわからない場合には、開始時刻の2時間後とする。\
なお、メールの書かれた日時は、' + emailDate + 'である。\
また、メールの内容は次のとおりである。:\
' + emailBody + ' \
'}];
//OpenAIのAPIリクエストに必要なヘッダー情報を設定
const headers = {
'Authorization':'Bearer '+ apiKey,
'Content-type': 'application/json',
'X-Slack-No-Retry': 1
};
//ChatGPTモデルやトークン上限、プロンプトをオプションに設定
const options = {
'muteHttpExceptions' : true,
'headers': headers,
'method': 'POST',
'payload': JSON.stringify({
'model': 'gpt-3.5-turbo',
'max_tokens' : 2048,
'temperature' : 0.0,
'messages': messages})
};
//OpenAIのChatGPTにAPIリクエストを送り、結果を変数に格納
const response = JSON.parse(UrlFetchApp.fetch(apiUrl, options).getContentText());
Logger.log(response);
//ChatGPTのAPIレスポンスをログ出力
Logger.log(response.choices[0].message.content);
return response.choices[0].message.content; // メールから取り出した期限・期日
}
// メールへのURL生成
//
function genMailURL(message) {
const urlHeader = 'https://mail.google.com/mail/#search/in%3Aanywhere+';
return urlHeader + encodeURIComponent('rfc822msgid:'+message.getHeader('Message-ID'));
}
4-2. スプレッドシートからカレンダーに登録
スプレッドシートのコンテナバインドのGASを書く。
- カレンダーIDは、予め取得しておいて、スクリプトプロマティに仕込んでおく。
- カレンダーIDは、特に新たなカレンダーを作っていなければ、Googleのメールアドレスになる(要確認)。
- 他人のカレンダーの場合には、きちんと書き込める許可があることを確認する。
function addEventFromSheet() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var rows = sheet.getDataRange().getValues(); // シートの全データを取得
var calendarId = PropertiesService.getScriptProperties().getProperty('calID');
var calendar = CalendarApp.getCalendarById(calendarId);
// 2行目からデータを読み込む(1行目はヘッダーと仮定)
for (var i = 1; i < rows.length; i++) {
var row = rows[i];
var status = row[1]; // 状態をチェックする列
if (status !== '') continue; // 既に処理された行はスキップ
var startTime = new Date(row[2]); // 開始時刻
var endTime = new Date(row[3]); // 終了時刻
var title = row[4]; // 件名
var description = row[5]; // 説明(Message-ID による URL)
// カレンダーにイベントを追加
var event = calendar.createEvent(title, startTime, endTime, {description: description});
// 処理した行にステータスを更新(例:'登録済み')
sheet.getRange(i + 1, 2).setValue('登録済み');
Logger.log('Event created: ' + title);
}
}
5. 活用・応用
- メールを転送してすぐに登録されるわけではない。2つのGASは、それぞれ毎分起動するので、2分程度を見込む必要がある。
- 共用のカレンダーにしておくと、利便性が高まるかもしれない。(みんなが勝手に登録して、同じイベントが複数登録されてしまうこともありうる。)
- 前も書いたが、料金は安い。秘書さんに予定を管理してもらうことを考えると、利便性は高いと思う。
- 間違いをしそうなので、送り込む文面の作り込みとか、まだまだ工夫の余地はありそう。
- APIでAIが使えると、応用範囲が広い。他にもいろいろできそう。