Merge branch 'ircv3.2' into testing

This commit is contained in:
Valentin Lorentz 2015-05-16 00:22:42 +02:00
commit 39d40ec617
6 changed files with 145 additions and 28 deletions

View File

@ -147,6 +147,13 @@ class NickAuth(callbacks.Plugin):
irc.queueMsg(ircmsgs.whois(nick, nick))
auth = wrap(auth, [])
def inFilter(self, irc, msg):
"""If the messages has a server tag with account name, tries to
authenticate it."""
if msg.server_tags and 'account' in msg.server_tags:
self._auth(irc, msg.prefix, msg.server_tags['account'])
return msg
def do330(self, irc, msg):
mynick, theirnick, theiraccount, garbage = msg.args
# I would like to use a dict comprehension, but we have to support
@ -176,18 +183,8 @@ class NickAuth(callbacks.Plugin):
account = msg.args[0]
user = ircdb.users.getUserFromNick(irc.network, account)
if not user:
try:
user = ircdb.users.getUser(msg.prefix)
except KeyError:
user = None
if user:
if account == '*':
user.clearAuth()
else:
user.addAuth(msg.prefix)
ircdb.users.setUser(user, flush=False)
if account != '*':
self._auth(irc, msg.prefix, account)
def doJoin(self, irc, msg):
@ -196,18 +193,8 @@ class NickAuth(callbacks.Plugin):
return
account = msg.args[1]
user = ircdb.users.getUserFromNick(irc.network, account)
if not user:
try:
user = ircdb.users.getUser(msg.prefix)
except KeyError:
user = None
if user:
if account != '*':
user.addAuth(msg.prefix)
ircdb.users.setUser(user, flush=False)
if account != '*':
self._auth(irc, msg.prefix, account)
def do354(self, irc, msg):
if len(msg.args) != 6 or msg.args[1] != '1':
@ -217,6 +204,12 @@ class NickAuth(callbacks.Plugin):
prefix = '%s!%s@%s' % (nick, ident, host)
user = ircdb.users.getUserFromNick(irc.network, account)
if account != '0':
self._auth(irc, prefix, account)
def _auth(self, irc, prefix, account):
user = ircdb.users.getUserFromNick(irc.network, account)
if not user:
try:
user = ircdb.users.getUser(prefix)
@ -224,7 +217,6 @@ class NickAuth(callbacks.Plugin):
user = None
if user:
if account != '0':
user.addAuth(prefix)
ircdb.users.setUser(user, flush=False)

View File

@ -45,6 +45,12 @@ class NickCapture(callbacks.Plugin):
self.__parent = super(NickCapture, self)
self.__parent.__init__(irc)
self.lastIson = 0
self.monitoring = []
def die(self):
for irc in self.monitoring:
nick = self._getNick(irc.network)
irc.unmonitor(nick)
def _getNick(self, network):
network_nick = conf.supybot.networks.get(network).nick()
@ -60,10 +66,16 @@ class NickCapture(callbacks.Plugin):
# We used to check this, but nicksToHostmasks is never cleared
# except on reconnects, which can cause trouble.
# if nick not in irc.state.nicksToHostmasks:
self._ison(irc, nick)
if 'monitor' in irc.state.supported:
if irc not in self.monitoring:
irc.monitor(nick)
self.monitoring.append(irc)
else:
self._ison(irc, nick)
self.__parent.__call__(irc, msg)
def _ison(self, irc, nick):
assert 'monitor' not in irc.state.supported
if self.registryValue('ison'):
now = time.time()
if now - self.lastIson > self.registryValue('ison.period'):
@ -95,6 +107,16 @@ class NickCapture(callbacks.Plugin):
nick = self._getNick(irc.network)
if nick:
self._sendNick(irc, nick)
def do731(self, irc, msg):
"""This is sent by the MONITOR when a nick goes offline."""
nick = self._getNick(irc.network)
for target in msg.args[1].split(','):
if nick == target:
self._sendNick(irc, nick)
self.monitoring.remove(irc)
irc.unmonitor(nick)
break
NickCapture = internationalizeDocstring(NickCapture)
Class = NickCapture

View File

