1 from oauth2client.client import AccessTokenRefreshError
12 class gcal_renderer(renderer.debuggable_abstaining_renderer):
13 """A renderer to fetch upcoming events from www.google.com/calendar"""
15 calendar_whitelist = frozenset(
19 "Holidays in United States",
23 "Scott Gasch External - Misc",
24 "Birthdays", # <-- from g+ contacts
28 class comparable_event(object):
29 """A helper class to sort events."""
31 def __init__(self, start_time, end_time, summary, calendar):
32 if start_time is None:
33 assert end_time is None
34 self.start_time = start_time
35 self.end_time = end_time
36 self.summary = summary
37 self.calendar = calendar
39 def __lt__(self, that):
40 if self.start_time is None and that.start_time is None:
41 return self.summary < that.summary
42 if self.start_time is None or that.start_time is None:
43 return self.start_time is None
44 return (self.start_time, self.end_time, self.summary, self.calendar) < (
52 return "[%s] %s" % (self.timestamp(), self.friendly_name())
54 def friendly_name(self):
56 name = name.replace("countdown:", "")
57 return "<B>%s</B>" % name
60 if self.start_time is None:
62 elif self.start_time.hour == 0:
63 return datetime.datetime.strftime(self.start_time, "%a %b %d %Y")
65 return datetime.datetime.strftime(
66 self.start_time, "%a %b %d %Y %H:%M%p"
69 def __init__(self, name_to_timeout_dict, oauth):
70 super(gcal_renderer, self).__init__(name_to_timeout_dict, True)
72 self.client = self.oauth.calendar_service()
73 self.sortable_events = []
74 self.countdown_events = []
76 def debug_prefix(self):
79 def periodic_render(self, key):
80 self.debug_print('called for "%s"' % key)
81 if key == "Render Upcoming Events":
82 return self.render_upcoming_events()
83 elif key == "Look For Triggered Events":
84 return self.look_for_triggered_events()
86 raise error("Unexpected operation")
88 def render_upcoming_events(self):
91 def format_datetime(x):
92 return datetime.datetime.strftime(x, "%Y-%m-%dT%H:%M:%SZ")
94 now = datetime.datetime.now()
95 time_min = now - datetime.timedelta(1)
96 time_max = now + datetime.timedelta(95)
97 time_min, time_max = list(map(format_datetime, (time_min, time_max)))
98 self.debug_print("time_min is %s" % time_min)
99 self.debug_print("time_max is %s" % time_max)
102 # + "upcoming events",
103 # + a countdown timer for a subser of events,
104 f = file_writer.file_writer("gcal_3_86400.html")
105 f.write("<h1>Upcoming Calendar Events:</h1><hr>\n")
106 f.write("<center><table width=96%>\n")
108 g = file_writer.file_writer("countdown_3_7200.html")
109 g.write("<h1>Countdowns:</h1><hr><ul>\n")
112 self.sortable_events = []
113 self.countdown_events = []
116 self.client.calendarList().list(pageToken=page_token).execute()
118 for calendar in calendar_list["items"]:
119 if calendar["summary"] in gcal_renderer.calendar_whitelist:
123 calendarId=calendar["id"],
135 y = datetime.datetime.strptime(y, "%Y-%m-%d")
137 y = x.get("dateTime")
139 y = datetime.datetime.strptime(
140 y[:-6], "%Y-%m-%dT%H:%M:%S"
146 for event in events["items"]:
148 summary = event["summary"]
150 "event '%s' (%s to %s)"
151 % (summary, event["start"], event["end"])
153 start = parse_date(event["start"])
154 end = parse_date(event["end"])
155 self.sortable_events.append(
156 gcal_renderer.comparable_event(
157 start, end, summary, calendar["summary"]
161 "countdown" in summary
162 or "Holidays" in calendar["summary"]
163 or "Countdown" in summary
165 self.debug_print("event is countdown worthy")
166 self.countdown_events.append(
167 gcal_renderer.comparable_event(
168 start, end, summary, calendar["summary"]
171 except Exception as e:
172 print("gcal unknown exception, skipping event.")
174 self.debug_print("Skipping calendar '%s'" % calendar["summary"])
175 page_token = calendar_list.get("nextPageToken")
179 self.sortable_events.sort()
180 upcoming_sortable_events = self.sortable_events[:12]
181 for event in upcoming_sortable_events:
182 self.debug_print("sorted event: %s" % event.friendly_name())
186 <td style="padding-right: 1em;">
189 <td style="padding-left: 1em;">
193 % (event.timestamp(), event.friendly_name())
195 f.write("</table></center>\n")
198 self.countdown_events.sort()
199 upcoming_countdown_events = self.countdown_events[:12]
200 now = datetime.datetime.now()
203 for event in upcoming_countdown_events:
204 eventstamp = event.start_time
205 delta = eventstamp - now
206 name = event.friendly_name()
207 x = int(delta.total_seconds())
209 identifier = "id%d" % count
210 days = divmod(x, constants.seconds_per_day)
211 hours = divmod(days[1], constants.seconds_per_hour)
212 minutes = divmod(hours[1], constants.seconds_per_minute)
214 '<li><SPAN id="%s">%d days, %02d:%02d</SPAN> until %s</li>\n'
215 % (identifier, days[0], hours[0], minutes[0], name)
217 timestamps[identifier] = time.mktime(eventstamp.timetuple())
220 "countdown to %s is %dd %dh %dm"
221 % (name, days[0], hours[0], minutes[0])
224 g.write("<SCRIPT>\nlet timestampMap = new Map([")
225 for x in list(timestamps.keys()):
226 g.write(' ["%s", %f],\n' % (x, timestamps[x] * 1000.0))
230 // Pad things with a leading zero if necessary.
232 return (n < 10) ? ("0" + n) : n;
235 // Return an 's' if things are plural.
237 return (n == 1) ? "" : "s";
240 // Periodic function to run the page timers.
241 var fn = setInterval(function() {
242 var now = new Date().getTime();
243 for (let [id, timestamp] of timestampMap) {
244 var delta = timestamp - now;
247 var days = Math.floor(delta / (1000 * 60 * 60 * 24));
248 var hours = pad(Math.floor((delta % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)));
249 var minutes = pad(Math.floor((delta % (1000 * 60 * 60)) / (1000 * 60)));
250 var seconds = pad(Math.floor((delta % (1000 * 60)) / 1000));
252 var s = days + " day" + plural(days) + ", ";
253 s = s + hours + ":" + minutes;
254 document.getElementById(id).innerHTML = s;
256 document.getElementById(id).innerHTML = "EXPIRED";
264 except (gdata.service.RequestError, AccessTokenRefreshError):
265 print("********* TRYING TO REFRESH GCAL CLIENT *********")
266 self.oauth.refresh_token()
267 self.client = self.oauth.calendar_service()
272 def look_for_triggered_events(self):
273 f = file_writer.file_writer(constants.gcal_imminent_pagename)
274 f.write("<h1>Imminent Upcoming Calendar Events:</h1>\n<hr>\n")
275 f.write("<center><table width=99%>\n")
276 now = datetime.datetime.now()
278 for event in self.sortable_events:
279 eventstamp = event.start_time
280 delta = eventstamp - now
281 x = int(delta.total_seconds())
282 if x > 0 and x <= constants.seconds_per_minute * 3:
283 days = divmod(x, constants.seconds_per_day)
284 hours = divmod(days[1], constants.seconds_per_hour)
285 minutes = divmod(hours[1], constants.seconds_per_minute)
286 eventstamp = event.start_time
287 name = event.friendly_name()
288 calendar = event.calendar
290 "<LI> %s (%s) upcoming in %d minutes.\n"
291 % (name, calendar, minutes[0])
297 globals.put("gcal_triggered", True)
299 globals.put("gcal_triggered", False)