backend, frontend: messages refactoring (huge commit, not finished):

/!\ database schema has been modified, do a backup before updating

    message have been refactored, here are the main changes:

    - languages are now handled
    - all messages have an uid (internal to SàT)
    - message updating is anticipated
    - subject is now first class
    - new naming scheme is used newMessage => messageNew, getHistory => historyGet, sendMessage => messageSend
    - minimal compatibility refactoring in quick_frontend/Primitivus, better refactoring should follow
    - threads handling
    - delayed messages are saved into history
    - info messages may also be saved in history (e.g. to keep track of people joining/leaving a room)
    - duplicate messages should be avoided
    - historyGet return messages in right order, no need to sort again
    - plugins have been updated to follow new features, some of them need to be reworked (e.g. OTR)
    - XEP-0203 (Delayed Delivery) is now fully handled in core, the plugin just handle disco and creation of a delay element
    - /!\ jp and Libervia are currently broken, as some features of Primitivus

    It has been put in one huge commit to avoid breaking messaging between changes.
    This is the main part of message refactoring, other commits will follow to take profit of the new features/behaviour.
This commit is contained in:
Goffi 2016-05-24 22:11:04 +02:00
commit f12803fa65
30 changed files with 784 additions and 453 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.log import getLogger
log = getLogger(__name__)
from twisted.words.protocols.jabber import jid
@ -57,10 +58,10 @@ class ExportCommandProtocol(protocol.ProcessProtocol):
log.info("connectionMade :)")
def outReceived(self, data):
self.parent.host.sendMessage(self.target, self._clean(data), no_trigger=True, profile_key=self.profile)
self.parent.host.messageSend(self.target, {'': self._clean(data)}, no_trigger=True, profile_key=self.profile)
def errReceived(self, data):
self.parent.host.sendMessage(self.target, self._clean(data), no_trigger=True, profile_key=self.profile)
self.parent.host.messageSend(self.target, {'': self._clean(data)}, no_trigger=True, profile_key=self.profile)
def processEnded(self, reason):
log.info (u"process finished: %d" % (reason.value.exitCode,))
@ -102,20 +103,19 @@ class CommandExport(object):
except ValueError:
pass
def MessageReceivedTrigger(self, message, post_treat, profile):
def MessageReceivedTrigger(self, client, message, post_treat):
""" Check if source is linked and repeat message, else do nothing """
from_jid = jid.JID(message["from"])
spawned_key = (from_jid.userhostJID(), profile)
try:
body = [e for e in message.elements() if e.name == 'body'][0]
except IndexError:
# do not block message without body (chat state notification...)
log.debug("No body element found in message, following normal behaviour")
return True
mess_data = unicode(body) + '\n'
spawned_key = (from_jid.userhostJID(), client.profile)
if spawned_key in self.spawned:
try:
body = message.elements(C.NS_CLIENT, 'body').next()
except StopIteration:
# do not block message without body (chat state notification...)
return True
mess_data = unicode(body) + '\n'
processes_set = self.spawned[spawned_key]
_continue = False
exclusive = False

View file

