diff --git a/plugins/Http.py b/plugins/Http.py index fe04da273..7508a3780 100644 --- a/plugins/Http.py +++ b/plugins/Http.py @@ -186,137 +186,6 @@ class Http(callbacks.Privmsg): m = 'I couldn\'t find a listing for %s' % symbol irc.error(msg, m) - _cityregex = re.compile( - r''\ - r'(.*?), (.*?),(.*?)', re.IGNORECASE) - _interregex = re.compile( - r''\ - r'([^,]+), ([^<]+)', re.IGNORECASE) - _condregex = re.compile( - r''\ - r'([^<]+)', re.IGNORECASE) - _tempregex = re.compile( - r''\ - r'([^<]+)', re.IGNORECASE) - _chillregex = re.compile( - r'Wind Chill:\s+'\ - r'([^<]+)', re.I | re.S) - _heatregex = re.compile( - r'Heat Index:\s+'\ - r'([^<]+)', re.I | re.S) - # States - _realStates = sets.Set(['ak', 'al', 'ar', 'az', 'ca', 'co', 'ct', - 'dc', 'de', 'fl', 'ga', 'hi', 'ia', 'id', - 'il', 'in', 'ks', 'ky', 'la', 'ma', 'md', - 'me', 'mi', 'mn', 'mo', 'ms', 'mt', 'nc', - 'nd', 'ne', 'nh', 'nj', 'nm', 'nv', 'ny', - 'oh', 'ok', 'or', 'pa', 'ri', 'sc', 'sd', - 'tn', 'tx', 'ut', 'va', 'vt', 'wa', 'wi', - 'wv', 'wy']) - # Provinces. (Province being a metric state measurement mind you. :D) - _fakeStates = sets.Set(['ab', 'bc', 'mb', 'nb', 'nf', 'ns', 'nt', - '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): - """ - - Returns the approximate weather conditions for a given city. - """ - - #If we received more than one argument, then we have received - #a city and state argument that we need to process. - 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() - state = state.lower() - city = '+'.join(args) - city = city.rstrip(',') - city = city.lower() - #We must break the States up into two sections. The US and - #Canada are the only countries that require a State argument. - - if state in self._realStates: - country = 'us' - elif state in self._fakeStates: - country = 'ca' - else: - country = state - state = '' - if country in self._countryMap.keys(): - country = self._countryMap[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) - html = webutils.getUrl(url) - if 'was not found' in html: - url = 'http://www.hamweather.net/cgi-bin/hw3/hw3.cgi?'\ - 'pass=&dpp=&forecast=zandh&config=&'\ - 'place=%s&state=&country=%s' % \ - (city, state) - html = webutils.getUrl(url) - if 'was not found' in html: - irc.error(msg, 'No such location could be found.') - return - - #We received a single argument. Zipcode or station id. - else: - zip = privmsgs.getArgs(args) - zip = zip.replace(',', '') - zip = zip.lower().split() - url = 'http://www.hamweather.net/cgi-bin/hw3/hw3.cgi?'\ - 'config=&forecast=zandh&pands=%s&Submit=GO' % args[0] - html = webutils.getUrl(url) - if 'was not found' in html: - irc.error(msg, 'No such location could be found.') - return - - headData = self._cityregex.search(html) - if headData: - (city, state, country) = headData.groups() - else: - headData = self._interregex.search(html) - if headData: - (city, state) = headData.groups() - else: - irc.error(msg, 'No such location could be found.') - return - - city = city.strip() - state = state.strip() - temp = self._tempregex.search(html) - if temp: - temp = temp.group(1) - conds = self._condregex.search(html) - if conds: - conds = conds.group(1) - chill = self._chillregex.search(html) - if chill: - chill = chill.group(1) - heat = self._heatregex.search(html) - if heat: - heat = heat.group(1) - - if heat[:-2] > temp[:-2]: - index = ' (Heat Index: %s)' % heat - elif chill[:-2] < temp[:-2]: - index = ' (Wind Chill: %s)' % chill - else: - index = '' - - if temp and conds and city and state: - conds = conds.replace('Tsra', 'Thunder Storms') - s = 'The current temperature in %s, %s is %s%s. Conditions: %s' % \ - (city, state, temp, index, conds) - irc.reply(msg, s) - else: - irc.error(msg, 'The format of the page was odd.') - _mlgeekquotere = re.compile('

(.*?)

