diff --git a/plugins/Admin/plugin.py b/plugins/Admin/plugin.py index 63489d4e2..d6c7533c5 100644 --- a/plugins/Admin/plugin.py +++ b/plugins/Admin/plugin.py @@ -56,8 +56,8 @@ class Admin(callbacks.Plugin): def do437(self, irc, msg): """Nick/channel temporarily unavailable.""" target = msg.args[0] + t = time.time() + 30 if irc.isChannel(target): # We don't care about nicks. - t = time.time() + 30 # Let's schedule a rejoin. networkGroup = conf.supybot.networks.get(irc.network) def rejoin(): @@ -67,6 +67,16 @@ class Admin(callbacks.Plugin): schedule.addEvent(rejoin, t) self.log.info('Scheduling a rejoin to %s at %s; ' 'Channel temporarily unavailable.', target, t) + else: + irc = self.pendingNickChanges.get(irc, None) + if irc is not None: + def nick(): + irc.queueMsg(ircmsgs.nick(target)) + schedule.addEvent(nick, t) + self.log.info('Scheduling a nick change to %s at %s; ' + 'Nick temporarily unavailable.', target, t) + else: + self.log.debug('Got 437 without Admin.nick being called.') def do471(self, irc, msg): try: diff --git a/plugins/Network/plugin.py b/plugins/Network/plugin.py index 548039e95..e356abace 100644 --- a/plugins/Network/plugin.py +++ b/plugins/Network/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher -# Copyright (c) 2010, James McCoy +# Copyright (c) 2010,2015 James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -185,99 +185,10 @@ class Network(callbacks.Plugin): if (irc, loweredNick) not in self._whois: return (replyIrc, replyMsg, d, command) = self._whois[(irc, loweredNick)] - START_CODE = '311' if command == 'whois' else '314' - hostmask = '@'.join(d[START_CODE].args[2:4]) - user = d[START_CODE].args[-1] - if '319' in d: - channels = [] - for msg in d['319']: - channels.extend(msg.args[-1].split()) - ops = [] - voices = [] - normal = [] - halfops = [] - for channel in channels: - origchan = channel - channel = channel.lstrip('@%+~!') - # UnrealIRCd uses & for user modes and disallows it as a - # channel-prefix, flying in the face of the RFC. Have to - # handle this specially when processing WHOIS response. - testchan = channel.lstrip('&') - if testchan != channel and irc.isChannel(testchan): - channel = testchan - diff = len(channel) - len(origchan) - modes = origchan[:diff] - chan = irc.state.channels.get(channel) - # The user is in a channel the bot is in, so the ircd may have - # responded with otherwise private data. - if chan: - # Skip channels the caller isn't in. This prevents - # us from leaking information when the channel is +s or the - # target is +i. - if replyMsg.nick not in chan.users: - continue - # Skip +s channels the target is in only if the reply isn't - # being sent to that channel. - if 's' in chan.modes and \ - not ircutils.strEqual(replyMsg.args[0], channel): - continue - if not modes: - normal.append(channel) - elif utils.iter.any(lambda c: c in modes,('@', '&', '~', '!')): - ops.append(channel) - elif utils.iter.any(lambda c: c in modes, ('%',)): - halfops.append(channel) - elif utils.iter.any(lambda c: c in modes, ('+',)): - voices.append(channel) - L = [] - if ops: - L.append(format(_('is an op on %L'), ops)) - if halfops: - L.append(format(_('is a halfop on %L'), halfops)) - if voices: - L.append(format(_('is voiced on %L'), voices)) - if normal: - if L: - L.append(format(_('is also on %L'), normal)) - else: - L.append(format(_('is on %L'), normal)) - else: - if command == 'whois': - L = [_('isn\'t on any publicly visible channels')] - else: - L = [] - channels = format('%L', L) - if '317' in d: - idle = utils.timeElapsed(d['317'].args[2]) - signon = time.strftime(conf.supybot.reply.format.time(), - time.localtime(float(d['317'].args[3]))) - else: - idle = _('') - signon = _('') - if '312' in d: - server = d['312'].args[2] - if len(d['312']) > 3: - signoff = d['312'].args[3] - else: - server = _('') - if '301' in d: - away = ' %s is away: %s.' % (nick, d['301'].args[2]) - else: - away = '' - if '320' in d: - if d['320'].args[2]: - identify = _(' identified') - else: - identify = '' - else: - identify = '' - if command == 'whois': - s = _('%s (%s) has been%s on server %s since %s (idle for %s). %s ' - '%s.%s') % (user, hostmask, identify, server, - signon, idle, nick, channels, away) - else: - s = _('%s (%s) has been%s on server %s and disconnected on %s.') % \ - (user, hostmask, identify, server, signoff) + d['318'] = msg + s = ircutils.formatWhois(irc, d, caller=replyMsg.nick, + channel=replyMsg.args[0], + command=command) replyIrc.reply(s) del self._whois[(irc, loweredNick)] do369 = do318 diff --git a/plugins/QuoteGrabs/plugin.py b/plugins/QuoteGrabs/plugin.py index 00f444e6b..12439a2de 100644 --- a/plugins/QuoteGrabs/plugin.py +++ b/plugins/QuoteGrabs/plugin.py @@ -279,7 +279,7 @@ class QuoteGrabs(callbacks.Plugin): # opposed to channel which is used to determine which db to store the # quote in chan = msg.args[0] - if chan is None or not ircutils.isChannel(chan): + if chan is None or not irc.isChannel(chan): raise callbacks.ArgumentError if ircutils.nickEqual(nick, msg.nick): irc.error(_('You can\'t quote grab yourself.'), Raise=True) diff --git a/plugins/Relay/plugin.py b/plugins/Relay/plugin.py index 2b86ebcf0..a5c2baa2e 100644 --- a/plugins/Relay/plugin.py +++ b/plugins/Relay/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher -# Copyright (c) 2010, James McCoy +# Copyright (c) 2010,2015 James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -202,64 +202,9 @@ class Relay(callbacks.Plugin): if (irc, loweredNick) not in self._whois: return (replyIrc, replyMsg, d) = self._whois[(irc, loweredNick)] - hostmask = '@'.join(d['311'].args[2:4]) - user = d['311'].args[-1] - if '319' in d: - channels = d['319'].args[-1].split() - ops = [] - voices = [] - normal = [] - halfops = [] - for channel in channels: - if channel.startswith('@'): - ops.append(channel[1:]) - elif channel.startswith('%'): - halfops.append(channel[1:]) - elif channel.startswith('+'): - voices.append(channel[1:]) - else: - normal.append(channel) - L = [] - if ops: - L.append(format(_('is an op on %L'), ops)) - if halfops: - L.append(format(_('is a halfop on %L'), halfops)) - if voices: - L.append(format(_('is voiced on %L'), voices)) - if normal: - if L: - L.append(format(_('is also on %L'), normal)) - else: - L.append(format(_('is on %L'), normal)) - else: - L = [_('isn\'t on any non-secret channels')] - channels = format('%L', L) - if '317' in d: - idle = utils.timeElapsed(d['317'].args[2]) - signon = time.strftime(conf.supybot.reply.format.time(), - time.localtime(float(d['317'].args[3]))) - else: - idle = _('') - signon = _('') - if '312' in d: - server = d['312'].args[2] - else: - server = _('') - if '301' in d: - away = format(_(' %s is away: %s.'), nick, d['301'].args[2]) - else: - away = '' - if '320' in d: - if d['320'].args[2]: - identify = _(' identified') - else: - identify = '' - else: - identify = '' - s = format(_('%s (%s) has been%s on server %s since %s (idle for %s) ' - 'and %s.%s'), - user, hostmask, identify, server, signon, idle, - channels, away) + d['318'] = msg + s = ircutils.formatWhois(irc, d, caller=replyMsg.nick, + channel=replyMsg.args[0]) replyIrc.reply(s) del self._whois[(irc, loweredNick)] diff --git a/plugins/ShrinkUrl/config.py b/plugins/ShrinkUrl/config.py index 97c70c014..b211098be 100644 --- a/plugins/ShrinkUrl/config.py +++ b/plugins/ShrinkUrl/config.py @@ -71,7 +71,7 @@ conf.registerChannelValue(ShrinkUrl, 'shrinkSnarfer', shrink snarfer is enabled. This snarfer will watch for URLs in the channel, and if they're sufficiently long (as determined by supybot.plugins.ShrinkUrl.minimumLength) it will post a - smaller URL from tinyurl.com, as denoted in + smaller URL from the service as denoted in supybot.plugins.ShrinkUrl.default."""))) conf.registerChannelValue(ShrinkUrl.shrinkSnarfer, 'showDomain', registry.Boolean(True, _("""Determines whether the snarfer will show the diff --git a/src/callbacks.py b/src/callbacks.py index 89acba278..ffbed9840 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -1289,7 +1289,7 @@ class Commands(BasePlugin): if 'command has no help.' in help: # Note: this case will never happen, unless 'checkDoc' is set # to False. - irc.error(_('Invalid arguments for %s.') % ' '.join(command)) + irc.error(_('Invalid arguments for %s.') % formatCommand(command)) else: irc.reply(help) except (SyntaxError, Error) as e: diff --git a/src/commands.py b/src/commands.py index 2ff209c1a..f1adf6fbf 100644 --- a/src/commands.py +++ b/src/commands.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2005, Jeremiah Fincher -# Copyright (c) 2009-2010, James McCoy +# Copyright (c) 2009-2010,2015, James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -443,8 +443,8 @@ def _getRe(f): else: state.args.append(s) else: - state.errorInvalid(_('regular expression'), s) - except IndexError: + raise ValueError + except (ValueError, IndexError): args[:] = original state.errorInvalid(_('regular expression'), s) return get diff --git a/src/irclib.py b/src/irclib.py index 4a0a54711..94380f9b2 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -1135,8 +1135,11 @@ class Irc(IrcCommandDispatcher): if not self.afterConnect: newNick = self._getNextNick() assert newNick != self.nick - log.info('Got 433: %s %s. Trying %s.',self.nick, problem, newNick) + log.info('Got %s: %s %s. Trying %s.', + msg.command, self.nick, problem, newNick) self.sendMsg(ircmsgs.nick(newNick)) + def do437(self, msg): + self.do43x(msg, 'is temporarily unavailable') def do433(self, msg): self.do43x(msg, 'is in use') def do432(self, msg): diff --git a/src/ircutils.py b/src/ircutils.py index 2cd853d8c..576e6f894 100644 --- a/src/ircutils.py +++ b/src/ircutils.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2005, Jeremiah Fincher -# Copyright (c) 2009,2011, James McCoy +# Copyright (c) 2009,2011,2015 James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -375,6 +375,123 @@ def stripFormatting(s): s = stripUnderline(s) return s.replace('\x0f', '').replace('\x0F', '') +_containsFormattingRe = re.compile(r'[\x02\x03\x16\x1f]') +def formatWhois(irc, replies, caller='', channel='', command='whois'): + """Returns a string describing the target of a WHOIS command. + + Arguments are: + * irc: the irclib.Irc object on which the replies was received + + * replies: a dict mapping the reply codes ('311', '312', etc.) to their + corresponding ircmsg.IrcMsg + + * caller: an optional nick specifying who requested the whois information + + * channel: an optional channel specifying where the reply will be sent + + If provided, caller and channel will be used to avoid leaking information + that the caller/channel shouldn't be privy to. + """ + hostmask = '@'.join(replies['311'].args[2:4]) + nick = replies['318'].args[1] + user = replies['311'].args[-1] + (replyIrc, replyMsg, d, command) = self._whois[(irc, loweredNick)] + START_CODE = '311' if command == 'whois' else '314' + hostmask = '@'.join(d[START_CODE].args[2:4]) + user = d[START_CODE].args[-1] + if _containsFormattingRe.search(user) and user[-1] != '\x0f': + # For good measure, disable any formatting + user = '%s\x0f' % user + if '319' in replies: + channels = replies['319'].args[-1].split() + ops = [] + voices = [] + normal = [] + halfops = [] + for chan in channels: + origchan = chan + chan = chan.lstrip('@%+~!') + # UnrealIRCd uses & for user modes and disallows it as a + # channel-prefix, flying in the face of the RFC. Have to + # handle this specially when processing WHOIS response. + testchan = chan.lstrip('&') + if testchan != chan and irc.isChannel(testchan): + chan = testchan + diff = len(chan) - len(origchan) + modes = origchan[:diff] + chanState = irc.state.channels.get(chan) + # The user is in a channel the bot is in, so the ircd may have + # responded with otherwise private data. + if chanState: + # Skip channels the callee isn't in. This helps prevents + # us leaking information when the channel is +s or the + # target is +i + if caller not in chanState.users: + continue + # Skip +s channels the target is in only if the reply isn't + # being sent to that channel + if 's' in chanState.modes and \ + not ircutils.strEqual(channel or '', chan): + continue + if not modes: + normal.append(chan) + elif utils.iter.any(lambda c: c in modes,('@', '&', '~', '!')): + ops.append(chan[1:]) + elif utils.iter.any(lambda c: c in modes, ('%',)): + halfops.append(chan[1:]) + elif utils.iter.any(lambda c: c in modes, ('+',)): + voices.append(chan[1:]) + L = [] + if ops: + L.append(format(_('is an op on %L'), ops)) + if halfops: + L.append(format(_('is a halfop on %L'), halfops)) + if voices: + L.append(format(_('is voiced on %L'), voices)) + if normal: + if L: + L.append(format(_('is also on %L'), normal)) + else: + L.append(format(_('is on %L'), normal)) + else: + if command == 'whois': + L = [_('isn\'t on any non-secret channels')] + else: + L = [] + channels = format('%L', L) + if '317' in replies: + idle = utils.timeElapsed(replies['317'].args[2]) + signon = utils.str.timestamp(float(replies['317'].args[3])) + else: + idle = '' + signon = '' + if '312' in replies: + server = replies['312'].args[2] + if len(d['312']) > 3: + signoff = d['312'].args[3] + else: + server = '' + if '301' in replies: + away = ' %s is away: %s.' % (nick, replies['301'].args[2]) + else: + away = '' + if '320' in replies: + if replies['320'].args[2]: + identify = ' identified' + else: + identify = '' + else: + identify = '' + if command == 'whois': + s = _('%s (%s) has been%s on server %s since %s (idle for %s) and ' + '%s.%s').decode('utf8') % (user, hostmask, identify, server, + signon, idle, channels, away) + else: + s = _('%s (%s) has been%s on server %s and disconnect on %s.') \ + .decode('utf8') % \ + (user, hostmask, identify, server, signoff) + return s + class FormatContext(object): def __init__(self): self.reset() diff --git a/test/test_commands.py b/test/test_commands.py index 76d51e9f6..b6960a9b3 100644 --- a/test/test_commands.py +++ b/test/test_commands.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2002-2005, Jeremiah Fincher +# Copyright (c) 2015, James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -196,13 +197,24 @@ class FirstTestCase(CommandsTestCase): self.assertStateErrored([first('int', 'something')], ['words'], errored=False) +<<<<<<< HEAD +======= + def testLongRegexp(self): + spec = [first('regexpMatcher', 'regexpReplacer'), 'text'] + self.assertStateErrored(spec, ['s/foo/bar/', 'x' * 512], errored=False) + +>>>>>>> supybot/master class GetoptTestCase(PluginTestCase): plugins = ('Misc',) # We put something so it does not complain class Foo(callbacks.Plugin): def bar(self, irc, msg, args, optlist): irc.reply(' '.join(sorted(['%s:%d'%x for x in optlist]))) +<<<<<<< HEAD bar = wrap(bar, [getopts({'foo': 'int', 'fbar': 'int'})], checkDoc=False) +======= + bar = wrap(bar, [getopts({'foo': 'int', 'fbar': 'int'})]) +>>>>>>> supybot/master def testGetoptsExact(self): self.irc.addCallback(self.Foo(self.irc)) @@ -211,6 +223,9 @@ class GetoptTestCase(PluginTestCase): self.assertResponse('bar --f 3 --fb 5', 'Error: Invalid arguments for bar.') +<<<<<<< HEAD +======= +>>>>>>> supybot/master # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: