1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Gmailで届いた会議、研究会のスケジュールを、メール転送するだけでGoogleカレンダーに登録する

Last updated at Posted at 2024-02-10

Gmailで届いた会議、研究会のスケジュールを、メール転送するだけでGoogleカレンダーに登録する

1. やることのポイント

特定のサブアドレスに届いたメールをGASで処理

AIを用いた日時の判定

Googleカレンダーに、スケジュール日時、メールの件名、Gmailへのリンクを登録

Qiita_メールからカレンダーへの自動登録.png

※ この記事は、「仕事の依頼メールを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を調べておく。
Qiita_メールからカレンダーへの自動登録_Sheet.png

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が使えると、応用範囲が広い。他にもいろいろできそう。
1
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?