LoginSignup
wakabas
@wakabas

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

GASでGoogleカレンダー上で繰り返しイベントの「削除」を行ったらメールで通知

解決したいこと

初めまして、
表題の件で躓いてしまっている件でご相談があります。
以下Googleカレンダーでの作業になります。

【イベントが繰り返さない場合】

「作成、更新、削除」の場合、問題なくメールの通知があります。

【イベントが繰り返えす場合】

「作成、更新」の場合、問題なくメールの通知があります。
ただし「削除」の場合、
「定期的な予定の削除」というポップアップが現れ、
「この予定」を選択すると「削除」の場合の通知が着ません。
ちなみに
「これ以降のすべての予定」
「すべての予定」
を選択するとクリックしたイベントの情報だけが掲載されたメール1通着ます。

発生している問題

以下がGASのコード全てです。

function sendToCalendar(e) {
  if (e) {
    try {
      // 表示用の文字列
      const isNotExist = 'が設定されていません';

      // 過去にイベントオブジェクトが複数発生(スクリプトが複数起動)したことへの対応
      const lock = LockService.getScriptLock();
      lock.waitLock(0);
      
      // プロパティサービスから前回実行日時を取得
      const properties = PropertiesService.getScriptProperties();
      const lastUpdated = new Date(properties.getProperty('lastUpdated'));
      const currentTime = new Date();

      // 実行日と2週間後及び1ヶ月後の日付を生成
      const today = new Date();
      today.setHours(0, 0, 0, 0); // 時刻をクリア
      const twoWeeksLater = new Date(today);
      twoWeeksLater.setDate(twoWeeksLater.getDate() + 14); // 2週間後の日付
      const sixMonthsLater = new Date(today);
      sixMonthsLater.setMonth(today.getMonth() + 1); // 1ヶ月後の日付

      // 更新されたカレンダーを2ヶ月後まで取得
      const calendar = CalendarApp.getCalendarById(e.calendarId);
      const events = calendar.getEvents(today, sixMonthsLater) || []; // 1ヶ月後までの予定を取得、もしnullなら空の配列を返す

      // 削除確認用の予定の控えをスプレッドシートから復元し、2週間分のみ抽出(シート名はカレンダーIDの最初の16文字)
      const ss = SpreadsheetApp.openById('スプレッドシートのIDを入力');
      // シート名はカレンダーIDに基づきます
      const sheet = ss.getSheetByName(e.calendarId.slice(0, 16)) ?? ss.insertSheet(e.calendarId.slice(0, 16));
      const savedEvents = sheet.getDataRange().getValues();
      const twoWeeksSavedEvents = savedEvents.filter(data => data[5] >= today && data[5] <= twoWeeksLater);

      // Googleカレンダーから取得したイベントIDのリストを生成
      const currentEventIds = events.map(event => event.getId());

      // シートから保存されているイベント情報を取得
      const savedEventsFromSheet = sheet.getDataRange().getValues();

      // シートに保存されているイベントを反復処理し、Googleカレンダーに存在しないイベントを削除
      for (const data of savedEventsFromSheet) {
          const eventId = data[0];
          if (!currentEventIds.includes(eventId)) {
              // Googleカレンダーに存在しないイベントを削除する処理を実行
              const rowIndex = savedEventsFromSheet.indexOf(data) + 1; // 行のインデックスは1から始まります
              sheet.deleteRow(rowIndex);
          }
      }

      let noticeCount = 0; // 通知されるイベントの数をカウントする変数
      const mailBodies = []; // 通知内容を蓄積する配列
      const twoWeeksMap = new Map();

      // 追加・更新された予定を検出
      for (const event of events) {
        const eventUpdated = event.getLastUpdated();
        if (eventUpdated > twoWeeksLater) {
          break;
        } else if (eventUpdated > lastUpdated) {
          twoWeeksMap.set(event.getId(), eventUpdated);
          // メール通知項目を生成
          const eventDetails = {
            title: event.getTitle() || 'タイトル' + isNotExist,
            startTime: event.getStartTime() ? formatDate_(event.getStartTime()) : '開始日時' + isNotExist,
            endTime: event.getEndTime() ? formatDate_(event.getEndTime()) : '終了日時' + isNotExist,
            updateTime: formatDate_(eventUpdated),
            description: event.getDescription() || '詳細' + isNotExist,
            location: event.getLocation() || '場所' + isNotExist,
            url: 'https://www.google.com/calendar/event?eid=' + Utilities.base64Encode(event.getId().split('@')[0] + ' ' + e.calendarId), // URL を直接指定
            calendarId: e.calendarId,
            calendarName: calendar.getName(),
          };

          // メール本文を蓄積する
          mailBodies.push(eventDetails);
          noticeCount++; // 通知されるイベントの数を増やす
        }
      }

      try {
        // 削除されたイベントIDのリストを格納するための配列
        const deletedEventIds = [];

        // 2週間分の保存されたイベントを反復処理
        for (const data of twoWeeksSavedEvents) {
            if (!currentEventIds.includes(data[0])) {
                deletedEventIds.push(data[0]);
            }
        }

        // deletedEventIds を元に削除されたイベントの詳細を取得し、メール通知の準備を行う
        for (const eventId of deletedEventIds) {
          // 削除されたイベントの詳細を取得
          const deletedEventData = savedEvents.find(data => data[0] === eventId);
          if (deletedEventData) {
            // 削除されたイベントの情報、オブジェクト作成
            const eventDetails = {
              title: deletedEventData[3] || 'タイトル' + isNotExist,
              startTime: deletedEventData[5] ? formatDate_(deletedEventData[5]) : '開始日時' + isNotExist,
              endTime: deletedEventData[6] ? formatDate_(deletedEventData[6]) : '終了日時' + isNotExist,
              updateTime: '削除', // 削除されたイベントなので更新日時は「削除」と設定
              description: deletedEventData[8] || '詳細' + isNotExist,
              location: deletedEventData[7] || '場所' + isNotExist,
              url: '',
              calendarId: e.calendarId,
              calendarName: calendar.getName(),
            };

            // メール本文を蓄積する
            mailBodies.push(eventDetails);
            noticeCount++; // 通知されるイベントの数を増やす
          }
        }
      } catch (error) {
        Logger.log('エラーが発生しました: ' + error);
      }

      if (mailBodies.length > 0) {
        // 削除されたイベントがあるかどうかをチェック
        const hasDeletedEvents = mailBodies.some(item => item.updateTime === '削除');
        // メールを送信
        sendEmailNotification_(mailBodies, hasDeletedEvents);
        // 最後の通知時刻を保存
        properties.setProperty('lastUpdated', currentTime.toISOString());
      }

      // 6ヶ月分の予定の控えを更新(保存)
      if (events.length > 0) {
        const values = events.map(event => [
          event.getId(),  // イベントID[0]
          calendar.getName(), // カレンダー名[1]
          e.calendarId,  // カレンダーID[2]
          event.getTitle(),  // タイトル[3]
          event.getLastUpdated(), // 最終更新日時[4]
          event.getStartTime(), // 開始日時[5]
          event.getEndTime(), // 終了日時[6]
          event.getLocation(), // 場所[7]
          event.getDescription(), // 詳細[8]
        ]);
        sheet.clearContents();
        sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
      }

      // スクリプトのロックを解放
      Utilities.sleep(300);
      lock.releaseLock();
    } catch (error) {
      if (error.toString().includes('Lock timeout')) {
        Logger.log('実行中のスクリプトが重複しているので処理を中断しました');
      } else {
        Logger.log('予定の確認中に次のエラーが発生しました: ' + error);
      }
    }
  }
}

