mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-26 20:59:27 +01:00
Add support for receiving commands from draft/multiline batches.
This commit is contained in:
parent
975a9101f4
commit
4aca6e3d5a
@ -44,7 +44,6 @@ import supybot.i18n as i18n
|
|||||||
import supybot.utils as utils
|
import supybot.utils as utils
|
||||||
import supybot.world as world
|
import supybot.world as world
|
||||||
import supybot.ircdb as ircdb
|
import supybot.ircdb as ircdb
|
||||||
from supybot.commands import *
|
|
||||||
import supybot.irclib as irclib
|
import supybot.irclib as irclib
|
||||||
import supybot.plugin as plugin
|
import supybot.plugin as plugin
|
||||||
import supybot.plugins as plugins
|
import supybot.plugins as plugins
|
||||||
@ -54,6 +53,7 @@ import supybot.ircmsgs as ircmsgs
|
|||||||
import supybot.ircutils as ircutils
|
import supybot.ircutils as ircutils
|
||||||
import supybot.registry as registry
|
import supybot.registry as registry
|
||||||
import supybot.callbacks as callbacks
|
import supybot.callbacks as callbacks
|
||||||
|
from supybot.commands import additional, getopts, optional, wrap
|
||||||
from supybot.i18n import PluginInternationalization, internationalizeDocstring
|
from supybot.i18n import PluginInternationalization, internationalizeDocstring
|
||||||
_ = PluginInternationalization('Owner')
|
_ = PluginInternationalization('Owner')
|
||||||
|
|
||||||
@ -226,11 +226,103 @@ class Owner(callbacks.Plugin):
|
|||||||
|
|
||||||
def setFloodQueueTimeout(self, *args, **kwargs):
|
def setFloodQueueTimeout(self, *args, **kwargs):
|
||||||
self.commands.timeout = conf.supybot.abuse.flood.interval()
|
self.commands.timeout = conf.supybot.abuse.flood.interval()
|
||||||
|
|
||||||
|
def doBatch(self, irc, msg):
|
||||||
|
if not conf.supybot.protocols.irc.experimentalExtensions():
|
||||||
|
return
|
||||||
|
|
||||||
|
batch = msg.tagged('batch') # Always not-None on a BATCH message
|
||||||
|
|
||||||
|
if msg.args[0].startswith('+'):
|
||||||
|
# Start of a batch, we're not interested yet.
|
||||||
|
return
|
||||||
|
if batch.type != 'draft/multiline':
|
||||||
|
# This is not a multiline batch, also not interested.
|
||||||
|
return
|
||||||
|
|
||||||
|
assert msg.args[0].startswith("-"), (
|
||||||
|
"BATCH's first argument should start with either - or +, but "
|
||||||
|
"it is %s."
|
||||||
|
) % msg.args[0]
|
||||||
|
# End of multiline batch. It may be a long command.
|
||||||
|
|
||||||
|
payloads = []
|
||||||
|
first_privmsg = None
|
||||||
|
|
||||||
|
for message in batch.messages:
|
||||||
|
if message.command != "PRIVMSG":
|
||||||
|
# We're only interested in PRIVMSGs for the payloads.
|
||||||
|
# (eg. exclude NOTICE)
|
||||||
|
continue
|
||||||
|
elif not payloads:
|
||||||
|
# This is the first PRIVMSG of the batch
|
||||||
|
first_privmsg = message
|
||||||
|
payloads.append(message.args[1])
|
||||||
|
elif 'draft/multiline-concat' in message.server_tags:
|
||||||
|
# This message is not a new line, but the continuation
|
||||||
|
# of the previous one.
|
||||||
|
payloads.append(message.args[1])
|
||||||
|
else:
|
||||||
|
# New line; stop here. We're not processing extra lines
|
||||||
|
# either as the rest of the command or as new commands.
|
||||||
|
# This may change in the future.
|
||||||
|
break
|
||||||
|
|
||||||
|
payload = ''.join(payloads)
|
||||||
|
if not payload:
|
||||||
|
self.log.error(
|
||||||
|
'Got empty multiline payload. This is a bug, please '
|
||||||
|
'report it along with logs.'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
assert first_privmsg, "This shouldn't be None unless payload is empty"
|
||||||
|
|
||||||
|
# Let's build a synthetic message from the various parts of the
|
||||||
|
# batch, to look like the multiline batch was a single (large)
|
||||||
|
# PRIVMSG:
|
||||||
|
# * copy the tags and server tags of the 'BATCH +' command,
|
||||||
|
# * copy the prefix and channel of any of the PRIVMSGs
|
||||||
|
# inside the batch
|
||||||
|
# * create a new args[1]
|
||||||
|
target = first_privmsg.args[0]
|
||||||
|
synthetic_msg = ircmsgs.IrcMsg(
|
||||||
|
msg=batch.messages[0], # tags, server_tags, time
|
||||||
|
prefix=first_privmsg.prefix,
|
||||||
|
command='PRIVMSG',
|
||||||
|
args=(target, payload)
|
||||||
|
)
|
||||||
|
|
||||||
|
self._doPrivmsgs(irc, synthetic_msg)
|
||||||
|
|
||||||
def doPrivmsg(self, irc, msg):
|
def doPrivmsg(self, irc, msg):
|
||||||
|
if conf.supybot.protocols.irc.experimentalExtensions():
|
||||||
|
if 'batch' in msg.server_tags \
|
||||||
|
and any(batch.type =='draft/multiline'
|
||||||
|
for batch in irc.state.getParentBatches(msg)):
|
||||||
|
# We will handle the message in doBatch when the entire batch ends.
|
||||||
|
return
|
||||||
|
|
||||||
|
self._doPrivmsgs(irc, msg)
|
||||||
|
|
||||||
|
def _doPrivmsgs(self, irc, msg):
|
||||||
|
"""If the given message is a command, triggers Limnoria's
|
||||||
|
command-dispatching for that command.
|
||||||
|
|
||||||
|
Takes the same arguments as ``doPrivmsg`` would, but ``msg`` can
|
||||||
|
potentially be an artificial message synthesized in doBatch
|
||||||
|
from a multiline batch.
|
||||||
|
|
||||||
|
Usually, a command is a single message, so ``payload=msg.params[0]``
|
||||||
|
However, when ``msg`` is part of a multiline message, the payload
|
||||||
|
is the concatenation of multiple messages.
|
||||||
|
See <https://ircv3.net/specs/extensions/multiline>.
|
||||||
|
"""
|
||||||
assert self is irc.callbacks[0], \
|
assert self is irc.callbacks[0], \
|
||||||
'Owner isn\'t first callback: %r' % irc.callbacks
|
'Owner isn\'t first callback: %r' % irc.callbacks
|
||||||
if ircmsgs.isCtcp(msg):
|
if ircmsgs.isCtcp(msg):
|
||||||
return
|
return
|
||||||
|
|
||||||
s = callbacks.addressed(irc, msg)
|
s = callbacks.addressed(irc, msg)
|
||||||
if s:
|
if s:
|
||||||
ignored = ircdb.checkIgnored(msg.prefix)
|
ignored = ircdb.checkIgnored(msg.prefix)
|
||||||
|
@ -118,4 +118,73 @@ class OwnerTestCase(PluginTestCase):
|
|||||||
self.assertError('defaultplugin foobar owner')
|
self.assertError('defaultplugin foobar owner')
|
||||||
|
|
||||||
|
|
||||||
|
class CommandsTestCase(PluginTestCase):
|
||||||
|
plugins = ('Owner', 'Utilities')
|
||||||
|
|
||||||
|
def testSimpleCommand(self):
|
||||||
|
self.irc.feedMsg(
|
||||||
|
ircmsgs.privmsg(self.irc.nick, 'echo foo $nick!$user@$host', self.prefix))
|
||||||
|
response = self.irc.takeMsg()
|
||||||
|
self.assertEqual(response.args, (self.nick, 'foo ' + self.prefix))
|
||||||
|
|
||||||
|
def testMultilineCommandDisabled(self):
|
||||||
|
self._sendBatch()
|
||||||
|
|
||||||
|
# response to 'echo '
|
||||||
|
self.assertRegexp('', '(echo <text>)')
|
||||||
|
|
||||||
|
# response to 'foo '
|
||||||
|
self.assertResponse('', 'Error: "foo" is not a valid command.')
|
||||||
|
|
||||||
|
# response to '$prefix'
|
||||||
|
self.assertResponse(
|
||||||
|
'', 'Error: "$nick!$user@$host" is not a valid command.')
|
||||||
|
|
||||||
|
# response to 'echo nope'
|
||||||
|
self.assertResponse('', 'nope')
|
||||||
|
|
||||||
|
def testMultilineCommand(self):
|
||||||
|
with conf.supybot.protocols.irc.experimentalExtensions.context(True):
|
||||||
|
self._sendBatch()
|
||||||
|
response = self.irc.takeMsg()
|
||||||
|
self.assertEqual(response.args, (self.nick, 'foo ' + self.prefix))
|
||||||
|
|
||||||
|
response = self.irc.takeMsg()
|
||||||
|
self.assertIsNone(response, 'Should not respond to second line')
|
||||||
|
|
||||||
|
def _sendBatch(self):
|
||||||
|
self.irc.feedMsg(ircmsgs.IrcMsg(
|
||||||
|
command='BATCH',
|
||||||
|
args=('+123', 'draft/multiline', self.irc.nick)))
|
||||||
|
|
||||||
|
# one line
|
||||||
|
self.irc.feedMsg(ircmsgs.IrcMsg(
|
||||||
|
server_tags={'batch': '123'},
|
||||||
|
prefix=self.prefix,
|
||||||
|
command='PRIVMSG',
|
||||||
|
args=(self.irc.nick, 'echo ')))
|
||||||
|
self.irc.feedMsg(ircmsgs.IrcMsg(
|
||||||
|
server_tags={'batch': '123', 'draft/multiline-concat': None},
|
||||||
|
prefix=self.prefix,
|
||||||
|
command='PRIVMSG',
|
||||||
|
args=(self.irc.nick, 'foo ')))
|
||||||
|
self.irc.feedMsg(ircmsgs.IrcMsg(
|
||||||
|
server_tags={'batch': '123', 'draft/multiline-concat': None},
|
||||||
|
prefix=self.prefix,
|
||||||
|
command='PRIVMSG',
|
||||||
|
args=(self.irc.nick, '$nick!$user@$host')))
|
||||||
|
|
||||||
|
# an other line
|
||||||
|
self.irc.feedMsg(ircmsgs.IrcMsg(
|
||||||
|
server_tags={'batch': '123'},
|
||||||
|
prefix=self.prefix,
|
||||||
|
command='PRIVMSG',
|
||||||
|
args=(self.irc.nick, 'echo nope')))
|
||||||
|
|
||||||
|
self.irc.feedMsg(ircmsgs.IrcMsg(
|
||||||
|
command='BATCH',
|
||||||
|
args=('-123',)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|
||||||
|
@ -1588,6 +1588,10 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
|
|||||||
To check if a capability was negotiated, use `irc.state.capabilities_ack`.
|
To check if a capability was negotiated, use `irc.state.capabilities_ack`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
REQUEST_EXPERIMENTAL_CAPABILITIES = set(['draft/multiline'])
|
||||||
|
"""Like REQUEST_CAPABILITIES, but these capabilities are only requested
|
||||||
|
if supybot.protocols.irc.experimentalExtensions is enabled."""
|
||||||
|
|
||||||
def _queueConnectMessages(self):
|
def _queueConnectMessages(self):
|
||||||
if self.zombie:
|
if self.zombie:
|
||||||
self.driver.die()
|
self.driver.die()
|
||||||
@ -1940,9 +1944,12 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
|
|||||||
# Normally at this point, self.state.capabilities_ack should be
|
# Normally at this point, self.state.capabilities_ack should be
|
||||||
# empty; but let's just make sure we're not requesting the same
|
# empty; but let's just make sure we're not requesting the same
|
||||||
# caps twice for no reason.
|
# caps twice for no reason.
|
||||||
|
want_capabilities = self.REQUEST_CAPABILITIES
|
||||||
|
if conf.supybot.protocols.irc.experimentalExtensions():
|
||||||
|
want_capabilities |= self.REQUEST_EXPERIMENTAL_CAPABILITIES
|
||||||
new_caps = (
|
new_caps = (
|
||||||
set(self.state.capabilities_ls) &
|
set(self.state.capabilities_ls) &
|
||||||
self.REQUEST_CAPABILITIES -
|
want_capabilities -
|
||||||
self.state.capabilities_ack)
|
self.state.capabilities_ack)
|
||||||
# NOTE: Capabilities are requested in alphabetic order, because
|
# NOTE: Capabilities are requested in alphabetic order, because
|
||||||
# sets are unordered, and their "order" is nondeterministic.
|
# sets are unordered, and their "order" is nondeterministic.
|
||||||
@ -2138,7 +2145,8 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
|
|||||||
if not self.afterConnect:
|
if not self.afterConnect:
|
||||||
self.triedNicks.add(self.nick)
|
self.triedNicks.add(self.nick)
|
||||||
newNick = self._getNextNick()
|
newNick = self._getNextNick()
|
||||||
assert newNick != self.nick
|
assert newNick != self.nick, \
|
||||||
|
(self.nick, self.alternateNicks, self.triedNicks)
|
||||||
log.info('Got %s: %s %s. Trying %s.',
|
log.info('Got %s: %s %s. Trying %s.',
|
||||||
msg.command, self.nick, problem, newNick)
|
msg.command, self.nick, problem, newNick)
|
||||||
self.sendMsg(ircmsgs.nick(newNick))
|
self.sendMsg(ircmsgs.nick(newNick))
|
||||||
|
Loading…
Reference in New Issue
Block a user