mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-12-25 20:22:45 +01:00
Route commands from Network.command back to the original network (#1540)
Add a replyIrc parameter to ReplyIrcProxy to run a command on one network, but route the replies to another. This fixes a long standing issue where replies for remote commands are often lost to the void, as the nick of the caller may not exist on the target network (or worse, it could belong to a completely unrelated person). Closes GH-556. Co-authored-by: Val Lorentz <progval+git@progval.net>
This commit is contained in:
parent
654937ecfc
commit
416a05e326
@ -149,7 +149,7 @@ class Network(callbacks.Plugin):
|
|||||||
|
|
||||||
Gives the bot <command> (with its associated <arg>s) on <network>.
|
Gives the bot <command> (with its associated <arg>s) on <network>.
|
||||||
"""
|
"""
|
||||||
self.Proxy(otherIrc, msg, commandAndArgs)
|
self.Proxy(otherIrc, msg, commandAndArgs, replyIrc=irc)
|
||||||
command = wrap(command, ['admin', ('networkIrc', True), many('something')])
|
command = wrap(command, ['admin', ('networkIrc', True), many('something')])
|
||||||
|
|
||||||
def cmdall(self, irc, msg, args, commandAndArgs):
|
def cmdall(self, irc, msg, args, commandAndArgs):
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
from supybot.test import *
|
from supybot.test import *
|
||||||
|
|
||||||
class NetworkTestCase(PluginTestCase):
|
class NetworkTestCase(PluginTestCase):
|
||||||
plugins = ['Network', 'Utilities']
|
plugins = ['Network', 'Utilities', 'String', 'Misc']
|
||||||
def testNetworks(self):
|
def testNetworks(self):
|
||||||
self.assertNotError('networks')
|
self.assertNotError('networks')
|
||||||
|
|
||||||
@ -39,6 +39,28 @@ class NetworkTestCase(PluginTestCase):
|
|||||||
self.assertResponse('network command %s echo 1' % self.irc.network,
|
self.assertResponse('network command %s echo 1' % self.irc.network,
|
||||||
'1')
|
'1')
|
||||||
|
|
||||||
|
def testCommandRoutesBackToCaller(self):
|
||||||
|
self.otherIrc = getTestIrc("testnet1")
|
||||||
|
# This will fail with timeout if the response never comes back
|
||||||
|
self.assertResponse(
|
||||||
|
'network command testnet1 echo $network', 'testnet1')
|
||||||
|
|
||||||
|
def testCommandRoutesErrorsBackToCaller(self):
|
||||||
|
self.otherIrc = getTestIrc("testnet2")
|
||||||
|
self.assertRegexp(
|
||||||
|
f'network command testnet2 re s/.*// test',
|
||||||
|
'I tried to send you an empty message')
|
||||||
|
|
||||||
|
def testCommandRoutesMoreBackToCaller(self):
|
||||||
|
self.otherIrc = getTestIrc("testnet3")
|
||||||
|
self.assertNotError('clearmores')
|
||||||
|
self.assertError('more')
|
||||||
|
self.assertRegexp(
|
||||||
|
f'network command testnet3 echo {"Hello"*300}',
|
||||||
|
r'Hello.*\(\d+ more messages\)')
|
||||||
|
self.assertRegexp(
|
||||||
|
'more',
|
||||||
|
r'Hello.*\(\d+ more messages\)')
|
||||||
|
|
||||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
||||||
|
|
||||||
|
@ -638,10 +638,15 @@ _repr = repr
|
|||||||
class ReplyIrcProxy(RichReplyMethods):
|
class ReplyIrcProxy(RichReplyMethods):
|
||||||
"""This class is a thin wrapper around an irclib.Irc object that gives it
|
"""This class is a thin wrapper around an irclib.Irc object that gives it
|
||||||
the reply() and error() methods (as well as everything in RichReplyMethods,
|
the reply() and error() methods (as well as everything in RichReplyMethods,
|
||||||
based on those two)."""
|
based on those two).
|
||||||
|
|
||||||
|
If ``replyIrc`` is given in addition to ``irc``, commands will be run on ``irc``
|
||||||
|
but replies will be delivered to ``replyIrc``. This is used by the Network
|
||||||
|
plugin to run commands on other networks."""
|
||||||
_mores = ircutils.IrcDict()
|
_mores = ircutils.IrcDict()
|
||||||
def __init__(self, irc, msg):
|
def __init__(self, irc, msg, replyIrc=None):
|
||||||
self.irc = irc
|
self.irc = irc
|
||||||
|
self.replyIrc = replyIrc or irc
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.getRealIrc()._setMsgChannel(self.msg)
|
self.getRealIrc()._setMsgChannel(self.msg)
|
||||||
|
|
||||||
@ -669,8 +674,8 @@ class ReplyIrcProxy(RichReplyMethods):
|
|||||||
if msg is None:
|
if msg is None:
|
||||||
msg = self.msg
|
msg = self.msg
|
||||||
if s:
|
if s:
|
||||||
m = _makeErrorReply(self, msg, s, **kwargs)
|
m = _makeErrorReply(self.replyIrc, msg, s, **kwargs)
|
||||||
self.irc.queueMsg(m)
|
self.replyIrc.queueMsg(m)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
def _defaultPrefixNick(self, msg):
|
def _defaultPrefixNick(self, msg):
|
||||||
@ -754,29 +759,29 @@ class ReplyIrcProxy(RichReplyMethods):
|
|||||||
def _sendReply(self, s, target, msg, sendImmediately=False,
|
def _sendReply(self, s, target, msg, sendImmediately=False,
|
||||||
noLengthCheck=False, **kwargs):
|
noLengthCheck=False, **kwargs):
|
||||||
if sendImmediately:
|
if sendImmediately:
|
||||||
sendMsg = self.irc.sendMsg
|
sendMsg = self.replyIrc.sendMsg
|
||||||
else:
|
else:
|
||||||
sendMsg = self.irc.queueMsg
|
sendMsg = self.replyIrc.queueMsg
|
||||||
|
|
||||||
if isinstance(self.irc, self.__class__):
|
if isinstance(self.replyIrc, self.__class__):
|
||||||
s = s[:conf.supybot.reply.maximumLength()]
|
s = s[:conf.supybot.reply.maximumLength()]
|
||||||
return self.irc.reply(s,
|
return self.replyIrc.reply(s,
|
||||||
noLengthCheck=noLengthCheck,
|
noLengthCheck=noLengthCheck,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
elif noLengthCheck:
|
elif noLengthCheck:
|
||||||
# noLengthCheck only matters to NestedCommandsIrcProxy, so
|
# noLengthCheck only matters to NestedCommandsIrcProxy, so
|
||||||
# it's not used here. Just in case you were wondering.
|
# it's not used here. Just in case you were wondering.
|
||||||
m = _makeReply(self, msg, s, **kwargs)
|
m = _makeReply(self.replyIrc, msg, s, **kwargs)
|
||||||
sendMsg(m)
|
sendMsg(m)
|
||||||
return m
|
return m
|
||||||
else:
|
else:
|
||||||
s = ircutils.safeArgument(s)
|
s = ircutils.safeArgument(s)
|
||||||
allowedLength = conf.get(conf.supybot.reply.mores.length,
|
allowedLength = conf.get(conf.supybot.reply.mores.length,
|
||||||
channel=target, network=self.irc.network)
|
channel=target, network=self.replyIrc.network)
|
||||||
if not allowedLength: # 0 indicates this.
|
if not allowedLength: # 0 indicates this.
|
||||||
allowedLength = 512 - self._replyOverhead(msg, **kwargs)
|
allowedLength = 512 - self._replyOverhead(msg, **kwargs)
|
||||||
maximumMores = conf.get(conf.supybot.reply.mores.maximum,
|
maximumMores = conf.get(conf.supybot.reply.mores.maximum,
|
||||||
channel=target, network=self.irc.network)
|
channel=target, network=self.replyIrc.network)
|
||||||
maximumLength = allowedLength * maximumMores
|
maximumLength = allowedLength * maximumMores
|
||||||
if len(s) > maximumLength:
|
if len(s) > maximumLength:
|
||||||
log.warning('Truncating to %s bytes from %s bytes.',
|
log.warning('Truncating to %s bytes from %s bytes.',
|
||||||
@ -785,12 +790,12 @@ class ReplyIrcProxy(RichReplyMethods):
|
|||||||
s_size = len(s.encode()) if minisix.PY3 else len(s)
|
s_size = len(s.encode()) if minisix.PY3 else len(s)
|
||||||
if s_size <= allowedLength or \
|
if s_size <= allowedLength or \
|
||||||
not conf.get(conf.supybot.reply.mores,
|
not conf.get(conf.supybot.reply.mores,
|
||||||
channel=target, network=self.irc.network):
|
channel=target, network=self.replyIrc.network):
|
||||||
# There's no need for action=self.action here because
|
# There's no need for action=self.action here because
|
||||||
# action implies noLengthCheck, which has already been
|
# action implies noLengthCheck, which has already been
|
||||||
# handled. Let's stick an assert in here just in case.
|
# handled. Let's stick an assert in here just in case.
|
||||||
assert not kwargs.get('action')
|
assert not kwargs.get('action')
|
||||||
m = _makeReply(self, msg, s, **kwargs)
|
m = _makeReply(self.replyIrc, msg, s, **kwargs)
|
||||||
sendMsg(m)
|
sendMsg(m)
|
||||||
return m
|
return m
|
||||||
# The '(XX more messages)' may have not the same
|
# The '(XX more messages)' may have not the same
|
||||||
@ -803,7 +808,7 @@ class ReplyIrcProxy(RichReplyMethods):
|
|||||||
chunks.reverse()
|
chunks.reverse()
|
||||||
|
|
||||||
instant = conf.get(conf.supybot.reply.mores.instant,
|
instant = conf.get(conf.supybot.reply.mores.instant,
|
||||||
channel=target, network=self.irc.network)
|
channel=target, network=self.replyIrc.network)
|
||||||
|
|
||||||
# Big complex loop ahead, with lots of cases and opportunities for
|
# Big complex loop ahead, with lots of cases and opportunities for
|
||||||
# off-by-one errors. Here is the meaning of each of the variables
|
# off-by-one errors. Here is the meaning of each of the variables
|
||||||
@ -859,9 +864,9 @@ class ReplyIrcProxy(RichReplyMethods):
|
|||||||
if is_instant and not is_first:
|
if is_instant and not is_first:
|
||||||
d = kwargs.copy()
|
d = kwargs.copy()
|
||||||
d['prefixNick'] = False
|
d['prefixNick'] = False
|
||||||
msgs.append(_makeReply(self, msg, chunk, **d))
|
msgs.append(_makeReply(self.replyIrc, msg, chunk, **d))
|
||||||
else:
|
else:
|
||||||
msgs.append(_makeReply(self, msg, chunk, **kwargs))
|
msgs.append(_makeReply(self.replyIrc, msg, chunk, **kwargs))
|
||||||
|
|
||||||
instant_messages = []
|
instant_messages = []
|
||||||
|
|
||||||
@ -876,13 +881,15 @@ class ReplyIrcProxy(RichReplyMethods):
|
|||||||
# return m
|
# return m
|
||||||
|
|
||||||
if conf.supybot.protocols.irc.experimentalExtensions() \
|
if conf.supybot.protocols.irc.experimentalExtensions() \
|
||||||
and 'draft/multiline' in self.state.capabilities_ack \
|
and 'draft/multiline' in \
|
||||||
|
self.replyIrc.state.capabilities_ack \
|
||||||
and len(instant_messages) > 1:
|
and len(instant_messages) > 1:
|
||||||
# More than one message to send now, and we are allowed to use
|
# More than one message to send now, and we are allowed to use
|
||||||
# multiline batches, so let's do it
|
# multiline batches, so let's do it
|
||||||
self.queueMultilineBatches(
|
self.replyIrc.queueMultilineBatches(
|
||||||
instant_messages, target, msg.nick, concat=True,
|
instant_messages, target, msg.nick, concat=True,
|
||||||
allowedLength=allowedLength, sendImmediately=sendImmediately)
|
allowedLength=allowedLength,
|
||||||
|
sendImmediately=sendImmediately)
|
||||||
else:
|
else:
|
||||||
for instant_msg in instant_messages:
|
for instant_msg in instant_messages:
|
||||||
sendMsg(instant_msg)
|
sendMsg(instant_msg)
|
||||||
@ -892,7 +899,7 @@ class ReplyIrcProxy(RichReplyMethods):
|
|||||||
prefix = msg.prefix
|
prefix = msg.prefix
|
||||||
if target and ircutils.isNick(target):
|
if target and ircutils.isNick(target):
|
||||||
try:
|
try:
|
||||||
state = self.getRealIrc().state
|
state = self.replyIrc.state
|
||||||
prefix = state.nickToHostmask(target)
|
prefix = state.nickToHostmask(target)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass # We'll leave it as it is.
|
pass # We'll leave it as it is.
|
||||||
@ -973,9 +980,10 @@ SimpleProxy = ReplyIrcProxy # Backwards-compatibility
|
|||||||
|
|
||||||
class NestedCommandsIrcProxy(ReplyIrcProxy):
|
class NestedCommandsIrcProxy(ReplyIrcProxy):
|
||||||
"A proxy object to allow proper nesting of commands (even threaded ones)."
|
"A proxy object to allow proper nesting of commands (even threaded ones)."
|
||||||
def __init__(self, irc, msg, args, nested=0):
|
def __init__(self, irc, msg, args, nested=0, replyIrc=None):
|
||||||
assert isinstance(args, list), 'Args should be a list, not a string.'
|
assert isinstance(args, list), 'Args should be a list, not a string.'
|
||||||
super(NestedCommandsIrcProxy, self).__init__(irc, msg)
|
super(NestedCommandsIrcProxy, self).__init__(
|
||||||
|
irc, msg, replyIrc=replyIrc)
|
||||||
self.nested = nested
|
self.nested = nested
|
||||||
self.repliedTo = False
|
self.repliedTo = False
|
||||||
if not self.nested and isinstance(irc, self.__class__):
|
if not self.nested and isinstance(irc, self.__class__):
|
||||||
|
Loading…
Reference in New Issue
Block a user