Merge remote-tracking branch 'supybot/master' into testing

Conflicts:
	.mailmap
	README
	docs/FAQ.rst
	plugins/Ctcp/plugin.py
	plugins/Misc/plugin.py
	plugins/Network/plugin.py
	plugins/QuoteGrabs/plugin.py
	plugins/RSS/README.txt
	plugins/Relay/plugin.py
	plugins/ShrinkUrl/config.py
	plugins/ShrinkUrl/plugin.py
	plugins/ShrinkUrl/test.py
	setup.py
	src/callbacks.py
	src/commands.py
	src/conf.py
	test/test_commands.py
This commit is contained in:
Valentin Lorentz 2015-05-15 14:41:08 +02:00
commit 487f8c8af5
10 changed files with 163 additions and 162 deletions

View File

@ -56,8 +56,8 @@ class Admin(callbacks.Plugin):
def do437(self, irc, msg): def do437(self, irc, msg):
"""Nick/channel temporarily unavailable.""" """Nick/channel temporarily unavailable."""
target = msg.args[0] target = msg.args[0]
t = time.time() + 30
if irc.isChannel(target): # We don't care about nicks. if irc.isChannel(target): # We don't care about nicks.
t = time.time() + 30
# Let's schedule a rejoin. # Let's schedule a rejoin.
networkGroup = conf.supybot.networks.get(irc.network) networkGroup = conf.supybot.networks.get(irc.network)
def rejoin(): def rejoin():
@ -67,6 +67,16 @@ class Admin(callbacks.Plugin):
schedule.addEvent(rejoin, t) schedule.addEvent(rejoin, t)
self.log.info('Scheduling a rejoin to %s at %s; ' self.log.info('Scheduling a rejoin to %s at %s; '
'Channel temporarily unavailable.', target, t) '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): def do471(self, irc, msg):
try: try:

View File

@ -1,6 +1,6 @@
### ###
# Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2002-2004, Jeremiah Fincher
# Copyright (c) 2010, James McCoy # Copyright (c) 2010,2015 James McCoy
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # 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: if (irc, loweredNick) not in self._whois:
return return
(replyIrc, replyMsg, d, command) = self._whois[(irc, loweredNick)] (replyIrc, replyMsg, d, command) = self._whois[(irc, loweredNick)]
START_CODE = '311' if command == 'whois' else '314' d['318'] = msg
hostmask = '@'.join(d[START_CODE].args[2:4]) s = ircutils.formatWhois(irc, d, caller=replyMsg.nick,
user = d[START_CODE].args[-1] channel=replyMsg.args[0],
if '319' in d: command=command)
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 = _('<unknown>')
signon = _('<unknown>')
if '312' in d:
server = d['312'].args[2]
if len(d['312']) > 3:
signoff = d['312'].args[3]
else:
server = _('<unknown>')
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)
replyIrc.reply(s) replyIrc.reply(s)
del self._whois[(irc, loweredNick)] del self._whois[(irc, loweredNick)]
do369 = do318 do369 = do318

View File

@ -279,7 +279,7 @@ class QuoteGrabs(callbacks.Plugin):
# opposed to channel which is used to determine which db to store the # opposed to channel which is used to determine which db to store the
# quote in # quote in
chan = msg.args[0] 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 raise callbacks.ArgumentError
if ircutils.nickEqual(nick, msg.nick): if ircutils.nickEqual(nick, msg.nick):
irc.error(_('You can\'t quote grab yourself.'), Raise=True) irc.error(_('You can\'t quote grab yourself.'), Raise=True)

View File

@ -1,6 +1,6 @@
### ###
# Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2002-2004, Jeremiah Fincher
# Copyright (c) 2010, James McCoy # Copyright (c) 2010,2015 James McCoy
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # 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: if (irc, loweredNick) not in self._whois:
return return
(replyIrc, replyMsg, d) = self._whois[(irc, loweredNick)] (replyIrc, replyMsg, d) = self._whois[(irc, loweredNick)]
hostmask = '@'.join(d['311'].args[2:4]) d['318'] = msg
user = d['311'].args[-1] s = ircutils.formatWhois(irc, d, caller=replyMsg.nick,
if '319' in d: channel=replyMsg.args[0])
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 = _('<unknown>')
signon = _('<unknown>')
if '312' in d:
server = d['312'].args[2]
else:
server = _('<unknown>')
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)
replyIrc.reply(s) replyIrc.reply(s)
del self._whois[(irc, loweredNick)] del self._whois[(irc, loweredNick)]

View File

