diff --git a/modules/Weather.py b/modules/Weather.py index 8e3903d..c884f32 100644 --- a/modules/Weather.py +++ b/modules/Weather.py @@ -1,6 +1,6 @@ # coding: utf-8 """ -Weather - query various weather services for info +Weather - query weather underground for info Copyright (C) 2010 Brian S. Stephan This program is free software: you can redistribute it and/or modify @@ -17,55 +17,192 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +import json +import MySQLdb as mdb import re - -from extlib import pywapi - -from urllib import quote -from urllib2 import URLError +import urllib2 from Module import Module class Weather(Module): """Provide weather lookup services to the bot.""" - def do(self, connection, event, nick, userhost, what, admin_unlocked): - """Query Google Weather for a location's weather.""" + def __init__(self, irc, config, server): + """Set up regexes and wunderground API.""" - match = re.search('^!weather\s+(.*)$', what) - if match: - query = match.group(1) + Module.__init__(self, irc, config, server) + + self.weatherre = re.compile("^!weather\s+(.*)$") + + # get the API key from config + api_key = self._get_api_key_from_db() + if api_key is None: + raise Exception("weather underground API key not found!") + + self.wu_base = 'http://api.wunderground.com/api/{0:s}/'.format(api_key) + + def db_init(self): + """Initialize the tiny table that stores the API key.""" + + version = self.db_module_registered(self.__class__.__name__) + if version == None: + db = self.get_db() try: - google_weather = pywapi.get_weather_from_google(quote(query)) - city = google_weather['forecast_information']['city'].encode('utf-8') - condition = google_weather['current_conditions']['condition'].encode('utf-8') - temp_f = google_weather['current_conditions']['temp_f'].encode('utf-8') - temp_c = google_weather['current_conditions']['temp_c'].encode('utf-8') - wind = google_weather['current_conditions']['wind_condition'].encode('utf-8') - humidity = google_weather['current_conditions']['humidity'].encode('utf-8') + version = 1 + cur = db.cursor(mdb.cursors.DictCursor) + cur.execute(''' + CREATE TABLE weather_settings ( + api_key VARCHAR(256) NOT NULL + ) + ''') - wind_speed_regex = re.compile("Wind: [NSEW]+ at ([0-9]+) mph") - matches = wind_speed_regex.search(wind) + db.commit() + self.db_register_module_version(self.__class__.__name__, version) + except mdb.Error as e: + db.rollback() + self.log.error("database error trying to create tables") + self.log.exception(e) + raise + finally: cur.close() - if matches is not None and float(temp_f) < 50.0: - wind_speed = matches.group(1) - windchill = 35.74 + (0.6215 * float(temp_f)) - (35.75 * float(wind_speed)**0.16) + (0.4275 * float(temp_f) * float(wind_speed)**0.16) - else: - windchill = "" + def do(self, connection, event, nick, userhost, what, admin_unlocked): + """Handle IRC input for a weather queries.""" - weatherstr = "Current weather for " + city + ": " + condition + ". " + temp_f + "°F (" + temp_c + "°C), " + wind + ", " + humidity - if windchill is not "": - weatherstr += ", Wind chill: " + str(round(windchill, 2)) + "°F" - weatherstr += "." + match = self.weatherre.search(what) + if match: + # see if there was a specific command given in the query, first + query = match.group(1) + queryitems = query.split(" ") + if len(queryitems) <= 0: + return - for city in google_weather['forecasts']: - weatherstr += " " + city['day_of_week'].encode('utf-8') + ": " + city['condition'].encode('utf-8') + ". High " + city['high'].encode('utf-8') + "°F, Low " + city['low'].encode('utf-8') + "°F." + # search for commands + if queryitems[0] == "conditions": + # current weather query + results = self.get_conditions_for_query(queryitems[1:]) + return self.reply(connection, event, results) + else: + # assume they wanted current weather + results = self.get_conditions_for_query(queryitems) + return self.reply(connection, event, results) - return self.reply(connection, event, weatherstr) - except URLError as e: - return self.reply(connection, event, "error connecting to google weather:" + str(e)) - except IndexError as e: - return self.reply(connection, event, "error in pywapi: " + str(e)) + def get_conditions_for_query(self, queryitems): + """Make a wunderground conditions call, return as string.""" + + # recombine the query into a string + query = ' '.join(queryitems) + query = query.replace(' ', '_') + + try: + url = self.wu_base + ('{0:s}/q/{1:s}.json'.format('conditions', + query)) + json_resp = urllib2.urlopen(url) + condition_data = json.load(json_resp) + except IOError as e: + self.log.error("error while making conditions query") + self.log.exception(e) + raise + + # condition data is loaded. the rest of this is obviously specific to + # http://www.wunderground.com/weather/api/d/docs?d=data/conditions + self.log.debug(condition_data) + + try: + # just see if we have current_observation data + current = condition_data['current_observation'] + except KeyError as e: + self.log.error("error or bad query in conditions lookup") + self.log.exception(e) + return "No results." + else: + try: + location = current['display_location']['full'] + reply = "Conditions for {0:s}: ".format(location) + + weather_str = current['weather'] + if weather_str != '': + reply += "{0:s}, ".format(weather_str) + + temp_f = current['temp_f'] + temp_c = current['temp_c'] + temp_str = current['temperature_string'] + if temp_f != '' and temp_c != '': + reply += "{0:.1f}°F ({1:.1f}°C)".format(temp_f, temp_c) + elif temp_str != '': + reply += "{0:s}".format(temp_str) + + # append feels like if we have it + feelslike_f = current['feelslike_f'] + feelslike_c = current['feelslike_c'] + feelslike_str = current['feelslike_string'] + if feelslike_f != '' and feelslike_c != '': + reply += ", feels like {0:s}°F ({1:s}°C)".format(feelslike_f, feelslike_c) + elif feelslike_str != '': + reply += ", feels like {0:s}".format(feelslike_str) + + # whether this is current or current + feelslike, terminate sentence + reply += ". " + + humidity_str = current['relative_humidity'] + if humidity_str != '': + reply += "Humidity: {0:s}. ".format(humidity_str) + + wind_str = current['wind_string'] + if wind_str != '': + reply += "Wind: {0:s}. ".format(wind_str) + + pressure_in = current['pressure_in'] + pressure_trend = current['pressure_trend'] + if pressure_in != '': + reply += "Pressure: {0:s}\"".format(pressure_in) + if pressure_trend != '': + reply += " and {0:s}".format("dropping" if pressure_trend == '-' else "rising") + reply += ". " + + heat_index_str = current['heat_index_string'] + if heat_index_str != '' and heat_index_str != 'NA': + reply += "Heat index: {0:s}. ".format(heat_index_str) + + windchill_str = current['windchill_string'] + if windchill_str != '' and windchill_str != 'NA': + reply += "Wind chill: {0:s}. ".format(windchill_str) + + visibility_mi = current['visibility_mi'] + if visibility_mi != '': + reply += "Visibility: {0:s} miles. ".format(visibility_mi) + + precip_in = current['precip_today_in'] + if precip_in != '': + reply += "Precipitation today: {0:s}\". ".format(precip_in) + + observation_time = current['observation_time'] + if observation_time != '': + reply += "{0:s}. ".format(observation_time) + + return reply.rstrip() + except KeyError as e: + self.log.error("error or unexpected results in conditions reply") + self.log.exception(e) + return "Error parsing results." + + def _get_api_key_from_db(self): + """Get the API key string from the database, or None if unset.""" + + api_key = None + db = self.get_db() + try: + cur = db.cursor(mdb.cursors.DictCursor) + query = '''SELECT api_key FROM weather_settings''' + cur.execute(query) + value = cur.fetchone() + if (value != None): + api_key = value['api_key'] + except mdb.Error as e: + self.log.error("database error during api key retrieval") + self.log.exception(e) + finally: cur.close() + + return api_key # vi:tabstop=4:expandtab:autoindent # kate: indent-mode python;indent-width 4;replace-tabs on;