@ -43,22 +43,21 @@ class Exp_Parrot(object):
"""Parrot mode plugin: repeat messages from one entity or MUC room to another one"""
#XXX: This plugin can be potentially dangerous if we don't trust entities linked
# this is specially true if we have other triggers.
# sendMessageTrigger avoid other triggers execution, it's deactivated to allow
# messageSendTrigger avoid other triggers execution, it's deactivated to allow
# /unparrot command in text commands plugin.
def __init__(self, host):
log.info(_("Plugin Parrot initialization"))
self.host = host
host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100)
#host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100)
#host.trigger.add("messageSend", self.messageSendTrigger, priority=100)
try:
self.host.plugins[C.TEXT_CMDS].registerTextCommands(self)
except KeyError:
log.info(_("Text commands not available"))
#def sendMessageTrigger(self, mess_data, treatments, profile):
#def messageSendTrigger(self, client, mess_data, treatments):
# """ Deactivate other triggers if recipient is in parrot links """
# client = self.host.getClient(profile)
# try:
# _links = client.parrot_links
# except AttributeError:
@ -68,8 +67,10 @@ class Exp_Parrot(object):
# log.debug("Parrot link detected, skipping other triggers")
# raise trigger.SkipOtherTriggers
def MessageReceivedTrigger(self, message, post_treat, profile):
def MessageReceivedTrigger(self, client, message, post_treat):
""" Check if source is linked and repeat message, else do nothing """
# TODO: many things are not repeated (subject, thread, etc)
profile = client.profile
client = self.host.getClient(profile)
from_jid = jid.JID(message["from"])
@ -81,28 +82,27 @@ class Exp_Parrot(object):
if not from_jid.userhostJID() in _links:
return True
for e in message.elements():
if e.name == "body":
mess_body = e.children[0] if e.children else ""
message = {}
for e in message.elements(C.NS_CLIENT, 'body'):
body = unicode(e)
lang = e.getAttribute('lang') or ''
try:
entity_type = self.host.memory.getEntityData(from_jid, ['type'], profile)["type"]
except (UnknownEntityError, KeyError):
entity_type = "contact"
if entity_type == 'chatroom':
src_txt = from_jid.resource
if src_txt == self.host.plugins["XEP-0045"].getRoomNick(from_jid.userhostJID(), profile):
#we won't repeat our own messages
return True
else:
src_txt = from_jid.user
msg = "[%s] %s" % (src_txt, mess_body)
try:
entity_type = self.host.memory.getEntityData(from_jid, ['type'], profile)["type"]
except (UnknownEntityError, KeyError):
entity_type = "contact"
if entity_type == 'chatroom':
src_txt = from_jid.resource
if src_txt == self.host.plugins["XEP-0045"].getRoomNick(from_jid.userhostJID(), profile):
#we won't repeat our own messages
return True
else:
src_txt = from_jid.user
message[lang] = u"[{}] {}".format(src_txt, body)
linked = _links[from_jid.userhostJID()]
linked = _links[from_jid.userhostJID()]
self.host.sendMessage(jid.JID(unicode(linked)), msg, None, "auto", no_trigger=True, profile_key=profile)
else:
log.warning("No body element found in message, following normal behaviour")
self.host.messageSend(jid.JID(unicode(linked)), message, None, "auto", no_trigger=True, profile_key=profile)
return True

View file

@ -154,11 +154,11 @@ class SatMailbox(object):
log.debug(u'Mailbox init (%s)' % name)
if name != "INBOX":
raise imap4.MailboxException("Only INBOX is managed for the moment")
self.mailbox = self.host.plugins["Maildir"].accessMessageBox(name, self.newMessage, profile)
self.mailbox = self.host.plugins["Maildir"].accessMessageBox(name, self.messageNew, profile)
def newMessage(self):
def messageNew(self):
"""Called when a new message is in the mailbox"""
log.debug("newMessage signal received")
log.debug("messageNew signal received")
nb_messages = self.getMessageCount()
for listener in self.listeners:
listener.newMessages(nb_messages, None)

View file

