実現したいこと
表題の件ですがGASを使用してGoogleカレンダーでイベントを作成、更新、削除した場合、
任意のメールアドレスに送信されるという仕組みです。
発生している問題・分からないこと
今回の場合、
以下の点で困っています。
・なぜかメール通知がなされない
・削除の場合の送信ができない
該当のソースコード
GAS
1function monitorMyCalendar(e) { // 関数名も変えました 2 if (e) { 3 try { 4 // 表示用の文字列 5 const isNotExist = 'が設定されていません'; 6 7 // 過去にイベントオブジェクトが複数発生(スクリプトが複数起動)したことへの対応 8 const lock = LockService.getScriptLock(); 9 lock.waitLock(0); 10 11 // プロパティサービスから前回実行日時を取得 12 const properties = PropertiesService.getScriptProperties(); 13 const lastUpdated = new Date(properties.getProperty('lastUpdated')); 14 const noticedDate = formatDate_(lastUpdated); 15 16 // スクリプトの実行日時を取得 17 const currentTime = new Date(); 18 const currentDate = formatDate_(currentTime); 19 20 // 実行日と2週間後及び6ヶ月後の日付を生成 21 const today = new Date(); 22 today.setHours(0, 0, 0, 0); // 時刻をクリア 23 const twoWeeksLater = new Date(today); 24 twoWeeksLater.setDate(twoWeeksLater.getDate() + 14); // 2週間後の日付 25 const sixMonthsLater = new Date(today); 26 sixMonthsLater.setMonth(today.getMonth() + 6); // 6ヶ月後の日付 27 28 // 更新されたカレンダーを6ヶ月後まで取得 29 const calendar = CalendarApp.getCalendarById(e.calendarId); 30 const events = calendar.getEvents(today, sixMonthsLater); // 6ヶ月後までの予定を取得 31 32 let noticeCount = 0; // 通知されるイベントの数をカウントする変数 33 const mailBodies = []; // 通知内容を蓄積する配列 34 const twoWeeksMap = new Map(); 35 36 // 追加・更新された予定を検出 37 for (const event of events) { 38 const eventUpdated = event.getLastUpdated(); 39 const isNewEvent = eventUpdated > lastUpdated || event.getStartTime() > lastUpdated; // 新しいイベントまたは更新されたイベントかどうかを判定 40 if (eventUpdated > twoWeeksLater) { 41 break; 42 } else if (isNewEvent) { 43 twoWeeksMap.set(event.getId(), eventUpdated); 44 // メール通知項目を生成 45 const eventDetails = { 46 title: event.getTitle() || 'タイトル' + isNotExist, 47 startTime: event.getStartTime() ? formatDate_(event.getStartTime()) : '開始日時' + isNotExist, 48 endTime: event.getEndTime() ? formatDate_(event.getEndTime()) : '終了日時' + isNotExist, 49 updateTime: formatDate_(eventUpdated), 50 description: event.getDescription() || '詳細' + isNotExist, 51 location: event.getLocation() || '場所' + isNotExist, 52 url: 'https://www.google.com/calendar/event?eid=' + Utilities.base64Encode(event.getId().split('@')[0] + ' ' + e.calendarId), // URL を直接指定 53 calendarId: e.calendarId, 54 calendarName: calendar.getName(), 55 }; 56 // メール本文を蓄積する 57 mailBodies.push(eventDetails); 58 noticeCount++; // 通知されるイベントの数を増やす 59 } 60 } 61 62 // 削除確認用の予定の控えをスプレッドシートから復元し、2週間分のみ抽出(シート名はカレンダーIDの最初の16文字) 63 const ss = SpreadsheetApp.openById("シートID"); 64 const sheet = ss.getSheetByName(e.calendarId.slice(0, 16)) ?? ss.insertSheet(e.calendarId.slice(0, 16)); 65 const savedEvents = sheet.getDataRange().getValues(); 66 const twoWeeksSavedEvents = savedEvents.filter(data => data[5] >= today && data[5] <= twoWeeksLater); 67 68 // 削除されたイベントIDのリストを格納するための配列 69 const deletedEventIds = []; 70 71 // 2週間分の保存されたイベントを反復処理 72 for (const data of twoWeeksSavedEvents) { 73 if (!twoWeeksMap.has(data[0])) { 74 // 削除されたイベントIDを検出し、deletedEventIds に追加 75 deletedEventIds.push(data[0]); 76 } 77 } 78 79 // deletedEventIds を元に削除されたイベントの詳細を取得し、メール通知の準備を行う 80 for (const eventId of deletedEventIds) { 81 // 削除されたイベントの詳細を取得 82 const deletedEventData = savedEvents.find(data => data[0] === eventId); 83 84 // 削除されたイベントの詳細が見つかった場合、メール通知項目を生成して mailBodies に追加 85 if (deletedEventData) { 86 const eventDetails = { 87 title: deletedEventData[3] || 'タイトル' + isNotExist, 88 startTime: deletedEventData[5] ? formatDate_(deletedEventData[5]) : '開始日時' + isNotExist, 89 endTime: deletedEventData[6] ? formatDate_(deletedEventData[6]) : '終了日時' + isNotExist, 90 updateTime: formatDate_(calendar.getEventById(eventId).getLastUpdated()), 91 description: deletedEventData[8] || '詳細' + isNotExist, 92 location: deletedEventData[7] || '場所' + isNotExist, 93 url: '', 94 calendarId: e.calendarId, 95 calendarName: calendar.getName(), 96 }; 97 // メール本文を蓄積する 98 mailBodies.push(eventDetails); 99 noticeCount++; // 通知されるイベントの数を増やす 100 } 101 } 102 103 if (mailBodies.length > 0) { 104 // 開始日時順に並び替え 105 mailBodies.sort((a, b) => new Date(a.startTime) - new Date(b.startTime)); 106 // メールを送信 107 sendEmailNotification_(mailBodies, { noticedDate, currentDate }, properties, lastUpdated); 108 // 最後の通知時刻を保存 109 properties.setProperty('lastUpdated', currentTime.toISOString()); 110 } 111 Logger.log('通知されるイベントの数: ' + noticeCount); 112 113 // 6ヶ月分の予定の控えを更新(保存) 114 if (events.length > 0) { 115 const values = events.map(event => [ 116 event.getId(), // イベントID[0] 117 calendar.getName(), // カレンダー名[1] 118 e.calendarId, // カレンダーID[2] 119 event.getTitle(), // タイトル[3] 120 event.getLastUpdated(), // 最終更新日時[4] 121 event.getStartTime(), // 開始日時[5] 122 event.getEndTime(), // 終了日時[6] 123 event.getLocation(), // 場所[7] 124 event.getDescription(), // 詳細[8] 125 ]); 126 sheet.clearContents(); 127 sheet.getRange(1, 1, values.length, values[0].length).setValues(values); 128 } 129 130 // スクリプトのロックを解放 131 Utilities.sleep(300); 132 lock.releaseLock(); 133 134 } catch (error) { 135 if (error.toString().includes('Lock timeout')) { 136 Logger.log('実行中のスクリプトが重複しているので処理を中断しました'); 137 } else { 138 Logger.log('予定の確認中に次のエラーが発生しました: ' + error); 139 } 140 } 141 } else { 142 console.log('エディタからは実行できません'); 143 } 144} 145 146// 日付を指定された形式に整形(日付が文字列の場合に対応) 147function formatDate_(date) { 148 return Utilities.formatDate(new Date(date), 'JST', 'yyyy/MM/dd HH:mm'); // 日本時間で表示 149} 150 151// 通知を送信 152function sendEmailNotification_(mailBodies, date, properties, lastUpdated, isNewEvent, isDeletedEvent) { 153 try { 154 const lastNotificationDate = new Date(properties.getProperty('lastNotificationDate')); 155 const currentTime = new Date(); 156 157 // 前回の通知日時と現在の日時を比較し、新しいイベントまたは削除されたイベントがある場合にメール通知を送信する 158 if (lastNotificationDate < lastUpdated && currentTime >= lastUpdated && (isNewEvent || isDeletedEvent)) { 159 const recipientEmail = 'test@gmail.com'; // 送信先のメールアドレスを設定してください 160 161 // 新しいイベントの情報を含む通知メール 162 if (isNewEvent) { 163 const subject = "Googleカレンダーに新しいイベントが追加されました"; 164 let body = '前回の通知(' + date.noticedDate + ')以降、Googleカレンダーに新しいイベントが追加されました。\n' + 165 '今回の通知(' + date.currentDate + ')対象のカレンダー名: ' + mailBodies[0].calendarName + '\n\n'; 166 167 for (const item of mailBodies) { 168 body += 169 '作成日時: ' + item.updateTime + '\n' + 170 'タイトル: ' + item.title + '\n' + 171 '開始日時: ' + item.startTime + '\n' + 172 '終了日時: ' + item.endTime + '\n' + 173 '場所: ' + item.location + '\n' + 174 '詳細: ' + item.description + '\n' + 175 'URL: ' + item.url + '\n\n'; 176 } 177 MailApp.sendEmail(recipientEmail, subject, body); 178 Logger.log('新しいイベントの通知メールが送信されました: ' + body); 179 } 180 181 // 削除されたイベントの情報を含む通知メール 182 if (isDeletedEvent) { 183 const subject = "Googleカレンダーのイベントが削除されました"; 184 let body = '前回の通知(' + date.noticedDate + ')以降、Googleカレンダーからイベントが削除されました。\n' + 185 '今回の通知(' + date.currentDate + ')対象のカレンダー名: ' + mailBodies[0].calendarName + '\n\n'; 186 187 for (const item of mailBodies) { 188 body += 189 '削除日時: ' + item.updateTime + '\n' + 190 'タイトル: ' + item.title + '\n' + 191 '開始日時: ' + item.startTime + '\n' + 192 '終了日時: ' + item.endTime + '\n' + 193 '場所: ' + item.location + '\n' + 194 '詳細: ' + item.description + '\n\n'; 195 } 196 MailApp.sendEmail(recipientEmail, subject, body); 197 Logger.log('削除されたイベントの通知メールが送信されました: ' + body); 198 } 199 200 // 最後の通知時刻を保存 201 properties.setProperty('lastNotificationDate', currentTime.toISOString()); 202 } else { 203 Logger.log('新しいイベントまたは削除されたイベントがないか、前回の通知以降に新しいイベントまたは削除されたイベントがありません。メールを送信しませんでした。'); 204 } 205 } catch (error) { 206 // メール送信中にエラーが発生した場合、エラーメッセージをログに出力する 207 Logger.log('通知メールの送信中にエラーが発生しました: ' + error); 208 } 209} 210 211
試したこと・調べたこと
- teratailやGoogle等で検索した
- ソースコードを自分なりに変更した
- 知人に聞いた
- その他
上記の詳細・結果
まずはトリガーの設定は以下に設定しています。
今回はいくつか同じ内容が来てしまうのでその場合は
removeDuplicatesFromSheet関数で削除を行っていますが
一度のメールがこのコードですと「0通」になってしまいます。
if (!lastEmailSent || (currentTime - lastEmailSent) >= emailInterval) {
上記の条件分岐が当てはまっていない気がします。
すみませんが皆様からのご教示のほどよろしくお願いいたします。
追試してみましたが、正常に(?)2通のメールが届きました。
通知されるイベントがログに表示されていて、通知されるイベントの数が0なので、
eventIdのチェックで除外されているのでは?(通知済みの予定を変更したとか)
なお、当方にはログが2つ残り、ログにはそれぞれ1通のメール送信のログがありましたが、
そちらのログで通知される予定の表示が2通分あって9秒の間隔が空いているのが不思議です。
カレンダーを共有していて、それぞれのカレンダーに同じ予定が表示されるようになっていますか。
YellowGreen様
先日はありがとうございました、
またコメントもありがとうございます。
すみませんが2つほど不明点がございます。
>eventIdのチェックで除外されているのでは?(通知済みの予定を変更したとか)
→こちらもう少し教えていただけますでしょうか?
例えば既にあるイベントの更新(タイトル変更、時間変更等)のみの通知なのですが。
>カレンダーを共有していて、それぞれのカレンダーに同じ予定が表示されるようになっていますか。
→カレンダーは共有されていますが、他にグーグルカレンダー側で何かしらの条件があるのでしょうか?
よろしくお願いいたします。
前回と異なり、今回のコードは次のチェックが追加されていますので、
作成時に通知済みのイベントは対象外になりますよ。
// イベントのIDが通知済みリストに含まれていないかを確認
if (!notifiedEvents.includes(eventId)) {
// 通知されていない場合のみ通知を送信
コメントありがとうございます。
通知済みにも許可得るようにしましたが、それでもメールに送信されませんね。。
以下がその時のログです。「TEST77777」が変更されたイベント名です。
=======
2024/04/11 10:52:46 情報 通知されるイベント: TEST77777 (2024/04/13 20:45 - 2024/04/13 22:15)
2024/04/11 10:52:46 情報 通知されるイベントが既に通知済みです: TEST77777
2024/04/11 10:52:48 情報 通知されるイベント: TEST77777 (2024/04/13 20:45 - 2024/04/13 22:15)
2024/04/11 10:52:48 情報 通知されるイベントが既に通知済みです: TEST77777
2024/04/11 10:52:57 情報 通知されるイベント: TEST77777 (2024/04/13 20:45 - 2024/04/13 22:15)
2024/04/11 10:52:57 情報 通知されるイベントが既に通知済みです: TEST77777
2024/04/11 10:52:57 情報 通知されるイベントの数: 0
=======
発生している状況についての説明が少ないでので、何をしようとして何が問題なのかが良くわかりませんが。
「なぜかメール通知がなされない」
確かに、なぜなんでしょうね。
質問にあるスクリプトでスプレッドシートのIDなどを書き換えてみました。
なぜか正常にメールが送信されましたので何が問題なのかわからないままです。
IDやシート名やメールアドレスを再確認しましたか?
atlanticSalmon様
コメントありがとうございます。
該当のソースコードで
例えば「TEST1」というカレンダーイベント更新しました。
その時monitorAllCalendars関数内のログは
======
2024/04/12 9:44:48 情報 イベントがスプレッドシートに追加されました: TEST1
2024/04/12 9:44:51 情報 イベントがスプレッドシートに追加されました: TEST1
2024/04/12 9:45:01 情報 イベントがスプレッドシートに追加されました: TEST1
2024/04/12 9:45:01 情報 スプレッドシートに追加されたイベントの数: 3
======
と必ず出力されます。
ただし次にメール送信がされる
MailApp.sendEmail
の処理まで届いていないため、メールが届いていないと思われます。
>IDやシート名やメールアドレスを再確認しましたか?
すみませんが以下の件ですよね?
=====
var spreadsheetId = 'スプレッドシートのID';
var recipientEmail = '任意のメールアドレス'; // 送信先のメールアドレス
=====
はい、こちらは間違いなくカレンダーに書き込みがありますし、IDは間違っていないかと思います。
スプレッドシートやメールアドレス制限がかかることがあったりしますでしょうか?
なぜかシート名に触れられていませんが、
2箇所あるシート名は確認しましたか?
それと、時間主導型トリガーで実行されるremoveDuplicatesFromSheetは、
なぜ
// 更新されたイベントのみを抽出
var updatedEvents = newData.filter(function(row) {
return row[5] !== ''; // URLが空でない場合、更新されたイベントとみなす
});
で更新されたイベントのみが抽出されるとお考えでしたか?
URLは送信後もそのままなので、
ここで必ず更新されたイベントと判断されます。
時間主導型のトリガーを5分ごとに設定すると
実行されるたびに同じメールが何度も届きます。
追加です。
131行目のvar dataRange = sheet.getRange(range);は、データがないシートの全ての行を確認する必要なないので、var dataRange = sheet.getRange(range + sheet.getLastRow());とすべきでは。
失礼しました。
var sheetName = 'カレンダー変更通知の管理';
こちらも同じで問題ありませんでした。
「更新されたイベントのみを抽出」部分は
それそれイベントの内容を抽出(タイトル名、内容等)してそれをメール内容に含めて送信しようと考えていました。
条件文を外すとメールはきますがおっしゃる通りで時間主導型トリガーの設定のため
定期的に送られてきて困っています。
「条件文を外すとメールはきます」
68、69行目でURLも含めてシートに記入していますが、
メール送信前のシートにURLは記入されていないのですか?
ご指摘ありがとうございます。
>131行目のvar dataRange = sheet.getRange(range);は、データがないシートの全ての行を確認する必要なないので、var dataRange = sheet.getRange(range + sheet.getLastRow());とすべきでは。
→上記のソースコードに反映して修正しました。
>68、69行目でURLも含めてシートに記入していますが、
>メール送信前のシートにURLは記入されていないのですか?
→ごめんなさい、こちらの部分がわかりかねるのですが、具体的に教えていただけますか?
68,69行目でURLも含めてシートに記入しているので、
164-167行目のrow[5] !== ''はtrueになるので、(F列にURLが記入されているので)
重複削除後の全ての行が毎回updatedEventsに格納されるのではないですか。
そのため、時間主導型のトリガーでシートに記入されている全ての予定が毎回通知されています。
シートに記入されているイベントを確認してみてください。
全ての予定にURLは記入されていると思います。
URLが空白かどうかで行を選択すると毎回全ての行が選択されます。
メールは全ての行を対象に送信されることになります。
私より丁寧な解説がありましたね。
スプレッドシートで重複を回避しようとしているところで恐縮ですが、
一度これまで試行してきたプロジェクトの全てのトリガーを解除した上で、
新規にファイルを作成して、
そのスクリプトエディタに私が回答欄で提案したスクリプトをコピペして、
メールアドレスを通知先に設定してから、
カレンダーから発動するトリガーを新たに設定してみて試していただけますか。
これまで作成してきた関数名の重複やトリガーの競合などを無くして
試行するためです。
atlanticSalmon様
YellowGreen様
色々とご指摘ありがとうございます。
すみません、ひとつわかったことがありますので共有します。
自分のメールアドレスが「testA@gmail.com」をrecipientEmailにセットして、
自分でカレンダーイベント更新するとメールが来ません。
他人のメールアドレス「testB@gmail.com」をrecipientEmailにセットして、
自分でカレンダーイベント更新すると「testB@gmail.com」宛てにメールが1通届きました。
他人のメールアドレス「testB@gmail.com」をrecipientEmailにセットして、
他人でカレンダーイベント更新するとメールが来ません。
恐らくGoogle カレンダーの設定のほうで問題があるという事でしょうか?
自身のアドレスにもメールは届きます。ブラウザのGmailでは届いているのを確認することができます。
メールソフトなどメールを確認する方法(スレッド表示で送信済みの方に分類されるなど)の違いでしょうか。
メールソフトが原因かどうかは、ご自身でご自身にメールを送信してみて確認できると思います。
atlanticSalmon様
現在使用しているのがGmailのメールアドレスです、他人の方も同様です。
自分自身で送信したら、メールが届きます。
スレッドで何か分けているという事もありません(迷惑メール等)。
>自身のアドレスにもメールは届きます。ブラウザのGmailでは届いているのを確認することができます。
こちらの事象の再現できないので、何とも言えないですね。。
当方でもrecipientEmailを自分のアドレスに変更しても届きます。
念のためですが、テスト用のアカウントで使い慣れていないなどで
アドレスの記入誤りはないでしょうか。
それから、送信済みに送信履歴があるかどうかは確認できますか。
YellowGreen様
ご確認ありがとうございます。
recipientEmailでセットしてほかの方でもイベント更新通知が来るということは
セットしたメールアドレスに何かしらの設定等に不具合があるということでしょうか?
「メールアドレスに何かしらの設定等に不具合」
ためしに誤ったメールアドレスを設定してみたのですが、
スクリプトによる送信後にバウンスメールは返ってきません。
送信済みには誤ったアドレスへの送信記録が残っています。
たびたびすみません
======
自分のメールアドレスが「testA@gmail.com」をrecipientEmailにセットして、
自分でカレンダーイベント更新するとメールが来ません。
他人のメールアドレス「testB@gmail.com」をrecipientEmailにセットして、
自分でカレンダーイベント更新すると「testB@gmail.com」宛てにメールが1通届きました。
他人のメールアドレス「testB@gmail.com」をrecipientEmailにセットして、
他人でカレンダーイベント更新するとメールが来ません。
======
→こちらですが他人のメールアドレスが1通着たのは「招待メール」であって
今回のGASを通したメールではありませんでした。。
つまり
======
他人のメールアドレス「testB@gmail.com」をrecipientEmailにセットして、
自分でカレンダーイベント更新すると「testB@gmail.com」宛てにもきませんでした
======
という事になります。
そのアカウントのブラウザのGmailで送信済みを確認しましたか?
もう一つは、
更新のタイミングは60秒以上経過後になっていますか?
「60秒以上経過後」
とは、上のスクリプトの場合です。
> 他人のメールアドレス「testB@gmail.com」をrecipientEmailにセットして、
> 自分でカレンダーイベント更新すると「testB@gmail.com」宛てにもきませんでした
> ======
>
> という事になります。
結局どうやってもメールは送信されていないということでしょうか。
当方の回答欄へのコメントはご覧になっていますか。
atlanticSalmon様
>そのアカウントのブラウザのGmailで送信済みを確認しましたか?
こちらは以前から届いています。
>もう一つは、
>更新のタイミングは60秒以上経過後になっていますか?
およそ1分後のタイミングになっています。
>「60秒以上経過後」
>とは、上のスクリプトの場合です。
こちら認識合っています。
YellowGreen様
>念のためですが、テスト用のアカウントで使い慣れていないなどで
>アドレスの記入誤りはないでしょうか。
こちらは何度も見返しましたが、誤りはありませんでした。
>それから、送信済みに送信履歴があるかどうかは確認できますか。
Gamil送信済みで確認ができます、複数メールは一度に一斉メールが来ている履歴が残っています。
>結局どうやってもメールは送信されていないということでしょうか。
こちらスプレッドシートにかませる方法と、別回答のメールアドレスに送信の2パターンですが
どちらとも「recipientEmail」にセットしたメールアドレスに届きませんでした。
送信済み記録があるのに届かないのは、
やはり、メールの受信環境などのスクリプト以外の要因ではないでしょうか。
atlanticSalmon様
コメントありがとうございます。
>やはり、メールの受信環境などのスクリプト以外の要因ではないでしょうか。
Google側の設定を確認してみましたが、デフォルトのままで特に何か設定を触ったという事もありませんでした。
カレンダーの更新トリガーでイベントオブジェクトが複数発生する現象が解消されたようですね。
実行日から6ヶ月後までの予定の配列をカレンダーから取得
2週間分の予定の配列は、6ヶ月分の予定の配列に対し
.filter(開始日時が2週間後の日以前)メソッドで抽出できますので、
6ヶ月間の予定②を取得した後で2週間分にできますよ。
シートから取得した①の配列の方も同じように.filterメソッドで
実行日以降で2週間後以内の要素を抽出すれば、
2つの配列の差分をとることで削除された予定が取り出せると思います。
回答された方からのスクリプトの提示がなくなったら無反応になりましたが、
結局この質問は?
atlanticSalmon様
ご連絡が遅くなり申し訳ございません。
本文のほうを更新しましたが未だ、以下のメールタイトルでメール2通来てしまうのと
Googleカレンダーのイベント通知
Googleカレンダーのイベントが更新されました
「削除」の通知メールだけが飛んできていない状況です。
久しぶりに通知が来たので反応しました。
憶測になりますが、これまでの経緯を見ていると質問者様はコードをご理解されていなくてコピペすれば動くものを要求するために、回答がある度に質問を変更して回答者の回答が的ハズレの回答であるように装っているのだと思いますが。。。
コピペで動くコードが欲しいのであれば、Yahoo!知恵袋に質問されてはどうでしょうか。ここと違ってスクリプトの作成依頼に厳しくないですよ。マルチポストにならないようにこちらには同じ質問を投稿されないように。
あくまでもこれまでの経緯からの憶測(妄想)ですので違いましたらごめんなさい。
既に解決済みとなっていますが、試してみました。
> 以下のメールタイトルでメール2通来てしまうのと
>
> Googleカレンダーのイベント通知
> Googleカレンダーのイベントが更新されました
>
> 「削除」の通知メールだけが飛んできていない状況です。
について、
回答のコードでメールアドレスの部分だけを書き換えて利用すると、
通知は一つだけで削除された予定も通知されます。
おかしいのは、
> Googleカレンダーのイベント通知
というタイトルのメールは、
回答のsendEmailNotification_のコードにも
質問者様が独自に変更している方のsendEmailNotification_のコードにもないので
質問者様が別なスクリプトを実行してそちらの通知が届いているということだと考えられます。
削除された時の通知が来ないのは、
回答のsendEmailNotification_を使わずに
独自に必要のないコードを追加・変更してしいることが
原因と考えられます。
これは、動作不良となるのが当前です。
回答3件
あなたの回答
tips
プレビュー