', re.M | re.DOTALL) def geekquote(self, irc, msg, args): """[] diff --git a/plugins/Weather.py b/plugins/Weather.py new file mode 100644 index 000000000..3319f248c --- /dev/null +++ b/plugins/Weather.py @@ -0,0 +1,198 @@ +#!/n/bronze/7/fincher/bin/python + +### +# Copyright (c) 2002, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +""" +This plugin does weather-related stuff. +""" + +import plugins + +import re +import sets + +import utils +import webutils +import privmsgs +import callbacks + + +def configure(onStart, afterConnect, advanced): + # This will be called by setup.py to configure this module. onStart and + # afterConnect are both lists. Append to onStart the commands you would + # like to be run when the bot is started; append to afterConnect the + # commands you would like to be run when the bot has finished connecting. + from questions import expect, anything, something, yn + onStart.append('load Weather') + + +class Weather(callbacks.Privmsg): + threaded = True + def callCommand(self, method, irc, msg, *L): + try: + callbacks.Privmsg.callCommand(self, method, irc, msg, *L) + except webutils.WebError, e: + irc.error(msg, str(e)) + + _cityregex = re.compile( + r''\ + r'(.*?), (.*?),(.*?)', re.IGNORECASE) + _interregex = re.compile( + r''\ + r'([^,]+), ([^<]+)', re.IGNORECASE) + _condregex = re.compile( + r''\ + r'([^<]+)', re.IGNORECASE) + _tempregex = re.compile( + r''\ + r'([^<]+)', re.IGNORECASE) + _chillregex = re.compile( + r'Wind Chill:\s+'\ + r'([^<]+)', re.I | re.S) + _heatregex = re.compile( + r'Heat Index:\s+'\ + r'([^<]+)', re.I | re.S) + # States + _realStates = sets.Set(['ak', 'al', 'ar', 'az', 'ca', 'co', 'ct', + 'dc', 'de', 'fl', 'ga', 'hi', 'ia', 'id', + 'il', 'in', 'ks', 'ky', 'la', 'ma', 'md', + 'me', 'mi', 'mn', 'mo', 'ms', 'mt', 'nc', + 'nd', 'ne', 'nh', 'nj', 'nm', 'nv', 'ny', + 'oh', 'ok', 'or', 'pa', 'ri', 'sc', 'sd', + 'tn', 'tx', 'ut', 'va', 'vt', 'wa', 'wi', + 'wv', 'wy']) + # Provinces. (Province being a metric state measurement mind you. :D) + _fakeStates = sets.Set(['ab', 'bc', 'mb', 'nb', 'nf', 'ns', 'nt', + '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): + """ + + Returns the approximate weather conditions for a given city. + """ + + #If we received more than one argument, then we have received + #a city and state argument that we need to process. + 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() + state = state.lower() + city = '+'.join(args) + city = city.rstrip(',') + city = city.lower() + #We must break the States up into two sections. The US and + #Canada are the only countries that require a State argument. + + if state in self._realStates: + country = 'us' + elif state in self._fakeStates: + country = 'ca' + else: + country = state + state = '' + if country in self._countryMap.keys(): + country = self._countryMap[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) + html = webutils.getUrl(url) + if 'was not found' in html: + url = 'http://www.hamweather.net/cgi-bin/hw3/hw3.cgi?'\ + 'pass=&dpp=&forecast=zandh&config=&'\ + 'place=%s&state=&country=%s' % \ + (city, state) + html = webutils.getUrl(url) + if 'was not found' in html: + irc.error(msg, 'No such location could be found.') + return + + #We received a single argument. Zipcode or station id. + else: + zip = privmsgs.getArgs(args) + zip = zip.replace(',', '') + zip = zip.lower().split() + url = 'http://www.hamweather.net/cgi-bin/hw3/hw3.cgi?'\ + 'config=&forecast=zandh&pands=%s&Submit=GO' % args[0] + html = webutils.getUrl(url) + if 'was not found' in html: + irc.error(msg, 'No such location could be found.') + return + + headData = self._cityregex.search(html) + if headData: + (city, state, country) = headData.groups() + else: + headData = self._interregex.search(html) + if headData: + (city, state) = headData.groups() + else: + irc.error(msg, 'No such location could be found.') + return + + city = city.strip() + state = state.strip() + temp = self._tempregex.search(html) + if temp: + temp = temp.group(1) + conds = self._condregex.search(html) + if conds: + conds = conds.group(1) + chill = self._chillregex.search(html) + if chill: + chill = chill.group(1) + heat = self._heatregex.search(html) + if heat: + heat = heat.group(1) + + if heat[:-2] > temp[:-2]: + index = ' (Heat Index: %s)' % heat + elif chill[:-2] < temp[:-2]: + index = ' (Wind Chill: %s)' % chill + else: + index = '' + + if temp and conds and city and state: + conds = conds.replace('Tsra', 'Thunder Storms') + s = 'The current temperature in %s, %s is %s%s. Conditions: %s' % \ + (city, state, temp, index, conds) + irc.reply(msg, s) + else: + irc.error(msg, 'The format of the page was odd.') + + +Class = Weather + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/test/test_Weather.py b/test/test_Weather.py new file mode 100644 index 000000000..30439633f --- /dev/null +++ b/test/test_Weather.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +### +# Copyright (c) 2002, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +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') + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: +