@ -666,6 +666,7 @@ class Irc(IrcCommandDispatcher):
self._setNonResettingVariables()
self._queueConnectMessages()
self.startedSync = ircutils.IrcDict()
self.monitoring = ircutils.IrcDict()
def isChannel(self, s):
"""Helper function to check whether a given string is a channel on
@ -965,7 +966,8 @@ class Irc(IrcCommandDispatcher):
elif self.sasl_username and self.sasl_password:
self.sasl = 'plain'
for cap in ('account-notify', 'extended-join', 'multi-prefix'):
for cap in ('account-notify', 'extended-join', 'multi-prefix',
'metadata-notify', 'account-tag'):
self.queueMsg(ircmsgs.IrcMsg(command='CAP', args=('REQ', cap)))
if self.sasl:
@ -1041,6 +1043,38 @@ class Irc(IrcCommandDispatcher):
command='CAP',
args=('END',)))
def monitor(self, targets):
"""Increment a counter of how many callbacks monitor each target;
and send a MONITOR + to the server if the target is not yet
monitored."""
if isinstance(targets, str):
targets = [targets]
not_yet_monitored = set()
for target in targets:
if target in self.monitoring:
self.monitoring[target] += 1
else:
not_yet_monitored.add(target)
self.monitoring[target] = 1
if not_yet_monitored:
self.queueMsg(ircmsgs.monitor('+', not_yet_monitored))
return not_yet_monitored
def unmonitor(self, targets):
"""Decrements a counter of how many callbacks monitor each target;
and send a MONITOR - to the server if the counter drops to 0."""
if isinstance(targets, str):
targets = [targets]
should_be_unmonitored = set()
for target in targets:
self.monitoring[target] -= 1
if self.monitoring[target] == 0:
del self.monitoring[target]
should_be_unmonitored.add(target)
if should_be_unmonitored:
self.queueMsg(ircmsgs.monitor('-', should_be_unmonitored))
return should_be_unmonitored
def do903(self, msg):
log.info('%s: SASL authentication successful', self.network)
self.queueMsg(ircmsgs.IrcMsg(command='CAP', args=('END',)))

View File

@ -50,6 +50,37 @@ from .utils.iter import all
class MalformedIrcMsg(ValueError):
pass
# http://ircv3.net/specs/core/message-tags-3.2.html#escaping-values
SERVER_TAG_ESCAPE = [
('\\', '\\\\'), # \ -> \\
(' ', r'\s'),
(';', r'\:'),
('\r', r'\r'),
('\n', r'\n'),
]
escape_server_tag_value = utils.str.MultipleReplacer(
dict(SERVER_TAG_ESCAPE))
unescape_server_tag_value = utils.str.MultipleReplacer(
dict(map(lambda x:(x[1],x[0]), SERVER_TAG_ESCAPE)))
def parse_server_tags(s):
server_tags = {}
for tag in s.split(';'):
if '=' not in tag:
server_tags[tag] = None
else:
(key, value) = tag.split('=', 1)
server_tags[key] = unescape_server_tag_value(value)
return server_tags
def format_server_tags(server_tags):
parts = []
for (key, value) in server_tags.items():
if value is None:
parts.append(key)
else:
parts.append('%s=%s' % (key, escape_server_tag_value(value)))
return '@' + ';'.join(parts)
class IrcMsg(object):
"""Class to represent an IRC message.
@ -81,7 +112,8 @@ class IrcMsg(object):
# data. Goodbye, __slots__.
# On second thought, let's use methods for tagging.
__slots__ = ('args', 'command', 'host', 'nick', 'prefix', 'user',
'_hash', '_str', '_repr', '_len', 'tags', 'reply_env')
'_hash', '_str', '_repr', '_len', 'tags', 'reply_env',
'server_tags')
def __init__(self, s='', command='', args=(), prefix='', msg=None,
reply_env=None):
assert not (msg and s), 'IrcMsg.__init__ cannot accept both s and msg'
@ -99,6 +131,11 @@ class IrcMsg(object):
if not s.endswith('\n'):
s += '\n'
self._str = s
if s[0] == '@':
(server_tags, s) = s.split(' ', 1)
self.server_tags = parse_server_tags(server_tags[1:])
else:
self.server_tags = {}
if s[0] == ':':
self.prefix, s = s[1:].split(None, 1)
else:
@ -818,6 +855,19 @@ def ison(nick, prefix='', msg=None):
prefix = msg.prefix
return IrcMsg(prefix=prefix, command='ISON', args=(nick,), msg=msg)
def monitor(subcommand, nicks=None, prefix='', msg=None):
if conf.supybot.protocols.irc.strictRfc():
assert isNick(nick), repr(nick)
assert subcommand in '+-CLS'
if subcommand in 'CLS':
assert nicks is None
if msg and not prefix:
prefix = msg.prefix
if not isinstance(nicks, str):
nicks = ','.join(nicks)
return IrcMsg(prefix=prefix, command='MONITOR', args=(subcommand, nicks),
msg=msg)
def error(s, msg=None):
return IrcMsg(command='ERROR', args=(s,), msg=msg)

View File

@ -391,6 +391,10 @@ class IrcTestCase(SupyTestCase):
m = self.irc.takeMsg()
self.failUnless(m.command == 'CAP', 'Expected CAP, got %r.' % m)
m = self.irc.takeMsg()
self.failUnless(m.command == 'CAP', 'Expected CAP, got %r.' % m)
m = self.irc.takeMsg()
self.failUnless(m.command == 'CAP', 'Expected CAP, got %r.' % m)
m = self.irc.takeMsg()
self.failUnless(m.command == 'USER', 'Expected USER, got %r.' % m)
def testPingResponse(self):
@ -492,6 +496,8 @@ class IrcCallbackTestCase(SupyTestCase):
ircmsgs.IrcMsg(command='CAP', args=('REQ', 'account-notify')),
ircmsgs.IrcMsg(command='CAP', args=('REQ', 'extended-join')),
ircmsgs.IrcMsg(command='CAP', args=('REQ', 'multi-prefix')),
ircmsgs.IrcMsg(command='CAP', args=('REQ', 'metadata-notify')),
ircmsgs.IrcMsg(command='CAP', args=('REQ', 'account-tag')),
ircmsgs.IrcMsg(command='CAP', args=('END',)),
ircmsgs.user('limnoria', user)
]

View File

@ -128,6 +128,19 @@ class IrcMsgTestCase(SupyTestCase):
m.tag('repliedTo', 12)
self.assertEqual(m.repliedTo, 12)
def testServerTags(self):
s = '@aaa=b\\:bb;ccc;example.com/ddd=ee\\\\se ' \
':nick!ident@host.com PRIVMSG me :Hello'
m = ircmsgs.IrcMsg(s)
self.assertEqual(m.server_tags, {
'aaa': 'b;bb',
'ccc': None,
'example.com/ddd': 'ee\\se'})
self.assertEqual(m.prefix, 'nick!ident@host.com')
self.assertEqual(m.command, 'PRIVMSG')
self.assertEqual(m.args, ('me', 'Hello'))
self.assertEqual(str(m), s + '\n')
class FunctionsTestCase(SupyTestCase):
def testIsAction(self):
L = [':jemfinch!~jfincher@ts26-2.homenet.ohio-state.edu PRIVMSG'