@@ -9,7 +9,7 @@ import logging
9
9
import os
10
10
import sys
11
11
import time
12
- from typing import List , Optional , Set , Tuple
12
+ from typing import List , Optional , Set , Tuple , TypedDict
13
13
14
14
import dateutil .parser
15
15
import pytz
@@ -28,8 +28,22 @@ CLIENT_SECRET_FILE = "client_secret.json" # noqa: S105
28
28
APPLICATION_NAME = "Zulip"
29
29
HOME_DIR = os .path .expanduser ("~" )
30
30
31
+
32
+ class Event (TypedDict ):
33
+ id : int
34
+ start : datetime .datetime
35
+ end : datetime .datetime
36
+ summary : str
37
+ html_link : str
38
+ status : str
39
+ location : str
40
+ description : str
41
+ organizer : str
42
+ hangout_link : str
43
+
44
+
31
45
# Our cached view of the calendar, updated periodically.
32
- events : List [Tuple [ int , datetime . datetime , str ] ] = []
46
+ events : List [Event ] = []
33
47
34
48
# Unique keys for events we've already sent, so we don't remind twice.
35
49
sent : Set [Tuple [int , datetime .datetime ]] = set ()
@@ -123,63 +137,111 @@ def populate_events() -> Optional[None]:
123
137
)
124
138
.execute ()
125
139
)
126
-
127
140
events .clear ()
128
141
for event in feed ["items" ]:
129
142
try :
130
143
start = dateutil .parser .parse (event ["start" ]["dateTime" ])
144
+ end = dateutil .parser .parse (event ["end" ]["dateTime" ])
131
145
# According to the API documentation, a time zone offset is required
132
146
# for start.dateTime unless a time zone is explicitly specified in
133
147
# start.timeZone.
134
- if start .tzinfo is None :
148
+ if start .tzinfo is None or end . tzinfo is None :
135
149
event_timezone = pytz .timezone (event ["start" ]["timeZone" ])
136
150
# pytz timezones include an extra localize method that's not part
137
151
# of the tzinfo base class.
138
152
start = event_timezone .localize (start )
153
+ end = event_timezone .localize (end )
139
154
except KeyError :
140
155
# All-day events can have only a date.
141
156
start_naive = dateutil .parser .parse (event ["start" ]["date" ])
142
-
157
+ end_naive = dateutil . parser . parse ( event [ "end" ][ "date" ])
143
158
# All-day events don't have a time zone offset; instead, we use the
144
159
# time zone of the calendar.
145
160
calendar_timezone = pytz .timezone (feed ["timeZone" ])
146
161
# pytz timezones include an extra localize method that's not part
147
162
# of the tzinfo base class.
148
163
start = calendar_timezone .localize (start_naive )
164
+ end = calendar_timezone .localize (end_naive )
165
+ id = event ["id" ]
166
+ summary = event .get ("summary" , "(No Title)" )
167
+ html_link = event ["htmlLink" ]
168
+ status = event .get ("status" , "confirmed" )
169
+ location = event .get ("location" , "" )
170
+ description = event .get ("description" , "" )
171
+ organizer = (
172
+ ""
173
+ if (
174
+ event ["organizer" ]["email" ] == options .zulip_email or event ["organizer" ].get ("self" )
175
+ )
176
+ else event ["organizer" ].get ("displayName" , event ["organizer" ]["email" ])
177
+ )
178
+ hangout_link = event .get ("hangoutLink" , "" )
179
+ events .append (
180
+ {
181
+ "id" : id ,
182
+ "start" : start ,
183
+ "end" : end ,
184
+ "summary" : summary ,
185
+ "html_link" : html_link ,
186
+ "status" : status ,
187
+ "location" : location ,
188
+ "description" : description ,
189
+ "organizer" : organizer ,
190
+ "hangout_link" : hangout_link ,
191
+ }
192
+ )
149
193
150
- try :
151
- events .append ((event ["id" ], start , event ["summary" ]))
152
- except KeyError :
153
- events .append ((event ["id" ], start , "(No Title)" ))
194
+
195
+ def event_to_message (event : Event ) -> str :
196
+ """Parse the event dictionary and return a string that can be sent as a message.
197
+
198
+ The message includes the event title, start and end times, location, organizer, hangout link, and description.
199
+
200
+ Returns:
201
+ str: The message to be sent.
202
+ """
203
+ line = f"**[{ event ['summary' ]} ]({ event ['html_link' ]} )**\n "
204
+ if event ["start" ].hour == 0 and event ["start" ].minute == 0 :
205
+ line += "Scheduled for today.\n "
206
+ else :
207
+ line += f"Scheduled from **{ event ['start' ].strftime ('%H:%M' )} ** to **{ event ['end' ].strftime ('%H:%M' )} **.\n "
208
+ line += f"**Location:** { event ['location' ]} \n " if event ["location" ] else ""
209
+ line += f"**Organizer:** { event ['organizer' ]} \n " if event ["organizer" ] else ""
210
+ line += (
211
+ f"**Hangout Link:** [{ event ['hangout_link' ].split ('/' )[2 ]} ]({ event ['hangout_link' ]} )\n "
212
+ if event ["hangout_link" ]
213
+ else ""
214
+ )
215
+ line += f"**Status:** { event ['status' ]} \n " if event ["status" ] else ""
216
+ line += f"**Description:** { event ['description' ]} \n " if event ["description" ] else ""
217
+ return line
154
218
155
219
156
220
def send_reminders () -> Optional [None ]:
157
221
messages = []
158
222
keys = set ()
159
223
now = datetime .datetime .now (tz = pytz .utc )
160
224
161
- for id , start , summary in events :
162
- dt = start - now
225
+ for event in events :
226
+ dt = event [ " start" ] - now
163
227
if dt .days == 0 and dt .seconds < 60 * options .interval :
164
228
# The unique key includes the start time, because of
165
229
# repeating events.
166
- key = (id , start )
230
+ key = (event [ "id" ], event [ " start" ] )
167
231
if key not in sent :
168
- if start .hour == 0 and start .minute == 0 :
169
- line = f"{ summary } is today."
170
- else :
171
- line = "{} starts at {}" .format (summary , start .strftime ("%H:%M" ))
232
+ line = event_to_message (event )
172
233
print ("Sending reminder:" , line )
173
234
messages .append (line )
174
235
keys .add (key )
175
-
176
236
if not messages :
177
237
return
178
238
179
239
if len (messages ) == 1 :
180
- message = "Reminder: " + messages [0 ]
240
+ message = "** Reminder:** \n \n " + messages [0 ]
181
241
else :
182
- message = "Reminder:\n \n " + "\n " .join ("* " + m for m in messages )
242
+ message = "**Reminders:**\n \n " + "\n " .join (
243
+ str (i + 1 ) + ". " + m for i , m in enumerate (messages )
244
+ )
183
245
184
246
zulip_client .send_message (dict (type = "private" , to = options .zulip_email , content = message ))
185
247
0 commit comments