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が今回初めてですので
色々と至らない部分があるかもしれませんが、
どうぞよろしくお願い致します。