LoginSignup
1
0

仕事の依頼メールをAIを使ってGoogle TODOリストへ自動登録する

Last updated at Posted at 2024-02-03

仕事の依頼メールをGoogle ToDoリストへ自動登録

1. やることのポイント

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

AIを使った期限の読み取り

Google ToDoリストに、上記の期限、メールの件名、メールへのリンクを登録

Qiita_メールからToDoリストへの自動処理.png

2. 背景(ぼやき)

  • 現状認識
     私立大学の教員は、常に、10ぐらいの仕事が同時に走っている。週に5〜6コマの授業が走っているだけでなく、ゼミ対応、卒研対応、経費精算、委員会の仕事、もし引き受けていれば外部の仕事、…。そのような状況で、期限を切られて仕事が新規に舞い込んでくるのは、結構なストレスだ。(読み直して「研究」が抜けていることに、あとから気づいた。)
     しかも悪いことに、仕事の依頼はGmailで来る。もともとメールは、お手紙のようなコミュニケーション手段であったはずが、いつの間にか、情報収集手段、業務依頼の窓口になっている。コロナでそれに拍車がかかった。本来別の目的の連絡が全部、メールで届く。その上、最悪なことに、Gmailは「あいまい検索」になっていて、ちょっと間違って検索しても表示される=余計なメールがいっぱい表示される仕様になっている。困った。
  • 対策案
    AIとGASを組み合わせて、ToDoリストへの自動登録を試みる。ToDoには RFC822 のMessage-ID を使ったメールへのリンクを仕込む。これによって、期日の書き間違い、メールと何かを同時に開く面倒、メールを再び検索する面倒、他の人と「どのメールか」を伝える面倒を回避する。

3. 準備

3-1. Open AIのAPI

  • 機密保持
    OpenAIのAPIを使った場合には、データは学習には利用されない。もちろん、情報漏洩のリスクがないわけではないが、メールの処理をするには相対的には良い。そこで、OpenAIのAPIをGASで使って、メールから期日を抽出することにする。
  • 登録
    次に OpenAIのAPIを使うことについては、各所に掲載されているので、それを参照する。たとえばOpenAI APIとは?使い方を実際の手順を紹介しながら解説など。私はいつの間にか初期のサービス分が尽きていたようなので10ドル入金した。
  • モデルの種類
    Open AI は、いろいろなモデルを供出している。(cf.OpenAI API で提供されている モデル まとめ) ここではgpt-3.5-turboを使う。

3-2. サブアドレス

  • サブアドレスとは
    hoge@example.com に対して、hoge+todo@example.com のようなアドレスのこと。RFC5233で定められている。+todo の部分が余分でも、同じユーザーに届く。Gmailでも当然対応している。届いたメールアドレスを元になにか処理するときに使える。
  • テストしておく
    これを予め、自分で自分のサブアドレスにメールを送って確かめておく。

3-3. GAS とGoogle ToDo リスト

これを使うために、あらかじめGASでTasks(ToDo)追加するを読んでおく。なお、「ワンマンカー」というのが和製英語であるように(?)、Google ToDoリストは日本語の表記で、英語では Google Tasks のようである。(cf. Google ToDo リストの概要)

3-4. Googleスプレッドシートの作成

あらかじめ、スタンドアロンのGASの書き出し用のGoogleスプレッドシートを作っておく。2行目以下は空欄にしておく。
Qiita_メールからToDoリストへの自動処理_シートのみ.png

4. プログラム

4-1. メールから情報抽出してスプレッドシートへ記載

スタンドアロンのGASを書いた。ポイントはいくつかある。

  • ChatGPT3 は今日の日付を答えられない。「明日まで」を理解するためには基点がわかる必要がある。そこで、メールのヘッダから日付を取得して与える。
  • スクリプトプロパティを使った。プログラムを公開するときに安心。移植性もちょっと良くなる。
  • Gmailのスレッドの最後のメールを使っている。これについては後ほど活用・応用のところでコメントする。
  • 初回は手動で1回実行する。実行許可が問われるので許可する。
  • トリガーを時間主導で1分おきに動くようにする。
  • Gmailへのリンクは、RFC822の Message ID を利用している。私だけでなく、送信者や、同報で送られた人も(Gmailを使っていれば)使えるURLなので利便性が高い。
サブアドレスに送られてきた仕事依頼のメールの以下の情報をスプレッドシートに書き出す
// サブアドレスに送られてきた仕事依頼のメールの以下の情報をスプレッドシートに書き出す。
//  ・件名
//  ・仕事の期限
//  ・メッセージIDに基づくGmailの検索URL
// Ver.01 : 2024-02-02

// 左側の 「プロジェクトの設定」(歯車マーク)> スクリプト プロパティ で、次の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('@', '+todo@'); // メールのサブアドレス hoge+todo@foo.com を設定。

 var sheet = SpreadsheetApp.openById(spreadsheetId).getActiveSheet();

 const ProcessedLabel = 'ToDoProcessed'; // 処理済みとわかるためのラベル名。任意。
 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 message = messages[messages.length - 1]; // スレッド内の最後(最新)のメッセージを取得

   if (!message.isInTrash()) {
     // メールから取り出すのは3つ。件名、メッセージID、日付
     var subject      = message.getSubject(); 
     var messageId    = message.getHeader('Message-ID');
     var dateHeader   = message.getDate();
     // AI で期限を取得
     var dateDeadLine = getDeadLineWithOpenAI(message.getPlainBody().slice(0,999), dateHeader);

     // スプレッドシートに追記(4列のデータ)
     sheet.appendRow([
         new Date(),    // 登録日時
         '',            // シートのコンテナバインドGASの処理状況欄。最初は空欄。
         dateDeadLine,  // 期限・期日
         subject,       // メールの件名
         'https://mail.google.com/mail/#search/in%3Aanywhere+'+encodeURIComponent('rfc822msgid:'+messageId)]); // メールのスレッドへの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': 'メールの内容を分析し、仕事の期限(締め切り)の日時を、文章を用いず、yyyy-MM-dd 形式で答えよ。理由などの記述は省く。\
 ただし、メールの書かれた日時は、' + 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; // メールから取り出した期限・期日
}

