1
|
|
|
import requests |
2
|
|
|
import re |
3
|
|
|
import json |
4
|
|
|
from event import Event |
5
|
|
|
try: |
6
|
|
|
from basemodule import BaseModule |
7
|
|
|
except ImportError: |
8
|
|
|
from modules.basemodule import BaseModule |
9
|
|
|
|
10
|
|
|
class Weather2(BaseModule): |
11
|
|
|
|
12
|
|
|
def post_init(self): |
13
|
|
|
weather2 = Event('__.weather2__') |
14
|
|
|
weather2.define(msg_definition='^\.[Ww]eather') |
15
|
|
|
weather2.subscribe(self) |
16
|
|
|
|
17
|
|
|
forecast = Event('__.forecast__') |
18
|
|
|
forecast.define(msg_definition='^\.[Ff]orecast') |
19
|
|
|
forecast.subscribe(self) |
20
|
|
|
|
21
|
|
|
self.bot.register_event(forecast, self) |
22
|
|
|
self.bot.register_event(weather2, self) |
23
|
|
|
self.api_key = "1fe31b3b4cfdab66" |
24
|
|
|
|
25
|
|
|
def forecast(self, url, channel): |
26
|
|
|
q = requests.get(url) |
27
|
|
|
try: |
28
|
|
|
q.raise_for_status() |
29
|
|
|
except requests.exceptions.HTTPError: |
30
|
|
|
self.say(channel, "Encountered an error with the WUnderground API") |
31
|
|
|
return None |
32
|
|
|
results = q.json() |
33
|
|
|
# we're only doing two days for now |
34
|
|
|
counter = 0 |
35
|
|
|
phrase = "" |
36
|
|
|
for item in results['forecast']['txt_forecast']['forecastday']: |
37
|
|
|
if counter > 3: |
38
|
|
|
break |
39
|
|
|
phrase = phrase + item['title'] + ": " + item['fcttext'] + " " |
40
|
|
|
counter += 1 |
41
|
|
|
|
42
|
|
|
print phrase |
43
|
|
|
return phrase[:-1] # super hackish way to remove the trailing comma |
44
|
|
|
|
45
|
|
|
|
46
|
|
|
def api_request(self, location, channel, command="conditions"): |
47
|
|
|
"""location is a search string after the .weather command. This function |
48
|
|
|
will determine whether it is a zip code or a named location and return an |
49
|
|
|
appropriate API call""" |
50
|
|
|
#note for future: if wanted, add further detection of conditions or forecast searching |
51
|
|
|
query = None |
52
|
|
|
try: |
53
|
|
|
# test if is a zipcode or a single city name |
54
|
|
|
a = float(location.split()[0]) |
55
|
|
|
if a and len(location.split()[0]) < 5: |
56
|
|
|
self.say(channel,"valid zipcode required, numbnuts") |
57
|
|
|
return None |
58
|
|
|
zipcode = re.match('[0-9]{5}', location) |
59
|
|
|
query = '/q/'+zipcode.string |
60
|
|
|
except ValueError: # it's a city name (or broken encoding on numbers or something) |
61
|
|
|
#create autocomplete search |
62
|
|
|
q = requests.get('http://autocomplete.wunderground.com/aq', params={'query':location}) |
63
|
|
|
try: |
64
|
|
|
q.raise_for_status() |
65
|
|
|
except requests.exceptions.HTTPError: |
66
|
|
|
self.say(channel, "Encountered an error contacting the WUnderground API") |
67
|
|
|
return None |
68
|
|
|
results = q.json() |
69
|
|
|
try: |
70
|
|
|
#attempt to grab the 'l' field from the first result |
71
|
|
|
#assuming it exists, this field will be what we use to search the conditions api |
72
|
|
|
query = results['RESULTS'][0]['l'] |
73
|
|
|
except (IndexError, KeyError): |
74
|
|
|
#in case there were no results, let channel know |
75
|
|
|
self.say(channel, "No results found") |
76
|
|
|
return None |
77
|
|
|
|
78
|
|
|
if query: |
79
|
|
|
#return the full URL of the query we want to make |
80
|
|
|
return 'http://api.wunderground.com/api/'+self.api_key+'/' + command + query+'.json' |
81
|
|
|
return None |
82
|
|
|
|
83
|
|
|
def get_conditions(self, query, channel): |
84
|
|
|
"""given a fully formed query to the wundeground API, format an output string""" |
85
|
|
|
r = requests.get(query) |
86
|
|
|
try: |
87
|
|
|
r.raise_for_status() |
88
|
|
|
except requests.exceptions.HTTPError: |
89
|
|
|
self.say(channel, "Encountered an error contacting the WUnderground API") |
90
|
|
|
return |
91
|
|
|
weather = r.json() |
92
|
|
|
try: |
93
|
|
|
#grab the relevant data we want for formatting |
94
|
|
|
location = weather['current_observation']['display_location']['full'] |
95
|
|
|
conditions = weather['current_observation']['weather'] |
96
|
|
|
temp_f = str(weather['current_observation']['temp_f']) |
97
|
|
|
temp_c = str(weather['current_observation']['temp_c']) |
98
|
|
|
humidity = weather['current_observation']['relative_humidity'] |
99
|
|
|
except KeyError: |
100
|
|
|
self.say(channel, "Unable to get weather data from results. Sorry.") |
101
|
|
|
return |
102
|
|
|
#return the formatted string of weather data |
103
|
|
|
return location + ': ' + conditions + ', ' + temp_f + 'F (' + temp_c + 'C). Humidity: ' + humidity |
104
|
|
|
|
105
|
|
|
|
106
|
|
|
def handle(self, event): |
107
|
|
|
#split the line beginning with .weather into 2 parts, the command and the search string |
108
|
|
|
weather_line = event.msg.split(None, 1) |
109
|
|
|
if len(weather_line) > 1: |
110
|
|
|
if event.msg.startswith(".forecast"): |
111
|
|
|
self.say(event.channel, "forecast " + weather_line[1] + ": " + self.forecast(self.api_request(weather_line[1], event.channel, "forecast"), event.channel)) |
112
|
|
|
return |
113
|
|
|
|
114
|
|
|
#if we're sure there's actually a search string, then continue |
115
|
|
|
query = self.api_request(weather_line[1], event.channel) |
116
|
|
|
if not query: |
117
|
|
|
return |
118
|
|
|
weather = self.get_conditions(query, event.channel) |
119
|
|
|
if not weather: |
120
|
|
|
return |
121
|
|
|
self.say(event.channel, weather) |
122
|
|
|
else: |
123
|
|
|
#chastise the user for being silly and not actually searching for a location |
124
|
|
|
self.say(event.channel, "It would help if you supplied an actual location to search for.") |
125
|
|
|
|