comparison sat/core/xmpp.py @ 2701:2ea2369ae7de

plugin XEP-0313: implementation of MAM for messages: - (core/xmpp): new messageGetBridgeArgs to easily retrieve arguments used in bridge from message data - : parseMessage is not static anymore - : new "message_parse" trigger point - (xep-0313) : new "MAMGet" bridge method to retrieve history from MAM instead of local one - : on profileConnected, if previous MAM message is found (i.e. message with a stanza_id), message received while offline are retrieved and injected in message workflow. In other words, one2one history is synchronised on connection. - : new "parseExtra" method which parse MAM (and optionally RSM) option from extra dictionary used in bridge.
author Goffi <goffi@goffi.org>
date Sat, 01 Dec 2018 10:33:43 +0100
parents a8ec00731ce7
children 57eac4fd0ec0
comparison
equal deleted inserted replaced
2700:035901dc946d 2701:2ea2369ae7de
58 # else, it's a deferred which fire on disconnection 58 # else, it's a deferred which fire on disconnection
59 self._connected_d = None 59 self._connected_d = None
60 self.profile = profile 60 self.profile = profile
61 self.host_app = host_app 61 self.host_app = host_app
62 self.cache = cache.Cache(host_app, profile) 62 self.cache = cache.Cache(host_app, profile)
63 self._mess_id_uid = {} # map from message id to uid used in history. 63 self.mess_id2uid = {} # map from message id to uid used in history.
64 # Key: (full_jid,message_id) Value: uid 64 # Key: (full_jid, message_id) Value: uid
65 # this Deferred fire when entity is connected 65 # this Deferred fire when entity is connected
66 self.conn_deferred = defer.Deferred() 66 self.conn_deferred = defer.Deferred()
67 self._progress_cb = {} # callback called when a progress is requested 67 self._progress_cb = {} # callback called when a progress is requested
68 # (key = progress id) 68 # (key = progress id)
69 self.actions = {} # used to keep track of actions for retrieval (key = action_id) 69 self.actions = {} # used to keep track of actions for retrieval (key = action_id)
538 log.warning( 538 log.warning(
539 u"No message found" 539 u"No message found"
540 ) # empty body should be managed by plugins before this point 540 ) # empty body should be managed by plugins before this point
541 return data 541 return data
542 542
543 def messageGetBridgeArgs(self, data):
544 """Generate args to use with bridge from data dict"""
545 return (data[u"uid"], data[u"timestamp"], data[u"from"].full(),
546 data[u"to"].full(), data[u"message"], data[u"subject"],
547 data[u"type"], data[u"extra"])
548
549
543 def messageSendToBridge(self, data): 550 def messageSendToBridge(self, data):
544 """Send message to bridge, so frontends can display it 551 """Send message to bridge, so frontends can display it
545 552
546 @param data: message data dictionnary 553 @param data: message data dictionnary
547 @param client: profile's client 554 @param client: profile's client
548 """ 555 """
549 if data[u"type"] != C.MESS_TYPE_GROUPCHAT: 556 if data[u"type"] != C.MESS_TYPE_GROUPCHAT:
550 # we don't send groupchat message to bridge, as we get them back 557 # we don't send groupchat message to bridge, as we get them back
551 # and they will be added the 558 # and they will be added the
552 if ( 559 if (data[u"message"] or data[u"subject"]): # we need a message to send
553 data[u"message"] or data[u"subject"] 560 # something
554 ): # we need a message to send something 561
555 # We send back the message, so all frontends are aware of it 562 # We send back the message, so all frontends are aware of it
556 self.host_app.bridge.messageNew( 563 self.host_app.bridge.messageNew(
557 data[u"uid"], 564 *self.messageGetBridgeArgs(data),
558 data[u"timestamp"], 565 profile=self.profile
559 data[u"from"].full(),
560 data[u"to"].full(),
561 data[u"message"],
562 data[u"subject"],
563 data[u"type"],
564 data[u"extra"],
565 profile=self.profile,
566 ) 566 )
567 else: 567 else:
568 log.warning(_(u"No message found")) 568 log.warning(_(u"No message found"))
569 return data 569 return data
570 570
841 class SatMessageProtocol(xmppim.MessageProtocol): 841 class SatMessageProtocol(xmppim.MessageProtocol):
842 def __init__(self, host): 842 def __init__(self, host):
843 xmppim.MessageProtocol.__init__(self) 843 xmppim.MessageProtocol.__init__(self)
844 self.host = host 844 self.host = host
845 845
846 @staticmethod 846 def parseMessage(self, message_elt):
847 def parseMessage(message_elt, client=None):
848 """parse a message XML and return message_data 847 """parse a message XML and return message_data
849 848
850 @param message_elt(domish.Element): raw <message> xml 849 @param message_elt(domish.Element): raw <message> xml
851 @param client(SatXMPPClient, None): client to map message id to uid 850 @param client(SatXMPPClient, None): client to map message id to uid
852 if None, mapping will not be done 851 if None, mapping will not be done
853 @return(dict): message data 852 @return(dict): message data
854 """ 853 """
854 if message_elt.name != u"message":
855 log.warning(_(
856 u"parseMessage used with a non <message/> stanza, ignoring: {xml}"
857 .format(xml=message_elt.toXml())))
858 return {}
859 client = self.parent
855 message = {} 860 message = {}
856 subject = {} 861 subject = {}
857 extra = {} 862 extra = {}
858 data = { 863 data = {
859 "from": jid.JID(message_elt["from"]), 864 u"from": jid.JID(message_elt["from"]),
860 "to": jid.JID(message_elt["to"]), 865 u"to": jid.JID(message_elt["to"]),
861 "uid": message_elt.getAttribute( 866 u"uid": message_elt.getAttribute(
862 "uid", unicode(uuid.uuid4()) 867 u"uid", unicode(uuid.uuid4())
863 ), # XXX: uid is not a standard attribute but may be added by plugins 868 ), # XXX: uid is not a standard attribute but may be added by plugins
864 "message": message, 869 u"message": message,
865 "subject": subject, 870 u"subject": subject,
866 "type": message_elt.getAttribute("type", "normal"), 871 u"type": message_elt.getAttribute(u"type", u"normal"),
867 "extra": extra, 872 u"extra": extra,
868 } 873 }
869 874
870 if client is not None: 875 try:
871 try: 876 message_id = data[u"extra"][u"message_id"] = message_elt[u"id"]
872 data["stanza_id"] = message_elt["id"] 877 except KeyError:
873 except KeyError: 878 pass
874 pass 879 else:
875 else: 880 client.mess_id2uid[(data["from"], message_id)] = data["uid"]
876 client._mess_id_uid[(data["from"], data["stanza_id"])] = data["uid"]
877 881
878 # message 882 # message
879 for e in message_elt.elements(C.NS_CLIENT, "body"): 883 for e in message_elt.elements(C.NS_CLIENT, "body"):
880 message[e.getAttribute((C.NS_XML, "lang"), "")] = unicode(e) 884 message[e.getAttribute((C.NS_XML, "lang"), "")] = unicode(e)
881 885
892 parsed_delay = delay.Delay.fromElement(delay_elt) 896 parsed_delay = delay.Delay.fromElement(delay_elt)
893 data["timestamp"] = calendar.timegm(parsed_delay.stamp.utctimetuple()) 897 data["timestamp"] = calendar.timegm(parsed_delay.stamp.utctimetuple())
894 data["received_timestamp"] = unicode(time.time()) 898 data["received_timestamp"] = unicode(time.time())
895 if parsed_delay.sender: 899 if parsed_delay.sender:
896 data["delay_sender"] = parsed_delay.sender.full() 900 data["delay_sender"] = parsed_delay.sender.full()
901
902 self.host.trigger.point("message_parse", client, message_elt, data)
897 return data 903 return data
898 904
899 def _onMessageStartWorkflow(self, cont, client, message_elt, post_treat): 905 def _onMessageStartWorkflow(self, cont, client, message_elt, post_treat):
900 """Parse message and do post treatments 906 """Parse message and do post treatments
901 907
905 may have be modified by triggers 911 may have be modified by triggers
906 @param post_treat(defer.Deferred): post parsing treatments 912 @param post_treat(defer.Deferred): post parsing treatments
907 """ 913 """
908 if not cont: 914 if not cont:
909 return 915 return
910 data = self.parseMessage(message_elt, client=client) 916 data = self.parseMessage(message_elt)
911 post_treat.addCallback(self.skipEmptyMessage) 917 post_treat.addCallback(self.skipEmptyMessage)
912 post_treat.addCallback(self.addToHistory, client) 918 post_treat.addCallback(self.addToHistory, client)
913 post_treat.addCallback(self.bridgeSignal, client, data) 919 post_treat.addCallback(self.bridgeSignal, client, data)
914 post_treat.addErrback(self.cancelErrorTrap) 920 post_treat.addErrback(self.cancelErrorTrap)
915 post_treat.callback(data) 921 post_treat.callback(data)