4-2. スプレッドシートからToDoリストへ登録

上記のGASを実行した結果は、スプレッドシートに保管される。そのスプレッドシートにコンテナバインドのGASを追加する。

ポイントとしては、次のようなところがある。

  • taskListIdのところはvar taskListId = '@default'; とすれば、IDを調べる必要はない。個人名のついたToDoリストに登録される。
  • 手作業でシートにToDo を書き込んでも良いように、GASを2つに分けている。シートの3列目(期日)を入力し直して修正、2列めを空欄にすれば、再びToDoリストにエントリーできる。
  • このスクリプトも1回手作業で実行し、権限を与えてから、トリガーで1分ごとに起動するようにする。
スプレッドシートからToDoリストへ登録
// スプレッドシートから ToDoリスト に仕事を登録する。
// Ver.1 2024-02-02
// 
// ※ シートは、メールの +todo のサブアドレス宛のメールを受信して、1分ごとのトリガーで追加・更新されている。
// ※ このシートに手書きすることを考えて、上の作業を実行するGASとは別に、ここで1分ごとのトリガーで起動する。
// ※ 準備として、ToDoリストのIDを取得する。次のURLにアクセスして、Execute
//     https://developers.google.com/tasks/reference/rest/v1/tasklists/list?hl=ja
//     リストは複数作れる。あらかじめ、"ByMail"というリストを作ってある。IDさえ取得できれば、名前は何でも構わない。
//     (cf. https://taibonn.com/gsuite/gas-auto-add-tasks/ )
//     取得した ID は、「プロジェクトの設定」>「スクリプトプロパティ」に taskID_ByMail として保存した。
// ※ 左側の「サービス」をクリックして、「Google TASKS API」を選んで「追加」をクリックする。左側に「Tasks」という表示が現れる。
//    1回、手動で実行して、各種パーミッションを許可してから、トリガーで1分ごとに実行するようにする。

// 必要なスクリプトプロパティ
// 1) taskID_ByMail : Google の TODO List のID

function processSpreadsheetForTasks() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var data  = sheet.getDataRange().getValues();
  var urlGS = SpreadsheetApp.getActiveSpreadsheet().getUrl();

  // Google Tasks APIを扱うためのToDoリストIDを取得して指定。
  //
  // デフォルトのリストの場合
  // var taskListId = '@default';
  // ByMail というToDoリストのIDは、スクリプトプロパティから取得
  var taskListId = PropertiesService.getScriptProperties().getProperty('taskID_ByMail');

  for (var i = 1; i < data.length; i++) { // 1行目(ヘッダー行)をスキップ
    var row = data[i];    
    if (row[1] === '' && isValidDateTime(row[2])) {
      var dateDL = new Date(row[2]);
      var dateDLstring = Utilities.formatDate(dateDL, "JST", "yyyy-MM-dd")+ "T00:00:00.000Z";
      try {
        var task = {
          title: row[3],
          notes: '■ 詳細のメールはこちら:\n' + row[4] +'\n■ スプレッドシートはこちら: ' + urlGS,
          due: dateDLstring
        };

        // タスクを追加
        var createdTask = Tasks.Tasks.insert(task, taskListId);

        // スプレッドシートに「Order」をマーク
        sheet.getRange(i + 1, 2).setValue('Ordered');
      } catch(e) {
        // エラー処理
        console.error('Error adding task: ' + e.message);
      }
    }
  }
}

// 日時または日付として妥当かどうかを判定
function isValidDateTime(value) {
  var date = new Date(value);
  return !isNaN(date.getTime()); // 日時として解釈可能ならtrueを返す
}

5. 活用・応用

  • 周りの人に「私に期限付きの仕事を振るときには、このメールアドレスに送ってね!♡」なんて言っても送ってくれいないだろう。そういうメールが来そうなメーリングリストへの登録を、サブアドレスにしておく可能性がある。期日をAIが判定できなければ、登録されない。
  • 自分でメールを読んでいて、「あ、これ登録しなきゃ」とおもったら、自分のサブアドレスに転送するとよい。登録される。メールへのURLもスレッドへのURLになるので、転送メールと転送前の着信メールと、両方開ける。
  • プログラムではスレッドを取得している。繰り返しになるが、Gmailでは、転送されたメールも同じスレッドになる。だから、件名に Fwd: がある場合には、転送メールと判断し、一つ前のメールを登録すると良いかもしれない。
  • Googleカレンダーの方が他の人と共有できていいのかもしれない。

6. 感想

OpenAIのAPIは安い。まだ1ドルも使っていない。こんなに安ければ、活用したくなる。業務の効率化は、アイデア次第で色々できそうだ。しかし、数年すれば、こんなスクリプト書かなくても、標準サービスで自動でやってくれそうな気もするし、そういった方向への進化を期待している。

もっとスタイリッシュにやっている人がいそうな気もしますが、自分の備忘録を兼ねて。

1
0
0

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
0