PageRenderTime 61ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/client/modules/Weather.py

https://gitlab.com/leiftomas/jasper-client
Python | 172 lines | 106 code | 25 blank | 41 comment | 22 complexity | 30b03452742302d3169e93c7e0089cac MD5 | raw file
  1. # -*- coding: utf-8-*-
  2. import re
  3. import datetime
  4. import struct
  5. import urllib
  6. import feedparser
  7. import requests
  8. import bs4
  9. from client.app_utils import getTimezone
  10. from semantic.dates import DateService
  11. WORDS = ["WEATHER", "TODAY", "TOMORROW"]
  12. def replaceAcronyms(text):
  13. """
  14. Replaces some commonly-used acronyms for an improved verbal weather report.
  15. """
  16. def parseDirections(text):
  17. words = {
  18. 'N': 'north',
  19. 'S': 'south',
  20. 'E': 'east',
  21. 'W': 'west',
  22. }
  23. output = [words[w] for w in list(text)]
  24. return ' '.join(output)
  25. acronyms = re.findall(r'\b([NESW]+)\b', text)
  26. for w in acronyms:
  27. text = text.replace(w, parseDirections(w))
  28. text = re.sub(r'(\b\d+)F(\b)', '\g<1> Fahrenheit\g<2>', text)
  29. text = re.sub(r'(\b)mph(\b)', '\g<1>miles per hour\g<2>', text)
  30. text = re.sub(r'(\b)in\.', '\g<1>inches', text)
  31. return text
  32. def get_locations():
  33. r = requests.get('http://www.wunderground.com/about/faq/' +
  34. 'international_cities.asp')
  35. soup = bs4.BeautifulSoup(r.text)
  36. data = soup.find(id="inner-content").find('pre').string
  37. # Data Stucture:
  38. # 00 25 location
  39. # 01 1
  40. # 02 2 region
  41. # 03 1
  42. # 04 2 country
  43. # 05 2
  44. # 06 4 ID
  45. # 07 5
  46. # 08 7 latitude
  47. # 09 1
  48. # 10 7 logitude
  49. # 11 1
  50. # 12 5 elevation
  51. # 13 5 wmo_id
  52. s = struct.Struct("25s1s2s1s2s2s4s5s7s1s7s1s5s5s")
  53. for line in data.splitlines()[3:]:
  54. row = s.unpack_from(line)
  55. info = {'name': row[0].strip(),
  56. 'region': row[2].strip(),
  57. 'country': row[4].strip(),
  58. 'latitude': float(row[8].strip()),
  59. 'logitude': float(row[10].strip()),
  60. 'elevation': int(row[12].strip()),
  61. 'id': row[6].strip(),
  62. 'wmo_id': row[13].strip()}
  63. yield info
  64. def get_forecast_by_name(location_name):
  65. entries = feedparser.parse("http://rss.wunderground.com/auto/rss_full/%s"
  66. % urllib.quote(location_name))['entries']
  67. if entries:
  68. # We found weather data the easy way
  69. return entries
  70. else:
  71. # We try to get weather data via the list of stations
  72. for location in get_locations():
  73. if location['name'] == location_name:
  74. return get_forecast_by_wmo_id(location['wmo_id'])
  75. def get_forecast_by_wmo_id(wmo_id):
  76. return feedparser.parse("http://rss.wunderground.com/auto/" +
  77. "rss_full/global/stations/%s.xml"
  78. % wmo_id)['entries']
  79. def handle(text, mic, profile):
  80. """
  81. Responds to user-input, typically speech text, with a summary of
  82. the relevant weather for the requested date (typically, weather
  83. information will not be available for days beyond tomorrow).
  84. Arguments:
  85. text -- user-input, typically transcribed speech
  86. mic -- used to interact with the user (for both input and output)
  87. profile -- contains information related to the user (e.g., phone
  88. number)
  89. """
  90. forecast = None
  91. if 'wmo_id' in profile:
  92. forecast = get_forecast_by_wmo_id(str(profile['wmo_id']))
  93. elif 'location' in profile:
  94. forecast = get_forecast_by_name(str(profile['location']))
  95. if not forecast:
  96. mic.say("I'm sorry, I can't seem to access that information. Please " +
  97. "make sure that you've set your location on the dashboard.")
  98. return
  99. tz = getTimezone(profile)
  100. service = DateService(tz=tz)
  101. date = service.extractDay(text)
  102. if not date:
  103. date = datetime.datetime.now(tz=tz)
  104. weekday = service.__daysOfWeek__[date.weekday()]
  105. if date.weekday() == datetime.datetime.now(tz=tz).weekday():
  106. date_keyword = "Today"
  107. elif date.weekday() == (
  108. datetime.datetime.now(tz=tz).weekday() + 1) % 7:
  109. date_keyword = "Tomorrow"
  110. else:
  111. date_keyword = "On " + weekday
  112. output = None
  113. for entry in forecast:
  114. try:
  115. date_desc = entry['title'].split()[0].strip().lower()
  116. if date_desc == 'forecast':
  117. # For global forecasts
  118. date_desc = entry['title'].split()[2].strip().lower()
  119. weather_desc = entry['summary']
  120. elif date_desc == 'current':
  121. # For first item of global forecasts
  122. continue
  123. else:
  124. # US forecasts
  125. weather_desc = entry['summary'].split('-')[1]
  126. if weekday == date_desc:
  127. output = date_keyword + \
  128. ", the weather will be " + weather_desc + "."
  129. break
  130. except:
  131. continue
  132. if output:
  133. output = replaceAcronyms(output)
  134. mic.say(output)
  135. else:
  136. mic.say(
  137. "I'm sorry. I can't see that far ahead.")
  138. def isValid(text):
  139. """
  140. Returns True if the text is related to the weather.
  141. Arguments:
  142. text -- user-input, typically transcribed speech
  143. """
  144. return bool(re.search(r'\b(weathers?|temperature|forecast|outside|hot|' +
  145. r'cold|jacket|coat|rain)\b', text, re.IGNORECASE))