Mercurial > libervia-backend
view src/plugins/plugin_xep_0313.py @ 1596:b7ee113183fc
jp: better profile commands:
- new "profile/default" command
- info doesn't show password anymore by default, need to be explicitly requested
- info and modify don't need to connect anymore
- modify can now set default profile. As use_profile is set, at least a profile session need to be started when it would not be mandatory technicaly (if just setting the profile as default is needed). But this option should not be used often, and it's not a big side effect, so I don't feel the need to create a new dedicated command, or to do complicated checks to avoid the session start.
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 14 Nov 2015 19:18:10 +0100 |
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 []