@ -71,7 +71,7 @@ conf.registerChannelValue(ShrinkUrl, 'shrinkSnarfer',
shrink snarfer is enabled. This snarfer will watch for URLs in the shrink snarfer is enabled. This snarfer will watch for URLs in the
channel, and if they're sufficiently long (as determined by channel, and if they're sufficiently long (as determined by
supybot.plugins.ShrinkUrl.minimumLength) it will post a 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."""))) supybot.plugins.ShrinkUrl.default.""")))
conf.registerChannelValue(ShrinkUrl.shrinkSnarfer, 'showDomain', conf.registerChannelValue(ShrinkUrl.shrinkSnarfer, 'showDomain',
registry.Boolean(True, _("""Determines whether the snarfer will show the registry.Boolean(True, _("""Determines whether the snarfer will show the

View File

@ -1289,7 +1289,7 @@ class Commands(BasePlugin):
if 'command has no help.' in help: if 'command has no help.' in help:
# Note: this case will never happen, unless 'checkDoc' is set # Note: this case will never happen, unless 'checkDoc' is set
# to False. # to False.
irc.error(_('Invalid arguments for %s.') % ' '.join(command)) irc.error(_('Invalid arguments for %s.') % formatCommand(command))
else: else:
irc.reply(help) irc.reply(help)
except (SyntaxError, Error) as e: except (SyntaxError, Error) as e:

View File

@ -1,6 +1,6 @@
### ###
# Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2002-2005, Jeremiah Fincher
# Copyright (c) 2009-2010, James McCoy # Copyright (c) 2009-2010,2015, James McCoy
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -443,8 +443,8 @@ def _getRe(f):
else: else:
state.args.append(s) state.args.append(s)
else: else:
state.errorInvalid(_('regular expression'), s) raise ValueError
except IndexError: except (ValueError, IndexError):
args[:] = original args[:] = original
state.errorInvalid(_('regular expression'), s) state.errorInvalid(_('regular expression'), s)
return get return get

View File

@ -1135,8 +1135,11 @@ class Irc(IrcCommandDispatcher):
if not self.afterConnect: if not self.afterConnect:
newNick = self._getNextNick() newNick = self._getNextNick()
assert newNick != self.nick 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)) self.sendMsg(ircmsgs.nick(newNick))
def do437(self, msg):
self.do43x(msg, 'is temporarily unavailable')
def do433(self, msg): def do433(self, msg):
self.do43x(msg, 'is in use') self.do43x(msg, 'is in use')
def do432(self, msg): def do432(self, msg):

View File

@ -1,6 +1,6 @@
### ###
# Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2002-2005, Jeremiah Fincher
# Copyright (c) 2009,2011, James McCoy # Copyright (c) 2009,2011,2015 James McCoy
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -375,6 +375,123 @@ def stripFormatting(s):
s = stripUnderline(s) s = stripUnderline(s)
return s.replace('\x0f', '').replace('\x0F', '') 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 = '<unknown>'
signon = '<unknown>'
if '312' in replies:
server = replies['312'].args[2]
if len(d['312']) > 3:
signoff = d['312'].args[3]
else:
server = '<unknown>'
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): class FormatContext(object):
def __init__(self): def __init__(self):
self.reset() self.reset()

View File

@ -1,5 +1,6 @@
### ###
# Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2002-2005, Jeremiah Fincher
# Copyright (c) 2015, James McCoy
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -196,13 +197,24 @@ class FirstTestCase(CommandsTestCase):
self.assertStateErrored([first('int', 'something')], ['words'], self.assertStateErrored([first('int', 'something')], ['words'],
errored=False) 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): class GetoptTestCase(PluginTestCase):
plugins = ('Misc',) # We put something so it does not complain plugins = ('Misc',) # We put something so it does not complain
class Foo(callbacks.Plugin): class Foo(callbacks.Plugin):
def bar(self, irc, msg, args, optlist): def bar(self, irc, msg, args, optlist):
irc.reply(' '.join(sorted(['%s:%d'%x for x in optlist]))) irc.reply(' '.join(sorted(['%s:%d'%x for x in optlist])))
<<<<<<< HEAD
bar = wrap(bar, [getopts({'foo': 'int', 'fbar': 'int'})], bar = wrap(bar, [getopts({'foo': 'int', 'fbar': 'int'})],
checkDoc=False) checkDoc=False)
=======
bar = wrap(bar, [getopts({'foo': 'int', 'fbar': 'int'})])
>>>>>>> supybot/master
def testGetoptsExact(self): def testGetoptsExact(self):
self.irc.addCallback(self.Foo(self.irc)) self.irc.addCallback(self.Foo(self.irc))
@ -211,6 +223,9 @@ class GetoptTestCase(PluginTestCase):
self.assertResponse('bar --f 3 --fb 5', self.assertResponse('bar --f 3 --fb 5',
'Error: Invalid arguments for bar.') 'Error: Invalid arguments for bar.')
<<<<<<< HEAD
=======
>>>>>>> supybot/master
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: