backend, frontends: message refactoring (huge commit):

/!\ several features are temporarily disabled, like notifications in frontends

next step in refactoring, with the following changes:
    - jp: updated jp message to follow changes in backend/bridge
    - jp: added --lang, --subject, --subject_lang, and --type options to jp message + fixed unicode handling for jid
    - quick_frontend (QuickApp, QuickChat):
        - follow backend changes
        - refactored chat, message are now handled in OrderedDict and uid are kept so they can be updated
        - Message and Occupant classes handle metadata, so frontend just have to display them
    - Primitivus (Chat):
        - follow backend/QuickFrontend changes
        - info & standard messages are handled in the same MessageWidget class
        - improved/simplified handling of messages, removed update() method
        - user joined/left messages are merged when next to each other
        - a separator is shown when message is received while widget is out of focus, so user can quickly see the new messages
        - affiliation/role are shown (in a basic way for now) in occupants panel
        - removed "/me" messages handling, as it will be done by a backend plugin
        - message language is displayed when available (only one language per message for now)
        - fixed :history and :search commands
    - core (constants): new constants for messages type, XML namespace, entity type
    - core: *Message methods renamed to follow new code sytle (e.g. sendMessageToBridge => messageSendToBridge)
    - core (messages handling): fixed handling of language
    - core (messages handling): mes_data['from'] and ['to'] are now jid.JID
    - core (core.xmpp): reorganised message methods, added getNick() method to client.roster
    - plugin text commands: fixed plugin and adapted to new messages behaviour. client is now used in arguments instead of profile
    - plugins: added information for cancellation reason in CancelError calls
    - plugin XEP-0045: various improvments, but this plugin still need work:
        - trigger is used to avoid message already handled by the plugin to be handled a second time
        - changed the way to handle history, the last message from DB is checked and we request only messages since this one, in seconds (thanks Poezio folks :))
        - subject reception is waited before sending the roomJoined signal, this way we are sure that everything including history is ready
        - cmd_* method now follow the new convention with client instead of profile
        - roomUserJoined and roomUserLeft messages are removed, the events are now handled with info message with a "ROOM_USER_JOINED" info subtype
    - probably other forgotten stuffs :p
This commit is contained in:
Goffi 2016-06-20 18:41:53 +02:00
commit a7de473107

View file

