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 = sets.ImmutableSet([
18 'Holidays in United States',
22 'Scott Gasch External - Misc',
23 'Birthdays', # <-- from g+ contacts
26 class comparable_event(object):
27 """A helper class to sort events."""
28 def __init__(self, start_time, end_time, summary, calendar):
29 if start_time is None:
30 assert(end_time is None)
31 self.start_time = start_time
32 self.end_time = end_time
33 self.summary = summary
34 self.calendar = calendar
36 def __lt__(self, that):
37 if self.start_time is None and that.start_time is None:
38 return self.summary < that.summary
39 if self.start_time is None or that.start_time is None:
40 return self.start_time is None
41 return (self.start_time,
44 self.calendar) < (that.start_time,
50 return '[%s] %s' % (self.timestamp(), self.friendly_name())
52 def friendly_name(self):
54 name = name.replace("countdown:", "")
55 return "<B>%s</B>" % name
58 if self.start_time is None:
60 elif (self.start_time.hour == 0):
61 return datetime.datetime.strftime(self.start_time,
64 return datetime.datetime.strftime(self.start_time,
65 '%a %b %d %Y %H:%M%p')
67 def __init__(self, name_to_timeout_dict, oauth):
68 super(gcal_renderer, self).__init__(name_to_timeout_dict, True)
70 self.client = self.oauth.calendar_service()
71 self.sortable_events = []
72 self.countdown_events = []
74 def debug_prefix(self):
77 def periodic_render(self, key):
78 self.debug_print('called for "%s"' % key)
79 if (key == "Render Upcoming Events"):
80 return self.render_upcoming_events()
81 elif (key == "Look For Triggered Events"):
82 return self.look_for_triggered_events()
84 raise error('Unexpected operation')
86 def render_upcoming_events(self):
88 def format_datetime(x):
89 return datetime.datetime.strftime(x, '%Y-%m-%dT%H:%M:%SZ')
90 time_min = datetime.datetime.now()
91 time_max = time_min + datetime.timedelta(95)
92 time_min, time_max = map(format_datetime, (time_min, time_max))
93 self.debug_print("time_min is %s" % time_min)
94 self.debug_print("time_max is %s" % time_max)
97 # + "upcoming events",
98 # + a countdown timer for a subser of events,
99 f = file_writer.file_writer('gcal_3_none.html')
100 f.write('<h1>Upcoming Calendar Events:</h1><hr>\n')
101 f.write('<center><table width=96%>\n')
103 g = file_writer.file_writer('countdown_3_7200.html')
104 g.write('<h1>Countdowns:</h1><hr><ul>\n')
107 self.sortable_events = []
108 self.countdown_events = []
110 calendar_list = self.client.calendarList().list(
111 pageToken=page_token).execute()
112 for calendar in calendar_list['items']:
113 if (calendar['summary'] in gcal_renderer.calendar_whitelist):
114 events = self.client.events().list(
115 calendarId=calendar['id'],
119 maxResults=50).execute()
124 y = datetime.datetime.strptime(y, '%Y-%m-%d')
126 y = x.get('dateTime')
128 y = datetime.datetime.strptime(y[:-6],
134 for event in events['items']:
136 summary = event['summary']
137 self.debug_print("event '%s' (%s to %s)" % (
138 summary, event['start'], event['end']))
139 start = parse_date(event['start'])
140 end = parse_date(event['end'])
141 self.sortable_events.append(
142 gcal_renderer.comparable_event(start,
145 calendar['summary']))
146 if ('countdown' in summary or
147 'Holidays' in calendar['summary'] or
148 'Countdown' in summary):
149 self.debug_print("event is countdown worthy")
150 self.countdown_events.append(
151 gcal_renderer.comparable_event(start,
154 calendar['summary']))
155 except Exception as e:
156 print("gcal unknown exception, skipping event.");
158 self.debug_print("Skipping calendar '%s'" % calendar['summary'])
159 page_token = calendar_list.get('nextPageToken')
160 if not page_token: break
162 self.sortable_events.sort()
163 upcoming_sortable_events = self.sortable_events[:12]
164 for event in upcoming_sortable_events:
165 self.debug_print("sorted event: %s" % event.friendly_name())
168 <td style="padding-right: 1em;">
171 <td style="padding-left: 1em;">
174 </tr>\n""" % (event.timestamp(), event.friendly_name()))
175 f.write('</table></center>\n')
178 self.countdown_events.sort()
179 upcoming_countdown_events = self.countdown_events[:12]
180 now = datetime.datetime.now()
183 for event in upcoming_countdown_events:
184 eventstamp = event.start_time
185 delta = eventstamp - now
186 name = event.friendly_name()
187 x = int(delta.total_seconds())
189 identifier = "id%d" % count
190 days = divmod(x, constants.seconds_per_day)
191 hours = divmod(days[1], constants.seconds_per_hour)
192 minutes = divmod(hours[1], constants.seconds_per_minute)
193 g.write('<li><SPAN id="%s">%d days, %02d:%02d</SPAN> until %s</li>\n' % (identifier, days[0], hours[0], minutes[0], name))
194 timestamps[identifier] = time.mktime(eventstamp.timetuple())
196 self.debug_print("countdown to %s is %dd %dh %dm" % (
197 name, days[0], hours[0], minutes[0]))
199 g.write('<SCRIPT>\nlet timestampMap = new Map([')
200 for x in timestamps.keys():
201 g.write(' ["%s", %f],\n' % (x, timestamps[x] * 1000.0))
204 // Pad things with a leading zero if necessary.
206 return (n < 10) ? ("0" + n) : n;
209 // Return an 's' if things are plural.
211 return (n == 1) ? "" : "s";
214 // Periodic function to run the page timers.
215 var fn = setInterval(function() {
216 var now = new Date().getTime();
217 for (let [id, timestamp] of timestampMap) {
218 var delta = timestamp - now;
221 var days = Math.floor(delta / (1000 * 60 * 60 * 24));
222 var hours = pad(Math.floor((delta % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)));
223 var minutes = pad(Math.floor((delta % (1000 * 60 * 60)) / (1000 * 60)));
224 var seconds = pad(Math.floor((delta % (1000 * 60)) / 1000));
226 var s = days + " day" + plural(days) + ", ";
227 s = s + hours + ":" + minutes;
228 document.getElementById(id).innerHTML = s;
230 document.getElementById(id).innerHTML = "EXPIRED";
237 except (gdata.service.RequestError, AccessTokenRefreshError):
238 print("********* TRYING TO REFRESH GCAL CLIENT *********")
239 self.oauth.refresh_token()
240 self.client = self.oauth.calendar_service()
245 def look_for_triggered_events(self):
246 f = file_writer.file_writer(constants.gcal_imminent_pagename)
247 f.write('<h1>Imminent Upcoming Calendar Events:</h1>\n<hr>\n')
248 f.write('<center><table width=99%>\n')
249 now = datetime.datetime.now()
251 for event in self.sortable_events:
252 eventstamp = event.start_time
253 delta = eventstamp - now
254 x = int(delta.total_seconds())
255 if x > 0 and x <= constants.seconds_per_minute * 3:
256 days = divmod(x, constants.seconds_per_day)
257 hours = divmod(days[1], constants.seconds_per_hour)
258 minutes = divmod(hours[1], constants.seconds_per_minute)
259 eventstamp = event.start_time
260 name = event.friendly_name()
261 calendar = event.calendar
262 f.write("<LI> %s (%s) upcoming in %d minutes.\n" % (name, calendar, minutes[0]))
267 globals.put("gcal_triggered", True)
269 globals.put("gcal_triggered", False)