回答編集履歴

5

参考コードを掲載

2024/04/26 19:15

投稿

YellowGreen
YellowGreen

スコア731

test CHANGED
@@ -1,37 +1,249 @@
1
- 質問に回答てもその後に質問内容変わっていくので
2
- 具体的なコードの提示は控えますが、
3
- 次のように変更を加えると実現できると既にお示ししてあります。
4
-
5
- 現在ご提示コードは、
6
- 今回質問追加なった削除への対応についは含おりせんので、
7
- ご自身でトライしてみて不具合等がありましたら、
8
- そのコードをお示しの上で改めてご質問ください。
9
-
10
- > 一部更新あり
11
- >
12
- > (初回起動時)
13
- > カレダーにある6ヶ月程度先まで予定を取得しておいて
14
- > スクリプトの終了時に保存するようにしておきます。…①(後述)
15
- >
16
- > (2回目以降の起動時)
17
- >クリプトが起動した6ヶ月程度先までの予定を取得し、…②
18
- > 次に前回の起動時に保存しておいた①の予定を復元します。
19
- > ①と②とを比較して起動日時から2週間以内の予定のうち
20
- > ①にあって②にない予定をメールで通知します。
21
- > 最後に保存されていた①を消去した上で②の予定を全て保存します。…新たな①(次回起動時に復元)
22
- >
23
- > 次回起動がいつなのか不明なので、
24
- > ①は2週間に限らず6ヶ月程度先までの予定を取得して保存しておきます。
25
- > ※ 削除後は取得できなくなるので、
26
- > ①にはそれぞれ予定のIDに加えタイトル、開始時、終了日時、場所、詳細など通知に必要な項目は全て保存しておきます。
27
- >
28
- > 以上のような方法で可能と考えられます。
29
- >
30
- > なお、①保存先としてスプレッドシートが考えられますが、
31
- > スプレッドシートのデータに基づき送信するスクリプトでは、
32
- > 質問者様環境はGmailが送信済みとなっていても(スクリプト上は送信実行できていても)
33
- > 相手にメールが届いていないということなので、
34
- > 新たなア改めて環境を構築するなど
35
- > メールの不達問題を解決することが必須になると思います。
36
- >
37
-
1
+ 質問者様から何の反応もなくなりまが、
2
+ 参考までにコードをアップしておきます
3
+
4
+ ```JavaScript
5
+ // 質問者様から提示いただいたコードに修正を加えたバージョン
6
+ // (質問内容の変更・追加が繰り返されたので、かり変わってす)
7
+ function monitorMyCalendar(e) { // 関数名も変えました
8
+ if (e) {
9
+ try {
10
+ // 表示用の文字列
11
+ const isNotExist = 'が設定されていません';
12
+
13
+ // 過去にイベトオブジェクトが複数発生(スクリプトが複数起動)したことへ対応
14
+ const lock = LockService.getScriptLock();
15
+ lock.waitLock(0);
16
+
17
+ // プロパティサービ前回実行日時を取得
18
+ const properties = PropertiesService.getScriptProperties();
19
+ const lastUpdated = new Date(properties.getProperty('lastUpdated'));
20
+ const noticedDate = formatDate_(lastUpdated);
21
+
22
+ // スクリプトの実行日時を取得
23
+ const currentTime = new Date();
24
+ const currentDate = formatDate_(currentTime);
25
+
26
+ // 実行日と2週間後及び6ヶ月後の日付を生成
27
+ const today = new Date();
28
+ today.setHours(0, 0, 0, 0); // 時刻をクリア
29
+ const twoWeeksLater = new Date(today);
30
+ twoWeeksLater.setDate(twoWeeksLater.getDate() + 14); // 2週間後日付
31
+ const sixMonthsLater = new Date(today);
32
+ sixMonthsLater.setMonth(today.getMonth() + 6); // 6ヶ月後日付
33
+
34
+ // 更されたカダーを6ヶ月後ま取得
35
+ const calendar = CalendarApp.getCalendarById(e.calendarId);
36
+ const events = calendar.getEvents(today, sixMonthsLater); // 6ヶ月後までの予定を取得
37
+
38
+ let noticeCount = 0; // 通知されるイベントの数をカウントする変数
39
+ const mailBodies = []; // 通知内容を蓄積する配列
40
+ const twoWeeksMap = new Map();
41
+
42
+ // 追加・更新された予定を検出
43
+ for (const event of events) {
44
+ const eventUpdated = event.getLastUpdated();
45
+ if (eventUpdated > twoWeeksLater) {
46
+ break;
47
+ } else if (eventUpdated > lastUpdated) {
48
+ twoWeeksMap.set(event.getId(), eventUpdated);
49
+ // メール通知項目を生成
50
+ const eventDetails = {
51
+ title: event.getTitle() || 'タイトル' + isNotExist,
52
+ startTime: event.getStartTime() ? formatDate_(event.getStartTime()) : '開始日時' + isNotExist,
53
+ endTime: event.getEndTime() ? formatDate_(event.getEndTime()) : '終了日時' + isNotExist,
54
+ updateTime: formatDate_(eventUpdated),
55
+ description: event.getDescription() || '詳細' + isNotExist,
56
+ location: event.getLocation() || '場所' + isNotExist,
57
+ url: 'https://www.google.com/calendar/event?eid=' + Utilities.base64Encode(event.getId().split('@')[0] + ' ' + e.calendarId), // URL を直接指定
58
+ calendarId: e.calendarId,
59
+ calendarName: calendar.getName(),
60
+ };
61
+ // メール本文を蓄積する
62
+ mailBodies.push(eventDetails);
63
+ noticeCount++; // 通知されるイベントの数を増やす
64
+ }
65
+ }
66
+
67
+ // 削除確認用の予定の控えをスプレッドシートから復元し、2週間分のみ抽出(シート名はカレンダーIDの最初の16文字)
68
+ const ss = sheet = SpreadsheetApp.openById(SPREADSHEET_ID);
69
+ const sheet = ss.getSheetByName(e.calendarId.slice(0, 16)) ?? ss.insertSheet(e.calendarId.slice(0, 16));
70
+ const savedEvents = sheet.getDataRange().getValues();
71
+ const twoWeeksSavedEvents = savedEvents.filter(data => data[5] >= today && data[5] <= twoWeeksLater);
72
+ for (const data of twoWeeksSavedEvents) {
73
+ if (!twoWeeksMap.has(data[0])) {
74
+ // 削除された予定を検出
75
+ for (const data of deletedEvents) {
76
+ // メール通知項目を生成
77
+ const eventDetails = {
78
+ title: data[3] || 'タイトル' + isNotExist,
79
+ startTime: data[5] ? formatDate_(data[5]) : '開始日時' + isNotExist,
80
+ endTime: data[6] ? formatDate_(data[6]) : '終了日時' + isNotExist,
81
+ updateTime: formatDate_(calendar.getEventById(data[0]).getLastUpdated()),
82
+ description: data[8] || '詳細' + isNotExist,
83
+ location: data[7] || '場所' + isNotExist,
84
+ url: '',
85
+ calendarId: e.calendarId,
86
+ calendarName: calendar.getName(),
87
+ };
88
+ // メール本文を蓄積する
89
+ mailBodies.push(eventDetails)
90
+ noticeCount++; // 通知されるイベントの数を増やす
91
+ }
92
+ }
93
+ }
94
+
95
+ if (mailBodies.length > 0) {
96
+ // 開始日時順に並び替え
97
+ mailBodies.sort((a, b) => new Date(a.startTime) - new Date(b.startTime));
98
+ // メールを送信
99
+ sendEmailNotification_(mailBodies, { noticedDate, currentDate });
100
+ // 最後の通知時刻を保存
101
+ properties.setProperty('lastUpdated', currentTime.toISOString());
102
+ }
103
+ Logger.log('通知されるイベントの数: ' + noticeCount);
104
+
105
+ // 6ヶ月分の予定の控えを更新(保存)
106
+ if (events.length > 0) {
107
+ const values = events.map(event => [
108
+ event.getId(), // イベントID[0]
109
+ calendar.getName(), // カレンダー名[1]
110
+ e.calendarId, // カレンダーID[2]
111
+ event.getTitle(), // タイトル[3]
112
+ event.getLastUpdated(), // 最終更新日時[4]
113
+ event.getStartTime(), // 開始日時[5]
114
+ event.getEndTime(), // 終了日時[6]
115
+ event.getLocation(), // 場所[7]
116
+ event.getDescription(), // 詳細[8]
117
+ ]);
118
+ sheet.clearContents();
119
+ sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
120
+ }
121
+
122
+ // スクリプトのロックを解放
123
+ Utilities.sleep(300);
124
+ lock.releaseLock();
125
+
126
+ } catch (error) {
127
+ if (error.toString().includes('Lock timeout')) {
128
+ Logger.log('実行中のスクリプトが重複しているので処理を中断しました');
129
+ } else {
130
+ Logger.log('予定の確認中に次のエラーが発生しました: ' + error);
131
+ }
132
+ }
133
+ } else {
134
+ console.log('エディタからは実行できません');
135
+ }
136
+ }
137
+
138
+ // APIで同期するバージョン(Calendarサービスの登録が必要です)
139
+ // カレンダーの更新のトリガーで起動すると増分同期して更新された予定を通知
140
+ // 初回の同期の際は、更新されていない既存の予定も通知される
141
+ // 手動で実行すると「既定の」カレンダーに対して初回の同期を行う
142
+ function syncCalendar(e) {
143
+ try {
144
+ // 表示用の文字列
145
+ const isNotExist = 'が設定されていません';
146
+
147
+ // 過去にイベントオブジェクトが複数発生(スクリプトが複数起動)したことへの対応
148
+ const lock = LockService.getScriptLock();
149
+ lock.waitLock(0);
150
+
151
+ // プロパティーサービスから値を取得
152
+ const properties = PropertiesService.getScriptProperties();
153
+ const syncToken = e ? properties.getProperty('nextSyncToken#' + e.calendarId) : '';
154
+ const lastUpdated = new Date(properties.getProperty('lastUpdated#' + e.calendarId));
155
+ const noticedDate = formatDate_(lastUpdated);
156
+
157
+ // 日付の処理
158
+ const currentTime = new Date(); // スクリプトの実行日時
159
+ const currentDate = formatDate_(currentTime);
160
+
161
+ // APIのオプションを設定
162
+ const options = syncToken ?
163
+ { syncToken: syncToken } : // 増分同期の場合(削除された予定を含む)
164
+ { timeMin: currentTime.toISOString() }; // 初回同期の場合
165
+
166
+ // メインループ
167
+ let noticeCount = 0; // 通知されるイベントの数をカウントする変数
168
+ const mailBodies = []; // 通知内容を蓄積する配列
169
+ const calendarId = e ? e.calendarId : CalendarApp.getDefaultCalendar().getId();
170
+ const calendar = CalendarApp.getCalendarById(calendarId);
171
+ const events = Calendar.Events.list(calendarId, options); // カレンダーの同期
172
+ for (let item of events.items) {
173
+ if (item.status === 'cancelled') {
174
+ item = Calendar.Events.get(calendarId, item.id); // 削除された予定は個別に取得
175
+ item.summary = '【削除された予定】' + item.summary;
176
+ }
177
+ if (item.start.date) {
178
+ item.start.dateTime = item.start.date + ' 00:00';
179
+ item.end.dateTime = item.end.date + ' 00:00';
180
+ item.summary = '【終日の予定】' + item.summary;
181
+ }
182
+ if (item.recurrence) {
183
+ item.summary = '【定期的な予定】' + item.summary;
184
+ }
185
+ mailBodies.push({
186
+ title: item.summary,
187
+ startTime: item.start.dateTime ? formatDate_(item.start.dateTime) : '開始日時' + isNotExist,
188
+ endTime: item.end.dateTime ? formatDate_(item.end.dateTime) : '終了日時' + isNotExist,
189
+ updateTime: item.updated ? formatDate_(item.updated) : '作成・更新日時' + isNotExist,
190
+ location: item.location || '場所' + isNotExist,
191
+ description: item.description || '詳細' + isNotExist,
192
+ url: item.status === 'cancelled' ? '' : item.htmlLink,
193
+ calendarId: calendarId,
194
+ calendarName: calendar.getName(),
195
+ });
196
+ noticeCount++;
197
+ }
198
+ Logger.log('通知されるイベントの数: ' + noticeCount);
199
+
200
+ // メールを送信
201
+ if (mailBodies.length > 0) {
202
+ mailBodies.sort((a, b) => new Date(a.startTime) - new Date(b.startTime));
203
+ sendEmailNotification_(mailBodies, { noticedDate, currentDate });
204
+ properties.setProperty('lastUpdated#' + calendarId, currentTime.toISOString());
205
+ }
206
+
207
+ // 事後処理
208
+ properties.setProperty('nextSyncToken#' + calendarId, events.nextSyncToken);
209
+ lock.releaseLock();
210
+ } catch (error) {
211
+ if (error.toString().includes('Lock timeout')) {
212
+ Logger.log('既に実行中のスクリプトがあるため処理を中断しました');
213
+ } else {
214
+ Logger.log('カレンダーの同期中に次のエラーが発生しました: ' + error);
215
+ }
216
+ }
217
+ }
218
+
219
+ // 日付を指定された形式に整形(日付が文字列の場合に対応)
220
+ function formatDate_(date) {
221
+ return Utilities.formatDate(new Date(date), 'JST', 'yyyy/MM/dd HH:mm'); // 日本時間で表示
222
+ }
223
+
224
+ // 通知を送信
225
+ function sendEmailNotification_(mailBodies, date) {
226
+ try {
227
+ const recipientEmail = '任意のメールアドレス'; // 送信先のメールアドレスを設定してください
228
+ const subject = "Googleカレンダーのイベントが更新されました";
229
+ let body = '前回の通知(' + date.noticedDate + ')以降、Googleカレンダーのイベントが更新されました。\n' +
230
+ '今回の通知(' + date.currentDate + ')対象のカレンダー名: ' + mailBodies[0].calendarName + '\n\n';
231
+ for (const item of mailBodies) {
232
+ body +=
233
+ '作成・更新日時: ' + item.updateTime + '\n' +
234
+ 'タイトル: ' + item.title + '\n' +
235
+ '開始日時: ' + item.startTime + '\n' +
236
+ '終了日時: ' + item.endTime + '\n' +
237
+ '場所: ' + item.location + '\n' +
238
+ '詳細: ' + item.description + '\n' +
239
+ 'URL: ' + item.url + '\n\n';
240
+ }
241
+ MailApp.sendEmail(recipientEmail, subject, body);
242
+ Logger.log('メールが送信されました: ' + body);
243
+ } catch (error) {
244
+ // メール送信中にエラーが発生した場合、エラーメッセージをログに出力する
245
+ Logger.log('メールの送信中にエラーが発生しました: ' + error);
246
+ }
247
+ }
248
+ ```
249
+

