Mercurial > libervia-backend
comparison src/plugins/plugin_xep_0033.py @ 742:03744d9ebc13
plugin XEP-0033: implementation of the addressing feature:
- frontends pass the recipients in the extra parameter of sendMessage
- backend checks if the target server supports the feature (this is not done yet by prosody plugin)
- features and identities are cached per profile and server
- messages are duplicated in history for now (TODO: redesign the database)
- echos signals are also duplicated to the sender (FIXME)
author | souliane <souliane@mailoo.org> |
---|---|
date | Wed, 11 Dec 2013 17:16:53 +0100 |
parents | |
children | 192b804ee446 |
comparison
equal
deleted
inserted
replaced
741:00318e60a06a | 742:03744d9ebc13 |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SAT plugin for Extended Stanza Addressing (xep-0033) | |
5 # Copyright (C) 2013 Adrien Cossa (souliane@mailoo.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 import logging | |
21 from sat.core import exceptions | |
22 from wokkel import disco, iwokkel | |
23 from zope.interface import implements | |
24 from twisted.words.protocols.jabber.jid import JID | |
25 import copy | |
26 try: | |
27 from twisted.words.protocols.xmlstream import XMPPHandler | |
28 except ImportError: | |
29 from wokkel.subprotocols import XMPPHandler | |
30 from threading import Timer | |
31 from twisted.words.xish import domish | |
32 from twisted.internet import defer | |
33 | |
34 from sat.core.sat_main import MessageSentAndStored, AbortSendMessage | |
35 from sat.tools.misc import TriggerManager | |
36 | |
37 # TODO: fix Prosody "addressing" plugin to leave the concerned bcc according to the spec: | |
38 # | |
39 # http://xmpp.org/extensions/xep-0033.html#addr-type-bcc | |
40 # "This means that the server MUST remove these addresses before the stanza is delivered to anyone other than the given bcc addressee or the multicast service of the bcc addressee." | |
41 # | |
42 # http://xmpp.org/extensions/xep-0033.html#multicast | |
43 # "Each 'bcc' recipient MUST receive only the <address type='bcc'/> associated with that addressee." | |
44 | |
45 # TODO: fix Prosody "addressing" plugin to determine itself if remote servers supports this XEP | |
46 | |
47 | |
48 NS_XMPP_CLIENT = "jabber:client" | |
49 NS_ADDRESS = "http://jabber.org/protocol/address" | |
50 ATTRIBUTES = ["jid", "uri", "node", "desc", "delivered", "type"] | |
51 ADDRESS_TYPES = ["to", "cc", "bcc", "replyto", "replyroom", "noreply"] | |
52 | |
53 PLUGIN_INFO = { | |
54 "name": "Extended Stanza Addressing Protocol Plugin", | |
55 "import_name": "XEP-0033", | |
56 "type": "XEP", | |
57 "protocols": ["XEP-0033"], | |
58 "dependencies": [], | |
59 "main": "XEP_0033", | |
60 "handler": "yes", | |
61 "description": _("""Implementation of Extended Stanza Addressing""") | |
62 } | |
63 | |
64 | |
65 class XEP_0033(object): | |
66 """ | |
67 Implementation for XEP 0033 | |
68 """ | |
69 def __init__(self, host): | |
70 logging.info(_("Extended Stanza Addressing plugin initialization")) | |
71 self.host = host | |
72 host.trigger.add("sendMessage", self.sendMessageTrigger, TriggerManager.MIN_PRIORITY) | |
73 host.trigger.add("MessageReceived", self.messageReceivedTrigger) | |
74 | |
75 def sendMessageTrigger(self, mess_data, treatments, profile): | |
76 """Process the XEP-0033 related data to be sent""" | |
77 | |
78 def treatment(mess_data): | |
79 if not 'address' in mess_data['extra']: | |
80 return mess_data | |
81 | |
82 def discoCallback(entity): | |
83 if entity is None: | |
84 raise AbortSendMessage(_("XEP-0033 is being used but the server doesn't support it!")) | |
85 to = JID(mess_data["to"].host) | |
86 if to != entity: | |
87 logging.warning(_("Stanzas using XEP-0033 should be addressed to %s, not %s!") % (entity, to)) | |
88 logging.warning(_("TODO: addressing has be fixed by the backend... fix it in the frontend!")) | |
89 mess_data["to"] = entity | |
90 element = mess_data['xml'].addElement('addresses', NS_ADDRESS) | |
91 entries = [entry.split(':') for entry in mess_data['extra']['address'].split('\n') if entry != ''] | |
92 for type_, jid_ in entries: | |
93 element.addChild(domish.Element((None, 'address'), None, {'type': type_, 'jid': jid_})) | |
94 # when the prosody plugin is completed, we can immediately return mess_data from here | |
95 return self.sendAndStoreMessage(mess_data, entries, profile) | |
96 | |
97 d = self.host.requestServerDisco(NS_ADDRESS, profile_key=profile) | |
98 d.addCallbacks(discoCallback, lambda dummy: discoCallback(None)) | |
99 return d | |
100 | |
101 treatments.addCallback(treatment) | |
102 return True | |
103 | |
104 def sendAndStoreMessage(self, mess_data, entries, profile): | |
105 """Check if target servers support XEP-0033, send and store the messages | |
106 @raise: a friendly failure to let the core know that we sent the message already | |
107 | |
108 Later we should be able to remove this method because: | |
109 # XXX: sending the messages should be done by the local server | |
110 # FIXME: for now we duplicate the messages in the history for each recipient, this should change | |
111 # FIXME: for now we duplicate the echoes to the sender, this should also change | |
112 Ideas: | |
113 - fix Prosody plugin to check if target server support the feature | |
114 - redesign the database to save only one entry to the database | |
115 - change the newMessage signal to eventually pass more than one recipient | |
116 """ | |
117 def discoCallback(entity, to_jid): | |
118 new_data = copy.deepcopy(mess_data) | |
119 new_data['to'] = JID(to_jid) | |
120 new_data['xml']['to'] = to_jid | |
121 if entity: | |
122 if 'address' in mess_data['extra']: | |
123 self.host.sendAndStoreMessage(mess_data, False, profile) | |
124 # just to remember that the message has been sent | |
125 del mess_data['extra']['address'] | |
126 # we still need to fill the history and signal the echo... | |
127 self.host.sendAndStoreMessage(new_data, True, profile) | |
128 else: | |
129 # target server misses the addressing feature | |
130 self.host.sendAndStoreMessage(new_data, False, profile) | |
131 | |
132 def errback(failure, to_jid): | |
133 discoCallback(None, to_jid) | |
134 | |
135 for type_, jid_ in entries: | |
136 d = defer.Deferred() | |
137 d.addCallback(self.host.requestServerDisco, JID(JID(jid_).host), profile_key=profile) | |
138 d.addCallbacks(discoCallback, errback, callbackArgs=[jid_], errbackArgs=[jid_]) | |
139 d.callback(NS_ADDRESS) | |
140 | |
141 raise MessageSentAndStored("XEP-0033 took over") | |
142 | |
143 def messageReceivedTrigger(self, message, post_treat, profile): | |
144 """In order to save the addressing information in the history""" | |
145 def post_treat_addr(data, addresses): | |
146 data['extra']['addresses'] = "" | |
147 for address in addresses: | |
148 data['extra']['addresses'] += '%s:%s\n' % (address['type'], address['jid']) | |
149 return data | |
150 | |
151 try: | |
152 addresses = message.elements(NS_ADDRESS, 'addresses').next() | |
153 post_treat.addCallback(post_treat_addr, addresses.children) | |
154 except StopIteration: | |
155 pass # no addresses | |
156 return True | |
157 | |
158 def getHandler(self, profile): | |
159 return XEP_0033_handler(self, profile) | |
160 | |
161 | |
162 class XEP_0033_handler(XMPPHandler): | |
163 implements(iwokkel.IDisco) | |
164 | |
165 def __init__(self, plugin_parent, profile): | |
166 self.plugin_parent = plugin_parent | |
167 self.host = plugin_parent.host | |
168 self.profile = profile | |
169 | |
170 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): | |
171 return [disco.DiscoFeature(NS_ADDRESS)] | |
172 | |
173 def getDiscoItems(self, requestor, target, nodeIdentifier=''): | |
174 return [] |