ChatGPTを用いてGmailの予定をカレンダーに自動的に追加する

# 概要

日々様々なタスクが期限とともにメールで送られてきます。これらのタスクを自動的にカレンダーに追加することができれば、煩わしいスケジュール管理の手間を多少なりとも軽減することができます。

Gmailからタスクの締切を検出し、カレンダーに自動的に追加するスケジュール管理アプリケーションをGoogle Apps Script (GAS) とChatGPTで構築します。

個人的には、ChatGPTの真骨頂は ~~キャラなりきりやプロンプト芸ではなく~~ 文章のもつニュアンスや文脈を取り扱うことだと思います。ChatGPTを用いることで、メールの形式や言葉遣いの変化に強い処理が可能になります。また、このアプリケーションの本質はChatGPTにどのような入力を行うかなので、コードベースではなくプロンプトベース(日本語ベース)で細かな要件の変更の反映を行うことができます。


# 使用する技術

このプロジェクトでは、Google Apps Script (GAS) と OpenAIのChatGPTを組み合わせて使用します。

- **Google Apps Script (GAS)**: GASはGoogleの各種サービス（Gmail, Google Sheets, Google Calendar等）をJavaScriptベースのスクリプト言語で自動操作するためのツールです。Gmailからのメールを読み取り、Google Calendarにイベントを追加するのに使用します。

- **ChatGPT**: 今となっては言わずと知れた大規模な自然言語処理AIモデルで、自然な文章を生成し、文章から情報を抽出する能力があります。メールの本文から必要な情報を抽出するのに使用します。

## 実装とコード

### Gmailのメールを読み取る

まず、Gmailからメールを読み取ります。これは、Google Apps ScriptのGmailAppオブジェクトを用いて行います。

```javascript
GmailApp.search("label:inbox is:unread");
threads.forEach(function (thread) {
  var thread = threads[0]; // 最新のスレッドを取得
  var message = thread.getMessages()[0]; // スレッド内の最新のメッセージを取得
  var body = message.getBody(); // メッセージ本文を取得
    
  /*
  
  ChatGPTを用いたメール解析

  Googleカレンダーへの予定の追加
 
  */

  GmailApp.markThreadRead(thread); //スレッドを既読にする
});

```

ここでは、`GmailApp.search("label:inbox is:unread")`を使用してGmailの受信トレイから未読の全てのスレッドを取得し、その中から最新のスレッドとその最新のメッセージを選択しています。最終的には、そのメッセージの本文(`body`)を取得します。

### ChatGPTを用いたメール解析

次に、取得したメール本文をChatGPTで解析します。
以下のプロンプトは、ChatGPTに対してメールから必要な情報を抽出し、それをJSON形式で返すように依頼します。具体的には、タスクが存在するかどうか、タスクの内容、タスクの期日、そして送信者を抽出してJSONで出力するように依頼しています。

```
あなたは優秀なスケジュールアシスタントです。メールの本文から以下の要素をjson形式で出力してください。
・行わなければならないタスクの有無（TrueかFalse）
・タスクの内容
・タスクの期日
・差出人名

--出力例--
{
  "task_required":,
  "task_content": ,
  "task_deadline": ,
  "sender_name":
}
--
{ここにメール本文}

```
```javascript
var role = 'あなたは優秀なアシスタントです。以下のメッセージの以下の要素をjson形式で出力してください。\n・行わなければならないタスクの有無（TrueかFalse）\n・タスクの内容\n・（存在するなら）タスクの期日\n・差出人名\n\n--出力例--\n{\n  "task_required":,\n  "task_content": ,\n  "task_deadline": ,\n  "sender_name":\n}\n: \n' 
var prompt = role + '\n' + body;
```

作成したプロンプトをChatGPTに送信し、その結果を取得します。これは、OpenAIのAPIを用いて行います。

```javascript
  var url = 'https://api.openai.com/v1/chat/completions';
  var request = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization':'Bearer ' + 'your_access_token',
    },
    payload: JSON.stringify({
      "model": "gpt-3.5-turbo",
      "messages": [{"role": "assistant", "content": prompt}]
    }),
  };
  const response = JSON.parse(UrlFetchApp.fetch(url, request).getContentText());
  var schedule = JSON.parse(response.choices[0].message.content);
```

ここでは、`UrlFetchApp.fetch()`関数を使用してOpenAIのChat Models APIを呼び出しています。`fetch()`関数は指定されたURLからデータを取得します。その際、HTTPリクエストのメソッド（ここではPOST）、ヘッダー、そしてボディ（ここでは`payload`）を指定できます。ヘッダーにはContent-TypeとAuthorizationを設定します。Authorizationヘッダーには、OpenAIから取得したAPIキーをセットします。

https://platform.openai.com/docs/api-reference/chat/create

### Googleカレンダーへの予定の追加

ChatGPTから取得した結果を用いて、必要であればGoogleカレンダーに予定を追加します。

```javascript
  if (schedule.task_required){
    var calendar = CalendarApp.getDefaultCalendar();
    var title = schedule.sender_name + " / " + schedule.task_content;
    var startTime = new Date(schedule.task_deadline);
    var endTime = new Date(startTime.getTime() + 10 * 60 * 1000); 
    var event = calendar.createEvent(title, startTime, endTime);
    Logger.log('Event ID: ' + event.getId());
  }
```

ここでは、`CalendarApp.getDefaultCalendar()`を使用してデフォルトのカレンダーを取得し、`createEvent()`関数を用いて新しいイベントを作成します。イベントのタイトル、開始時間、そして終了時間を引数として渡します。

### トリガー設定
GASのトリガー設定を行って、上記のコードを定期的に実行するようにします。

<img width="400" alt="スクリーンショット 2023-06-04 0.25.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3469777/d8a16667-f9e3-88a3-6833-2cef873224b9.png">

## 留意点

- OpenAIのAPIは有料であり、トークン数（入力文字数のようなもの）や処理メール数に応じて料金がかかります。一度のリクエストには入力トークン数の上限があるとはいえ、悪意のある人の長文メールの連投に注意する必要があります。
- 入力プロンプトの改善余地が大いにありますのでお好みにカスタマイズしてください。(幸い、自然言語でエンジニアリングできます。)

## 参考文献

[GASを用いてGmailの未読メールを１分おきにチェックしてスプレッドシートに記録する方法](https://qiita.com/yamadasuzaku/items/b1fe20d7e6c5a6c98f72)