Mercurial > libervia-backend
view src/plugins/plugin_xep_0280.py @ 2144:1d3f73e065e1
core, jp: component handling + client handling refactoring:
- SàT can now handle components
- plugin have now a "modes" key in PLUGIN_INFO where they declare if they can be used with clients and or components. They default to be client only.
- components are really similar to clients, but with some changes in behaviour:
* component has "entry point", which is a special plugin with a componentStart method, which is called just after component is connected
* trigger end with a different suffixes (e.g. profileConnected vs profileConnectedComponent), so a plugin which manage both clients and components can have different workflow
* for clients, only triggers of plugins handling client mode are launched
* for components, only triggers of plugins needed in dependencies are launched. They all must handle component mode.
* component have a sendHistory attribute (False by default) which can be set to True to allow saving sent messages into history
* for convenience, "client" is still used in method even if it can now be a component
* a new "component" boolean attribute tells if we have a component or a client
* components have to add themselve Message protocol
* roster and presence protocols are not added for components
* component default port is 5347 (which is Prosody's default port)
- asyncCreateProfile has been renamed for profileCreate, both to follow new naming convention and to prepare the transition to fully asynchronous bridge
- createProfile has a new "component" attribute. When used to create a component, it must be set to a component entry point
- jp: added --component argument to profile/create
- disconnect bridge method is now asynchronous, this way frontends can know when disconnection is finished
- new PI_* constants for PLUGIN_INFO values (not used everywhere yet)
- client/component connection workflow has been moved to their classes instead of being a host methods
- host.messageSend is now client.sendMessage, and former client.sendMessage is now client.sendMessageData.
- identities are now handled in client.identities list, so it can be updated dynamically by plugins (in the future, frontends should be able to update them too through bridge)
- profileConnecting* profileConnected* profileDisconnected* and getHandler now all use client instead of profile
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 12 Feb 2017 17:55:43 +0100 |
parents | 7de291c3cd0c |
children | 33c8c4973743 |
line wrap: on
line source
#!/usr/bin/env python2 # -*- coding: utf-8 -*- # SAT plugin for managing xep-0280 # Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.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.i18n import _, D_ from sat.core.log import getLogger log = getLogger(__name__) from sat.core import exceptions from sat.core.constants import Const as C from sat.core.xmpp import SatMessageProtocol from twisted.words.protocols.jabber.error import StanzaError from twisted.internet import defer from wokkel import disco, iwokkel from zope.interface import implements try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: from wokkel.subprotocols import XMPPHandler PARAM_CATEGORY = "Misc" PARAM_NAME = "carbon" PARAM_LABEL = D_(u"Message carbons") NS_CARBONS = 'urn:xmpp:carbons:2' PLUGIN_INFO = { "name": u"XEP-0280 Plugin", "import_name": u"XEP-0280", "type": u"XEP", "protocols": [u"XEP-0280"], "dependencies": [], "main": u"XEP_0280", "handler": u"yes", "description": D_(u"""Implementation of Message Carbons""") } class XEP_0280(object): # TODO: param is only checked at profile connection # activate carbons on param change even after profile connection # TODO: chat state notifications are not handled yet (and potentially other XEPs?) params = """ <params> <individual> <category name="{category_name}" label="{category_label}"> <param name="{param_name}" label="{param_label}" value="true" type="bool" security="0" /> </category> </individual> </params> """.format( category_name = PARAM_CATEGORY, category_label = D_(PARAM_CATEGORY), param_name = PARAM_NAME, param_label = PARAM_LABEL, ) def __init__(self, host): log.info(_("Plugin XEP_0280 initialization")) self.host = host host.memory.updateParams(self.params) host.trigger.add("MessageReceived", self.messageReceivedTrigger, priority=1000) def getHandler(self, client): return XEP_0280_handler() def setPrivate(self, message_elt): """Add a <private/> element to a message this method is intented to be called on final domish.Element by other plugins (in particular end 2 end encryption plugins) @param message_elt(domish.Element): <message> stanza """ if message_elt.name != u'message': log.error(u"addPrivateElt must be used with <message> stanzas") return message_elt.addElement((NS_CARBONS, u'private')) @defer.inlineCallbacks def profileConnected(self, client): """activate message carbons on connection if possible and activated in config""" activate = self.host.memory.getParamA(PARAM_NAME, PARAM_CATEGORY, profile_key=client.profile) if not activate: log.info(_(u"Not activating message carbons as requested in params")) return try: yield self.host.checkFeatures((NS_CARBONS,), profile=client.profile) except exceptions.FeatureNotFound: log.warning(_(u"server doesn't handle message carbons")) else: log.info(_(u"message carbons available, enabling it")) iq_elt = client.IQ() iq_elt.addElement((NS_CARBONS, 'enable')) try: yield iq_elt.send() except StanzaError as e: log.warning(u"Can't activate message carbons: {}".format(e)) else: log.info(_(u"message carbons activated")) def messageReceivedTrigger(self, client, message_elt, post_treat): """get message and handle it if carbons namespace is present""" carbons_elt = None for e in message_elt.elements(): if e.uri == NS_CARBONS: carbons_elt = e break if carbons_elt is None: # this is not a message carbons, # we continue normal behaviour return True if message_elt['from'] != client.jid.userhost(): log.warning(u"The message carbon received is not from our server, hack attempt?\n{xml}".format( xml = message_elt.toXml(), )) return forwarded_elt = next(carbons_elt.elements(C.NS_FORWARD, 'forwarded')) cc_message_elt = next(forwarded_elt.elements(C.NS_CLIENT, 'message')) if carbons_elt.name == 'received': # on receive we replace the wrapping message with the CCed one # and continue the normal behaviour message_elt['from'] = cc_message_elt['from'] del message_elt.children[:] for c in cc_message_elt.children: message_elt.addChild(c) return True elif carbons_elt.name == 'sent': # on send we parse the message and just add it to history # and send it to frontends (without normal sending treatments) mess_data = SatMessageProtocol.parseMessage(cc_message_elt, client) if not mess_data['message'] and not mess_data['subject']: return False self.host.messageAddToHistory(mess_data, client) self.host.sendMessageToBridge(mess_data, client) else: log.warning(u"invalid message carbons received:\n{xml}".format( xml = message_elt.toXml())) return False class XEP_0280_handler(XMPPHandler): implements(iwokkel.IDisco) def getDiscoInfo(self, requestor, target, nodeIdentifier=''): return [disco.DiscoFeature(NS_CARBONS)] def getDiscoItems(self, requestor, target, nodeIdentifier=''): return []