plugin text commands: refactoring, text now only contain main commands, and other plugin can add commands themselve:

- registerTextCommands can be called by a plugin in its __init__ method, it looks for methods starting with "cmd_" and register them as new commands
    - addWhoIsCb: add a callback to /whois command, the callback can complete whois informations
    - plugins parrot, XEP-0045 and XEP-0092 now manage their own commands
This commit is contained in:
Goffi 2014-03-24 10:57:15 +01:00
commit 8a52f6defa
5 changed files with 217 additions and 148 deletions

View file

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sat.core.i18n import _
from sat.core.constants import Const as C
from sat.core.sat_main import MessageSentAndStored
from twisted.words.protocols.jabber import jid
from twisted.internet import defer
@ -26,10 +27,10 @@ from logging import debug, info, warning, error
PLUGIN_INFO = {
"name": "Text commands",
"import_name": "TEXT-COMMANDS",
"import_name": C.TEXT_CMDS,
"type": "Misc",
"protocols": [],
"dependencies": ["XEP-0045", "EXP-PARROT", "XEP-0092"],
"dependencies": [],
"main": "TextCommands",
"handler": "no",
"description": _("""IRC like text commands""")
@ -45,6 +46,45 @@ class TextCommands(object):
info(_("Text commands initialization"))
self.host = host
host.trigger.add("sendMessage", self.sendMessageTrigger)
self._commands = {}
self._whois = []
self.registerTextCommands(self)
def registerTextCommands(self, instance):
""" Add a text command
@param instance: instance of a class containing text commands
"""
for attr in dir(instance):
if attr.startswith('cmd_'):
cmd = getattr(instance, attr)
if not callable(cmd):
warning(_("Skipping not callable [%s] attribute") % attr)
continue
cmd_name = attr[4:]
if not cmd_name:
warning(_("Skipping cmd_ method"))
if cmd_name in self._commands:
suff=2
while (cmd_name + suff) in self._commands:
suff+=1
new_name = cmd_name + suff
warning(_("Conflict for command [%(old_name)s], renaming it to [%(new_name)s]") % {'old_name': cmd_name, 'new_name': new_name})
cmd_name = new_name
self._commands[cmd_name] = cmd
info(_("Registered text command [%s]") % cmd_name)
def addWhoIsCb(self, callback, priority=0):
"""Add a callback which give information to the /whois command
@param callback: a callback which will be called with the following arguments
- whois_msg: list of information strings to display, callback need to append its own strings to it
- target_jid: full jid from who we want informations
- profile: %(doc_profile)s
@param priority: priority of the information to show (the highest priority will be displayed first)
"""
self._whois.append((priority, callback))
self._whois.sort(key=lambda item: item[0])
def sendMessageTrigger(self, mess_data, pre_xml_treatments, post_xml_treatments, profile):
""" Install SendMessage command hook """
@ -89,14 +129,14 @@ class TextCommands(object):
try:
mess_data["unparsed"] = msg[1 + len(command):] # part not yet parsed of the message
d = defer.maybeDeferred(getattr(self, "cmd_%s" % command), mess_data, profile)
d = defer.maybeDeferred(self._commands[command], mess_data, profile)
d.addCallback(retHandling)
except AttributeError:
except KeyError:
pass
return d or mess_data # if a command is detected, we should have a deferred, else be send the message normally
def _getRoomJID(self, arg, service_jid):
def getRoomJID(self, arg, service_jid):
"""Return a room jid with a shortcut
@param arg: argument: can be a full room jid (e.g.: sat@chat.jabberfr.org)
or a shortcut (e.g.: sat or sat@ for sat on current service)
@ -109,7 +149,7 @@ class TextCommands(object):
return jid.JID(arg + service_jid)
return jid.JID(u"%s@%s" % (arg, service_jid))
def _feedBack(self, message, mess_data, profile):
def feedBack(self, message, mess_data, profile):
"""Give a message back to the user"""
if mess_data["type"] == 'groupchat':
_from = mess_data["to"].userhostJID()
@ -118,128 +158,6 @@ class TextCommands(object):
self.host.bridge.newMessage(unicode(mess_data["to"]), message, mess_data['type'], unicode(_from), {}, profile=profile)
def cmd_nick(self, mess_data, profile):
"""change nickname"""
debug("Catched nick command")
if mess_data['type'] != "groupchat":
#/nick command does nothing if we are not on a group chat
info("Ignoring /nick command on a non groupchat message")
return True
nick = mess_data["unparsed"].strip()
room = mess_data["to"]
self.host.plugins["XEP-0045"].nick(room, nick, profile)
return False
def cmd_join(self, mess_data, profile):
"""join a new room (on the same service if full jid is not specified)"""
debug("Catched join command")
if mess_data['type'] != "groupchat":
#/leave command does nothing if we are not on a group chat
info("Ignoring /join command on a non groupchat message")
return True
if mess_data["unparsed"].strip():
room = self._getRoomJID(mess_data["unparsed"].strip(), mess_data["to"].host)
nick = (self.host.plugins["XEP-0045"].getRoomNick(mess_data["to"].userhost(), profile) or
self.host.getClient(profile).jid.user)
self.host.plugins["XEP-0045"].join(room, nick, {}, profile)
return False
def cmd_leave(self, mess_data, profile):
"""quit a room"""
debug("Catched leave command")
if mess_data['type'] != "groupchat":
#/leave command does nothing if we are not on a group chat
info("Ignoring /leave command on a non groupchat message")
return True
if mess_data["unparsed"].strip():
room = self._getRoomJID(mess_data["unparsed"].strip(), mess_data["to"].host)
else:
room = mess_data["to"]
self.host.plugins["XEP-0045"].leave(room, profile)
return False
def cmd_part(self, mess_data, profile):
"""just a synonym of /leave"""
return self.cmd_leave(mess_data, profile)
def cmd_title(self, mess_data, profile):
"""change room's subject"""
debug("Catched title command")
if mess_data['type'] != "groupchat":
#/leave command does nothing if we are not on a group chat
info("Ignoring /title command on a non groupchat message")
return True
subject = mess_data["unparsed"].strip()
if subject:
room = mess_data["to"]
self.host.plugins["XEP-0045"].subject(room, subject, profile)
return False
def cmd_topic(self, mess_data, profile):
"""just a synonym of /title"""
return self.cmd_title(mess_data, profile)
def cmd_parrot(self, mess_data, profile):
"""activate Parrot mode between 2 entities, in both directions."""
#TODO: these commands must not be hardcoded, an interface should be made
# to allow plugins to register simple commands like this.
debug("Catched parrot command")
try:
link_left_jid = jid.JID(mess_data["unparsed"].strip())
if not link_left_jid.user or not link_left_jid.host:
raise jid.InvalidFormat
except jid.InvalidFormat:
self._feedBack("Can't activate Parrot mode for invalid jid", mess_data, profile)
return False
link_right_jid = mess_data['to']
self.host.plugins["EXP-PARROT"].addParrot(link_left_jid, link_right_jid, profile)
self.host.plugins["EXP-PARROT"].addParrot(link_right_jid, link_left_jid, profile)
self._feedBack("Parrot mode activated for %s" % (unicode(link_left_jid), ), mess_data, profile)
return False
def cmd_unparrot(self, mess_data, profile):
"""remove Parrot mode between 2 entities, in both directions."""
debug("Catched unparrot command")
try:
link_left_jid = jid.JID(mess_data["unparsed"].strip())
if not link_left_jid.user or not link_left_jid.host:
raise jid.InvalidFormat
except jid.InvalidFormat:
self._feedBack("Can't deactivate Parrot mode for invalid jid", mess_data, profile)
return False
link_right_jid = mess_data['to']
self.host.plugins["EXP-PARROT"].removeParrot(link_left_jid, profile)
self.host.plugins["EXP-PARROT"].removeParrot(link_right_jid, profile)
self._feedBack("Parrot mode deactivated for %s and %s" % (unicode(link_left_jid), unicode(link_right_jid)), mess_data, profile)
return False
def cmd_whois(self, mess_data, profile):
"""show informations on entity"""
debug("Catched whois command")
@ -259,7 +177,7 @@ class TextCommands(object):
if not target_jid.user or not target_jid.host:
raise jid.InvalidFormat
except (jid.InvalidFormat, RuntimeError):
self._feedBack(_("Invalid jid, can't whois"), mess_data, profile)
self.feedBack(_("Invalid jid, can't whois"), mess_data, profile)
return False
if not target_jid.resource:
@ -267,23 +185,12 @@ class TextCommands(object):
whois_msg = [_(u"whois for %(jid)s") % {'jid': target_jid}]
# version
def versionCb(version_data):
name, version, os = version_data
if name:
whois_msg.append(_("Client name: %s") % name)
if version:
whois_msg.append(_("Client version: %s") % version)
if os:
whois_msg.append(_("Operating system: %s") % os)
d = self.host.plugins['XEP-0092'].getVersion(target_jid, profile)
d.addCallback(versionCb)
#TODO: add informations here (vcard, etc)
d = defer.succeed(None)
for ignore, callback in self._whois:
d.addCallback(lambda ignore: callback(whois_msg, target_jid, profile))
def feedBack(ignore):
self._feedBack(u"\n".join(whois_msg), mess_data, profile)
self.feedBack(u"\n".join(whois_msg), mess_data, profile)
return False
d.addCallback(feedBack)
@ -295,14 +202,14 @@ class TextCommands(object):
longuest = max([len(command) for command in commands])
help_cmds = []
for command in commands:
method = getattr(self, command)
for command in self._commands:
method = self._commands[command]
try:
help_str = method.__doc__.split('\n')[0]
except AttributeError:
help_str = ''
spaces = (longuest - len(command)) * ' '
help_cmds.append(" /%s: %s %s" % (command[4:], spaces, help_str))
help_cmds.append(" /%s: %s %s" % (command, spaces, help_str))
help_mess = _(u"Text commands available:\n%s") % (u'\n'.join(help_cmds), )
self._feedBack(help_mess, mess_data, profile)
self.feedBack(help_mess, mess_data, profile)