Mercurial > libervia-backend
view src/plugins/plugin_xep_0313.py @ 1422:be1fccf4854d
tmp (wokkel): licenses fixes:
the licenses headers were wrong, it was fixed: original work from Adrien Cossa is directly under AGPL v3 (with his agreement), work derivated from Wokkel is sublicensed to AGPL v3 as allowed by the original license, to stay consistent with the rest of the code base.
Theses files (and only these ones) can be relicensed again to fill Wokkel license if Ralph plan to merge them upstream...
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 23 Apr 2015 10:57:40 +0200 |
parents | 3265a2639182 |
children | d17772b0fe22 |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- # SAT plugin for Message Archive Management (XEP-0313) # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Jérôme Poisson (goffi@goffi.org) # Copyright (C) 2013, 2014, 2015 Adrien Cossa (souliane@mailoo.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.constants import Const as C from sat.core.i18n import _ from sat.core.log import getLogger log = getLogger(__name__) try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: from wokkel.subprotocols import XMPPHandler from twisted.words.xish import domish from twisted.words.protocols.jabber import jid from zope.interface import implements from wokkel import disco, data_form from wokkel.generic import parseXml from wokkel.pubsub import NS_PUBSUB_EVENT, ItemsEvent # TODO: change this when RSM and MAM are in wokkel from sat.tmp.wokkel.rsm import RSMRequest from sat.tmp.wokkel import mam NS_MAM = 'urn:xmpp:mam:0' NS_SF = 'urn:xmpp:forward:0' NS_DD = 'urn:xmpp:delay' NS_CLIENT = 'jabber:client' PLUGIN_INFO = { "name": "Message Archive Management", "import_name": "XEP-0313", "type": "XEP", "protocols": ["XEP-0313"], "dependencies": ["XEP-0059", "XEP-0297", "XEP-0203"], "recommendations": ["XEP-0334"], "main": "XEP_0313", "handler": "yes", "description": _("""Implementation of Message Archive Management""") } class XEP_0313(object): def __init__(self, host): log.info(_("Message Archive Management plugin initialization")) self.host = host self.clients = {} # bind profile name to SatMAMClient host.bridge.addMethod("MAMqueryFields", ".plugin", in_sign='ss', out_sign='s', method=self._queryFields, async=True, doc={}) host.bridge.addMethod("MAMqueryArchive", ".plugin", in_sign='ssa{ss}ss', out_sign='s', method=self._queryArchive, async=True, doc={}) host.bridge.addMethod("MAMgetPrefs", ".plugin", in_sign='ss', out_sign='s', method=self._getPrefs, async=True, doc={}) host.bridge.addMethod("MAMsetPrefs", ".plugin", in_sign='ssasass', out_sign='s', method=self._setPrefs, async=True, doc={}) host.trigger.add("MessageReceived", self.messageReceivedTrigger) def getHandler(self, profile): self.clients[profile] = SatMAMClient(self, profile) return self.clients[profile] def profileDisconnected(self, profile): try: del self.clients[profile] except KeyError: pass def _queryFields(self, service_s=None, profile_key=C.PROF_KEY_NONE): service = jid.JID(service_s) if service_s else None return self.queryFields(service, profile_key) def queryFields(self, service=None, profile_key=C.PROF_KEY_NONE): """Ask the server about additional supported fields. @param service: entity offering the MAM service (None for user archives) @param profile_key (unicode): %(doc_profile_key)s @return: the server response as a Deferred domish.Element """ # http://xmpp.org/extensions/xep-0313.html#query-form def eb(failure): # typically StanzaError with condition u'service-unavailable' log.error(failure.getErrorMessage()) return '' profile = self.host.memory.getProfileName(profile_key) d = self.clients[profile].queryFields(service) return d.addCallbacks(lambda elt: elt.toXml(), eb) def _queryArchive(self, service_s=None, form_xml=None, rsm_dict=None, node=None, profile_key=C.PROF_KEY_NONE): service = jid.JID(service_s) if service_s else None if form_xml: form = data_form.Form.fromElement(parseXml(form_xml)) if form.formNamespace != NS_MAM: log.error(_(u"Expected a MAM Data Form, got instead: %s") % form.formNamespace) form = None else: form = None rsm = RSMRequest(**rsm_dict) if rsm_dict else None return self.queryArchive(service, form, rsm, node, profile_key) def queryArchive(self, service=None, form=None, rsm=None, node=None, profile_key=C.PROF_KEY_NONE): """Query a user, MUC or pubsub archive. @param service: entity offering the MAM service (None for user archives) @param form (Form): data form to filter the request @param rsm (RSMRequest): RSM request instance @param node (unicode): pubsub node to query, or None if inappropriate @param profile_key (unicode): %(doc_profile_key)s @return: a Deferred when the message has been sent """ def eb(failure): # typically StanzaError with condition u'service-unavailable' log.error(failure.getErrorMessage()) return '' profile = self.host.memory.getProfileName(profile_key) d = self.clients[profile].queryArchive(service, form, rsm, node) return d.addCallbacks(lambda elt: elt.toXml(), eb) # TODO: add the handler for receiving the final message def _getPrefs(self, service_s=None, profile_key=C.PROF_KEY_NONE): service = jid.JID(service_s) if service_s else None return self.getPrefs(service, profile_key) def getPrefs(self, service=None, profile_key=C.PROF_KEY_NONE): """Retrieve the current user preferences. @param service: entity offering the MAM service (None for user archives) @param profile_key (unicode): %(doc_profile_key)s @return: the server response as a Deferred domish.Element """ # http://xmpp.org/extensions/xep-0313.html#prefs def eb(failure): # typically StanzaError with condition u'service-unavailable' log.error(failure.getErrorMessage()) return '' profile = self.host.memory.getProfileName(profile_key) d = self.clients[profile].queryPrefs(service) return d.addCallbacks(lambda elt: elt.toXml(), eb) def _setPrefs(self, service_s=None, default='roster', always=None, never=None, profile_key=C.PROF_KEY_NONE): service = jid.JID(service_s) if service_s else None always_jid = [jid.JID(entity) for entity in always] never_jid = [jid.JID(entity) for entity in never] #TODO: why not build here a MAMPrefs object instead of passing the args separately? return self.setPrefs(service, default, always_jid, never_jid, profile_key) def setPrefs(self, service=None, default='roster', always=None, never=None, profile_key=C.PROF_KEY_NONE): """Set news user preferences. @param service: entity offering the MAM service (None for user archives) @param default (unicode): a value in ('always', 'never', 'roster') @param always (list): a list of JID instances @param never (list): a list of JID instances @param profile_key (unicode): %(doc_profile_key)s @return: the server response as a Deferred domish.Element """ # http://xmpp.org/extensions/xep-0313.html#prefs def eb(failure): # typically StanzaError with condition u'service-unavailable' log.error(failure.getErrorMessage()) return '' profile = self.host.memory.getProfileName(profile_key) d = self.clients[profile].setPrefs(service, default, always, never) return d.addCallbacks(lambda elt: elt.toXml(), eb) def messageReceivedTrigger(self, message, post_treat, profile): """Check if the message is a MAM result. If so, extract the original message, stop processing the current message and process the original message instead. """ try: result = domish.generateElementsQNamed(message.elements(), "result", NS_MAM).next() except StopIteration: return True try: forwarded = domish.generateElementsQNamed(result.elements(), "forwarded", NS_SF).next() except StopIteration: log.error(_("MAM result misses its <forwarded/> mandatory element!")) return False try: # TODO: delay is not here for nothing, get benefice of it! delay = domish.generateElementsQNamed(forwarded.elements(), "delay", NS_DD).next() msg = domish.generateElementsQNamed(forwarded.elements(), "message", NS_CLIENT).next() except StopIteration: log.error(_("<forwarded/> element misses a mandatory child!")) return False log.debug(_("MAM found a forwarded message")) if msg.event and msg.event.uri == NS_PUBSUB_EVENT: event = ItemsEvent(jid.JID(message['from']), jid.JID(message['to']), msg.event.items['node'], msg.event.items.elements(), {}) self.host.plugins["XEP-0060"].clients[profile].itemsReceived(event) return False client = self.host.getClient(profile) client.messageProt.onMessage(msg) return False class SatMAMClient(mam.MAMClient): implements(disco.IDisco) def __init__(self, plugin_parent, profile): self.plugin_parent = plugin_parent self.host = plugin_parent.host self.profile = profile mam.MAMClient.__init__(self) def getDiscoInfo(self, requestor, target, nodeIdentifier=''): return [disco.DiscoFeature(NS_MAM)] def getDiscoItems(self, requestor, target, nodeIdentifier=''): return []