def zone(self, user): """Returns the time zone at the user's home address.""" try: home = user.get('home') if not home: raise DataError('Missing home address') location = self._geocoder[home] return timezone(location.timezone) except (AstralError, KeyError) as e: raise DataError(e)
def rewrite_cron(self, cron, reference, user): """Replace references to sunrise and sunset in a cron expression.""" # Skip if there is nothing to rewrite. if 'sunrise' not in cron and 'sunset' not in cron: return cron # Replace H:M:S in the reference time so that we get the correct sunrise / set reference_midnight = reference.replace(hour=0, minute=0, second=0, microsecond=0) # Determine the first two days of the cron expression after the # reference, which covers all candidate sunrises and sunsets. yesterday = reference_midnight - timedelta(days=1) midnight_cron = cron.replace('sunrise', '0 0').replace('sunset', '0 0') try: first_day = croniter(midnight_cron, yesterday).get_next(datetime) second_day = croniter(midnight_cron, first_day).get_next(datetime) except ValueError as e: raise DataError(e) zone = self._local_time.zone(user) try: home = self._astral[user.get('home')] except (AstralError, KeyError) as e: raise DataError(e) # Calculate the closest future sunrise time and replace the term in the # cron expression with minutes and hours. if 'sunrise' in cron: sunrises = map(lambda x: home.sunrise(x).astimezone(zone), [first_day, second_day]) next_sunrise = min(filter(lambda x: x >= reference_midnight, sunrises)) sunrise_cron = cron.replace('sunrise', '%d %d' % ( next_sunrise.minute, next_sunrise.hour)) info('Rewrote cron: (%s) -> (%s), reference %s' % ( cron, sunrise_cron, reference_midnight.strftime('%A %B %d %Y %H:%M:%S %Z'))) return sunrise_cron # Calculate the closest future sunset time and replace the term in the # cron expression with minutes and hours. if 'sunset' in cron: sunsets = map(lambda x: home.sunset(x).astimezone(zone), [first_day, second_day]) next_sunset = min(filter(lambda x: x >= reference_midnight, sunsets)) sunset_cron = cron.replace('sunset', '%d %d' % (next_sunset.minute, next_sunset.hour)) info('Rewrote cron: (%s) -> (%s), reference %s' % ( cron, sunset_cron, reference_midnight.strftime('%A %B %d %Y %H:%M:%S %Z'))) return sunset_cron
def image(self, user, width, height): """Generates the current commute image.""" # Extract the directions data. try: directions = self._google_maps.directions(user) status = directions['status'] if status != 'OK': try: error_message = directions['error_message'] raise DataError(error_message) except KeyError: raise DataError(status) routes = directions['routes'] route = routes[0] polyline = route['overview_polyline']['points'] summary = route['summary'] leg = route['legs'][0] # Expect one leg. try: duration = leg['duration_in_traffic']['text'] except KeyError: duration = leg['duration']['text'] except (DataError, IndexError, KeyError) as e: raise ContentError(e) # Get the static map with the route as an image. try: image = self._google_maps.map_image(width, height, polyline=polyline) except DataError as e: raise ContentError(e) # Draw the directions text inside a centered box. if summary: directions_text = '%s via %s' % (duration, summary) else: directions_text = duration draw_text(directions_text, font_spec=SUBVARIO_CONDENSED_MEDIUM, text_color=DIRECTIONS_TEXT_COLOR, anchor='center', box_color=DIRECTIONS_BOX_COLOR, box_padding=DIRECTIONS_BOX_PADDING, border_color=DIRECTIONS_BORDER_COLOR, border_width=DIRECTIONS_BORDER_WIDTH, image=image) return image
def _home_location(self, user): """Gets the location of the user's home address.""" try: home = user.get('home') return self.geocoder[home] except (AstralError, KeyError) as e: raise DataError(e)
def _route_url(self, home, work, travel_mode): """Constructs the URL for the Directions API request.""" if not home: raise DataError('Missing home address') if not work: raise DataError('Missing work address') if not travel_mode: raise DataError('Missing travel mode') url = DIRECTIONS_URL url += '?key=%s' % self.google_maps_api_key url += '&origin=%s' % quote(home) url += '&destination=%s' % quote(work) url += '&mode=%s' % travel_mode url += '&departure_time=now' return url
def _download_map(self, polyline=None, markers=None, marker_icon=None, hide_map=False): """Downloads the image data from the Google Static Map API.""" image_url = self._static_map_url(polyline=polyline, markers=markers, marker_icon=marker_icon, hide_map=hide_map) try: image_response = get(image_url).content except RequestException as e: raise DataError(e) image_data = BytesIO(image_response) return image_data
def _request_icon(self, location): """Requests the current weather icon from the OpenWeather API.""" # Look up the current weather conditions at the location. request_url = OPEN_WEATHER_URL % ( location.latitude, location.longitude, self._open_weather_api_key) try: response_json = get(request_url).json() icon = response_json['current']['weather'][0]['icon'] except (RequestException, JSONDecodeError, KeyError) as e: raise DataError(e) info('Weather: %s' % icon) return icon
def _request_icon(self, location): """Requests the current weather icon from the Dark Sky API.""" # Look up the weather forecast at the location. forecast_url = FORECAST_URL % (self.dark_sky_api_key, location.latitude, location.longitude) try: forecast = get(forecast_url).json() except (RequestException, JSONDecodeError) as e: raise DataError(e) # Get the icon encoding the current weather. icon = forecast['currently']['icon'] info('Weather: %s' % icon) return icon
def directions(self, user): """Gets the directions from the user's home to work.""" # Get the user's addresses. try: home = user.get('home') work = user.get('work') travel_mode = user.get('travel_mode') except KeyError as e: raise DataError(e) # Make the Directions API request. directions_url = self._route_url(home, work, travel_mode) directions = get(directions_url).json() return directions
def is_daylight(self, user): """Calculate whether the sun is currently up.""" # Find the sunrise and sunset times for today. time = self._local_time.now(user) zone = self._local_time.zone(user) try: home = self._astral[user.get('home')] except (AstralError, KeyError) as e: raise DataError(e) sunrise = home.sunrise(time).astimezone(zone) sunset = home.sunset(time).astimezone(zone) is_daylight = time > sunrise and time < sunset info('Daylight: %s (%s)' % (is_daylight, time.strftime('%A %B %d %Y %H:%M:%S %Z'))) return is_daylight
def _download_map(self, width, height, polyline=None, markers=None, marker_icon=None, hide_map=False): """Downloads the image data from the Google Static Map API.""" image_url = self._static_map_url(width, height, polyline=polyline, markers=markers, marker_icon=marker_icon, hide_map=hide_map) try: return get(image_url).content except RequestException as e: raise DataError(e)
def map_image(self, width, height, polyline=None, markers=None, marker_icon=None): """Creates a map image with optional route or markers.""" # Get the static map as an image. image_data = self._download_map(width, height, polyline=polyline, markers=markers, marker_icon=marker_icon) with BytesIO(image_data) as buffer: image = Image.open(buffer).convert('RGB') # Catch map size restrictions. if image.width != width or image.height != height: raise DataError('Requested a %dx%d map but got %dx%d. Try this: ht' 'tps://developers.google.com/maps/documentation/ma' 'ps-static/start#Largerimagesizes' % (width, height, image.width, image.height)) # NOTE: Unfortunately, making the copyright text readable has been # interpreted as being against the Google Maps Platform Terms of # Service: https://cloud.google.com/maps-platform/terms # # Replace the copyright text with a more readable pixel font. # copyright_text = self._copyright_text(width, height, # polyline=polyline, # markers=markers, # marker_icon=marker_icon) # draw_text(copyright_text, # font_spec=SCREENSTAR_SMALL_REGULAR, # text_color=COPYRIGHT_TEXT_COLOR, # anchor='bottom_right', # box_color=COPYRIGHT_BOX_COLOR, # box_padding=COPYRIGHT_BOX_PADDING, # image=image) return image
def rewrite_cron(self, cron, reference, user, forward=True): """Replaces references to sunrise and sunset in a cron expression.""" # Skip if there is nothing to rewrite. if 'sunrise' not in cron and 'sunset' not in cron: return cron # Determine the two days surrounding the cron expression for the # reference time, which covers all candidate sunrises and sunsets. yesterday = reference - timedelta(days=2) midnight_cron = cron.replace('sunrise', '0 0').replace('sunset', '0 0') try: prev_day = croniter(midnight_cron, yesterday).get_next(datetime) current_day = croniter(midnight_cron, prev_day).get_next(datetime) next_day = croniter(midnight_cron, current_day).get_next(datetime) except ValueError as e: raise DataError(e) zone = self._local_time.zone(user) try: home = self._astral[user.get('home')] except (AstralError, KeyError) as e: raise DataError(e) # Set the candidate days and filter based on direction candidate_days = [] direction_filter = None sorter = None def forward_filter(x): return x >= reference def backward_filter(x): return x <= reference if forward: candidate_days.extend([current_day, next_day]) direction_filter = forward_filter sorter = min else: candidate_days.extend([prev_day, current_day]) direction_filter = backward_filter sorter = max # Calculate the closest sunrise time and replace the term in the # cron expression with minutes and hours. if 'sunrise' in cron: sunrises = map( lambda x: home.sunrise(x).astimezone(zone), candidate_days) next_sunrise = sorter(filter(direction_filter, sunrises)) sunrise_cron = cron.replace('sunrise', '%d %d' % ( next_sunrise.minute, next_sunrise.hour)) info('Rewrote cron: (%s) -> (%s), reference %s' % ( cron, sunrise_cron, reference.strftime('%A %B %d %Y %H:%M:%S %Z'))) return sunrise_cron # Calculate the closest future sunset time and replace the term in the # cron expression with minutes and hours. if 'sunset' in cron: sunsets = map( lambda x: home.sunset(x).astimezone(zone), candidate_days) next_sunset = sorter(filter(direction_filter, sunsets)) sunset_cron = cron.replace('sunset', '%d %d' % (next_sunset.minute, next_sunset.hour)) info('Rewrote cron: (%s) -> (%s), reference %s' % ( cron, sunset_cron, reference.strftime('%A %B %d %Y %H:%M:%S %Z'))) return sunset_cron