@ -46,6 +46,7 @@ class InvalidCommandSyntax(Exception):
CMD_KEY = "@command"
CMD_TYPES = ('group', 'one2one', 'all')
FEEDBACK_INFO_TYPE = "TEXT_CMD"
class TextCommands(object):
@ -148,7 +149,7 @@ class TextCommands(object):
while (cmd_name + str(suff)) in self._commands:
suff+=1
new_name = cmd_name + str(suff)
log.warning(_(u"Conflict for command [%(old_name)s], renaming it to [%(new_name)s]") % {'old_name': cmd_name, 'new_name': new_name})
log.warning(_(u"Conflict for command [{old_name}], renaming it to [{new_name}]").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
cmd_data.update(self._parseDocString(cmd, cmd_name))
@ -182,7 +183,6 @@ class TextCommands(object):
@param mess_data(dict): data comming from messageSend trigger
@param profile: %(doc_profile)s
"""
profile = client.profile
try:
msg = mess_data["message"]['']
msg_lang = ''
@ -217,7 +217,7 @@ class TextCommands(object):
if ret:
return mess_data
else:
log.debug("text commands took over")
log.debug(u"text command detected ({})".format(command))
raise failure.Failure(exceptions.CancelError())
def genericErrback(failure):
@ -225,26 +225,26 @@ class TextCommands(object):
msg = u"with condition {}".format(failure.value.condition)
except AttributeError:
msg = u"with error {}".format(failure.value)
self.feedBack(u"Command failed {}".format(msg), mess_data, profile)
self.feedBack(client, u"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(_("Unknown command /%s. ") % command + self.HELP_SUGGESTION, mess_data, profile)
log.debug("text commands took over")
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")
feedback = _("/{command} command only applies in {context}.").format(command=command, context=context_txt)
self.feedBack(u"{} {}".format(feedback, self.HELP_SUGGESTION), mess_data, profile)
log.debug("text commands took over")
self.feedBack(client, u"{} {}".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"], mess_data, profile)
d = defer.maybeDeferred(cmd_data["callback"], client, mess_data)
d.addErrback(genericErrback)
d.addCallback(retHandling)
@ -276,16 +276,22 @@ 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, client, message, mess_data, info_type=FEEDBACK_INFO_TYPE):
"""Give a message back to the user"""
if mess_data["type"] == 'groupchat':
_from = mess_data["to"].userhostJID()
to_ = mess_data["to"].userhostJID()
else:
_from = self.host.getJidNStream(profile)[0]
to_ = client.jid
self.host.bridge.messageNew(unicode(mess_data["to"]), {'': message}, {}, C.MESS_TYPE_INFO, unicode(_from), {}, profile=profile)
# we need to invert send message back, so sender need to original destinee
mess_data["from"] = mess_data["to"]
mess_data["to"] = to_
mess_data["type"] = C.MESS_TYPE_INFO
mess_data["message"] = {'': message}
mess_data["extra"]["info_type"] = info_type
self.host.messageSendToBridge(mess_data, client)
def cmd_whois(self, mess_data, profile):
def cmd_whois(self, client, mess_data):
"""show informations on entity
@command: [JID|ROOM_NICK]
@ -299,7 +305,7 @@ class TextCommands(object):
if mess_data['type'] == "groupchat":
room = mess_data["to"].userhostJID()
try:
if self.host.plugins["XEP-0045"].isNickInRoom(room, entity, profile):
if self.host.plugins["XEP-0045"].isNickInRoom(room, entity, client.profile):
entity = u"%s/%s" % (room, entity)
except KeyError:
log.warning("plugin XEP-0045 is not present")
@ -312,20 +318,20 @@ class TextCommands(object):
if not target_jid.user or not target_jid.host:
raise jid.InvalidFormat
except (RuntimeError, jid.InvalidFormat, AttributeError):
self.feedBack(_("Invalid jid, can't whois"), mess_data, profile)
self.feedBack(client, _("Invalid jid, can't whois"), mess_data)
return False
if not target_jid.resource:
target_jid.resource = self.host.memory.getMainResource(target_jid, profile)
target_jid.resource = self.host.memory.getMainResource(target_jid, client.profile)
whois_msg = [_(u"whois for %(jid)s") % {'jid': target_jid}]
d = defer.succeed(None)
for ignore, callback in self._whois:
d.addCallback(lambda ignore: callback(whois_msg, mess_data, target_jid, profile))
d.addCallback(lambda ignore: callback(client, whois_msg, mess_data, target_jid))
def feedBack(ignore):
self.feedBack(u"\n".join(whois_msg), mess_data, profile)
self.feedBack(client, u"\n".join(whois_msg), mess_data)
return False
d.addCallback(feedBack)
@ -345,7 +351,7 @@ class TextCommands(object):
return strings
def cmd_help(self, mess_data, profile):
def cmd_help(self, client, mess_data):
"""show help on available commands
@command: [cmd_name]
@ -355,7 +361,7 @@ class TextCommands(object):
if cmd_name and cmd_name[0] == "/":
cmd_name = cmd_name[1:]
if cmd_name and cmd_name not in self._commands:
self.feedBack(_(u"Invalid command name [{}]\n".format(cmd_name)), mess_data, profile)
self.feedBack(client, _(u"Invalid command name [{}]\n".format(cmd_name)), mess_data)
cmd_name = ""
if not cmd_name:
# we show the global help
@ -384,13 +390,4 @@ class TextCommands(object):
syntax=_(" "*4+"syntax: {}\n").format(syntax) if syntax else "",
args_help=u'\n'.join([u" "*8+"{}".format(line) for line in self._getArgsHelp(cmd_data)]))
self.feedBack(help_mess, mess_data, profile)
def cmd_me(self, mess_data, profile):
"""Display a message at third person
@command: message
- message: message to display at the third person
"""
# We just catch the method and continue it as the frontends should manage /me display
return True
self.feedBack(client, help_mess, mess_data)