Strip statusmsg chars in core to determine the actual channel.

Use msg.channel if relevant, otherwise strip them locally.
This commit is contained in:
Valentin Lorentz 2019-08-24 14:14:33 +02:00
parent 5b40b5136d
commit 8bb31a54e7
9 changed files with 173 additions and 111 deletions

View File

@ -261,7 +261,7 @@ class Owner(callbacks.Plugin):
tokens = callbacks.tokenize(s, channel=msg.args[0])
self.Proxy(irc, msg, tokens)
except SyntaxError as e:
irc.queueMsg(callbacks.error(msg, str(e)))
irc.error(str(e))
def logmark(self, irc, msg, args, text):
"""<text>

View File

@ -156,17 +156,25 @@ def canonicalName(command, preserve_spaces=False):
command = command[:-1]
return ''.join([x for x in command if x not in special]).lower() + reAppend
def reply(msg, s, prefixNick=None, private=None,
notice=None, to=None, action=None, error=False,
stripCtcp=True):
def reply(*args, **kwargs):
warnings.warn('callbacks.reply is deprecated. Use irc.reply instead.',
DeprecationWarning)
return _makeReply(dynamic.irc, *args, **kwargs)
def _makeReply(irc, msg, s,
prefixNick=None, private=None,
notice=None, to=None, action=None, error=False,
stripCtcp=True):
msg.tag('repliedTo')
# Ok, let's make the target:
# XXX This isn't entirely right. Consider to=#foo, private=True.
target = ircutils.replyTo(msg)
if ircutils.isChannel(to):
def isPublic(s):
return irc.isChannel(irc._stripChannelPrefix(s))
if to is not None and isPublic(to):
target = to
if ircutils.isChannel(target):
channel = target
if isPublic(target):
channel = irc._stripChannelPrefix(target)
else:
channel = None
if notice is None:
@ -195,11 +203,11 @@ def reply(msg, s, prefixNick=None, private=None,
s = ircutils.safeArgument(s)
if not s and not action:
s = _('Error: I tried to send you an empty message.')
if prefixNick and ircutils.isChannel(target):
if prefixNick and isPublic(target):
# Let's may sure we don't do, "#channel: foo.".
if not ircutils.isChannel(to):
if not isPublic(to):
s = '%s: %s' % (to, s)
if not ircutils.isChannel(target):
if not isPublic(target):
if conf.supybot.reply.withNoticeWhenPrivate():
notice = True
# And now, let's decide whether it's a PRIVMSG or a NOTICE.
@ -214,11 +222,16 @@ def reply(msg, s, prefixNick=None, private=None,
ret.tag('inReplyTo', msg)
return ret
def error(msg, s, **kwargs):
def error(*args, **kwargs):
warnings.warn('callbacks.error is deprecated. Use irc.error instead.',
DeprecationWarning)
return _makeErrorReply(dynamic.irc, *args, **kwargs)
def _makeErrorReply(irc, msg, s, **kwargs):
"""Makes an error reply to msg with the appropriate error payload."""
kwargs['error'] = True
msg.tag('isError')
return reply(msg, s, **kwargs)
return _makeReply(irc, msg, s, **kwargs)
def getHelp(method, name=None, doc=None):
if name is None:
@ -400,8 +413,8 @@ def checkCommandCapability(msg, cb, commandName):
checkCapability(antiCommand)
checkAtEnd = [commandName]
default = conf.supybot.capabilities.default()
if ircutils.isChannel(msg.args[0]):
channel = msg.args[0]
if msg.channel:
channel = msg.channel
checkCapability(ircdb.makeChannelCapability(channel, antiCommand))
chanCommand = ircdb.makeChannelCapability(channel, commandName)
checkAtEnd += [chanCommand]
@ -426,7 +439,7 @@ class RichReplyMethods(object):
return ircutils.standardSubstitute(self, self.msg, s)
def _getConfig(self, wrapper):
return conf.get(wrapper, self.msg.args[0])
return conf.get(wrapper, self.msg.channel)
def replySuccess(self, s='', **kwargs):
v = self._getConfig(conf.supybot.replies.success)
@ -599,7 +612,7 @@ class ReplyIrcProxy(RichReplyMethods):
raise ArgumentError
if msg is None:
msg = self.msg
m = error(msg, s, **kwargs)
m = _makeErrorReply(self, msg, s, **kwargs)
self.irc.queueMsg(m)
return m
@ -609,7 +622,7 @@ class ReplyIrcProxy(RichReplyMethods):
assert not isinstance(s, ircmsgs.IrcMsg), \
'Old code alert: there is no longer a "msg" argument to reply.'
kwargs.pop('noLengthCheck', None)
m = reply(msg, s, **kwargs)
m = _makeReply(self, msg, s, **kwargs)
self.irc.queueMsg(m)
return m
@ -665,9 +678,9 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
self.notice = None
self.private = None
self.noLengthCheck = None
if self.irc.isChannel(self.msg.args[0]):
if self.msg.channel:
self.prefixNick = conf.get(conf.supybot.reply.withNickPrefix,
self.msg.args[0])
self.msg.channel)
else:
self.prefixNick = conf.supybot.reply.withNickPrefix()
@ -894,12 +907,12 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
elif self.noLengthCheck:
# noLengthCheck only matters to NestedCommandsIrcProxy, so
# it's not used here. Just in case you were wondering.
m = reply(msg, s, to=self.to,
notice=self.notice,
action=self.action,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
m = _makeReply(self, msg, s, to=self.to,
notice=self.notice,
action=self.action,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
sendMsg(m)
return m
else:
@ -930,11 +943,11 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
# action implies noLengthCheck, which has already been
# handled. Let's stick an assert in here just in case.
assert not self.action
m = reply(msg, s, to=self.to,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
m = _makeReply(self, msg, s, to=self.to,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
sendMsg(m)
return m
# The '(XX more messages)' may have not the same
@ -946,11 +959,11 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
while instant > 1 and msgs:
instant -= 1
response = msgs.pop()
m = reply(msg, response, to=self.to,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
m = _makeReply(self, msg, response, to=self.to,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
sendMsg(m)
# XXX We should somehow allow these to be returned, but
# until someone complains, we'll be fine :) We
@ -976,15 +989,15 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
pass # We'll leave it as it is.
mask = prefix.split('!', 1)[1]
self._mores[mask] = msgs
public = self.irc.isChannel(msg.args[0])
public = bool(self.msg.channel)
private = self.private or not public
self._mores[msg.nick] = (private, msgs)
m = reply(msg, response, to=self.to,
action=self.action,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
m = _makeReply(self, msg, response, to=self.to,
action=self.action,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
sendMsg(m)
return m
finally:
@ -1035,7 +1048,7 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
if not isinstance(self.irc, irclib.Irc):
return self.irc.error(s, **kwargs)
else:
m = error(self.msg, s, **kwargs)
m = _makeErrorReply(self, self.msg, s, **kwargs)
self.irc.queueMsg(m)
return m
else:
@ -1346,7 +1359,7 @@ class Commands(BasePlugin, SynchronizedAndFirewalled):
help = getHelp
chan = None
if dynamic.msg is not None:
chan = dynamic.msg.args[0]
chan = dynamic.msg.channel
if simpleSyntax is None:
simpleSyntax = conf.get(conf.supybot.reply.showSimpleSyntax, chan)
if simpleSyntax:
@ -1385,7 +1398,7 @@ class PluginMixin(BasePlugin, irclib.IrcCallback):
else:
noIgnore = self.noIgnore
if noIgnore or \
not ircdb.checkIgnored(msg.prefix, msg.args[0]) or \
not ircdb.checkIgnored(msg.prefix, msg.channel) or \
not ircutils.isUserHostmask(msg.prefix): # Some services impl.
self.__parent.__call__(irc, msg)
else:

View File

@ -205,9 +205,12 @@ def urlSnarfer(f):
"""Protects the snarfer from loops (with other bots) and whatnot."""
def newf(self, irc, msg, match, *L, **kwargs):
url = match.group(0)
channel = msg.args[0]
if not irc.isChannel(channel) or (ircmsgs.isCtcp(msg) and not
ircmsgs.isAction(msg)):
channel = msg.channel
if not channel:
# Don't snarf in private
return
if not (ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg)):
# Don't snarf CTCPs unless they are a /me
return
if ircdb.channels.getChannel(channel).lobotomized:
self.log.debug('Not snarfing in %s: lobotomized.', channel)
@ -491,10 +494,11 @@ def getChannel(irc, msg, args, state):
return
if args and irc.isChannel(args[0]):
channel = args.pop(0)
elif irc.isChannel(msg.args[0]):
channel = msg.args[0]
elif msg.channel:
channel = msg.channel
else:
state.log.debug('Raising ArgumentError because there is no channel.')
print(msg.channel, msg)
raise callbacks.ArgumentError
state.channel = channel
state.args.append(channel)
@ -502,8 +506,8 @@ def getChannel(irc, msg, args, state):
def getChannels(irc, msg, args, state):
if args and all(map(irc.isChannel, args[0].split(','))):
channels = args.pop(0).split(',')
elif irc.isChannel(msg.args[0]):
channels = [msg.args[0]]
elif msg.channel:
channels = [msg.channel]
else:
state.log.debug('Raising ArgumentError because there is no channel.')
raise callbacks.ArgumentError
@ -535,11 +539,11 @@ def inChannel(irc, msg, args, state):
state.error(_('I\'m not in %s.') % state.channel, Raise=True)
def onlyInChannel(irc, msg, args, state):
if not (irc.isChannel(msg.args[0]) and msg.args[0] in irc.state.channels):
if not (msg.channel and msg.channel in irc.state.channels):
state.error(_('This command may only be given in a channel that I am '
'in.'), Raise=True)
else:
state.channel = msg.args[0]
state.channel = msg.channel
state.args.append(state.channel)
def callerInGivenChannel(irc, msg, args, state):
@ -576,8 +580,8 @@ def getChannelOrGlobal(irc, msg, args, state):
elif args and irc.isChannel(args[0]):
channel = args.pop(0)
state.channel = channel
elif irc.isChannel(msg.args[0]):
channel = msg.args[0]
elif msg.channel:
channel = msg.channel
state.channel = channel
else:
state.log.debug('Raising ArgumentError because there is no channel.')
@ -620,11 +624,11 @@ def getSomethingNoSpaces(irc, msg, args, state, *L):
getSomething(irc, msg, args, state, p=p, *L)
def private(irc, msg, args, state):
if irc.isChannel(msg.args[0]):
if msg.channel:
state.errorRequiresPrivacy(Raise=True)
def public(irc, msg, args, state, errmsg=None):
if not irc.isChannel(msg.args[0]):
if not msg.channel:
if errmsg is None:
errmsg = _('This message must be sent in a channel.')
state.error(errmsg, Raise=True)

View File

@ -875,25 +875,33 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
else:
return None
_numericErrorCommandRe = re.compile(r'^[45][0-9][0-9]$')
def feedMsg(self, msg):
"""Called by the IrcDriver; feeds a message received."""
def _tagMsg(self, msg):
"""Sets attribute on an incoming IRC message. Will usually only be
called by feedMsg, but may be useful in tests as well."""
msg.tag('receivedBy', self)
msg.tag('receivedOn', self.network)
msg.tag('receivedAt', time.time())
# Check if the message is sent to a channel
if msg.args:
channel = msg.args[0]
msg.channel = msg.args[0]
if msg.command in ('NOTICE', 'PRIVMSG') and \
not conf.supybot.protocols.irc.strictRfc():
statusmsg_chars = self.state.supported.get('statusmsg', '')
channel = channel.lstrip(statusmsg_chars)
if not self.isChannel(channel):
channel = None
msg.channel = self._stripChannelPrefix(msg.channel)
if not self.isChannel(msg.channel):
msg.channel = None
else:
channel = None
msg.channel = channel
msg.channel = None
def _stripChannelPrefix(self, channel):
statusmsg_chars = self.state.supported.get('statusmsg', '')
return channel.lstrip(statusmsg_chars)
_numericErrorCommandRe = re.compile(r'^[45][0-9][0-9]$')
def feedMsg(self, msg):
"""Called by the IrcDriver; feeds a message received."""
self._tagMsg(msg)
channel = msg.channel
preInFilter = str(msg).rstrip('\r\n')
log.debug('Incoming message (%s): %s', self.network, preInFilter)

View File

@ -659,7 +659,10 @@ def safeArgument(s):
def replyTo(msg):
"""Returns the appropriate target to send responses to msg."""
if isChannel(msg.args[0]):
if msg.channel:
# if message was sent to +#channel, we want to reply to +#channel;
# or unvoiced channel users will see the bot reply without the
# origin query
return msg.args[0]
else:
return msg.nick
@ -867,10 +870,7 @@ def standardSubstitute(irc, msg, text, env=None):
vars.update(msg.reply_env)
if irc and msg:
if isChannel(msg.args[0]):
channel = msg.args[0]
else:
channel = 'somewhere'
channel = msg.channel or 'somewhere'
def randNick():
if channel != 'somewhere':
L = list(irc.state.channels[channel].users)

View File

@ -175,6 +175,7 @@ class FunctionsTestCase(SupyTestCase):
self.assertEqual('foobar--', callbacks.canonicalName('foobar--'))
def testAddressed(self):
irc = getTestIrc()
oldprefixchars = str(conf.supybot.reply.whenAddressedBy.chars)
nick = 'supybot'
conf.supybot.reply.whenAddressedBy.chars.set('~!@')
@ -189,6 +190,7 @@ class FunctionsTestCase(SupyTestCase):
for msg in inChannel:
self.assertEqual('foo', callbacks.addressed(nick, msg), msg)
msg = ircmsgs.privmsg(nick, 'foo')
irc._tagMsg(msg)
self.assertEqual('foo', callbacks.addressed(nick, msg))
conf.supybot.reply.whenAddressedBy.chars.set(oldprefixchars)
msg = ircmsgs.privmsg('#foo', '%s::::: bar' % nick)
@ -216,52 +218,84 @@ class FunctionsTestCase(SupyTestCase):
conf.supybot.reply.whenNotAddressed.setValue(original)
def testAddressedWithMultipleNicks(self):
irc = getTestIrc()
msg = ircmsgs.privmsg('#foo', 'bar: baz')
irc._tagMsg(msg)
self.assertEqual(callbacks.addressed('bar', msg), 'baz')
# need to recreate the msg objects since the old ones have already
# been tagged
msg = ircmsgs.privmsg('#foo', 'bar: baz')
irc._tagMsg(msg)
self.assertEqual(callbacks.addressed('biff', msg, nicks=['bar']),
'baz')
def testAddressedWithNickAtEnd(self):
irc = getTestIrc()
msg = ircmsgs.privmsg('#foo', 'baz, bar')
irc._tagMsg(msg)
self.assertEqual(callbacks.addressed('bar', msg,
whenAddressedByNickAtEnd=True),
'baz')
def testAddressedPrefixCharsTakePrecedenceOverNickAtEnd(self):
irc = getTestIrc()
msg = ircmsgs.privmsg('#foo', '@echo foo')
irc._tagMsg(msg)
self.assertEqual(callbacks.addressed('foo', msg,
whenAddressedByNickAtEnd=True,
prefixChars='@'),
'echo foo')
def testReply(self):
irc = getTestIrc()
prefix = 'foo!bar@baz'
channelMsg = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix)
nonChannelMsg = ircmsgs.privmsg('supybot', 'bar baz', prefix=prefix)
irc._tagMsg(channelMsg)
irc._tagMsg(nonChannelMsg)
self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'),
callbacks.reply(channelMsg, 'foo', private=True))
callbacks._makeReply(irc, channelMsg, 'foo',
private=True))
self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'),
callbacks.reply(nonChannelMsg, 'foo'))
callbacks._makeReply(irc, nonChannelMsg, 'foo'))
self.assertEqual(ircmsgs.privmsg(channelMsg.args[0],
'%s: foo' % channelMsg.nick),
callbacks.reply(channelMsg, 'foo'))
callbacks._makeReply(irc, channelMsg, 'foo'))
self.assertEqual(ircmsgs.privmsg(channelMsg.args[0],
'foo'),
callbacks.reply(channelMsg, 'foo', prefixNick=False))
callbacks._makeReply(irc, channelMsg, 'foo',
prefixNick=False))
self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'),
callbacks.reply(channelMsg, 'foo',
notice=True, private=True))
callbacks._makeReply(irc, channelMsg, 'foo',
notice=True, private=True))
def testReplyStatusmsg(self):
irc = getTestIrc()
prefix = 'foo!bar@baz'
msg = ircmsgs.privmsg('+#foo', 'bar baz', prefix=prefix)
irc._tagMsg(msg)
# No statusmsg set, so understood as being a private message, so
# private reply
self.assertEqual(ircmsgs.notice(msg.nick, 'foo'),
callbacks._makeReply(irc, msg, 'foo'))
irc.state.supported['statusmsg'] = '+'
msg = ircmsgs.privmsg('+#foo', 'bar baz', prefix=prefix)
irc._tagMsg(msg)
print(msg.channel)
self.assertEqual(ircmsgs.privmsg('+#foo', '%s: foo' % msg.nick),
callbacks._makeReply(irc, msg, 'foo'))
def testReplyTo(self):
irc = getTestIrc()
prefix = 'foo!bar@baz'
msg = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix)
self.assertEqual(callbacks.reply(msg, 'blah', to='blah'),
irc._tagMsg(msg)
self.assertEqual(callbacks._makeReply(irc, msg, 'blah', to='blah'),
ircmsgs.privmsg('#foo', 'blah: blah'))
self.assertEqual(callbacks.reply(msg, 'blah', to='blah', private=True),
self.assertEqual(callbacks._makeReply(irc, msg, 'blah', to='blah',
private=True),
ircmsgs.notice('blah', 'blah'))
def testTokenize(self):
@ -682,7 +716,9 @@ class WithPrivateNoticeTestCase(ChannelPluginTestCase):
class ProxyTestCase(SupyTestCase):
def testHashing(self):
irc = getTestIrc()
msg = ircmsgs.ping('0')
irc._tagMsg(msg)
irc = irclib.Irc('test')
proxy = callbacks.SimpleProxy(irc, msg)
# First one way...

View File

@ -47,6 +47,7 @@ class CommandsTestCase(SupyTestCase):
realIrc = getTestIrc()
realIrc.nick = 'test'
realIrc.state.supported['chantypes'] = '#'
realIrc._tagMsg(msg)
irc = callbacks.SimpleProxy(realIrc, msg)
myspec = Spec(spec, **kwargs)
state = myspec(irc, msg, given)

View File

@ -281,12 +281,10 @@ class FunctionsTestCase(SupyTestCase):
def testStandardSubstitute(self):
# Stub out random msg and irc objects that provide what
# standardSubstitute wants
msg = ircmsgs.IrcMsg(':%s PRIVMSG #channel :stuff' % self.hostmask)
class Irc(object):
nick = 'bob'
network = 'testnet'
irc = getTestIrc()
irc = Irc()
msg = ircmsgs.IrcMsg(':%s PRIVMSG #channel :stuff' % self.hostmask)
irc._tagMsg(msg)
f = ircutils.standardSubstitute
vars = {'foo': 'bar', 'b': 'c', 'i': 100,
@ -344,9 +342,12 @@ class FunctionsTestCase(SupyTestCase):
self.assertEqual('{}|^', ircutils.toLower('[]\\~'))
def testReplyTo(self):
irc = getTestIrc()
prefix = 'foo!bar@baz'
channel = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix)
private = ircmsgs.privmsg('jemfinch', 'bar baz', prefix=prefix)
irc._tagMsg(channel)
irc._tagMsg(private)
self.assertEqual(ircutils.replyTo(channel), channel.args[0])
self.assertEqual(ircutils.replyTo(private), private.nick)

View File

@ -37,54 +37,53 @@ class holder:
users = set(map(str, range(1000)))
class FunctionsTestCase(SupyTestCase):
class irc:
class state:
channels = {'#foo': holder()}
nick = 'foobar'
network = 'testnet'
@retry()
def testStandardSubstitute(self):
irc = getTestIrc()
irc.state.channels = {'#foo': holder()}
f = ircutils.standardSubstitute
msg = ircmsgs.privmsg('#foo', 'filler', prefix='biff!quux@xyzzy')
s = f(self.irc, msg, '$rand')
irc._tagMsg(msg)
s = f(irc, msg, '$rand')
try:
int(s)
except ValueError:
self.fail('$rand wasn\'t an int.')
s = f(self.irc, msg, '$randomInt')
s = f(irc, msg, '$randomInt')
try:
int(s)
except ValueError:
self.fail('$randomint wasn\'t an int.')
self.assertEqual(f(self.irc, msg, '$botnick'), self.irc.nick)
self.assertEqual(f(self.irc, msg, '$who'), msg.nick)
self.assertEqual(f(self.irc, msg, '$WHO'),
self.assertEqual(f(irc, msg, '$botnick'), irc.nick)
self.assertEqual(f(irc, msg, '$who'), msg.nick)
self.assertEqual(f(irc, msg, '$WHO'),
msg.nick, 'stand. sub. not case-insensitive.')
self.assertEqual(f(self.irc, msg, '$nick'), msg.nick)
self.assertNotEqual(f(self.irc, msg, '$randomdate'), '$randomdate')
q = f(self.irc,msg,'$randomdate\t$randomdate')
self.assertEqual(f(irc, msg, '$nick'), msg.nick)
self.assertNotEqual(f(irc, msg, '$randomdate'), '$randomdate')
q = f(irc,msg,'$randomdate\t$randomdate')
dl = q.split('\t')
if dl[0] == dl[1]:
self.fail ('Two $randomdates in the same string were the same')
q = f(self.irc, msg, '$randomint\t$randomint')
q = f(irc, msg, '$randomint\t$randomint')
dl = q.split('\t')
if dl[0] == dl[1]:
self.fail ('Two $randomints in the same string were the same')
self.assertNotEqual(f(self.irc, msg, '$today'), '$today')
self.assertNotEqual(f(self.irc, msg, '$now'), '$now')
n = f(self.irc, msg, '$randnick')
self.failUnless(n in self.irc.state.channels['#foo'].users)
n = f(self.irc, msg, '$randomnick')
self.failUnless(n in self.irc.state.channels['#foo'].users)
n = f(self.irc, msg, '$randomnick '*100)
self.assertNotEqual(f(irc, msg, '$today'), '$today')
self.assertNotEqual(f(irc, msg, '$now'), '$now')
n = f(irc, msg, '$randnick')
self.failUnless(n in irc.state.channels['#foo'].users)
n = f(irc, msg, '$randomnick')
self.failUnless(n in irc.state.channels['#foo'].users)
n = f(irc, msg, '$randomnick '*100)
L = n.split()
self.failIf(all(L[0].__eq__, L), 'all $randomnicks were the same')
c = f(self.irc, msg, '$channel')
c = f(irc, msg, '$channel')
self.assertEqual(c, msg.args[0])
net = f(self.irc, msg, '$network')
self.assertEqual(net, self.irc.network)
net = f(irc, msg, '$network')
self.assertEqual(net, irc.network)