plugin text commands: handles coroutines + better command parsing:
- async coroutine can now be used for text commands (in addition to Deferred and regular callbacks) - isidentifier is now used to detect a command, allowing to use "_" in command names - if message starts with a single `/`, it is never sent (previously it could be sent is some corner cases) - don't use OrderedDict anymore as dict now keep insertion order by default
This commit is contained in:
parent
31e9cc7cbf
commit
f7fb2dd238
1 changed files with 71 additions and 62 deletions
|
|
@ -17,16 +17,17 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from twisted.words.protocols.jabber import jid
|
||||
from twisted.internet import defer
|
||||
from twisted.python import failure
|
||||
from sat.core.i18n import _
|
||||
from sat.core.constants import Const as C
|
||||
from sat.core import exceptions
|
||||
from twisted.words.protocols.jabber import jid
|
||||
from twisted.internet import defer
|
||||
from sat.core.log import getLogger
|
||||
from sat.tools import utils
|
||||
|
||||
|
||||
log = getLogger(__name__)
|
||||
from twisted.python import failure
|
||||
from collections import OrderedDict
|
||||
|
||||
PLUGIN_INFO = {
|
||||
C.PI_NAME: "Text commands",
|
||||
|
|
@ -81,10 +82,15 @@ class TextCommands(object):
|
|||
@return (dict): dictionary with parsed data where key can be:
|
||||
- "doc_short_help" (default: ""): the untranslated short documentation
|
||||
- "type" (default "all"): the command type as specified in documentation
|
||||
- "args" (default: ""): the arguments available, using syntax specified in documentation.
|
||||
- "args" (default: ""): the arguments available, using syntax specified in
|
||||
documentation.
|
||||
- "doc_arg_[name]": the doc of [name] argument
|
||||
"""
|
||||
data = OrderedDict([("doc_short_help", ""), ("type", "all"), ("args", "")])
|
||||
data = {
|
||||
"doc_short_help": "",
|
||||
"type": "all",
|
||||
"args": "",
|
||||
}
|
||||
docstring = cmd.__doc__
|
||||
if docstring is None:
|
||||
log.warning("No docstring found for command {}".format(cmd_name))
|
||||
|
|
@ -169,9 +175,7 @@ class TextCommands(object):
|
|||
).format(old_name=cmd_name, new_name=new_name)
|
||||
)
|
||||
cmd_name = new_name
|
||||
self._commands[cmd_name] = cmd_data = OrderedDict(
|
||||
{"callback": cmd}
|
||||
) # We use an Ordered dict to keep documenation order
|
||||
self._commands[cmd_name] = cmd_data = {"callback": cmd}
|
||||
cmd_data.update(self._parseDocString(cmd, cmd_name))
|
||||
log.info(_("Registered text command [%s]") % cmd_name)
|
||||
|
||||
|
|
@ -233,65 +237,70 @@ class TextCommands(object):
|
|||
|
||||
# we have a command
|
||||
d = None
|
||||
command = msg[1:].partition(" ")[0].lower()
|
||||
if command.isalpha():
|
||||
# looks like an actual command, we try to call the corresponding method
|
||||
def retHandling(ret):
|
||||
""" Handle command return value:
|
||||
if ret is True, normally send message (possibly modified by command)
|
||||
else, abord message sending
|
||||
"""
|
||||
if ret:
|
||||
return mess_data
|
||||
else:
|
||||
log.debug("text command detected ({})".format(command))
|
||||
raise failure.Failure(exceptions.CancelError())
|
||||
command = msg[1:].partition(" ")[0].lower().strip()
|
||||
if not command.isidentifier():
|
||||
self.feedBack(
|
||||
client,
|
||||
_("Invalid command /%s. ") % command + self.HELP_SUGGESTION,
|
||||
mess_data,
|
||||
)
|
||||
raise failure.Failure(exceptions.CancelError())
|
||||
|
||||
def genericErrback(failure):
|
||||
try:
|
||||
msg = "with condition {}".format(failure.value.condition)
|
||||
except AttributeError:
|
||||
msg = "with error {}".format(failure.value)
|
||||
self.feedBack(client, "Command failed {}".format(msg), mess_data)
|
||||
return False
|
||||
# looks like an actual command, we try to call the corresponding method
|
||||
def retHandling(ret):
|
||||
""" Handle command return value:
|
||||
if ret is True, normally send message (possibly modified by command)
|
||||
else, abord message sending
|
||||
"""
|
||||
if ret:
|
||||
return mess_data
|
||||
else:
|
||||
log.debug("text command detected ({})".format(command))
|
||||
raise failure.Failure(exceptions.CancelError())
|
||||
|
||||
mess_data["unparsed"] = msg[
|
||||
1 + len(command) :
|
||||
] # part not yet parsed of the message
|
||||
def genericErrback(failure):
|
||||
try:
|
||||
cmd_data = self._commands[command]
|
||||
except KeyError:
|
||||
self.feedBack(
|
||||
client,
|
||||
_("Unknown command /%s. ") % command + self.HELP_SUGGESTION,
|
||||
mess_data,
|
||||
msg = "with condition {}".format(failure.value.condition)
|
||||
except AttributeError:
|
||||
msg = "with error {}".format(failure.value)
|
||||
self.feedBack(client, "Command failed {}".format(msg), mess_data)
|
||||
return False
|
||||
|
||||
mess_data["unparsed"] = msg[
|
||||
1 + len(command) :
|
||||
] # part not yet parsed of the message
|
||||
try:
|
||||
cmd_data = self._commands[command]
|
||||
except KeyError:
|
||||
self.feedBack(
|
||||
client,
|
||||
_("Unknown command /%s. ") % command + self.HELP_SUGGESTION,
|
||||
mess_data,
|
||||
)
|
||||
log.debug("text command help message")
|
||||
raise failure.Failure(exceptions.CancelError())
|
||||
else:
|
||||
if not self._contextValid(mess_data, cmd_data):
|
||||
# The command is not launched in the right context, we throw a message with help instructions
|
||||
context_txt = (
|
||||
_("group discussions")
|
||||
if cmd_data["type"] == "group"
|
||||
else _("one to one discussions")
|
||||
)
|
||||
log.debug("text command help message")
|
||||
feedback = _("/{command} command only applies in {context}.").format(
|
||||
command=command, context=context_txt
|
||||
)
|
||||
self.feedBack(
|
||||
client, "{} {}".format(feedback, self.HELP_SUGGESTION), mess_data
|
||||
)
|
||||
log.debug("text command invalid message")
|
||||
raise failure.Failure(exceptions.CancelError())
|
||||
else:
|
||||
if not self._contextValid(mess_data, cmd_data):
|
||||
# The command is not launched in the right context, we throw a message with help instructions
|
||||
context_txt = (
|
||||
_("group discussions")
|
||||
if cmd_data["type"] == "group"
|
||||
else _("one to one discussions")
|
||||
)
|
||||
feedback = _("/{command} command only applies in {context}.").format(
|
||||
command=command, context=context_txt
|
||||
)
|
||||
self.feedBack(
|
||||
client, "{} {}".format(feedback, self.HELP_SUGGESTION), mess_data
|
||||
)
|
||||
log.debug("text command invalid message")
|
||||
raise failure.Failure(exceptions.CancelError())
|
||||
else:
|
||||
d = defer.maybeDeferred(cmd_data["callback"], client, mess_data)
|
||||
d.addErrback(genericErrback)
|
||||
d.addCallback(retHandling)
|
||||
d = utils.asDeferred(cmd_data["callback"], client, mess_data)
|
||||
d.addErrback(genericErrback)
|
||||
d.addCallback(retHandling)
|
||||
|
||||
return (
|
||||
d or mess_data
|
||||
) # if a command is detected, we should have a deferred, else we send the message normally
|
||||
return d
|
||||
|
||||
def _contextValid(self, mess_data, cmd_data):
|
||||
"""Tell if a command can be used in the given context
|
||||
|
|
|
|||
Loading…
Reference in a new issue