@ -98,18 +98,19 @@ class MaildirBox(object):
del self.__mailboxes[profile]
del self.data[profile]
def messageReceivedTrigger(self, message, post_treat, profile):
def messageReceivedTrigger(self, client, message, post_treat):
"""This trigger catch normal message and put the in the Maildir box.
If the message is not of "normal" type, do nothing
@param message: message xmlstrem
@return: False if it's a normal message, True else"""
for e in message.elements():
if e.name == "body":
mess_type = message['type'] if message.hasAttribute('type') else 'normal'
if mess_type != 'normal':
return True
self.accessMessageBox("INBOX", profile_key=profile).addMessage(message)
return not self.host.memory.getParamA(NAME, CATEGORY, profile_key=profile)
profile = client.profile
for e in message.elements(C.NS_CLIENT, 'body'):
mess_type = message.getAttribute('type', 'normal')
if mess_type != 'normal':
return True
self.accessMessageBox("INBOX", profile_key=profile).addMessage(message)
return not self.host.memory.getParamA(NAME, CATEGORY, profile_key=profile)
return True
def accessMessageBox(self, boxname, observer=None, profile_key=C.PROF_KEY_NONE):
"""Create and return a MailboxUser instance

View file

@ -87,7 +87,7 @@ class SatSmtpMessage(object):
"""handle end of message"""
mail = Parser().parsestr("\n".join(self.message))
try:
self.host._sendMessage(parseaddr(mail['to'].decode('utf-8', 'replace'))[1], mail.get_payload().decode('utf-8', 'replace'), # TODO: manage other charsets
self.host._messageSend(parseaddr(mail['to'].decode('utf-8', 'replace'))[1], mail.get_payload().decode('utf-8', 'replace'), # TODO: manage other charsets
subject=mail['subject'].decode('utf-8', 'replace'), mess_type='normal', profile_key=self.profile)
except:
exc_type, exc_value, exc_traceback = sys.exc_info()

View file

@ -58,7 +58,7 @@ class TextCommands(object):
def __init__(self, host):
log.info(_("Text commands initialization"))
self.host = host
host.trigger.add("sendMessage", self.sendMessageTrigger)
host.trigger.add("messageSend", self.messageSendTrigger)
self._commands = {}
self._whois = []
self.registerTextCommands(self)
@ -166,12 +166,12 @@ class TextCommands(object):
self._whois.append((priority, callback))
self._whois.sort(key=lambda item: item[0], reverse=True)
def sendMessageTrigger(self, mess_data, pre_xml_treatments, post_xml_treatments, profile):
""" Install SendMessage command hook """
pre_xml_treatments.addCallback(self._sendMessageCmdHook, profile)
def messageSendTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments):
"""Install SendMessage command hook """
pre_xml_treatments.addCallback(self._messageSendCmdHook, client)
return True
def _sendMessageCmdHook(self, mess_data, profile):
def _messageSendCmdHook(self, mess_data, client):
""" Check text commands in message, and react consequently
msg starting with / are potential command. If a command is found, it is executed, else and help message is sent
@ -179,14 +179,25 @@ class TextCommands(object):
commands can abord message sending (if they return anything evaluating to False), or continue it (if they return True), eventually after modifying the message
an "unparsed" key is added to message, containing part of the message not yet parsed
commands can be deferred or not
@param mess_data(dict): data comming from sendMessage trigger
@param mess_data(dict): data comming from messageSend trigger
@param profile: %(doc_profile)s
"""
msg = mess_data["message"]
profile = client.profile
try:
msg = mess_data["message"]['']
msg_lang = ''
except KeyError:
try:
# we have not default message, we try to take the first found
msg_lang, msg = mess_data["message"].iteritems().next()
except StopIteration:
log.debug(u"No message found, skipping text commands")
return mess_data
try:
if msg[:2] == '//':
# we have a double '/', it's the escape sequence
mess_data["message"] = msg[1:]
mess_data["message"][msg_lang] = msg[1:]
return mess_data
if msg[0] != '/':
return mess_data
@ -242,7 +253,7 @@ class TextCommands(object):
def _contextValid(self, mess_data, cmd_data):
"""Tell if a command can be used in the given context
@param mess_data(dict): message data as given in sendMessage trigger
@param mess_data(dict): message data as given in messageSend trigger
@param cmd_data(dict): command data as returned by self._parseDocString
@return (bool): True if command can be used in this context
"""
@ -272,7 +283,7 @@ class TextCommands(object):
else:
_from = self.host.getJidNStream(profile)[0]
self.host.bridge.newMessage(unicode(mess_data["to"]), message, C.MESS_TYPE_INFO, unicode(_from), {}, profile=profile)
self.host.bridge.messageNew(unicode(mess_data["to"]), {'': message}, {}, C.MESS_TYPE_INFO, unicode(_from), {}, profile=profile)
def cmd_whois(self, mess_data, profile):
"""show informations on entity

