From 83a8afde16695073049846723744036988b4bb9b Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Thu, 7 May 2020 21:17:55 +0200 Subject: [PATCH] Add experimental support for +draft/reply client capability on outgoing messages. --- src/callbacks.py | 8 ++++++ src/conf.py | 7 +++++ test/test_callbacks.py | 64 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/src/callbacks.py b/src/callbacks.py index 6a2151b5c..5c7f08ab3 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -236,6 +236,14 @@ def _makeReply(irc, msg, s, # Finally, we'll return the actual message. ret = msgmaker(target, s) ret.tag('inReplyTo', msg) + if 'msgid' in msg.server_tags \ + and conf.supybot.protocols.irc.experimentalExtensions() \ + and 'message-tags' in irc.state.capabilities_ack: + # In theory, msgid being in server_tags implies message-tags was + # negotiated, but the +reply spec requires it explicitly. Plus, there's + # no harm in doing this extra check, in case a plugin is replying + # across network (as it may happen with '@network command'). + ret.server_tags['+draft/reply'] = msg.server_tags['msgid'] return ret def error(*args, **kwargs): diff --git a/src/conf.py b/src/conf.py index a0b0bd269..e8ddbf3f1 100644 --- a/src/conf.py +++ b/src/conf.py @@ -1219,6 +1219,13 @@ registerGlobalValue(supybot.protocols.irc, 'strictRfc', requires you to message a nick such as services@this.network.server then you you should set this to False."""))) +registerGlobalValue(supybot.protocols.irc, 'experimentalExtensions', + registry.Boolean(False, _("""Determines whether the bot will enable + draft/experimental extensions of the IRC protocol. Setting this to True + may break your bot at any time without warning and/or break your + configuration irreversibly. So keep it False unless you know what you are + doing."""))) + registerGlobalValue(supybot.protocols.irc, 'certfile', registry.String('', _("""Determines what certificate file (if any) the bot will use connect with SSL sockets by default."""))) diff --git a/test/test_callbacks.py b/test/test_callbacks.py index 2b6fec9c0..4547eee0e 100644 --- a/test/test_callbacks.py +++ b/test/test_callbacks.py @@ -534,6 +534,70 @@ class PrivmsgTestCase(ChannelPluginTestCase): self.assertRegexp('help first firstcmd', 'First', 0) # no re.I flag. self.assertRegexp('help firstrepeat firstcmd', 'FirstRepeat', 0) + def testClientTagReply(self): + self.irc.addCallback(self.First(self.irc)) + + # no CAP, no msgid, no experimentalExtensions -> no +reply + self.irc.feedMsg(ircmsgs.IrcMsg( + command='PRIVMSG', prefix=self.prefix, + args=('#foo', '%s: firstcmd' % self.nick))) + msg = self.irc.takeMsg() + self.assertEqual(msg, ircmsgs.IrcMsg( + command='PRIVMSG', args=('#foo', '%s: foo' % self.nick))) + + # CAP and msgid, not no experimentalExtensions -> no +reply + self.irc.feedMsg(ircmsgs.IrcMsg( + command='PRIVMSG', prefix=self.prefix, + args=('#foo', '%s: firstcmd' % self.nick), + server_tags={'msgid': 'foobar'})) + msg = self.irc.takeMsg() + self.assertEqual(msg, ircmsgs.IrcMsg( + command='PRIVMSG', args=('#foo', '%s: foo' % self.nick))) + + with conf.supybot.protocols.irc.experimentalExtensions.context(True): + # no CAP, but msgid and experimentalExtensions -> no +reply + self.irc.feedMsg(ircmsgs.IrcMsg( + command='PRIVMSG', prefix=self.prefix, + args=('#foo', '%s: firstcmd' % self.nick), + server_tags={'msgid': 'foobar'})) + msg = self.irc.takeMsg() + self.assertEqual(msg, ircmsgs.IrcMsg( + command='PRIVMSG', args=('#foo', '%s: foo' % self.nick))) + + # msgid and experimentalExtensions, but no CAP -> no +reply + # (note that in theory it's impossible to receive msgid without + # the CAP, but the +reply spec explicitly requires to check it) + self.irc.feedMsg(ircmsgs.IrcMsg( + command='PRIVMSG', prefix=self.prefix, + args=('#foo', '%s: firstcmd' % self.nick), + server_tags={'msgid': 'foobar'})) + msg = self.irc.takeMsg() + self.assertEqual(msg, ircmsgs.IrcMsg( + command='PRIVMSG', args=('#foo', '%s: foo' % self.nick))) + + try: + self.irc.state.capabilities_ack.add('message-tags') + + # no msgid, but CAP and experimentalExtensions -> no +reply + self.irc.feedMsg(ircmsgs.IrcMsg( + command='PRIVMSG', prefix=self.prefix, + args=('#foo', '%s: firstcmd' % self.nick))) + msg = self.irc.takeMsg() + self.assertEqual(msg, ircmsgs.IrcMsg( + command='PRIVMSG', args=('#foo', '%s: foo' % self.nick))) + + # all of CAP, msgid, experimentalExtensions -> yes +reply + self.irc.feedMsg(ircmsgs.IrcMsg( + command='PRIVMSG', prefix=self.prefix, + args=('#foo', '%s: firstcmd' % self.nick), + server_tags={'msgid': 'foobar'})) + msg = self.irc.takeMsg() + self.assertEqual(msg, ircmsgs.IrcMsg( + command='PRIVMSG', args=('#foo', '%s: foo' % self.nick), + server_tags={'+draft/reply': 'foobar'})) + finally: + self.irc.state.capabilities_ack.remove('message-tags') + class TwoRepliesFirstAction(callbacks.Plugin): def testactionreply(self, irc, msg, args): irc.reply('foo', action=True)