diff --git a/plugins/Weather.py b/plugins/Weather.py index 158a60843..3a289c454 100644 --- a/plugins/Weather.py +++ b/plugins/Weather.py @@ -33,18 +33,53 @@ This plugin does weather-related stuff. """ +import urllib import plugins import re import sets +import conf import utils import webutils import privmsgs +import registry import callbacks +unitAbbrevs = utils.abbrev(['fahrenheit', 'celsius', 'centigrade', 'kelvin']) +unitAbbrevs['c'] = 'celsius' +unitAbbrevs['ce'] = 'celsius' + +class WeatherUnit(registry.String): + def set(self, s): + original = getattr(self, 'value', self.default) + registry.String.set(self, s) + s = self.value.lower() + if s not in unitAbbrevs: + setattr(self, 'value', original) + raise registry.InvalidRegistryValue,\ + 'Unit must be one of fahrenheit, celsius, or kelvin.' + +class WeatherCommand(registry.String): + def set(self, s): + original = getattr(self, 'value', self.default) + registry.String.set(self, s) + m = Weather.commands + if self.value not in m: + setattr(self, 'value', original) + raise registry.InvalidRegistryValue,\ + 'Command must be one of %s' % utils.commaAndify(m) + +conf.registerPlugin('Weather') +conf.registerChannelValue(conf.supybot.plugins.Weather, 'preferredUnit', + WeatherUnit('fahrenheit', """Sets the default temperature unit to use when + reporting the weather.""")) +conf.registerChannelValue(conf.supybot.plugins.Weather, 'weatherCommand', + WeatherCommand('cnn', """Sets the default command to use when retrieving + the weather.""")) class Weather(callbacks.Privmsg): + commands = ['ham', 'cnn'] threaded = True def callCommand(self, method, irc, msg, *L): try: @@ -52,6 +87,34 @@ class Weather(callbacks.Privmsg): except webutils.WebError, e: irc.error(str(e)) + def _getTemp(self, temp, deg, unit, chan): + default = self.registryValue('preferredUnit', chan).lower() + default = unitAbbrevs[default] + unit = unit.lower() + if unitAbbrevs[unit] == default: + return deg.join([str(temp), unit.upper()]) + try: + temp = int(temp) + except ValueError: + return deg.join([temp, unit]) + if unit == 'f': + temp = (temp - 32) * 5 / 9 + if default == 'kelvin': + temp = temp + 273.15 + unit = 'K' + deg = ' ' + else: + unit = 'C' + elif unit == 'c': + if default == 'kelvin': + temp = temp + 273.15 + unit = 'K' + deg = ' ' + elif default == 'fahrenheit': + temp = temp * 9 / 5 + 32 + unit = 'F' + return deg.join([str(temp), unit]) + _cityregex = re.compile( r'' r'(.*?), (.*?),(.*?)', re.I) @@ -86,8 +149,8 @@ class Weather(callbacks.Privmsg): 'nu', 'on', 'pe', 'qc', 'sk', 'yk']) # Certain countries are expected to use a standard abbreviation # The weather we pull uses weird codes. Map obvious ones here. - _countryMap = {'uk': 'gb'} - def weather(self, irc, msg, args): + _hamCountryMap = {'uk': 'gb'} + def ham(self, irc, msg, args): """ Returns the approximate weather conditions for a given city. @@ -115,8 +178,8 @@ class Weather(callbacks.Privmsg): else: country = state state = '' - if country in self._countryMap.keys(): - country = self._countryMap[country] + if country in self._hamCountryMap.keys(): + country = self._hamCountryMap[country] url = 'http://www.hamweather.net/cgi-bin/hw3/hw3.cgi?' \ 'pass=&dpp=&forecast=zandh&config=&' \ 'place=%s&state=%s&country=%s' % (city, state, country) @@ -134,9 +197,9 @@ class Weather(callbacks.Privmsg): else: zip = privmsgs.getArgs(args) zip = zip.replace(',', '') - zip = zip.lower().split() + zip = zip.lower() url = 'http://www.hamweather.net/cgi-bin/hw3/hw3.cgi?' \ - 'config=&forecast=zandh&pands=%s&Submit=GO' % args[0] + 'config=&forecast=zandh&pands=%s&Submit=GO' % zip html = webutils.getUrl(url) if 'was not found' in html: irc.error('No such location could be found.') @@ -158,6 +221,8 @@ class Weather(callbacks.Privmsg): temp = self._tempregex.search(html) if temp is not None: temp = temp.group(1) + (temp, deg, unit) = (temp[:-2], temp[-2], temp[-1]) + temp = self._getTemp(temp, deg, unit, msg.args[0]) conds = self._condregex.search(html) if conds is not None: conds = conds.group(1) @@ -181,6 +246,67 @@ class Weather(callbacks.Privmsg): else: irc.error('The format of the page was odd.') + _cnnUrl = 'http://weather.cnn.com/weather/search?wsearch=' + _fTemp = re.compile(r'(-?\d+°F)', re.I | re.S) + _conds = re.compile(r'align="center">([^<]+)', re.I|re.S) + _humidity = re.compile(r'Rel. Humidity: (\d+%)', re.I | re.S) + _wind = re.compile(r'Wind: ([^<]+)', re.I | re.S) + _location = re.compile(r'([^<]+)', re.I | re.S) + # Certain countries are expected to use a standard abbreviation + # The weather we pull uses weird codes. Map obvious ones here. + _cnnCountryMap = {'uk': 'en', 'de': 'ge'} + def cnn(self, irc, msg, args): + """ + + Returns the approximate weather conditions for a given city. + """ + if len(args) > 1: + #If we received more than 1 argument, then we got a city with a + #multi-word name. ie ['Garden', 'City', 'KS'] instead of + #['Liberal', 'KS']. We join it together with a + to pass + #to our query + state = args.pop().lower() + city = ' '.join(args) + city = city.rstrip(',').lower() + if state in self._cnnCountryMap: + state = self._cnnCountryMap[state] + loc = ' '.join([city, state]) + else: + #We received a single argument. Zipcode or station id. + zip = privmsgs.getArgs(args) + loc = zip.replace(',', '').lower() + url = '%s%s' % (self._cnnUrl, urllib.quote(loc)) + try: + text = webutils.getUrl(url) + except webutils.WebError, e: + irc.error(str(e)) + return + if "No search results" in text or "does not match a zip code" in text: + irc.error('No such location could be found.') + return + location = self._location.search(text) + temp = self._fTemp.search(text) + conds = self._conds.search(text) + humidity = self._humidity.search(text) + wind = self._wind.search(text) + if location and temp: + location = location.group(1) + location = location.split('-')[-1].strip() + temp = temp.group(1) + (temp, deg, unit) = (temp[:-2], temp[-2], temp[-1]) + temp = self._getTemp(temp, deg, unit, msg.args[0]) + resp = 'The current temperature in %s is %s.' % (location, temp) + resp = [resp] + if conds is not None: + resp.append('Conditions: %s.' % conds.group(1)) + if humidity is not None: + resp.append('Humidity: %s.' % humidity.group(1)) + if wind is not None: + resp.append('Wind: %s.' % wind.group(1)) + resp = map(utils.htmlToText, resp) + irc.reply(' '.join(resp)) + else: + irc.error('Could not find weather information.') Class = Weather diff --git a/test/test_Weather.py b/test/test_Weather.py index 419c3b1df..88cc774bc 100644 --- a/test/test_Weather.py +++ b/test/test_Weather.py @@ -34,19 +34,29 @@ from testsupport import * if network: class WeatherTest(PluginTestCase, PluginDocumentation): plugins = ('Weather',) - def testWeather(self): - self.assertNotError('weather Columbus, OH') - self.assertNotError('weather 43221') - self.assertNotRegexp('weather Paris, FR', 'Virginia') - self.assertError('weather alsdkfjasdl, asdlfkjsadlfkj') - self.assertNotError('weather London, uk') - self.assertNotError('weather London, UK') - self.assertNotError('weather Munich, de') - self.assertNotError('weather Tucson, AZ') - self.assertError('weather hell') + def testHam(self): + self.assertNotError('ham Columbus, OH') + self.assertNotError('ham 43221') + self.assertNotRegexp('ham Paris, FR', 'Virginia') + self.assertError('ham alsdkfjasdl, asdlfkjsadlfkj') + self.assertNotError('ham London, uk') + self.assertNotError('ham London, UK') + self.assertNotError('ham Munich, de') + self.assertNotError('ham Tucson, AZ') + self.assertError('ham hell') + + def testCnn(self): + self.assertNotError('cnn Columbus, OH') + self.assertNotError('cnn 43221') + self.assertNotRegexp('cnn Paris, FR', 'Virginia') + self.assertError('cnn alsdkfjasdl, asdlfkjsadlfkj') + self.assertNotError('cnn London, uk') + self.assertNotError('cnn London, UK') + self.assertNotError('cnn Munich, de') + self.assertNotError('cnn Tucson, AZ') def testNoEscapingWebError(self): - self.assertNotRegexp('weather "buenos aires"', 'WebError') + self.assertNotRegexp('ham "buenos aires"', 'WebError') # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: