1 from oauth2client.client import AccessTokenRefreshError
11 class gcal_renderer(renderer.debuggable_abstaining_renderer):
12 """A renderer to fetch upcoming events from www.google.com/calendar"""
14 calendar_whitelist = frozenset([
17 'Holidays in United States',
21 'Scott Gasch External - Misc',
22 'Birthdays', # <-- from g+ contacts
25 class comparable_event(object):
26 """A helper class to sort events."""
27 def __init__(self, start_time, end_time, summary, calendar):
28 if start_time is None:
29 assert(end_time is None)
30 self.start_time = start_time
31 self.end_time = end_time
32 self.summary = summary
33 self.calendar = calendar
35 def __lt__(self, that):
36 if self.start_time is None and that.start_time is None:
37 return self.summary < that.summary
38 if self.start_time is None or that.start_time is None:
39 return self.start_time is None
40 return (self.start_time,
43 self.calendar) < (that.start_time,
49 return '[%s] %s' % (self.timestamp(), self.friendly_name())
51 def friendly_name(self):
53 name = name.replace("countdown:", "")
54 return "<B>%s</B>" % name
57 if self.start_time is None:
59 elif (self.start_time.hour == 0):
60 return datetime.datetime.strftime(self.start_time,
63 return datetime.datetime.strftime(self.start_time,
64 '%a %b %d %Y %H:%M%p')
66 def __init__(self, name_to_timeout_dict, oauth):
67 super(gcal_renderer, self).__init__(name_to_timeout_dict, True)
69 self.client = self.oauth.calendar_service()
70 self.sortable_events = []
71 self.countdown_events = []
73 def debug_prefix(self):
76 def periodic_render(self, key):
77 self.debug_print('called for "%s"' % key)
78 if (key == "Render Upcoming Events"):
79 return self.render_upcoming_events()
80 elif (key == "Look For Triggered Events"):
81 return self.look_for_triggered_events()
83 raise error('Unexpected operation')
85 def render_upcoming_events(self):
87 def format_datetime(x):
88 return datetime.datetime.strftime(x, '%Y-%m-%dT%H:%M:%SZ')
89 time_min = datetime.datetime.now()
90 time_max = time_min + datetime.timedelta(95)
91 time_min, time_max = list(map(format_datetime, (time_min, time_max)))
92 self.debug_print("time_min is %s" % time_min)
93 self.debug_print("time_max is %s" % time_max)
96 # + "upcoming events",
97 # + a countdown timer for a subser of events,
98 f = file_writer.file_writer('gcal_3_none.html')
99 f.write('<h1>Upcoming Calendar Events:</h1><hr>\n')
100 f.write('<center><table width=96%>\n')
102 g = file_writer.file_writer('countdown_3_7200.html')
103 g.write('<h1>Countdowns:</h1><hr><ul>\n')
106 self.sortable_events = []
107 self.countdown_events = []
109 calendar_list = self.client.calendarList().list(
110 pageToken=page_token).execute()
111 for calendar in calendar_list['items']:
112 if (calendar['summary'] in gcal_renderer.calendar_whitelist):
113 events = self.client.events().list(
114 calendarId=calendar['id'],
118 maxResults=50).execute()
123 y = datetime.datetime.strptime(y, '%Y-%m-%d')
125 y = x.get('dateTime')
127 y = datetime.datetime.strptime(y[:-6],
133 for event in events['items']:
135 summary = event['summary']
136 self.debug_print("event '%s' (%s to %s)" % (
137 summary, event['start'], event['end']))
138 start = parse_date(event['start'])
139 end = parse_date(event['end'])
140 self.sortable_events.append(
141 gcal_renderer.comparable_event(start,
144 calendar['summary']))
145 if ('countdown' in summary or
146 'Holidays' in calendar['summary'] or
147 'Countdown' in summary):
148 self.debug_print("event is countdown worthy")
149 self.countdown_events.append(
150 gcal_renderer.comparable_event(start,
153 calendar['summary']))
154 except Exception as e:
155 print("gcal unknown exception, skipping event.");
157 self.debug_print("Skipping calendar '%s'" % calendar['summary'])
158 page_token = calendar_list.get('nextPageToken')
159 if not page_token: break
161 self.sortable_events.sort()
162 upcoming_sortable_events = self.sortable_events[:12]
163 for event in upcoming_sortable_events:
164 self.debug_print("sorted event: %s" % event.friendly_name())
167 <td style="padding-right: 1em;">
170 <td style="padding-left: 1em;">
173 </tr>\n""" % (event.timestamp(), event.friendly_name()))
174 f.write('</table></center>\n')
177 self.countdown_events.sort()
178 upcoming_countdown_events = self.countdown_events[:12]
179 now = datetime.datetime.now()
182 for event in upcoming_countdown_events:
183 eventstamp = event.start_time
184 delta = eventstamp - now
185 name = event.friendly_name()
186 x = int(delta.total_seconds())
188 identifier = "id%d" % count
189 days = divmod(x, constants.seconds_per_day)
190 hours = divmod(days[1], constants.seconds_per_hour)
191 minutes = divmod(hours[1], constants.seconds_per_minute)
192 g.write('<li><SPAN id="%s">%d days, %02d:%02d</SPAN> until %s</li>\n' % (identifier, days[0], hours[0], minutes[0], name))
193 timestamps[identifier] = time.mktime(eventstamp.timetuple())
195 self.debug_print("countdown to %s is %dd %dh %dm" % (
196 name, days[0], hours[0], minutes[0]))
198 g.write('<SCRIPT>\nlet timestampMap = new Map([')
199 for x in list(timestamps.keys()):
200 g.write(' ["%s", %f],\n' % (x, timestamps[x] * 1000.0))
203 // Pad things with a leading zero if necessary.
205 return (n < 10) ? ("0" + n) : n;
208 // Return an 's' if things are plural.
210 return (n == 1) ? "" : "s";
213 // Periodic function to run the page timers.
214 var fn = setInterval(function() {
215 var now = new Date().getTime();
216 for (let [id, timestamp] of timestampMap) {
217 var delta = timestamp - now;
220 var days = Math.floor(delta / (1000 * 60 * 60 * 24));
221 var hours = pad(Math.floor((delta % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)));
222 var minutes = pad(Math.floor((delta % (1000 * 60 * 60)) / (1000 * 60)));
223 var seconds = pad(Math.floor((delta % (1000 * 60)) / 1000));
225 var s = days + " day" + plural(days) + ", ";
226 s = s + hours + ":" + minutes;
227 document.getElementById(id).innerHTML = s;
229 document.getElementById(id).innerHTML = "EXPIRED";
236 except (gdata.service.RequestError, AccessTokenRefreshError):
237 print("********* TRYING TO REFRESH GCAL CLIENT *********")
238 self.oauth.refresh_token()
239 self.client = self.oauth.calendar_service()
244 def look_for_triggered_events(self):
245 f = file_writer.file_writer(constants.gcal_imminent_pagename)
246 f.write('<h1>Imminent Upcoming Calendar Events:</h1>\n<hr>\n')
247 f.write('<center><table width=99%>\n')
248 now = datetime.datetime.now()
250 for event in self.sortable_events:
251 eventstamp = event.start_time
252 delta = eventstamp - now
253 x = int(delta.total_seconds())
254 if x > 0 and x <= constants.seconds_per_minute * 3:
255 days = divmod(x, constants.seconds_per_day)
256 hours = divmod(days[1], constants.seconds_per_hour)
257 minutes = divmod(hours[1], constants.seconds_per_minute)
258 eventstamp = event.start_time
259 name = event.friendly_name()
260 calendar = event.calendar
261 f.write("<LI> %s (%s) upcoming in %d minutes.\n" % (name, calendar, minutes[0]))
266 globals.put("gcal_triggered", True)
268 globals.put("gcal_triggered", False)