[GAS] Gmailの内容をAIで分析して、Googleカレンダーに予定を自動登録

## できること
- AIがGmailに届いたメールを分析して、Googleカレンダーに予定登録

## 必要なもの
- Googleアカウント (言わなくても持ってる)
- ChatGPTのアカウント (APIを利用できるアカウントが必要)

## イチオシポイント
- 若干面倒だが、Firebaseを使って、一度処理したメールの識別子を保存することで、GAS側のメール取得回数の制限やChatGPTのAPI利用数を減らすことができる！
  - メールを別のラベル・フォルダを移す場合は過剰すぎる機能かもしれない。受信ボックスで管理して、移すのは自分でやりたいという人にはアリ
  - メールの識別子はメッセージのIDなので、スレッドの返信などにも対応可能
- 最新14日の識別子の保存にすることでFirabaseの容量・ダウンロード量も削減
- 上記の重複チェックにより、Googleカレンダーに重複して予定が入らない

## 作り方
1. [Apps Script (GAS)](https://script.google.com/home?hl=ja)から新しいプロジェクトを作成
2. [OpenAI](https://platform.openai.com/api-keys)のページからAPIキーを生成
![2024_11_01_15_59_33_j3tr6kO3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353940/761be363-90d5-2a8f-6f99-d8d493579ba4.png)

3. [Firebase](https://console.firebase.google.com/u/0/?hl=ja)でプロジェクト作成 & Realtime Database作成
4. Firebaseで作成したプロジェクトの[プロジェクトの設定]→[サービスアカウント]→[以前の認証情報]から作成したデータベースのシークレットを取得
5. 1で作成したGASの[プロジェクトの設定]→[スクリプトプロパティ]に以下の名前で2・4のトークンを設定
 * OPENAI_API_KEY : ChatGPTのAPIトークン
 * FIREBASE_SECRET : データベースアクセス用のトークン
6. コード.gsに以下のコードをコピペ

```js
// FirebaseのURLと認証情報を設定
var FIREBASE_URL = 'https://checker-30f77-default-rtdb.firebaseio.com';
var FIREBASE_SECRET = PropertiesService.getScriptProperties().getProperty('FIREBASE_SECRET');

// メールの一意IDをFirebaseに保存
function saveProcessedEmailId(emailId, messageId, receivedDate) {
  var url = FIREBASE_URL + '/processedEmails/' + emailId + '.json?auth=' + FIREBASE_SECRET;
  var options = {
    method: 'put',
    contentType: 'application/json',
    payload: JSON.stringify({ processed: true, messageId, receivedDate })
  };
  UrlFetchApp.fetch(url, options);
}

// 処理済みメールIDの一覧をFirebaseから取得
function getProcessedEmailIds() {
  var url = FIREBASE_URL + '/processedEmails.json?auth=' + FIREBASE_SECRET;
  var response = UrlFetchApp.fetch(url);
  var data = JSON.parse(response.getContentText());
  
  var processedIds = [];
  for (var email in data) {
    if (data[email].processed && data[email].messageId) {
      processedIds.push(data[email].messageId)
    }
  }
  return processedIds;
}

// 14日以上前のエントリを削除
function cleanOldEntries() {
  var url = FIREBASE_URL + '/processedEmails.json?auth=' + FIREBASE_SECRET;
  var response = UrlFetchApp.fetch(url);
  var data = JSON.parse(response.getContentText());

  // 14日前の日付を計算
  var cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - 14);
  // cutoffDate.setSeconds(cutoffDate.getSeconds() - 1); // test用

  for (var emailId in data) {
    var entry = data[emailId];
    var receivedDate = new Date(entry.receivedDate);

    // 受信日時が14日以上前なら削除
    if (receivedDate < cutoffDate) {
      var deleteUrl = FIREBASE_URL + '/processedEmails/' + emailId + '.json?auth=' + FIREBASE_SECRET;
      UrlFetchApp.fetch(deleteUrl, { method: 'delete' });
      Logger.log('削除されたメールID: ' + emailId);
    }
  }
}

// メイン関数
function checkEmailsAndCreateCalendarEvents() {
  var now = new Date();
  var hour = now.getHours();
  
  // 0時から6時までは処理をスキップ
  if (hour >= 0 && hour < 6) {
    Logger.log('夜間のため処理をスキップします。現在の時刻: ' + now);
    return;
  }

  try {
    // 処理済みのメールIDをFirebaseから取得
    var processedEmailIds = getProcessedEmailIds();
    var excludeQuery = processedEmailIds.map(id => `-rfc822msgid:${id}`).join(' ');
    
    // クエリで処理済みメールIDを除外して検索
    var threads = GmailApp.search(`in:inbox -is:starred ${excludeQuery}`);
    
    Logger.log(`取得したメール件数: ${threads.length}`)
    threads.forEach(function(thread) {
      var messages = thread.getMessages();
      messages.forEach(function(message) {
        // jsonのキー用のID
        const emailId = message.getId();
        // RFC822形式のMessage-IDを取得
        var messageId = message.getHeader('Message-ID').replace(/[<>]/g, "");
        var receiveDate = message.getDate();

        console.log(messageId, emailId)
        // メールが処理済みかどうかを確認
        if (processedEmailIds.includes(messageId)) {
          Logger.log('メールは既に処理済み: ' + messageId);
          return;
        }

        var body = message.getPlainBody();
        
        // OpenAI APIを使用してメール内容を解析
        var schedule = analyzeEmailWithOpenAI(body);
        saveProcessedEmailId(emailId, messageId, receiveDate);

        console.log(schedule)
        if (schedule.schedule_required) {
          // 既にカレンダーに登録されているか確認
          var isDuplicate = checkDuplicateEvent(schedule);
          
          if (!isDuplicate) {
            // Googleカレンダーにイベントを作成
            createCalendarEvent(schedule, message);
          }
        }
      });
    });
  } catch (error) {
    Logger.log('Error processing emails: ' + error);
  }
}

// OpenAI APIを使用してメール内容を解析
function analyzeEmailWithOpenAI(body) {
  var OPENAI_API_KEY = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
  
  var role = 'あなたは優秀な秘書です。Gmailにて、主人が何か予約したり、あるいは予定を入れたりしたことによって届いたメールを確認して、Googleカレンダーに登録するという業務を担当しています。以下のメールは予定に含めないこととしています。\n・サブスクリプションや継続課金の更新案内\n・広告や予定ではない何かの案内\n・応募していない抽選に関するメール\n・システムメンテナンスの案内\n・セール情報\n・配送情報\n本メッセージ下部のメール本文を確認して、以下の要素をjson形式で出力してください。コードブロックにする必要はなく、JSON.parseできる文字列で返却してください。\n・Googleカレンダーに予定を登録するべきかどうか（TrueかFalse）\n・予定の簡単な概要(カレンダーのタイトルとなる)\n・予定の日時(スタート時間)\n・差出人名\n\n--出力例--\n{\n  "schedule_required":,\n  "schedule_content": ,\n  "schedule_time": ,\n  "sender_name":\n}\n: \n'; 
  var prompt = role + '\n' + body;
  
  var url = 'https://api.openai.com/v1/chat/completions';
  var requestOptions = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'Authorization': 'Bearer ' + OPENAI_API_KEY
    },
    payload: JSON.stringify({
      "model": "gpt-4o-mini",
      "messages": [{"role": "user", "content": prompt}]
    }),
    muteHttpExceptions: true
  };
  
  var response = UrlFetchApp.fetch(url, requestOptions);
  
  if (response.getResponseCode() !== 200) {
    Logger.log('OpenAI API Error: ' + response.getContentText());
    return {};
  }
  
  var jsonResponse = JSON.parse(response.getContentText());
  var content = jsonResponse.choices[0].message.content;

  // 使用されたトークン数を取得
  var usage = jsonResponse.usage;
  if (usage) {
    Logger.log('Total tokens used: ' + usage.total_tokens);
  } else {
    Logger.log('No usage data available in the response');
  }

  
  try {
    var schedule = JSON.parse(content);
    return schedule;
  } catch (e) {
    Logger.log('JSON Parse Error: ' + e);
    Logger.log('Response Content: ' + content);
    return {};
  }
}

// 重複イベントをチェック
function checkDuplicateEvent(schedule) {
  var calendar = CalendarApp.getDefaultCalendar();
  var events = calendar.getEvents(new Date(schedule.schedule_time), new Date(new Date(schedule.schedule_time).getTime() + 60 * 60 * 1000));
  console.log(events)
  
  for (var i = 0; i < events.length; i++) {
    var event = events[i];
    if (event.getTitle() === schedule.task_content) {
      return true;
    }
  }
  
  return false;
}

// Googleカレンダーにイベントを作成
function createCalendarEvent(schedule, message) {
  var calendar = CalendarApp.getDefaultCalendar();
  var startDateTime = new Date(schedule.schedule_time);
  var endDateTime = new Date(startDateTime.getTime() + 60 * 60 * 1000); // 1時間後
  
  calendar.createEvent(schedule.schedule_content, startDateTime, endDateTime, {
    description: 'タスク自動登録: ' + schedule.sender_name
  });

  message.star();
}

```

## トリガーの作成
このままだとGASは自動で実行されないので、トリガーを設定する必要がある。
左のサイドメニューからトリガーを選択して、右下のボタンから以下のように追加。
メールが多い人は、時間間隔を5~10分くらいにしてもいいかも！
エラー通知頻度もお任せします！
![2024_11_01_16_23_40_k6bxHeTO.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353940/65452869-e58a-888d-04d4-be14d1cf7968.png)

古いメール識別子の削除の関数も以下のように設定して、トリガーしてください。
毎日0~1時の間に実行されます、多分！
![2024_11_01_16_26_24_owBeAgMF.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353940/944682cf-b03a-1437-2630-9a8b1c84fddd.png)

## プロンプトについて
ChatGPTに送る、指示文(プロンプト)は以下のようになっています。
除外条件などは皆さんのお好みに調整してください。（コード内の変数roleを参照）
下のJSONの出力を変更した場合はコードの変更も必要です。
```text
あなたは優秀な秘書です。
Gmailにて、主人が何か予約したり、あるいは予定を入れたりしたことによって届いたメールを確認して、Googleカレンダーに登録するという業務を担当しています。
以下のメールは予定に含めないこととしています。
・サブスクリプションや継続課金の更新案内
・広告や予定ではない何かの案内
・応募していない抽選に関するメール
・システムメンテナンスの案内
・セール情報
・配送情報

本メッセージ下部のメール本文を確認して、以下の要素をjson形式で出力してください。
コードブロックにする必要はなく、JSON.parseできる文字列で返却してください。
・Googleカレンダーに予定を登録するべきかどうか（TrueかFalse）
・予定の簡単な概要(カレンダーのタイトルとなる)
・予定の日時(スタート時間)
・差出人名

--出力例--
{
 "schedule_required":,
 "schedule_content": ,
 "schedule_time": ,
 "sender_name":
}
```

## 注意
- バリデーションの甘いところがあるかもしれません。
  エラーがあれば教えて下さい！直して更新しておきます。

## Special thanks
- [Qiita - ChatGPTを用いてGmailの予定をカレンダーに自動的に追加する](https://qiita.com/kaneda-masaya/items/52f7f1f28cd68b80f17b)

## 追記: 認証について
使っていると、１週間に１回くらい再度認証してくれと出てきます。

```text
Your script, GmailScheduleCheckerForChatGPT, has recently failed to finish successfully. A summary of the failure(s) is shown below. To configure the triggers for this script, or change your setting for receiving future failure notifications, click here.

Start	Function	Error Message	Trigger	End
12/6/24 7:17:03 AM JST	checkEmailsAndCreateCalendarEvents	Exceeded maximum execution time	time-based	12/6/24 7:23:03 AM JST
Sincerely,

Google Apps Script
```
これの原因はAppScriptで```CalendarApp.getDefaultCalendar()```などのネイティブなアプリを利用しているが、最初の認証後にトークンが更新される処理が無いので、再度の認証が必要になっている思われます。

そこで、ネイティブな機能を使わず、全部API経由で利用し、GCPのOAuth2リフレッシュトークンで認証しようと思います。
以下、試してみた方法です。（もしかしたらうまくいかないかもしれないので、ある程度時間が経って実証できるまでお待ち下さい。）
[追記]検証してみましたが、URLFetchでネイティブなサービスを利用しているため、認可の必要が出てきてしまうようです。。。

ChatGPTでやり方を生成してみました
# Google Apps Scriptでリフレッシュトークンを使用してGmail APIとGoogle Calendar APIを操作する手順

この手順では、Google Apps Scriptを使用してGmail APIとGoogle Calendar APIを操作する際に必要なコードの変更点と、OAuth認証フローの設定方法をわかりやすく解説します。

---

## 1. 必要なスコープの確認

スクリプトで操作するAPIに応じて、以下のスコープを設定します。

### 必要なスコープ一覧
1. Gmail API:
   - `https://www.googleapis.com/auth/gmail.modify`（メールの読み書き、ラベル付け、削除が可能）
2. Google Calendar API:
   - `https://www.googleapis.com/auth/calendar`（カレンダー全体の管理）
   - または
   - `https://www.googleapis.com/auth/calendar.events`（イベントの読み書きのみ）

---

## 2. OAuth認証フローの設定

1. **GCPプロジェクトを作成**
   - [Google Cloud Console](https://console.cloud.google.com/)にアクセスし、新しいプロジェクトを作成します。

2. **OAuthクライアントIDを作成**
   - メニューから「APIとサービス」 > 「認証情報」を選択。
   - 「認証情報を作成」 > 「OAuthクライアントID」を選択。
   - 「アプリケーションの種類」は「デスクトップアプリ」を選択。

3. **スコープを指定して認可URLを生成**
   以下のURLをブラウザに貼り付けてアクセスします。

   ```plaintext
   https://accounts.google.com/o/oauth2/auth?
   client_id=YOUR_CLIENT_ID&
   redirect_uri=urn:ietf:wg:oauth:2.0:oob&
   response_type=code&
   scope=https://www.googleapis.com/auth/gmail.modify https://www.googleapis.com/auth/calendar&
   access_type=offline
   ```

4. 認可コードを取得

  - URLにアクセスし、Googleアカウントでログイン後、認可を許可します。
  - 認可コードが表示されるのでコピーします。


5. リフレッシュトークンを取得 
  - 認可コードを使ってリフレッシュトークンを取得します。

  - リクエストエンドポイント: POST https://oauth2.googleapis.com/token
     ```
     {
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_CLIENT_SECRET",
        "code": "YOUR_AUTHORIZATION_CODE",
        "grant_type": "authorization_code",
        "redirect_uri": "urn:ietf:wg:oauth:2.0:oob"
     }
     ```

6. リフレッシュトークンをApps Scriptに保存 
   - リフレッシュトークンをスクリプトプロパティに保存します。
   - 'REFRESH_TOKEN': 'YOUR_REFRESH_TOKEN'

### 最新コード
```js
// FirebaseのURLと認証情報を設定
var FIREBASE_URL = 'FirebaseのURL';
var FIREBASE_SECRET = PropertiesService.getScriptProperties().getProperty('FIREBASE_SECRET');

function getAccessToken() {
  var clientId = 'CLIENT_ID;
  var clientSecret = 'SECRET';
  var refreshToken = PropertiesService.getScriptProperties().getProperty('REFRESH_TOKEN');

  var response = UrlFetchApp.fetch('https://oauth2.googleapis.com/token', {
    method: 'post',
    payload: {
      client_id: clientId,
      client_secret: clientSecret,
      refresh_token: refreshToken,
      grant_type: 'refresh_token'
    }
  });

  var token = JSON.parse(response.getContentText());
  return token.access_token; // 有効なアクセストークンを返す
}
// Gmail APIを使用してスレッドを取得
function getGmailThreads() {
  var accessToken = getAccessToken(); // アクセストークンを取得
  var url = 'https://gmail.googleapis.com/gmail/v1/users/me/threads';
  
  var options = {
    method: 'get',
    headers: {
      'Authorization': 'Bearer ' + accessToken
    }
  };

  var response = UrlFetchApp.fetch(url, options);
  if (response.getResponseCode() === 200) {
    var data = JSON.parse(response.getContentText());
    return data.threads || []; // スレッドのリストを返す
  } else {
    Logger.log('Gmail API Error: ' + response.getContentText());
    return [];
  }
}
function testGmailApi() {
  var accessToken = getAccessToken();
  var url = 'https://gmail.googleapis.com/gmail/v1/users/me/messages';
  
  var options = {
    method: 'get',
    headers: {
      'Authorization': 'Bearer ' + accessToken
    }
  };

  var response = UrlFetchApp.fetch(url, options);
  Logger.log(response.getContentText());
}
function testCalendarApi() {
  var accessToken = getAccessToken(); // アクセストークンを取得
  var url = `https://www.googleapis.com/calendar/v3/calendars/primary/events`;

  var options = {
    method: 'get',
    headers: {
      'Authorization': 'Bearer ' + accessToken
    }
  };

  var response = UrlFetchApp.fetch(url, options);
  Logger.log(response.getContentText());
}
// スレッド内のメッセージを取得
function getThreadMessages(threadId) {
  var accessToken = getAccessToken();
  var url = `https://gmail.googleapis.com/gmail/v1/users/me/threads/${threadId}`;
  
  var options = {
    method: 'get',
    headers: {
      'Authorization': 'Bearer ' + accessToken
    }
  };

  var response = UrlFetchApp.fetch(url, options);
  if (response.getResponseCode() === 200) {
    var data = JSON.parse(response.getContentText());
    return data.messages || []; // メッセージのリストを返す
  } else {
    Logger.log('Gmail API Error: ' + response.getContentText());
    return [];
  }
}
// メールの一意IDをFirebaseに保存
function saveProcessedEmailId(emailId, messageId, receivedDate) {
  var url = FIREBASE_URL + '/processedEmails/' + emailId + '.json?auth=' + FIREBASE_SECRET;
  var options = {
    method: 'put',
    contentType: 'application/json',
    payload: JSON.stringify({ processed: true, messageId, receivedDate })
  };
  UrlFetchApp.fetch(url, options);
}

// 処理済みメールIDの一覧をFirebaseから取得
function getProcessedEmailIds() {
  var url = FIREBASE_URL + '/processedEmails.json?auth=' + FIREBASE_SECRET;
  var response = UrlFetchApp.fetch(url);
  var data = JSON.parse(response.getContentText());
  
  var processedIds = [];
  for (var email in data) {
    if (data[email].processed && data[email].messageId) {
      processedIds.push(data[email].messageId)
    }
  }
  return processedIds;
}

// 14日以上前のエントリを削除
function cleanOldEntries() {
  var url = FIREBASE_URL + '/processedEmails.json?auth=' + FIREBASE_SECRET;
  var response = UrlFetchApp.fetch(url);
  var data = JSON.parse(response.getContentText());

  // 14日前の日付を計算
  var cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - 14);
  // cutoffDate.setSeconds(cutoffDate.getSeconds() - 1); // test用

  for (var emailId in data) {
    var entry = data[emailId];
    var receivedDate = new Date(entry.receivedDate);

    // 受信日時が14日以上前なら削除
    if (receivedDate < cutoffDate) {
      var deleteUrl = FIREBASE_URL + '/processedEmails/' + emailId + '.json?auth=' + FIREBASE_SECRET;
      UrlFetchApp.fetch(deleteUrl, { method: 'delete' });
      Logger.log('削除されたメールID: ' + emailId);
    }
  }
}

// メイン関数
function checkEmailsAndCreateCalendarEvents() {
  try {
    // 処理済みのメールIDをFirebaseから取得
    var processedEmailIds = getProcessedEmailIds();
    var threads = getGmailThreads(); // Gmail APIでスレッドを取得

    threads.forEach(function(thread) {
      var threadMessages = getThreadMessages(thread.id); // スレッド内のメッセージを取得
      threadMessages.forEach(function(message) {
        var emailId = message.id; // メッセージID
        var messageId = message.payload.headers.find(header => header.name === 'Message-ID').value;
        var receivedDate = new Date(parseInt(message.internalDate)); // 受信日時

        // メールが処理済みかどうかを確認
        if (processedEmailIds.includes(messageId)) {
          Logger.log('メールは既に処理済み: ' + messageId);
          return;
        }

        var bodyPart = message.payload.parts ? message.payload.parts[0].body.data : '';
        var body = decodeURIComponent(escape(Utilities.base64Decode(bodyPart.replace(/-/g, '+').replace(/_/g, '/')))); // Base64デコード

        // OpenAI APIを使用してメール内容を解析
        var schedule = analyzeEmailWithOpenAI(body);
        saveProcessedEmailId(emailId, messageId, receivedDate);

        if (schedule.schedule_required) {
          var isDuplicate = checkDuplicateEvent(schedule);
          if (!isDuplicate) {
            createCalendarEvent(schedule, message);
          }
        }
      });
    });
  } catch (error) {
    Logger.log('Error processing emails: ' + error);
  }
}
function starMessage(messageId) {
  var accessToken = getAccessToken();
  var url = `https://gmail.googleapis.com/gmail/v1/users/me/messages/${messageId}/modify`;
  
  var options = {
    method: 'post',
    headers: {
      'Authorization': 'Bearer ' + accessToken,
      'Content-Type': 'application/json'
    },
    payload: JSON.stringify({
      "addLabelIds": ["STARRED"]
    })
  };

  var response = UrlFetchApp.fetch(url, options);
  if (response.getResponseCode() !== 200) {
    Logger.log('Error starring message: ' + response.getContentText());
  } else {
    Logger.log('Message starred successfully: ' + messageId);
  }
}

// OpenAI APIを使用してメール内容を解析
function analyzeEmailWithOpenAI(body) {
  var OPENAI_API_KEY = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
  
  var role = 'あなたは優秀な秘書です。Gmailにて、主人が何か予約したり、あるいは予定を入れたりしたことによって届いたメールを確認して、Googleカレンダーに登録するという業務を担当しています。以下のメールは予定に含めないこととしています。\n・サブスクリプションや継続課金の更新案内\n・広告や予定ではない何かの案内\n・応募していない抽選に関するメール\n・システムメンテナンスの案内\n・セール情報\n・証券会社のイベント案内\n・配送情報\n・イベントの案内（参加申込メールは予定にいれる、まだ案内段階の場合は予定に入れない）\n本メッセージ下部のメール本文を確認して、以下の要素をjson形式で出力してください。コードブロックにする必要はなく、JSON.parseできる文字列で返却してください。\n・Googleカレンダーに予定を登録するべきかどうか（TrueかFalse）\n・予定の簡単な概要(カレンダーのタイトルとなる)\n・予定の日時(スタート時間)\n・差出人名\n\n--出力例--\n{\n  "schedule_required":,\n  "schedule_content": ,\n  "schedule_time": ,\n  "sender_name":\n}\n: \n'; 
  var prompt = role + '\n' + body;
  
  var url = 'https://api.openai.com/v1/chat/completions';
  var requestOptions = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'Authorization': 'Bearer ' + OPENAI_API_KEY
    },
    payload: JSON.stringify({
      "model": "gpt-4o-mini",
      "messages": [{"role": "user", "content": prompt}]
    }),
    muteHttpExceptions: true
  };
  
  var response = UrlFetchApp.fetch(url, requestOptions);
  
  if (response.getResponseCode() !== 200) {
    Logger.log('OpenAI API Error: ' + response.getContentText());
    return {};
  }
  
  var jsonResponse = JSON.parse(response.getContentText());
  var content = jsonResponse.choices[0].message.content;

  // 使用されたトークン数を取得
  var usage = jsonResponse.usage;
  if (usage) {
    Logger.log('Total tokens used: ' + usage.total_tokens);
  } else {
    Logger.log('No usage data available in the response');
  }

  
  try {
    var schedule = JSON.parse(content);
    return schedule;
  } catch (e) {
    Logger.log('JSON Parse Error: ' + e);
    Logger.log('Response Content: ' + content);
    return {};
  }
}

// 重複イベントをチェック
function checkDuplicateEvent(schedule) {
  var startTime = new Date(schedule.schedule_time);
  var endTime = new Date(startTime.getTime() + 60 * 60 * 1000); // 1時間後
  var events = getCalendarEvents(startTime, endTime);

  for (var i = 0; i < events.length; i++) {
    var event = events[i];
    if (event.summary === schedule.schedule_content) {
      return true;
    }
  }
  
  return false;
}
function getCalendarEvents(startTime, endTime) {
  var accessToken = getAccessToken();
  var url = `https://www.googleapis.com/calendar/v3/calendars/primary/events`;
  
  var options = {
    method: 'get',
    headers: {
      'Authorization': 'Bearer ' + accessToken
    },
    params: {
      'timeMin': startTime.toISOString(),
      'timeMax': endTime.toISOString(),
      'singleEvents': true
    }
  };

  var response = UrlFetchApp.fetch(url, options);
  if (response.getResponseCode() === 200) {
    var data = JSON.parse(response.getContentText());
    return data.items || []; // イベントリストを返す
  } else {
    Logger.log('Error retrieving calendar events: ' + response.getContentText());
    return [];
  }
}
// Googleカレンダーにイベントを作成
function createCalendarEvent(schedule, message) {
  var accessToken = getAccessToken();
  var url = `https://www.googleapis.com/calendar/v3/calendars/primary/events`;

  var event = {
    summary: schedule.schedule_content,
    start: {
      dateTime: new Date(schedule.schedule_time).toISOString()
    },
    end: {
      dateTime: new Date(new Date(schedule.schedule_time).getTime() + 60 * 60 * 1000).toISOString()
    },
    description: 'タスク自動登録: ' + schedule.sender_name
  };

  var options = {
    method: 'post',
    headers: {
      'Authorization': 'Bearer ' + accessToken,
      'Content-Type': 'application/json'
    },
    payload: JSON.stringify(event)
  };

  var response = UrlFetchApp.fetch(url, options);

  if (response.getResponseCode() === 200 || response.getResponseCode() === 201) {
    Logger.log('Event created successfully');
    starMessage(message.id);
  } else {
    Logger.log('Error creating event: ' + response.getContentText());
  }
}


```