Currently, when you create or update an event on Google Calendar, an email notification is sent to any email address, but when you delete an event, you do not receive an email notification.
The specifications are as follows.
-You will now receive a notification even if you change an existing schedule.
・As a workaround to avoid receiving two emails with the same notification,
Even if multiple scripts are started with one event object, the subsequent scripts will be interrupted.
- The date and time of the previous and current notifications, the calendar name, and the update date and time for each individual schedule are now displayed in the email.
-Even if there are multiple schedule updates, they will be combined into one email. - Only calendars with triggers set are processed.
Below is the GAS code.
function monitorMyCalendar(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 noticedDate = formatDate_(lastUpdated);
// スクリプトの実行日時を取得
const currentTime = new Date();
const currentDate = formatDate_(currentTime);
// 実行日と2週間後及び6ヶ月後の日付を生成
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() + 6); // 6ヶ月後の日付
// 更新されたカレンダーを6ヶ月後まで取得
const calendar = CalendarApp.getCalendarById(e.calendarId);
const events = calendar.getEvents(today, sixMonthsLater); // 6ヶ月後までの予定を取得
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++; // 通知されるイベントの数を増やす
}
}
// 削除確認用の予定の控えをスプレッドシートから復元し、2週間分のみ抽出(シート名はカレンダーIDの最初の16文字)
const SPREADSHEET_ID = "1YvJVA74M8otxcrGXbXbc7KAPKUwPqGrSHBdCR86yaxA";
const ss = SpreadsheetApp.openById(SPREADSHEET_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);
for (const data of twoWeeksSavedEvents) {
if (!twoWeeksMap.has(data[0])) {
// 削除された予定を検出
Logger.log('削除された予定を検出');
for (const data of deletedEvents) {
// メール通知項目を生成
const eventDetails = {
title: data[3] || 'タイトル' + isNotExist,
startTime: data[5] ? formatDate_(data[5]) : '開始日時' + isNotExist,
endTime: data[6] ? formatDate_(data[6]) : '終了日時' + isNotExist,
updateTime: formatDate_(calendar.getEventById(data[0]).getLastUpdated()),
description: data[8] || '詳細' + isNotExist,
location: data[7] || '場所' + isNotExist,
url: '',
calendarId: e.calendarId,
calendarName: calendar.getName(),
};
// メール本文を蓄積する
mailBodies.push(eventDetails)
noticeCount++; // 通知されるイベントの数を増やす
}
}
}
if (mailBodies.length > 0) {
// 開始日時順に並び替え
mailBodies.sort((a, b) => new Date(a.startTime) - new Date(b.startTime));
// メールを送信
sendEmailNotification_(mailBodies, { noticedDate, currentDate });
// 最後の通知時刻を保存
properties.setProperty('lastUpdated', currentTime.toISOString());
}
Logger.log('通知されるイベントの数: ' + noticeCount);
// 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);
}
}
} else {
console.log('エディタからは実行できません');
}
}
// 日付を指定された形式に整形(日付が文字列の場合に対応)
function formatDate_(date) {
return Utilities.formatDate(new Date(date), 'JST', 'yyyy/MM/dd HH:mm'); // 日本時間で表示
}
// 通知を送信
function sendEmailNotification_(mailBodies, date) {
try {
const recipientEmail = '[email protected]'; // 送信先のメールアドレスを設定してください
const subject = "Googleカレンダーのイベントが更新されました";
let body = '前回の通知(' + date.noticedDate + ')以降、Googleカレンダーのイベントが更新されました。n' +
'今回の通知(' + date.currentDate + ')対象のカレンダー名: ' + mailBodies[0].calendarName + 'nn';
for (const item of mailBodies) {
body +=
'作成・更新日時: ' + item.updateTime + 'n' +
'タイトル: ' + item.title + 'n' +
'開始日時: ' + item.startTime + 'n' +
'終了日時: ' + item.endTime + 'n' +
'場所: ' + item.location + 'n' +
'詳細: ' + item.description + 'n' +
'URL: ' + item.url + 'nn';
}
MailApp.sendEmail(recipientEmail, subject, body);
Logger.log('メールが送信されました: ' + body);
} catch (error) {
// メール送信中にエラーが発生した場合、エラーメッセージをログに出力する
Logger.log('メールの送信中にエラーが発生しました: ' + error);
}
}
If deletedEvents is not defined, you can check it in the trigger console, but I am at a loss as to how to write the processing here.
Make sure to save it when the script finishes. …① (described later)
When the script starts, it retrieves all future schedules and…②
Next, restore the schedule from ① that was saved at the last startup.
Comparing ① with ②, among the schedules within 2 weeks from the startup date and time
We will notify you by email of any plans that are not listed in ②.
Finally, save all the plans in ②. …New ① (restored at next startup)
*Since it is unknown when the next startup will be,
Get and save all future plans, not just for two weeks.
*After deletion, you will no longer be able to retrieve it.
In ①, save all the items necessary for notifications, such as the ID of each event, title, start date and time, end date and time, location, and details.
Also, multiple event objects occur with one calendar update trigger, so
It will also be necessary to address this issue.
Save all appointments in a spreadsheet, collate all old and new appointments, etc.
Depending on the number of schedules, it may take some time to execute.
The above is the goal, but our goal is to receive email notifications even when a message is “deleted,” so we appreciate your cooperation.