// 日付を指定された形式に整形
function formatDate_(date) {
  return Utilities.formatDate(new Date(date), 'JST', 'yyyy/MM/dd HH:mm'); // 日本時間で表示
}

// 通知を送信
function sendEmailNotification_(mailBodies, hasDeletedEvents) {
  try {
    const recipientEmail = 'test@gmail.com'; // 送信先のメールアドレスを設定してください
    let subject = "Googleカレンダーのイベント通知"; // デフォルトの件名
    let body = ''; // メールの本文を保持する変数を定義する

    // 現在の時刻が過去の場合はメールを送信しない
    if (new Date() < new Date(mailBodies[0].startTime)) {
      if (hasDeletedEvents) {
        subject = "Googleカレンダーのイベントが削除されました";
        let uniqueDeletedEvents = []; // 重複を除いた削除されたイベントの配列
        for (const deletedEvent of mailBodies.filter(item => item.updateTime === '削除')) {
          // ユニークな識別子を生成して、その識別子がすでに存在するか確認する
          const uniqueIdentifier = deletedEvent.title + deletedEvent.startTime + deletedEvent.endTime;
          if (!uniqueDeletedEvents.includes(uniqueIdentifier)) {
            uniqueDeletedEvents.push(uniqueIdentifier);
            body +=
              'イベントが削除されましたので、\nご確認をお願いします。\n\n' +
              '【タイトル】: ' + deletedEvent.title + '\n' +
              '【開始日時】: ' + deletedEvent.startTime + '\n' +
              '【終了日時】: ' + deletedEvent.endTime + '\n' +
              '【場所】: ' + deletedEvent.location + '\n' +
              '【詳細】: ' + deletedEvent.description + '\n\n';
          }
        }
      } else {
        // 作成・更新されたイベントの情報を本文に追加
        for (const item of mailBodies.filter(item => item.updateTime !== '削除')) {
          body +=
            'イベントの作成・更新がありましたので、\nご確認をお願いします。\n\n' +
            '【作成・更新日時】: ' + item.updateTime + '\n' +
            '【タイトル】: ' + item.title + '\n' +
            '【開始日時】: ' + item.startTime + '\n' +
            '【終了日時】: ' + item.endTime + '\n' +
            '【場所】: ' + item.location + '\n' +
            '【詳細】: ' + item.description + '\n' +
            '【URL】: ' + item.url + '\n\n';
        }       
      }

      Logger.log('hasDeletedEventsの結果は: ' + hasDeletedEvents);
      // メールを送信
      MailApp.sendEmail(recipientEmail, subject, body);
      Logger.log('メールが送信されました: ' + body);
    }
  } catch (error) {
    // メール送信中にエラーが発生した場合、エラーメッセージをログに出力する
    Logger.log('メールの送信中にエラーが発生しました: ' + error);
  }
}

確認したこと

sendEmailNotification_関数がメール送信ロジックですが
hasDeletedEventsは「true」の場合、「削除」の通知をさせます。
「false」であれば「作成、更新」の通知をさせます。
その「false」は常にログに表示させていますが
「この予定」選択時はトリガーのログに出てきませんでした。

差し替えが必要な個所

以下がスプレッドシートのURLですが、その「スプレッドシートのID」の部分を入れてください。
https://docs.google.com/spreadsheets/d/スプレッドシートのID/

test@gmail.com」は任意のメールアドレスに差し替えてください。

const ss = SpreadsheetApp.openById('スプレッドシートのIDを入力');
const recipientEmail = 'test@gmail.com'; // 送信先のメールアドレスを設定してください

その他

当方、GASが今回初めてですので
色々と至らない部分があるかもしれませんが、
どうぞよろしくお願い致します。

0

No Answers yet.

Your answer might help someone💌