def _send_messages(self, sub, msg, type): # Global silence if conf.get(conf.supybot.plugins.GitHubEventAnnounce.silence): return for chan in sub.channels: # See if we're under channel silence if conf.get(conf.supybot.plugins.GitHubEventAnnounce.silence, chan): # noqa return # Get config for event type in chan try: group = getattr(conf.supybot.plugins.GitHubEventAnnounce, "announce%ss" % (type)) except: e = sys.exc_info() logger.error("Failed to get config group for type %s" % (type)) logger.error(pp.pformat(e)) group = None # Allow if conf missing if group is None: event_allowed = True else: event_allowed = conf.get(group, chan) # Send allowed events if event_allowed: qmsg = ircmsgs.privmsg(chan, msg) sub.irc.queueMsg(qmsg)
def invalidCommand(self, irc, msg, tokens): assert not msg.repliedTo, 'repliedTo msg in Misc.invalidCommand.' assert self is irc.callbacks[-1], 'Misc isn\'t last callback.' self.log.debug('Misc.invalidCommand called (tokens %s)', tokens) channel = msg.args[0] # Only bother with the invaildCommand flood handling if it's actually # enabled if conf.supybot.abuse.flood.command.invalid(): # First, we check for invalidCommand floods. This is rightfully done # here since this will be the last invalidCommand called, and thus it # will only be called if this is *truly* an invalid command. maximum = conf.supybot.abuse.flood.command.invalid.maximum() banmasker = conf.supybot.protocols.irc.banmask.makeBanmask self.invalidCommands.enqueue(msg) if self.invalidCommands.len(msg) > maximum and \ not ircdb.checkCapability(msg.prefix, 'owner'): penalty = conf.supybot.abuse.flood.command.invalid.punishment() banmask = banmasker(msg.prefix) self.log.info('Ignoring %s for %s seconds due to an apparent ' 'invalid command flood.', banmask, penalty) if tokens and tokens[0] == 'Error:': self.log.warning('Apparent error loop with another Supybot ' 'observed. Consider ignoring this bot ' 'permanently.') ircdb.ignores.add(banmask, time.time() + penalty) if conf.supybot.abuse.flood.command.invalid.notify(): irc.reply('You\'ve given me %s invalid commands within ' 'the last minute; I\'m now ignoring you for %s.' % (maximum, utils.timeElapsed(penalty, seconds=False))) return # Now, for normal handling. if conf.get(conf.supybot.reply.whenNotCommand, channel): if len(tokens) >= 2: cb = irc.getCallback(tokens[0]) if cb: plugin = cb.name() irc.error(format('The %q plugin is loaded, but there is ' 'no command named %q in it. Try "list ' '%s" to see the commands in the %q ' 'plugin.', plugin, tokens[1], plugin, plugin)) else: irc.errorInvalid('command', tokens[0], repr=False) else: command = tokens and tokens[0] or '' irc.errorInvalid('command', command, repr=False) else: if tokens: # echo [] will get us an empty token set, but there's no need # to log this in that case anyway, it being a nested command. self.log.info('Not replying to %s, not a command.', tokens[0]) if irc.nested: bracketConfig = conf.supybot.commands.nested.brackets brackets = conf.get(bracketConfig, channel) if brackets: (left, right) = brackets irc.reply(left + ' '.join(tokens) + right) else: pass # Let's just do nothing, I can't think of better.
def _send_messages(self, sub, msg, type): # Global silence if conf.get(conf.supybot.plugins.GitHubEventAnnounce.silence): return for chan in sub.channels: # See if we're under channel silence if conf.get(conf.supybot.plugins.GitHubEventAnnounce.silence, chan): #noqa return # Get config for event type in chan try: group = getattr(conf.supybot.plugins.GitHubEventAnnounce, 'announce%ss' % (type)) except: e = sys.exc_info() logger.error('Failed to get config group for type %s' % (type)) logger.error(pp.pformat(e)) group = None # Allow if conf missing if group is None: event_allowed = True else: event_allowed = conf.get(group, chan) # Send allowed events if event_allowed: qmsg = ircmsgs.privmsg(chan, msg) sub.irc.queueMsg(qmsg)
def getIssue(self, irc, msg, match, force=False): """Get a Jira Issue""" if not ircutils.isChannel(msg.args[0]) and not force: return if conf.get(conf.supybot.plugins.Jira.lookup, msg.args[0]) == False: return issueName = match.group('issue') try: issue = self.jira[self.user].issue(issueName) except Exception as e: self.log.exception('Error loading issue.', e) irc.reply("Cannot find %s bug." % issueName) print("Invalid Jira snarf: %s" % issueName) return if issue: try: assignee = issue.fields.assignee.displayName except: assignee = "Unassigned" displayTime = display_time(issue.fields.timeestimate) url = ''.join((self.server, 'browse/', issue.key)) values = { "type": issue.fields.issuetype.name, "key": issue.key, "summary": issue.fields.summary, "status": _c(_b(issue.fields.status.name), "green"), "assignee": _c(assignee, "blue"), "displayTime": displayTime, "url": url, } replytext = (self.template % values) irc.reply(replytext, prefixNick=False)
def send(s): targetIRC = world.getIrc(relay.targetNetwork) if not targetIRC: self.log.info('LinkRelay: Not connected to network %s.' % relay.targetNetwork) elif targetIRC.zombie: self.log.info('LinkRelay: IRC %s appears to be a zombie'% relay.targetNetwork) elif irc.isChannel(relay.targetChannel) and \ relay.targetChannel not in targetIRC.state.channels: self.log.info('LinkRelay: I\'m not in in %s on %s' % (relay.targetChannel, relay.targetNetwork)) else: if isPrivmsg or \ self.registryValue('nonPrivmsgs', channel) == 'privmsg': f = ircmsgs.privmsg elif self.registryValue('nonPrivmsgs', channel) == 'notice': f = ircmsgs.notice else: return allowedLength = conf.get(conf.supybot.reply.mores.length, relay.targetChannel) or 470 cont = _('(continuation)') remainingLength = allowedLength - len(cont) - 1 head = s[0:allowedLength] tail = [cont + ' ' + s[i:i+remainingLength] for i in range(allowedLength, len(s), remainingLength)] for s in [head] + tail: msg = f(relay.targetChannel, s) msg.tag('relayedMsg') targetIRC.sendMsg(msg)
def _query_freebase(self, work_type, thing): key = conf.get(conf.supybot.plugins.Cast.FreebaseApiKey) props = FREEBASE_TYPES[work_type] url = "https://www.googleapis.com/freebase/v1/search?query=%s&type=%s&key=%s" % (web.urlquote(thing),props['type'],key) response = simplejson.loads(web.getUrl(url, headers=HEADERS)) if len(response['result']) == 0: return None else: fbid = response['result'][0]['id'] query = { "id": fbid, "type": props['type'], "name": None, "limit": 1 } query.update(props['subquery']) url = "https://www.googleapis.com/freebase/v1/mqlread?query=%s&key=%s" % (web.urlquote(simplejson.dumps(query)),key) response = simplejson.loads(web.getUrl(url, headers=HEADERS)) result = response['result'] if result is None: return None else: return({ 'props': props, 'url': "http://www.freebase.com" + result['id'], 'title': result['name'], 'characters': props['extractor'](result) })
def _query_freebase(self, work_type, thing): key = conf.get(conf.supybot.plugins.Cast.FreebaseApiKey) props = FREEBASE_TYPES[work_type] url = "https://www.googleapis.com/freebase/v1/search?query=%s&type=%s&key=%s" % ( web.urlquote(thing), props['type'], key) response = simplejson.loads(web.getUrl(url, headers=HEADERS)) if len(response['result']) == 0: return None else: fbid = response['result'][0]['id'] query = { "id": fbid, "type": props['type'], "name": None, "limit": 1 } query.update(props['subquery']) url = "https://www.googleapis.com/freebase/v1/mqlread?query=%s&key=%s" % ( web.urlquote(simplejson.dumps(query)), key) response = simplejson.loads(web.getUrl(url, headers=HEADERS)) result = response['result'] if result is None: return None else: return ({ 'props': props, 'url': "http://www.freebase.com" + result['id'], 'title': result['name'], 'characters': props['extractor'](result) })
def send(s): targetIRC = world.getIrc(relay.targetNetwork) if not targetIRC: self.log.info('LinkRelay: Not connected to network %s.' % relay.targetNetwork) elif targetIRC.zombie: self.log.info('LinkRelay: IRC %s appears to be a zombie' % relay.targetNetwork) elif irc.isChannel(relay.targetChannel) and \ relay.targetChannel not in targetIRC.state.channels: self.log.info('LinkRelay: I\'m not in in %s on %s' % (relay.targetChannel, relay.targetNetwork)) else: if isPrivmsg or \ self.registryValue('nonPrivmsgs', channel) == 'privmsg': f = ircmsgs.privmsg elif self.registryValue('nonPrivmsgs', channel) == 'notice': f = ircmsgs.notice else: return allowedLength = conf.get(conf.supybot.reply.mores.length, relay.targetChannel) or 470 cont = _('(continuation)') remainingLength = allowedLength - len(cont) - 1 head = s[0:allowedLength] tail = [ cont + ' ' + s[i:i + remainingLength] for i in range(allowedLength, len(s), remainingLength) ] for s in [head] + tail: msg = f(relay.targetChannel, s) msg.tag('relayedMsg') targetIRC.sendMsg(msg)
def api(self, irc, msg, args, nick, clas, func): """<class> [<function>] [<nick>] Link to API documentation for <class> (<function>), optionally telling it to <nick>""" # Set the reply to if its set if nick != None: # Don't allow people to tell to kohana-bot, he gets mad if re.match("kohana-bot",nick,flags=re.IGNORECASE): irc.reply("He doesn't care.") return msg.nick = nick # Build the link out = clas if type(func) == str: match = re.match("\$",func) if match is not None: out = out + "#property:" + func else: out = out + "#" + func msg = conf.get(conf.supybot.plugins.Kohanadocs.apilink) + out # And send it irc.reply(msg)
def quiet(self, irc, msg, args): """Turn polite-mode on.""" if conf.get(conf.supybot.plugins.Infobot.unaddressed.answerQuestions)\ or\ conf.get(conf.supybot.plugins.Infobot.unaddressed. replyExistingFactoid): if conf.get(conf.supybot.plugins.Infobot.personality): irc.reply("Sorry, {}, I'll try to stay " "quiet.".format(msg.nick), prefixNick=False) else: irc.reply("Entering polite mode.") conf.supybot.plugins.Infobot.unaddressed.answerQuestions.\ setValue(False) conf.supybot.plugins.Infobot.unaddressed.replyExistingFactoid.\ setValue(False) else: pass
def wake(self, irc, msg, args): """Turn off polite-mode.""" if conf.get(conf.supybot.plugins.Infobot.unaddressed.answerQuestions)\ or\ conf.get(conf.supybot.plugins.Infobot.unaddressed. replyExistingFactoid): pass else: if conf.get(conf.supybot.plugins.Infobot.personality): irc.reply("Good morning, {}.".format(msg.nick), prefixNick=False) else: irc.reply("Leaving polite mode.") conf.supybot.plugins.Infobot.unaddressed.answerQuestions.\ setValue(True) conf.supybot.plugins.Infobot.unaddressed.replyExistingFactoid.\ setValue(True)
def _getTemp(temp, deg, unit, chan): assert unit == unit.upper() assert temp == float(temp) default = conf.get(conf.supybot.plugins.Weather.temperatureUnit, chan) convert = conf.get(conf.supybot.plugins.Weather.convert, chan) # Short circuit if we're the same unit as the default or no conversion # has been requested if unitAbbrevs[unit] == default or not convert: return format('%0.1f%s%s', temp, deg, unit) temp = Weather._toCelsius(temp, unit) unit = 'C' if default == 'Kelvin': temp = temp + 273.15 unit = 'K' deg = ' ' elif default == 'Fahrenheit': temp = temp * 9 / 5 + 32 unit = 'F' return '%0.1f%s%s' % (temp, deg, unit)
def docs(self, irc, msg, args, nick, page): """<page> [<nick>] Link to the documentation for that <page>, optionally telling it to <nick>. If I don't recognize the page, I will try to guess""" # Set the reply to if its set if nick != None: # Don't allow people to tell to kohana-bot, he gets mad if re.match("kohana-bot",nick,flags=re.IGNORECASE): irc.reply("He doesn't care.") return msg.nick = nick # If no page specified, just return a link to the docs if page == None: msg = conf.get(conf.supybot.plugins.Kohanadocs.doclink) irc.reply(msg) return msg = conf.get(conf.supybot.plugins.Kohanadocs.doclink) + page irc.reply(msg)
def choose(self, irc, msg, args, choices): """<choice1> ... <choiceN> Randomly selects one of multiple choices. """ if choices is None: if conf.get(conf.supybot.plugins.Infobot.personality): irc.reply(choice(zero_choice_reply)) else: irc.reply(no_choice_reply) return options = [y.strip() for x in choices.split(' or ') for y in x.split(';') if y not in ['', ' ']] if len(options) is 1: if conf.get(conf.supybot.plugins.Infobot.personality): irc.reply(choice(one_choice_reply)) else: irc.reply(no_choice_reply) return else: irc.reply(choice(options))
def getCommandHelp(self, command, simpleSyntax=None): method = self.getCommandMethod(command) if method.im_func.func_name == "learn": chan = None if dynamic.msg is not None: chan = dynamic.msg.args[0] s = self.registryValue("learnSeparator", chan) help = callbacks.getHelp if simpleSyntax is None: simpleSyntax = conf.get(conf.supybot.reply.showSimpleSyntax, chan) if simpleSyntax: help = callbacks.getSyntax return help(method, doc=method._fake__doc__ % (s, s), name=callbacks.formatCommand(command)) return super(Factoids, self).getCommandHelp(command, simpleSyntax)
def _getTemp(temp, deg, unit, chan): assert unit == unit.upper() assert temp == int(temp) default = conf.get(conf.supybot.plugins.Weather.temperatureUnit, chan) if unitAbbrevs[unit] == default: # Short circuit if we're the same unit as the default. return format('%i%s%s', temp, deg, unit) temp = Weather._toCelsius(temp, unit) unit = 'C' if default == 'Kelvin': temp = temp + 273.15 unit = 'K' deg = ' ' elif default == 'Fahrenheit': temp = temp * 9 / 5 + 32 unit = 'F' return '%i%s%s' % (temp, deg, unit)
def getCommandHelp(self, command, simpleSyntax=None): method = self.getCommandMethod(command) if method.im_func.func_name == 'learn': chan = None if dynamic.msg is not None: chan = dynamic.msg.args[0] s = self.registryValue('learnSeparator', chan) help = callbacks.getHelp if simpleSyntax is None: simpleSyntax = conf.get(conf.supybot.reply.showSimpleSyntax, chan) if simpleSyntax: help = callbacks.getSyntax return help(method, doc=method._fake__doc__ % (s, s), name=callbacks.formatCommand(command)) return super(Factoids, self).getCommandHelp(command, simpleSyntax)
def random(self, irc, msg, args, optlist): """[--allow-english] Returns a random language supported by babelfish. If --allow-english is provided, will include English in the list of possible languages. """ allowEnglish = False for (option, arg) in optlist: if option == 'allow-english': allowEnglish = True languages = conf.get(conf.supybot.plugins.Babelfish.languages, msg.args[0]) if not languages: irc.error('I can\'t speak any other languages.', Raise=True) language = utils.iter.choice(languages) while not allowEnglish and language == 'English': language = utils.iter.choice(languages) irc.reply(language)
def time(self, irc, msg, args, channel, fmt, beats): """[<format>] <beats> Returns the current time represented by <beats> in <format>, or if <format> is not given, uses the configurable format for the current channel. """ if not fmt: if channel: fmt = conf.get(conf.supybot.reply.format.time, channel) else: fmt = conf.supybot.reply.format.get('time') midnight = arrow.now('UTC+1').replace(hour=0, minute=0, second=0, microsecond=0) seconds = float(midnight.strftime('%s')) + (beats * 86.4) irc.reply(time_.strftime(fmt, time_.localtime(seconds)))
def getChannelDb(irc, msg, args, state, **kwargs): channelSpecific = conf.supybot.databases.plugins.channelSpecific try: getChannel(irc, msg, args, state, **kwargs) channel = channelSpecific.getChannelLink(state.channel) state.channel = channel state.args[-1] = channel except (callbacks.ArgumentError, IndexError): if channelSpecific(): raise channel = channelSpecific.link() if not conf.get(channelSpecific.link.allow, channel): log.warning('channelSpecific.link is globally set to %s, but ' '%s disallowed linking to its db.', channel, channel) raise else: channel = channelSpecific.getChannelLink(channel) state.channel = channel state.args.append(channel)
def getChannelDb(irc, msg, args, state, **kwargs): channelSpecific = conf.supybot.databases.plugins.channelSpecific try: getChannel(irc, msg, args, state, **kwargs) channel = channelSpecific.getChannelLink(state.channel) state.channel = channel state.args[-1] = channel except (callbacks.ArgumentError, IndexError): if channelSpecific(): raise channel = channelSpecific.link() if not conf.get(channelSpecific.link.allow, channel): log.warning( 'channelSpecific.link is globally set to %s, but ' '%s disallowed linking to its db.', channel, channel) raise else: channel = channelSpecific.getChannelLink(channel) state.channel = channel state.args.append(channel)
def getIssue(self, irc, msg, match): """Get a Jira Issue""" if not ircutils.isChannel(msg.args[0]): return if conf.get(conf.supybot.plugins.Jira.lookup, msg.args[0]) == False: return issueName = match.group('issue') try: issue = self.jira[self.user].issue(issueName) except: irc.reply("Cannot find %s bug." % issueName) print "Invalid Jira snarf: %s" % issueName return if issue: try: assignee = issue.fields.assignee.displayName except: assignee = "Unassigned" try: time = issue.fields.timeestimate hours = time / 60 / 60 minutes = time / 60 % 60 displayTime = " / %ih%im" % (hours, minutes) except: displayTime = "" url = ''.join((self.server, '/browse/', issue.key)) values = { "type": issue.fields.issuetype.name, "key": issue.key, "summary": issue.fields.summary, "status": _c(_b(issue.fields.status.name), "green"), "assignee": _c(assignee, "light blue"), "displayTime": displayTime, "url": url, } replytext = (self.template % values) irc.reply(replytext, prefixNick=False)
def wunder(self, irc, msg, args, loc): """<US zip code | US/Canada city, state | Foreign city, country> Returns the approximate weather conditions for a given city. """ url = '%s%s' % (self._wunderUrl, utils.web.urlquote(loc)) text = utils.web.getUrl(url, headers=Weather.headers) if 'Search not found' in text or \ re.search(r'size="2"> Place </font>', text, re.I): Weather._noLocation() if 'Place: Temperature' in text: m = self._backupUrl.search(text) if m is not None: url = 'http://www.wunderground.com' + m.group(1) text = utils.web.getUrl(url, headers=Weather.headers) self._rss(irc, text) return severe = '' m = self._wunderSevere.search(text) if m: severe = ircutils.bold(format(' %s', m.group(1))) soup = BeautifulSoup.BeautifulSoup() soup.feed(text) # Get the table with all the weather info table = soup.first('table', {'border':'1'}) if not table: Weather._noLocation() trs = table.fetch('tr') (time, location) = trs.pop(0).fetch('b') time = time.string location = location.string info = {} def isText(t): return not isinstance(t, BeautifulSoup.NavigableText) \ and t.contents def getText(t): s = t.string if s is BeautifulSoup.Null: t = t.contents num = t[0].string units = t[1].string # htmlToText strips leading whitespace, so we have to # handle strings with differently. if units.startswith(' '): units = utils.web.htmlToText(units) s = ' '.join((num, units)) else: units = utils.web.htmlToText(units) s = ' '.join((num, units[0], units[1:])) return s for tr in trs: k = tr.td.string v = filter(isText, tr.fetch('td')[1].contents) value = map(getText, v) info[k] = ' '.join(value) temp = info['Temperature'] convert = conf.get(conf.supybot.plugins.Weather.convert, msg.args[0]) if location and temp: (temp, deg, unit) = temp.split()[3:] # We only want temp format if convert: temp = Weather._getTemp(float(temp), deg, unit, msg.args[0]) else: temp = deg.join((temp, unit)) resp = ['The current temperature in %s is %s (%s).' %\ (location, temp, time)] conds = info['Conditions'] resp.append('Conditions: %s.' % info['Conditions']) humidity = info['Humidity'] resp.append('Humidity: %s.' % info['Humidity']) # Apparently, the "Dew Point" and "Wind" categories are # occasionally set to "-" instead of an actual reading. So, # we'll just catch the ValueError from trying to unpack a tuple # of the wrong size. try: (dew, deg, unit) = info['Dew Point'].split()[3:] if convert: dew = Weather._getTemp(float(dew), deg, unit, msg.args[0]) else: dew = deg.join((dew, unit)) resp.append('Dew Point: %s.' % dew) except (ValueError, KeyError): pass try: wind = 'Wind: %s at %s %s.' % tuple(info['Wind'].split()) resp.append(wind) except (ValueError, TypeError): pass try: (chill, deg, unit) = info['Windchill'].split()[3:] if convert: chill = Weather._getTemp(float(chill), deg, unit, msg.args[0]) else: dew = deg.join((chill, unit)) resp.append('Windchill: %s.' % chill) except (ValueError, KeyError): pass if info['Pressure']: resp.append('Pressure: %s.' % info['Pressure']) resp.append(severe) resp = map(utils.web.htmlToText, resp) irc.reply(' '.join(resp)) else: Weather._noLocation()
def invalidCommand(self, irc, msg, tokens): assert not msg.repliedTo, "repliedTo msg in Misc.invalidCommand." assert self is irc.callbacks[-1], "Misc isn't last callback." assert msg.command in ("PRIVMSG", "NOTICE") self.log.debug("Misc.invalidCommand called (tokens %s)", tokens) # First, we check for invalidCommand floods. This is rightfully done # here since this will be the last invalidCommand called, and thus it # will only be called if this is *truly* an invalid command. maximum = conf.supybot.abuse.flood.command.invalid.maximum() self.invalidCommands.enqueue(msg) if ( self.invalidCommands.len(msg) > maximum and conf.supybot.abuse.flood.command.invalid() and not ircdb.checkCapability(msg.prefix, "owner") ): punishment = conf.supybot.abuse.flood.command.invalid.punishment() banmask = "*!%s@%s" % (msg.user, msg.host) self.log.info( "Ignoring %s for %s seconds due to an apparent " "invalid command flood.", banmask, punishment ) if tokens and tokens[0] == "Error:": self.log.warning( "Apparent error loop with another Supybot " "observed. Consider ignoring this bot " "permanently." ) ircdb.ignores.add(banmask, time.time() + punishment) if conf.supybot.abuse.flood.command.invalid.notify(): irc.reply( _("You've given me %s invalid commands within the last " "%i seconds; I'm now ignoring you for %s.") % (maximum, conf.supybot.abuse.flood.interval(), utils.timeElapsed(punishment, seconds=False)) ) return # Now, for normal handling. channel = msg.args[0] # Only bother with the invaildCommand flood handling if it's actually # enabled if conf.supybot.abuse.flood.command.invalid(): # First, we check for invalidCommand floods. This is rightfully done # here since this will be the last invalidCommand called, and thus it # will only be called if this is *truly* an invalid command. maximum = conf.supybot.abuse.flood.command.invalid.maximum() banmasker = conf.supybot.protocols.irc.banmask.makeBanmask self.invalidCommands.enqueue(msg) if ( self.invalidCommands.len(msg) > maximum and not ircdb.checkCapability(msg.prefix, "owner") and msg.prefix != irc.prefix and ircutils.isUserHostmask(msg.prefix) ): penalty = conf.supybot.abuse.flood.command.invalid.punishment() banmask = banmasker(msg.prefix, channel=None) self.log.info( "Ignoring %s for %s seconds due to an apparent " "invalid command flood.", banmask, penalty ) if tokens and tokens[0] == "Error:": self.log.warning( "Apparent error loop with another Supybot " "observed. Consider ignoring this bot " "permanently." ) ircdb.ignores.add(banmask, time.time() + penalty) if conf.supybot.abuse.flood.command.invalid.notify(): irc.reply( "You've given me %s invalid commands within " "the last minute; I'm now ignoring you for %s." % (maximum, utils.timeElapsed(penalty, seconds=False)) ) return # Now, for normal handling. if conf.get(conf.supybot.reply.whenNotCommand, channel): if len(tokens) >= 2: cb = irc.getCallback(tokens[0]) if cb: plugin = cb.name() irc.error( format( _( "The %q plugin is loaded, but there is " 'no command named %q in it. Try "list ' '%s" to see the commands in the %q ' "plugin." ), plugin, tokens[1], plugin, plugin, ) ) else: irc.errorInvalid(_("command"), tokens[0], repr=False) else: command = tokens and tokens[0] or "" irc.errorInvalid(_("command"), command, repr=False) else: if tokens: # echo [] will get us an empty token set, but there's no need # to log this in that case anyway, it being a nested command. self.log.info("Not replying to %s, not a command.", tokens[0]) if irc.nested: bracketConfig = conf.supybot.commands.nested.brackets brackets = conf.get(bracketConfig, channel) if brackets: (left, right) = brackets irc.reply(left + " ".join(tokens) + right) else: pass # Let's just do nothing, I can't think of better.
def wunder(self, irc, msg, args, loc): """<US zip code | US/Canada city, state | Foreign city, country> Returns the approximate weather conditions for a given city. """ url = '%s%s' % (self._wunderUrl, utils.web.urlquote(loc)) text = utils.web.getUrl(url, headers=Weather.headers) if 'Search not found' in text or \ re.search(r'size="2"> Place </font>', text, re.I): Weather._noLocation() if 'Place: Temperature' in text: m = self._backupUrl.search(text) if m is not None: url = 'http://www.wunderground.com' + m.group(1) text = utils.web.getUrl(url, headers=Weather.headers) self._rss(irc, text) return severe = '' m = self._wunderSevere.search(text) if m: severe = ircutils.bold(format(' %s', m.group(1))) soup = BeautifulSoup.BeautifulSoup() soup.feed(text) # Get the table with all the weather info table = soup.first('table', {'border':'1'}) if table is BeautifulSoup.Null: Weather._noLocation() trs = table.fetch('tr') try: time = trs.pop(0).b.string except AttributeError: time = '' info = {} def isText(t): return not isinstance(t, BeautifulSoup.NavigableText) \ and t.contents def getText(t): s = t.string if s is BeautifulSoup.Null: t = t.contents num = t[0].string units = t[1].string # htmlToText strips leading whitespace, so we have to # handle strings with differently. if units.startswith(' '): units = utils.web.htmlToText(units) s = ' '.join((num, units)) else: units = utils.web.htmlToText(units) s = ' '.join((num, units[0], units[1:])) return s for tr in trs: k = tr.td.string v = filter(isText, tr.fetch('td')[1].contents) value = map(getText, v) info[k] = ' '.join(value) location = self._wunderLoc.search(text) temp = info['Temperature'] convert = conf.get(conf.supybot.plugins.Weather.convert, msg.args[0]) if location and temp: (temp, deg, unit) = temp.split()[3:] # We only want temp format if convert: temp = Weather._getTemp(int(temp), deg, unit, msg.args[0]) else: temp = deg.join((temp, unit)) resp = ['The current temperature in %s is %s (%s).' %\ (location.group(1), temp, time)] conds = info['Conditions'] resp.append('Conditions: %s.' % info['Conditions']) humidity = info['Humidity'] resp.append('Humidity: %s.' % info['Humidity']) # Apparently, the "Dew Point" and "Wind" categories are # occasionally set to "-" instead of an actual reading. So, # we'll just catch the ValueError from trying to unpack a tuple # of the wrong size. try: (dew, deg, unit) = info['Dew Point'].split()[3:] if convert: dew = Weather._getTemp(int(dew), deg, unit, msg.args[0]) else: dew = deg.join((dew, unit)) resp.append('Dew Point: %s.' % dew) except (ValueError, KeyError): pass try: wind = 'Wind: %s at %s %s.' % tuple(info['Wind'].split()) resp.append(wind) except (ValueError, TypeError): pass try: (chill, deg, unit) = info['Windchill'].split()[3:] if convert: chill = Weather._getTemp(int(chill), deg, unit, msg.args[0]) else: dew = deg.join((chill, unit)) resp.append('Windchill: %s.' % chill) except (ValueError, KeyError): pass if info['Pressure']: resp.append('Pressure: %s.' % info['Pressure']) if info['Visibility']: resp.append('Visibility: %s.' % info['Visibility']) resp.append(severe) resp = map(utils.web.htmlToText, resp) irc.reply(' '.join(resp)) else: irc.error('Could not find weather information.')