@@ -40,13 +40,14 @@ class Event(TypedDict):
40
40
description : str
41
41
organizer : str
42
42
hangout_link : str
43
+ reminder : int
43
44
44
45
45
46
# Our cached view of the calendar, updated periodically.
46
47
events : List [Event ] = []
47
48
48
- # Unique keys for events we've already sent, so we don't remind twice.
49
- sent : Set [Tuple [int , datetime .datetime ]] = set ()
49
+ # Unique keys for reminders we've already sent, so we don't remind twice.
50
+ sent : Set [Tuple [int , datetime .datetime , int ]] = set ()
50
51
51
52
sys .path .append (os .path .dirname (__file__ ))
52
53
@@ -73,12 +74,11 @@ google-calendar --calendar calendarID@example.calendar.google.com
73
74
74
75
75
76
parser .add_argument (
76
- "--interval" ,
77
- dest = "interval" ,
78
- default = 30 ,
77
+ "--override" ,
78
+ dest = "override" ,
79
79
type = int ,
80
80
action = "store" ,
81
- help = "Minutes before event for reminder [default: 30] " ,
81
+ help = "Override the reminder time for all events. " ,
82
82
metavar = "MINUTES" ,
83
83
)
84
84
@@ -125,13 +125,12 @@ def get_credentials() -> Credentials:
125
125
def populate_events () -> Optional [None ]:
126
126
creds = get_credentials ()
127
127
service = build ("calendar" , "v3" , credentials = creds )
128
- now = datetime .datetime .now (pytz .utc ).isoformat ()
129
128
feed = (
130
129
service .events ()
131
130
.list (
132
131
calendarId = options .calendarID ,
133
- timeMin = now ,
134
- maxResults = 5 ,
132
+ timeMin = datetime . datetime . now ( pytz . utc ). isoformat () ,
133
+ timeMax = datetime . datetime . now ( pytz . utc ). isoformat (). split ( "T" )[ 0 ] + "T23:59:59Z" ,
135
134
singleEvents = True ,
136
135
orderBy = "startTime" ,
137
136
)
@@ -162,6 +161,9 @@ def populate_events() -> Optional[None]:
162
161
# of the tzinfo base class.
163
162
start = calendar_timezone .localize (start_naive )
164
163
end = calendar_timezone .localize (end_naive )
164
+ now = datetime .datetime .now (tz = start .tzinfo )
165
+ if start < now :
166
+ continue
165
167
id = event ["id" ]
166
168
summary = event .get ("summary" , "(No Title)" )
167
169
html_link = event ["htmlLink" ]
@@ -176,7 +178,25 @@ def populate_events() -> Optional[None]:
176
178
else event ["organizer" ].get ("displayName" , event ["organizer" ]["email" ])
177
179
)
178
180
hangout_link = event .get ("hangoutLink" , "" )
179
- events .append (
181
+ reminders = event ["reminders" ]
182
+ # If the user has specified an override, we use that for all events.
183
+ # If the event uses the calendar's default reminders, we use that.
184
+ # If the event has overrides on Google Calendar, we use that.
185
+ # If none of the above, we don't set a reminder.
186
+ if options .override :
187
+ reminder_minutes = [options .override ]
188
+ elif reminders .get ("useDefault" ):
189
+ calendar_list = service .calendarList ().get (calendarId = options .calendarID ).execute ()
190
+ reminder_minutes = (
191
+ [reminder ["minutes" ] for reminder in calendar_list ["defaultReminders" ]]
192
+ if calendar_list .get ("defaultReminders" )
193
+ else []
194
+ )
195
+ elif reminders .get ("overrides" ):
196
+ reminder_minutes = [reminder ["minutes" ] for reminder in reminders ["overrides" ]]
197
+ else :
198
+ reminder_minutes = []
199
+ events .extend (
180
200
{
181
201
"id" : id ,
182
202
"start" : start ,
@@ -188,7 +208,9 @@ def populate_events() -> Optional[None]:
188
208
"description" : description ,
189
209
"organizer" : organizer ,
190
210
"hangout_link" : hangout_link ,
211
+ "reminder" : reminder ,
191
212
}
213
+ for reminder in reminder_minutes
192
214
)
193
215
194
216
@@ -218,21 +240,30 @@ def event_to_message(event: Event) -> str:
218
240
219
241
220
242
def send_reminders () -> Optional [None ]:
221
- messages = []
243
+ messages : List [ str ] = []
222
244
keys = set ()
223
- now = datetime .datetime .now (tz = pytz .utc )
224
-
225
- for event in events :
226
- dt = event ["start" ] - now
227
- if dt .days == 0 and dt .seconds < 60 * options .interval :
228
- # The unique key includes the start time, because of
229
- # repeating events.
230
- key = (event ["id" ], event ["start" ])
245
+ # Sort events by the time of the reminder.
246
+ events .sort (
247
+ key = lambda event : (event ["start" ] - datetime .timedelta (minutes = event ["reminder" ])),
248
+ reverse = True ,
249
+ )
250
+ # Iterate through the events and send reminders for those whose reminder time has come or passed and remove them from the list.
251
+ # The instant a reminder's time is greater than the current time, we stop sending reminders and break out of the loop.
252
+ while len (events ):
253
+ event = events [- 1 ]
254
+ now = datetime .datetime .now (tz = event ["start" ].tzinfo )
255
+ dt = event ["start" ] - datetime .timedelta (minutes = event ["reminder" ])
256
+ if dt <= now :
257
+ key = (event ["id" ], event ["start" ], event ["reminder" ])
231
258
if key not in sent :
232
259
line = event_to_message (event )
233
260
print ("Sending reminder:" , line )
234
- messages . append ( line )
261
+ messages = [ line , * messages ]
235
262
keys .add (key )
263
+ events .pop ()
264
+ else :
265
+ break
266
+
236
267
if not messages :
237
268
return
238
269
0 commit comments