# -*- coding: utf-8-*- import re import datetime import struct import urllib import feedparser import requests import bs4 from client.app_utils import getTimezone from semantic.dates import DateService WORDS = ["WEATHER", "TODAY", "TOMORROW"] def replaceAcronyms(text): """ Replaces some commonly-used acronyms for an improved verbal weather report. """ def parseDirections(text): words = { 'N': 'north', 'S': 'south', 'E': 'east', 'W': 'west', } output = [words[w] for w in list(text)] return ' '.join(output) acronyms = re.findall(r'\b([NESW]+)\b', text) for w in acronyms: text = text.replace(w, parseDirections(w)) text = re.sub(r'(\b\d+)F(\b)', '\g<1> Fahrenheit\g<2>', text) text = re.sub(r'(\b)mph(\b)', '\g<1>miles per hour\g<2>', text) text = re.sub(r'(\b)in\.', '\g<1>inches', text) return text def get_locations(): r = requests.get('http://www.wunderground.com/about/faq/' + 'international_cities.asp') soup = bs4.BeautifulSoup(r.text) data = soup.find(id="inner-content").find('pre').string # Data Stucture: # 00 25 location # 01 1 # 02 2 region # 03 1 # 04 2 country # 05 2 # 06 4 ID # 07 5 # 08 7 latitude # 09 1 # 10 7 logitude # 11 1 # 12 5 elevation # 13 5 wmo_id s = struct.Struct("25s1s2s1s2s2s4s5s7s1s7s1s5s5s") for line in data.splitlines()[3:]: row = s.unpack_from(line) info = {'name': row[0].strip(), 'region': row[2].strip(), 'country': row[4].strip(), 'latitude': float(row[8].strip()), 'logitude': float(row[10].strip()), 'elevation': int(row[12].strip()), 'id': row[6].strip(), 'wmo_id': row[13].strip()} yield info def get_forecast_by_name(location_name): entries = feedparser.parse("http://rss.wunderground.com/auto/rss_full/%s" % urllib.quote(location_name))['entries'] if entries: # We found weather data the easy way return entries else: # We try to get weather data via the list of stations for location in get_locations(): if location['name'] == location_name: return get_forecast_by_wmo_id(location['wmo_id']) def get_forecast_by_wmo_id(wmo_id): return feedparser.parse("http://rss.wunderground.com/auto/" + "rss_full/global/stations/%s.xml" % wmo_id)['entries'] def handle(text, mic, profile): """ Responds to user-input, typically speech text, with a summary of the relevant weather for the requested date (typically, weather information will not be available for days beyond tomorrow). Arguments: text -- user-input, typically transcribed speech mic -- used to interact with the user (for both input and output) profile -- contains information related to the user (e.g., phone number) """ forecast = None if 'wmo_id' in profile: forecast = get_forecast_by_wmo_id(str(profile['wmo_id'])) elif 'location' in profile: forecast = get_forecast_by_name(str(profile['location'])) if not forecast: mic.say("I'm sorry, I can't seem to access that information. Please " + "make sure that you've set your location on the dashboard.") return tz = getTimezone(profile) service = DateService(tz=tz) date = service.extractDay(text) if not date: date = datetime.datetime.now(tz=tz) weekday = service.__daysOfWeek__[date.weekday()] if date.weekday() == datetime.datetime.now(tz=tz).weekday(): date_keyword = "Today" elif date.weekday() == ( datetime.datetime.now(tz=tz).weekday() + 1) % 7: date_keyword = "Tomorrow" else: date_keyword = "On " + weekday output = None for entry in forecast: try: date_desc = entry['title'].split()[0].strip().lower() if date_desc == 'forecast': # For global forecasts date_desc = entry['title'].split()[2].strip().lower() weather_desc = entry['summary'] elif date_desc == 'current': # For first item of global forecasts continue else: # US forecasts weather_desc = entry['summary'].split('-')[1] if weekday == date_desc: output = date_keyword + \ ", the weather will be " + weather_desc + "." break except: continue if output: output = replaceAcronyms(output) mic.say(output) else: mic.say( "I'm sorry. I can't see that far ahead.") def isValid(text): """ Returns True if the text is related to the weather. Arguments: text -- user-input, typically transcribed speech """ return bool(re.search(r'\b(weathers?|temperature|forecast|outside|hot|' + r'cold|jacket|coat|rain)\b', text, re.IGNORECASE))