View file

@ -32,6 +32,8 @@ from twisted.internet import defer
from sat.memory import persistent
import potr
import copy
import time
import uuid
NS_OTR = "otr_plugin"
PRIVATE_KEY = "PRIVATE KEY"
@ -73,11 +75,15 @@ class Context(potr.context.Context):
msg = msg_str.decode('utf-8')
client = self.user.client
log.debug(u'inject(%s, appdata=%s, to=%s)' % (msg, appdata, self.peer))
mess_data = {'message': msg,
'type': 'chat',
mess_data = {
'from': client.jid,
'to': self.peer,
'subject': None,
'to': self.peer,
'uid': unicode(uuid.uuid4()),
'message': {'': msg},
'subject': {},
'type': 'chat',
'extra': {},
'timestamp': time.time(),
}
self.host.generateMessageXML(mess_data)
client.xmlstream.send(mess_data['xml'])
@ -107,7 +113,7 @@ class Context(potr.context.Context):
return
client = self.user.client
self.host.bridge.newMessage(client.jid.full(),
self.host.bridge.messageNew(client.jid.full(),
feedback,
mess_type=C.MESS_TYPE_INFO,
to_jid=self.peer.full(),
@ -202,7 +208,7 @@ class OTR(object):
self.context_managers = {}
self.skipped_profiles = set()
host.trigger.add("MessageReceived", self.MessageReceivedTrigger, priority=100000)
host.trigger.add("sendMessage", self.sendMessageTrigger, priority=100000)
host.trigger.add("messageSend", self.messageSendTrigger, priority=100000)
host.bridge.addMethod("skipOTR", ".plugin", in_sign='s', out_sign='', method=self._skipOTR)
host.importMenu((MAIN_MENU, D_("Start/Refresh")), self._startRefresh, security_limit=0, help_string=D_("Start or refresh an OTR session"), type_=C.MENU_SINGLE)
host.importMenu((MAIN_MENU, D_("End session")), self._endSession, security_limit=0, help_string=D_("Finish an OTR session"), type_=C.MENU_SINGLE)
@ -275,7 +281,7 @@ class OTR(object):
log.error(_("jid key is not present !"))
return defer.fail(exceptions.DataError)
otrctx = self.context_managers[profile].getContextForUser(to_jid)
query = otrctx.sendMessage(0, '?OTRv?')
query = otrctx.messageSend(0, '?OTRv?')
otrctx.inject(query)
return {}
@ -406,18 +412,21 @@ class OTR(object):
encrypted = True
try:
res = otrctx.receiveMessage(data['body'].encode('utf-8'))
message = data['message'].itervalues().next() # FIXME: Q&D fix for message refactoring, message is now a dict
res = otrctx.receiveMessage(message.encode('utf-8'))
except potr.context.UnencryptedMessage:
if otrctx.state == potr.context.STATE_ENCRYPTED:
log.warning(u"Received unencrypted message in an encrypted context (from %(jid)s)" % {'jid': from_jid.full()})
client = self.host.getClient(profile)
self.host.bridge.newMessage(from_jid.full(),
self.host.bridge.messageNew(from_jid.full(),
_(u"WARNING: received unencrypted data in a supposedly encrypted context"),
mess_type=C.MESS_TYPE_INFO,
to_jid=client.jid.full(),
extra={},
profile=client.profile)
encrypted = False
except StopIteration:
return data
if not encrypted:
return data
@ -425,7 +434,7 @@ class OTR(object):
if res[0] != None:
# decrypted messages handling.
# receiveMessage() will return a tuple, the first part of which will be the decrypted message
data['body'] = res[0].decode('utf-8')
data['message'] = {'':res[0].decode('utf-8')} # FIXME: Q&D fix for message refactoring, message is now a dict
raise failure.Failure(exceptions.SkipHistory()) # we send the decrypted message to frontends, but we don't want it in history
else:
raise failure.Failure(exceptions.CancelError()) # no message at all (no history, no signal)
@ -433,19 +442,24 @@ class OTR(object):
def _receivedTreatmentForSkippedProfiles(self, data, profile):
"""This profile must be skipped because the frontend manages OTR itself,
but we still need to check if the message must be stored in history or not"""
body = data['body'].encode('utf-8')
if body.startswith(potr.proto.OTRTAG):
try:
message = data['message'].itervalues().next().encode('utf-8') # FIXME: Q&D fix for message refactoring, message is now a dict
except StopIteration:
return data
if message.startswith(potr.proto.OTRTAG):
raise failure.Failure(exceptions.SkipHistory())
return data
def MessageReceivedTrigger(self, message, post_treat, profile):
def MessageReceivedTrigger(self, client, message, post_treat):
profile = client.profile
if profile in self.skipped_profiles:
post_treat.addCallback(self._receivedTreatmentForSkippedProfiles, profile)
else:
post_treat.addCallback(self._receivedTreatment, profile)
return True
def sendMessageTrigger(self, mess_data, pre_xml_treatments, post_xml_treatments, profile):
def messageSendTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments):
profile = client.profile
if profile in self.skipped_profiles:
return True
to_jid = copy.copy(mess_data['to'])
@ -455,13 +469,19 @@ class OTR(object):
if mess_data['type'] != 'groupchat' and otrctx.state != potr.context.STATE_PLAINTEXT:
if otrctx.state == potr.context.STATE_ENCRYPTED:
log.debug(u"encrypting message")
otrctx.sendMessage(0, mess_data['message'].encode('utf-8'))
client = self.host.getClient(profile)
try:
msg = mess_data['message']['']
except KeyError:
try:
msg = mess_data['message'].itervalues().next()
except StopIteration:
log.warning(u"No message found")
return False
otrctx.sendMessage(0, msg.encode('utf-8'))
self.host.sendMessageToBridge(mess_data, client)
else:
feedback = D_("Your message was not sent because your correspondent closed the encrypted conversation on his/her side. Either close your own side, or refresh the session.")
client = self.host.getClient(profile)
self.host.bridge.newMessage(to_jid.full(),
self.host.bridge.messageNew(to_jid.full(),
feedback,
mess_type=C.MESS_TYPE_INFO,
to_jid=client.jid.full(),

View file

@ -72,11 +72,12 @@ class XEP_0033(object):
log.info(_("Extended Stanza Addressing plugin initialization"))
self.host = host
self.internal_data = {}
host.trigger.add("sendMessage", self.sendMessageTrigger, trigger.TriggerManager.MIN_PRIORITY)
host.trigger.add("messageSend", self.messageSendTrigger, trigger.TriggerManager.MIN_PRIORITY)
host.trigger.add("MessageReceived", self.messageReceivedTrigger)
def sendMessageTrigger(self, mess_data, pre_xml_treatments, post_xml_treatments, profile):
def messageSendTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments):
"""Process the XEP-0033 related data to be sent"""
profile = client.profile
def treatment(mess_data):
if not 'address' in mess_data['extra']:
@ -117,7 +118,7 @@ class XEP_0033(object):
Ideas:
- fix Prosody plugin to check if target server support the feature
- redesign the database to save only one entry to the database
- change the newMessage signal to eventually pass more than one recipient
- change the messageNew signal to eventually pass more than one recipient
"""
def send(mess_data, skip_send=False):
client = self.host.profiles[profile]
@ -161,7 +162,7 @@ class XEP_0033(object):
d = defer.Deferred().addCallback(lambda dummy: self.internal_data.pop(timestamp))
defer.DeferredList(defer_list).chainDeferred(d)
def messageReceivedTrigger(self, message, post_treat, profile):
def messageReceivedTrigger(self, client, message, post_treat):
"""In order to save the addressing information in the history"""
def post_treat_addr(data, addresses):
data['extra']['addresses'] = ""
@ -174,9 +175,10 @@ class XEP_0033(object):
try:
addresses = message.elements(NS_ADDRESS, 'addresses').next()
post_treat.addCallback(post_treat_addr, addresses.children)
except StopIteration:
pass # no addresses
else:
post_treat.addCallback(post_treat_addr, addresses.children)
return True
def getHandler(self, profile):

View file

@ -22,6 +22,7 @@ from sat.core import exceptions
from sat.core.log import getLogger
log = getLogger(__name__)
from twisted.internet import defer
from wokkel import disco, iwokkel
from zope.interface import implements
# from lxml import etree
@ -75,68 +76,108 @@ class XEP_0071(object):
def __init__(self, host):
log.info(_("XHTML-IM plugin initialization"))
self.host = host
self.synt_plg = self.host.plugins["TEXT-SYNTAXES"]
self.synt_plg.addSyntax(self.SYNTAX_XHTML_IM, lambda xhtml: xhtml, self.XHTML2XHTML_IM, [self.synt_plg.OPT_HIDDEN])
self._s = self.host.plugins["TEXT-SYNTAXES"]
self._s.addSyntax(self.SYNTAX_XHTML_IM, lambda xhtml: xhtml, self.XHTML2XHTML_IM, [self._s.OPT_HIDDEN])
host.trigger.add("MessageReceived", self.messageReceivedTrigger)
host.trigger.add("sendMessage", self.sendMessageTrigger)
host.trigger.add("messageSend", self.messageSendTrigger)
def getHandler(self, profile):
return XEP_0071_handler(self)
def _messagePostTreat(self, data, body_elt):
""" Callback which manage the post treatment of the message in case of XHTML-IM found
def _messagePostTreat(self, data, body_elts):
"""Callback which manage the post treatment of the message in case of XHTML-IM found
@param data: data send by MessageReceived trigger through post_treat deferred
@param xhtml_im: XHTML-IM body element found
@param body_elts: XHTML-IM body elements found
@return: the data with the extra parameter updated
"""
#TODO: check if text only body is empty, then try to convert XHTML-IM to pure text and show a warning message
# TODO: check if text only body is empty, then try to convert XHTML-IM to pure text and show a warning message
def converted(xhtml):
data['extra']['xhtml'] = xhtml
return data
d = self.synt_plg.convert(body_elt.toXml(), self.SYNTAX_XHTML_IM, safe=True)
d.addCallback(converted)
return d
if lang:
data['extra']['xhtml_{}'.format(lang)] = xhtml
else:
data['extra']['xhtml'] = xhtml
def _sendMessageAddRich(self, mess_data, profile):
defers = []
for body_elt in body_elts:
lang = body_elt.getAttribute('xml:lang', '')
d = self._s.convert(body_elt.toXml(), self.SYNTAX_XHTML_IM, safe=True)
d.addCallback(converted, lang)
defers.append(d)
d_list = defer.DeferredList(defers)
d_list.addCallback(lambda dummy: data)
return d_list
def _messageSendAddRich(self, data, client):
""" Construct XHTML-IM node and add it XML element
@param mess_data: message data as sended by sendMessage callback
@param data: message data as sended by messageSend callback
"""
def syntax_converted(xhtml_im):
message_elt = mess_data['xml']
html_elt = message_elt.addElement('html', NS_XHTML_IM)
body_elt = html_elt.addElement('body', NS_XHTML)
# at this point, either ['extra']['rich'] or ['extra']['xhtml'] exists
# but both can't exist at the same time
message_elt = data['xml']
html_elt = message_elt.addElement((NS_XHTML_IM, 'html'))
def syntax_converted(xhtml_im, lang):
body_elt = html_elt.addElement((NS_XHTML, 'body'))
if lang:
body_elt['xml:lang'] = lang
data['extra']['xhtml_{}'.format(lang)] = xhtml_im
else:
data['extra']['xhtml'] = xhtml_im
body_elt.addRawXml(xhtml_im)
mess_data['extra']['xhtml'] = xhtml_im
return mess_data
syntax = self.synt_plg.getCurrentSyntax(profile)
rich = mess_data['extra'].get('rich', '')
xhtml = mess_data['extra'].get('xhtml', '')
if rich:
d = self.synt_plg.convert(rich, syntax, self.SYNTAX_XHTML_IM)
if xhtml:
raise exceptions.DataError(_("Can't have xhtml and rich content at the same time"))
d.addCallback(syntax_converted)
return d
syntax = self._s.getCurrentSyntax(client.profile)
defers = []
try:
rich = data['extra']['rich']
except KeyError:
# we have directly XHTML
for lang, xhtml in data['extra']['xhtml'].iteritems():
d = self._s.convert(xhtml, self._s.SYNTAX_XHTML, self.SYNTAX_XHTML_IM)
d.addCallback(syntax_converted, lang)
defers.append(d)
else:
# we have rich syntax to convert
for lang, rich_data in rich.iteritems():
d = self._s.convert(rich_data, syntax, self.SYNTAX_XHTML_IM)
d.addCallback(syntax_converted, lang)
defers.append(d)
d_list = defer.DeferredList(defers)
d_list.addCallback(lambda dummy: data)
return d_list
def messageReceivedTrigger(self, message, post_treat, profile):
def messageReceivedTrigger(self, client, message, post_treat):
""" Check presence of XHTML-IM in message
"""
try:
html_elt = message.elements(NS_XHTML_IM, 'html').next()
body_elt = html_elt.elements(NS_XHTML, 'body').next()
# OK, we have found rich text
post_treat.addCallback(self._messagePostTreat, body_elt)
except StopIteration:
# No XHTML-IM
pass
else:
body_elts = html_elt.elements(NS_XHTML, 'body')
post_treat.addCallback(self._messagePostTreat, body_elts)
return True
def sendMessageTrigger(self, mess_data, pre_xml_treatments, post_xml_treatments, profile):
def messageSendTrigger(self, client, data, pre_xml_treatments, post_xml_treatments):
""" Check presence of rich text in extra
"""
if 'rich' in mess_data['extra'] or 'xhtml' in mess_data['extra']:
post_xml_treatments.addCallback(self._sendMessageAddRich, profile)
rich = {}
xhtml = {}
for key, value in data['extra'].iteritems():
if key.startswith('rich'):
rich[key[5:]] = value
elif key.startswith('xhtml'):
xhtml[key[6:]] = value
if rich and xhtml:
raise exceptions.DataError(_(u"Can't have XHTML and rich content at the same time"))
if rich or xhtml:
if rich:
data['rich'] = rich
else:
data['xhtml'] = xhtml
post_xml_treatments.addCallback(self._messageSendAddRich, client)
return True
def _purgeStyle(self, styles_raw):

View file

@ -103,7 +103,7 @@ class XEP_0085(object):
# triggers from core
host.trigger.add("MessageReceived", self.messageReceivedTrigger)
host.trigger.add("sendMessage", self.sendMessageTrigger)
host.trigger.add("messageSend", self.messageSendTrigger)
host.trigger.add("paramUpdateTrigger", self.paramUpdateTrigger)
# args: to_s (jid as string), profile
@ -156,11 +156,12 @@ class XEP_0085(object):
return False
return True
def messageReceivedTrigger(self, message, post_treat, profile):
def messageReceivedTrigger(self, client, message, post_treat):
"""
Update the entity cache when we receive a message with body.
Check for a chat state in the message and signal frontends.
"""
profile = client.profile
if not self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile):
return True
@ -197,11 +198,12 @@ class XEP_0085(object):
break
return True
def sendMessageTrigger(self, mess_data, pre_xml_treatments, post_xml_treatments, profile):
def messageSendTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments):
"""
Eventually add the chat state to the message and initiate
the state machine when sending an "active" state.
"""
profile = client.profile
def treatment(mess_data):
message = mess_data['xml']
to_jid = JID(message.getAttribute("to"))
@ -362,12 +364,15 @@ class ChatStateMachine(object):
# send a new message without body
log.debug(u"sending state '{state}' to {jid}".format(state=state, jid=self.to_jid.full()))
client = self.host.getClient(self.profile)
mess_data = {'message': None,
'type': self.mess_type,
'from': client.jid,
'to': self.to_jid,
'subject': None
}
mess_data = {
'from': client.jid,
'to': self.to_jid,
'uid': '',
'message': {},
'type': self.mess_type,
'subject': {},
'extra': {},
}
self.host.generateMessageXML(mess_data)
mess_data['xml'].addElement(state, NS_CHAT_STATES)
client.xmlstream.send(mess_data['xml'])

View file

@ -22,7 +22,6 @@ from sat.core.i18n import _
from sat.core.log import getLogger
log = getLogger(__name__)
from calendar import timegm
from wokkel import disco, iwokkel, delay
try:
from twisted.words.protocols.xmlstream import XMPPHandler
@ -49,8 +48,6 @@ class XEP_0203(object):
def __init__(self, host):
log.info(_("Delayed Delivery plugin initialization"))
self.host = host
host.trigger.add("MessageReceived", self.messageReceivedTrigger)
def getHandler(self, profile):
return XEP_0203_handler(self, profile)
@ -71,30 +68,6 @@ class XEP_0203(object):
parent.addChild(elt)
return elt
def messagePostTreat(self, data, timestamp):
"""Set the timestamp of a received message.
@param data (dict): data send by MessageReceived trigger through post_treat deferred
@param timestamp (int): original timestamp of a delayed message
@return: dict
"""
data['extra']['timestamp'] = unicode(timestamp)
return data
def messageReceivedTrigger(self, message, post_treat, profile):
"""Process a delay element from incoming message.
@param message (domish.Element): message element
@param post_treat (Deferred): deferred instance to add post treatments
"""
try:
delay_ = delay.Delay.fromElement([elm for elm in message.elements() if elm.name == 'delay'][0])
except IndexError:
return True
else:
timestamp = timegm(delay_.stamp.utctimetuple())
post_treat.addCallback(self.messagePostTreat, timestamp)
return True
class XEP_0203_handler(XMPPHandler):
implements(iwokkel.IDisco)

View file

@ -51,13 +51,13 @@ class XEP_0334(object):
def __init__(self, host):
log.info(_("Message Processing Hints plugin initialization"))
self.host = host
host.trigger.add("sendMessage", self.sendMessageTrigger)
host.trigger.add("messageSend", self.messageSendTrigger)
host.trigger.add("MessageReceived", self.messageReceivedTrigger)
def getHandler(self, profile):
return XEP_0334_handler(self, profile)
def sendMessageTrigger(self, mess_data, pre_xml_treatments, post_xml_treatments, profile):
def messageSendTrigger(self, client, mess_data, pre_xml_treatments, post_xml_treatments):
"""Add the hints element to the message to be sent"""
hints = []
for key in ('no-permanent-storage', 'no-storage', 'no-copy'):
@ -78,7 +78,7 @@ class XEP_0334(object):
post_xml_treatments.addCallback(treatment)
return True
def messageReceivedTrigger(self, message, post_treat, profile):
def messageReceivedTrigger(self, client, message, post_treat):
"""Check for hints in the received message"""
hints = []
for key in ('no-permanent-storage', 'no-storage'):