4

 

2024/04/20 04:06

投稿

YellowGreen
YellowGreen

スコア731

test CHANGED
@@ -34,6 +34,4 @@
34
34
  > 新たなアカウントで改めて環境を構築するなど
35
35
  > メールの不達問題を解決することが必須になると思います。
36
36
  >
37
- > また、削除された予定については、
37
+
38
- > この方法では更新日時(削除された日時)を取得することはできません。
39
- >

3

イベントオブジェクトが複数発生する問題が解消したようなので、回答内容を一部修正しました。

2024/04/20 03:49

投稿

YellowGreen
YellowGreen

スコア731

test CHANGED
@@ -33,11 +33,7 @@
33
33
  > 相手にメールが届いていないということなので、
34
34
  > 新たなアカウントで改めて環境を構築するなど
35
35
  > メールの不達問題を解決することが必須になると思います。
36
+ >
37
+ > また、削除された予定については、
38
+ > この方法では更新日時(削除された日時)を取得することはできません。
36
39
  >
37
- > また、1つのカレンダー更新トリガーでイベントオブジェクトが複数発生しているので、
38
- > その問題への対処も必要になってきます。
39
- > 全ての予定をスプレッドシートに保存、新旧の全ての予定の照合など
40
- > 予定の件数しだいでそれなりに実行時間が必要と考えられるので、
41
- > 今回のような一時的なロックで解決できるかどうかは
42
- > 実際に試してみないとわかりません。
43
- >

