# HG changeset patch # User Goffi # Date 1452032422 -3600 # Node ID 8b18e5f55a90674208a3a621271697f3be3ac7cf # Parent 4fc1bf1af48fe9d85afbafcd7b6ef477d3f10527 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 diff -r 4fc1bf1af48f -r 8b18e5f55a90 src/plugins/plugin_xep_0060.py --- 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'])