diff src/plugins/plugin_xep_0060.py @ 1777:8b18e5f55a90

plugin XEP-0060: MAM integration: - removed useless XEP-0059 recommendation - added XEP-0313 as a recommended plugin - parseExtra now manage mam_* keys (mam_filter_[name] to use a filter) - if a mam request is in 'mam' key of extra parameted in getItems, a MAM request is done, but the result is returned in the same way as for normal getItems, making like more easy for other plugins and for frontends
author Goffi <goffi@goffi.org>
date Tue, 05 Jan 2016 23:20:22 +0100
parents 6e867caf4621
children 442303b62a16
line wrap: on
line diff
--- a/src/plugins/plugin_xep_0060.py	Tue Jan 05 23:20:22 2016 +0100
+++ b/src/plugins/plugin_xep_0060.py	Tue Jan 05 23:20:22 2016 +0100
@@ -28,15 +28,16 @@
 from twisted.words.protocols.jabber import jid, error
 from twisted.internet import defer
 from wokkel import disco
-# XXX: tmp.pubsub is actually use instead of wokkel version
-#      same thing for rsm
+from wokkel import data_form
+from zope.interface import implements
+from collections import namedtuple
+import datetime
+from dateutil import tz
+# XXX: tmp.wokkel.pubsub is actually use instead of wokkel version
+#      mam and rsm come from tmp.wokkel too
 from wokkel import pubsub
 from wokkel import rsm
-from zope.interface import implements
-from collections import namedtuple
-
-
-UNSPECIFIED = "unspecified error"
+from wokkel import mam
 
 
 PLUGIN_INFO = {
@@ -45,12 +46,15 @@
     "type": "XEP",
     "protocols": ["XEP-0060"],
     "dependencies": [],
-    "recommendations": ["XEP-0059"],
+    "recommendations": ["XEP-0313"],
     "main": "XEP_0060",
     "handler": "yes",
     "description": _("""Implementation of PubSub Protocol""")
 }
 
+UNSPECIFIED = "unspecified error"
+MAM_FILTER = "mam_filter_"
+
 
 Extra = namedtuple('Extra', ('rsm_request', 'extra'))
 # rsm_request is the rsm.RSMRequest build with rsm_ prefixed keys, or None
@@ -77,6 +81,7 @@
     def __init__(self, host):
         log.info(_(u"PubSub plugin initialization"))
         self.host = host
+        self._mam = host.plugins.get('XEP-0313')
         self._node_cb = {} # dictionnary of callbacks for node (key: node, value: list of callbacks)
         self.rt_sessions = sat_defer.RTDeferredSessions()
         host.bridge.addMethod("psDeleteNode", ".plugin", in_sign='sss', out_sign='', method=self._deleteNode, async=True)
@@ -125,15 +130,46 @@
         @return(Extra): filled Extra instance
         """
         if extra is not None:
-            rsm_dict = { key[4:]: value for key, value in extra.iteritems() if key.startswith('rsm_') }
-            if rsm_dict:
+            # rsm
+            rsm_args = {}
+            for arg in ('max', 'after', 'before', 'index'):
                 try:
-                    rsm_dict['max_'] = rsm_dict.pop('max')
+                    argname = "max_" if arg == 'max' else arg
+                    rsm_args[argname] = extra.pop('rsm_{}'.format(arg))
                 except KeyError:
-                    pass
-                rsm_request = rsm.RSMRequest(**rsm_dict)
+                    continue
+
+            if rsm_args:
+                rsm_request = rsm.RSMRequest(**rsm_args)
             else:
                 rsm_request = None
+
+            # mam
+            mam_args = {}
+            for arg in ('start', 'end'):
+                try:
+                    mam_args[arg] = datetime.datetime.fromtimestamp(int(extra.pop('{}{}'.format(MAM_FILTER, arg))), tz.tzutc())
+                except (TypeError, ValueError):
+                    log.warning(u"Bad value for {} filter".format(arg))
+                except KeyError:
+                    continue
+
+            try:
+                mam_args['with_jid'] = jid.JID(extra.pop('{}jid'.format(MAM_FILTER)))
+            except (jid.InvalidFormat):
+                log.warning(u"Bad value for jid filter")
+            except KeyError:
+                pass
+
+            for name, value in extra:
+                if name.startswith(MAM_FILTER):
+                    var = name[len[MAM_FILTER]:]
+                    extra_fields = mam_args.setdefault('extra_fields', [])
+                    extra_fields.append(data_form.Field(var=var, value=value))
+
+            if mam_args:
+                assert 'mam' not in extra
+                extra['mam'] = mam.MAMRequest(mam.buildForm(**mam_args))
         else:
             rsm_request = None
             extra = {}
@@ -218,6 +254,18 @@
         client = self.host.getClient(profile_key)
         return client.pubsub_client.publish(service, nodeIdentifier, items, client.pubsub_client.parent.jid)
 
+    def _unwrapMAMMessage(self, message_elt):
+        try:
+            item_elt = (message_elt.elements(mam.NS_MAM, 'result').next()
+                                   .elements(C.NS_FORWARD, 'forwarded').next()
+                                   .elements(C.NS_CLIENT, 'message').next()
+                                   .elements('http://jabber.org/protocol/pubsub#event', 'event').next()
+                                   .elements('http://jabber.org/protocol/pubsub#event', 'items').next()
+                                   .elements('http://jabber.org/protocol/pubsub#event', 'item').next())
+        except StopIteration:
+            raise exceptions.DataError(u"Can't find Item in MAM message element")
+        return item_elt
+
     def getItems(self, service, node, max_items=None, item_ids=None, sub_id=None, rsm_request=None, extra=None, profile_key=C.PROF_KEY_NONE):
         """Retrieve pubsub items from a node.
 
@@ -238,7 +286,28 @@
         if extra is None:
             extra = {}
         client = self.host.getClient(profile_key)
-        d = client.pubsub_client.items(service, node, max_items, item_ids, sub_id, client.pubsub_client.parent.jid, rsm_request)
+        try:
+            mam_query = extra['mam']
+        except KeyError:
+            d = client.pubsub_client.items(service, node, max_items, item_ids, sub_id, None, rsm_request)
+        else:
+            # if mam is requested, we have to do a totally different query
+            if self._mam is None:
+                raise exceptions.NotFound(u"MAM (XEP-0313) plugin is not available")
+            if max_items is not None:
+                raise exceptions.DataError(u"max_items parameter can't be used with MAM")
+            if item_ids:
+                raise exceptions.DataError(u"items_ids parameter can't be used with MAM")
+            if mam_query.node is None:
+                mam_query.node = node
+            elif mam_query.node != node:
+                raise exceptions.DataError(u"MAM query node is incoherent with getItems's node")
+            if mam_query.rsm is None:
+                mam_query.rsm = rsm_request
+            else:
+                if mam_query.rsm != rsm_request:
+                    raise exceptions.DataError(u"Conflict between RSM request and MAM's RSM request")
+            d = self._mam.getArchives(client, mam_query, service, self._unwrapMAMMessage)
 
         try:
             subscribe = C.bool(extra['subscribe'])