2

 

2024/04/17 01:16

投稿

YellowGreen
YellowGreen

スコア731

test CHANGED
@@ -7,11 +7,13 @@
7
7
  ご自身でトライしてみて不具合等がありましたら、
8
8
  そのコードをお示しの上で改めてご質問ください。
9
9
 
10
+ > ※ 一部更新あり
11
+ >
10
12
  > (初回起動時)
11
13
  > カレンダーにある6ヶ月程度先までの予定を取得しておいて
12
14
  > スクリプトの終了時に保存するようにしておきます。…①(後述)
13
15
  >
14
- > (回以降の起動時)
16
+ > (2以降の起動時)
15
17
  > スクリプトが起動したら6ヶ月程度先までの予定を取得し、…②
16
18
  > 次に前回の起動時に保存しておいた①の予定を復元します。
17
19
  > ①と②とを比較して起動日時から2週間以内の予定のうち

1

ロジックを再考し整理しました。

2024/04/17 01:13

投稿

YellowGreen
YellowGreen

スコア731

test CHANGED
@@ -7,19 +7,19 @@
7
7
  ご自身でトライしてみて不具合等がありましたら、
8
8
  そのコードをお示しの上で改めてご質問ください。
9
9
 
10
- > (初回または前回起動時)
10
+ > (初回起動時)
11
- > カレンダーにある将来全ての予定を取得しておいて
11
+ > カレンダーにある6ヶ月程度先までの予定を取得しておいて
12
12
  > スクリプトの終了時に保存するようにしておきます。…①(後述)
