diff --git a/src/ircmsgs.py b/src/ircmsgs.py index 54ab272b3..3a4453de4 100644 --- a/src/ircmsgs.py +++ b/src/ircmsgs.py @@ -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: diff --git a/test/test_ircmsgs.py b/test/test_ircmsgs.py index 24960f500..583d0896a 100644 --- a/test/test_ircmsgs.py +++ b/test/test_ircmsgs.py @@ -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'