13
13
  >
14
- > (回起動時)
14
+ > (以降の起動時)
15
- > スクリプトが起動したら将来全ての予定を取得し、…②
15
+ > スクリプトが起動したら6ヶ月程度先までの予定を取得し、…②
16
16
  > 次に前回の起動時に保存しておいた①の予定を復元します。
17
- > ②を①と比較して起動日時から2週間以内の予定のうち
17
+ > ①とを比較して起動日時から2週間以内の予定のうち
18
- > 無くなってい予定をメールで通知します。
18
+ > ①にあって②にない予定をメールで通知します。
19
- > 最後に②から通知済みの予定削除て残りの予定を全て保存しておきます。…①(次回起動時に復元)
19
+ > 最後に保存されていた①消去た上で②の予定を全て保存します。…新たな①(次回起動時に復元)
20
20
  >
21
21
  > ※ 次回起動がいつなのか不明なので、
22
- > 将来の予定は2週間に限らず全て取得して保存しておきます。
22
+ > は2週間に限らず6ヶ月程度先までの予定を取得して保存しておきます。
23
23
  > ※ 削除後は取得できなくなるので、
24
24
  > ①にはそれぞれの予定のIDに加えタイトル、開始日時、終了日時、場所、詳細など通知に必要な項目は全て保存しておきます。
25
25
  >