Mercurial > libervia-backend
comparison sat/plugins/plugin_exp_events.py @ 2562:26edcf3a30eb
core, setup: huge cleaning:
- moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention
- move twisted directory to root
- removed all hacks from setup.py, and added missing dependencies, it is now clean
- use https URL for website in setup.py
- removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed
- renamed sat.sh to sat and fixed its installation
- added python_requires to specify Python version needed
- replaced glib2reactor which use deprecated code by gtk3reactor
sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 02 Apr 2018 19:44:50 +0200 |
parents | src/plugins/plugin_exp_events.py@0046283a285d |
children | 3e4e78de9cca |
comparison
equal
deleted
inserted
replaced
2561:bd30dc3ffe5a | 2562:26edcf3a30eb |
---|---|
1 #!/usr/bin/env python2 | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # SAT plugin to detect language (experimental) | |
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.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 from sat.core.i18n import _ | |
21 from sat.core import exceptions | |
22 from sat.core.constants import Const as C | |
23 from sat.core.log import getLogger | |
24 log = getLogger(__name__) | |
25 from sat.tools import utils | |
26 from sat.tools.common import uri as uri_parse | |
27 from twisted.internet import defer | |
28 from twisted.words.protocols.jabber import jid, error | |
29 from twisted.words.xish import domish | |
30 from wokkel import pubsub | |
31 | |
32 | |
33 PLUGIN_INFO = { | |
34 C.PI_NAME: "Event plugin", | |
35 C.PI_IMPORT_NAME: "EVENTS", | |
36 C.PI_TYPE: "EXP", | |
37 C.PI_PROTOCOLS: [], | |
38 C.PI_DEPENDENCIES: ["XEP-0060"], | |
39 C.PI_RECOMMENDATIONS: ["INVITATIONS", "XEP-0277"], | |
40 C.PI_MAIN: "Events", | |
41 C.PI_HANDLER: "no", | |
42 C.PI_DESCRIPTION: _("""Experimental implementation of XMPP events management""") | |
43 } | |
44 | |
45 NS_EVENT = 'org.salut-a-toi.event:0' | |
46 | |
47 | |
48 class Events(object): | |
49 """Q&D module to handle event attendance answer, experimentation only""" | |
50 | |
51 def __init__(self, host): | |
52 log.info(_(u"Event plugin initialization")) | |
53 self.host = host | |
54 self._p = self.host.plugins["XEP-0060"] | |
55 self._i = self.host.plugins.get("INVITATIONS") | |
56 self._b = self.host.plugins.get("XEP-0277") | |
57 host.bridge.addMethod("eventGet", ".plugin", | |
58 in_sign='ssss', out_sign='(ia{ss})', | |
59 method=self._eventGet, | |
60 async=True) | |
61 host.bridge.addMethod("eventCreate", ".plugin", | |
62 in_sign='ia{ss}ssss', out_sign='s', | |
63 method=self._eventCreate, | |
64 async=True) | |
65 host.bridge.addMethod("eventModify", ".plugin", | |
66 in_sign='sssia{ss}s', out_sign='', | |
67 method=self._eventModify, | |
68 async=True) | |
69 host.bridge.addMethod("eventInviteeGet", ".plugin", | |
70 in_sign='sss', out_sign='a{ss}', | |
71 method=self._eventInviteeGet, | |
72 async=True) | |
73 host.bridge.addMethod("eventInviteeSet", ".plugin", | |
74 in_sign='ssa{ss}s', out_sign='', | |
75 method=self._eventInviteeSet, | |
76 async=True) | |
77 host.bridge.addMethod("eventInviteesList", ".plugin", | |
78 in_sign='sss', out_sign='a{sa{ss}}', | |
79 method=self._eventInviteesList, | |
80 async=True), | |
81 host.bridge.addMethod("eventInvite", ".plugin", in_sign='ssssassssssss', out_sign='', | |
82 method=self._invite, | |
83 async=True) | |
84 | |
85 def _eventGet(self, service, node, id_=u'', profile_key=C.PROF_KEY_NONE): | |
86 service = jid.JID(service) if service else None | |
87 node = node if node else NS_EVENT | |
88 client = self.host.getClient(profile_key) | |
89 return self.eventGet(client, service, node, id_) | |
90 | |
91 @defer.inlineCallbacks | |
92 def eventGet(self, client, service, node, id_=NS_EVENT): | |
93 """Retrieve event data | |
94 | |
95 @param service(unicode, None): PubSub service | |
96 @param node(unicode): PubSub node of the event | |
97 @param id_(unicode): id_ with even data | |
98 @return (tuple[int, dict[unicode, unicode]): event data: | |
99 - timestamp of the event | |
100 - event metadata where key can be: | |
101 location: location of the event | |
102 image: URL of a picture to use to represent event | |
103 background-image: URL of a picture to use in background | |
104 """ | |
105 if not id_: | |
106 id_ = NS_EVENT | |
107 items, metadata = yield self._p.getItems(client, service, node, item_ids=[id_]) | |
108 try: | |
109 event_elt = next(items[0].elements(NS_EVENT, u'event')) | |
110 except IndexError: | |
111 raise exceptions.NotFound(_(u"No event with this id has been found")) | |
112 | |
113 try: | |
114 timestamp = utils.date_parse(next(event_elt.elements(NS_EVENT, "date"))) | |
115 except StopIteration: | |
116 timestamp = -1 | |
117 | |
118 data = {} | |
119 | |
120 for key in (u'name',): | |
121 try: | |
122 data[key] = event_elt[key] | |
123 except KeyError: | |
124 continue | |
125 | |
126 for elt_name in (u'description',): | |
127 try: | |
128 elt = next(event_elt.elements(NS_EVENT, elt_name)) | |
129 except StopIteration: | |
130 continue | |
131 else: | |
132 data[elt_name] = unicode(elt) | |
133 | |
134 for elt_name in (u'image', 'background-image'): | |
135 try: | |
136 image_elt = next(event_elt.elements(NS_EVENT, elt_name)) | |
137 data[elt_name] = image_elt['src'] | |
138 except StopIteration: | |
139 continue | |
140 except KeyError: | |
141 log.warning(_(u'no src found for image')) | |
142 | |
143 for uri_type in (u'invitees', u'blog'): | |
144 try: | |
145 elt = next(event_elt.elements(NS_EVENT, uri_type)) | |
146 uri = data[uri_type + u'_uri'] = elt['uri'] | |
147 uri_data = uri_parse.parseXMPPUri(uri) | |
148 if uri_data[u'type'] != u'pubsub': | |
149 raise ValueError | |
150 except StopIteration: | |
151 log.warning(_(u"no {uri_type} element found!").format(uri_type=uri_type)) | |
152 except KeyError: | |
153 log.warning(_(u"incomplete {uri_type} element").format(uri_type=uri_type)) | |
154 except ValueError: | |
155 log.warning(_(u"bad {uri_type} element").format(uri_type=uri_type)) | |
156 else: | |
157 data[uri_type + u'_service'] = uri_data[u'path'] | |
158 data[uri_type + u'_node'] = uri_data[u'node'] | |
159 | |
160 for meta_elt in event_elt.elements(NS_EVENT, 'meta'): | |
161 key = meta_elt[u'name'] | |
162 if key in data: | |
163 log.warning(u'Ignoring conflicting meta element: {xml}'.format(xml=meta_elt.toXml())) | |
164 continue | |
165 data[key] = unicode(meta_elt) | |
166 | |
167 defer.returnValue((timestamp, data)) | |
168 | |
169 def _eventCreate(self, timestamp, data, service, node, id_=u'', profile_key=C.PROF_KEY_NONE): | |
170 service = jid.JID(service) if service else None | |
171 node = node if node else NS_EVENT | |
172 client = self.host.getClient(profile_key) | |
173 return self.eventCreate(client, timestamp, data, service, node, id_ or NS_EVENT) | |
174 | |
175 @defer.inlineCallbacks | |
176 def eventCreate(self, client, timestamp, data, service, node=None, item_id=NS_EVENT): | |
177 """Create or replace an event | |
178 | |
179 @param service(jid.JID, None): PubSub service | |
180 @param node(unicode, None): PubSub node of the event | |
181 None will create instant node. | |
182 @param item_id(unicode): ID of the item to create. | |
183 @param timestamp(timestamp, None) | |
184 @param data(dict[unicode, unicode]): data to update | |
185 dict will be cleared, do a copy if data are still needed | |
186 key can be: | |
187 - name: name of the event | |
188 - description: details | |
189 - image: main picture of the event | |
190 - background-image: image to use as background | |
191 @return (unicode): created node | |
192 """ | |
193 if not item_id: | |
194 raise ValueError(_(u"item_id must be set")) | |
195 if not service: | |
196 service = client.jid.userhostJID() | |
197 event_elt = domish.Element((NS_EVENT, 'event')) | |
198 if timestamp is not None and timestamp != -1: | |
199 formatted_date = utils.xmpp_date(timestamp) | |
200 event_elt.addElement((NS_EVENT, 'date'), content=formatted_date) | |
201 for key in (u'name',): | |
202 if key in data: | |
203 event_elt[key] = data.pop(key) | |
204 for key in (u'description',): | |
205 if key in data: | |
206 event_elt.addElement((NS_EVENT, key), content=data.pop(key)) | |
207 for key in (u'image', u'background-image'): | |
208 if key in data: | |
209 elt = event_elt.addElement((NS_EVENT, key)) | |
210 elt['src'] = data.pop(key) | |
211 | |
212 # we first create the invitees and blog nodes (if not specified in data) | |
213 for uri_type in (u'invitees', u'blog'): | |
214 key = uri_type + u'_uri' | |
215 for to_delete in (u'service', u'node'): | |
216 k = uri_type + u'_' + to_delete | |
217 if k in data: | |
218 del data[k] | |
219 if key not in data: | |
220 # FIXME: affiliate invitees | |
221 uri_node = yield self._p.createNode(client, service) | |
222 yield self._p.setConfiguration(client, service, uri_node, {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST}) | |
223 uri_service = service | |
224 else: | |
225 uri = data.pop(key) | |
226 uri_data = uri_parse.parseXMPPUri(uri) | |
227 if uri_data[u'type'] != u'pubsub': | |
228 raise ValueError(_(u'The given URI is not valid: {uri}').format(uri=uri)) | |
229 uri_service = jid.JID(uri_data[u'path']) | |
230 uri_node = uri_data[u'node'] | |
231 | |
232 elt = event_elt.addElement((NS_EVENT, uri_type)) | |
233 elt['uri'] = uri_parse.buildXMPPUri('pubsub', path=uri_service.full(), node=uri_node) | |
234 | |
235 # remaining data are put in <meta> elements | |
236 for key in data.keys(): | |
237 elt = event_elt.addElement((NS_EVENT, 'meta'), content = data.pop(key)) | |
238 elt['name'] = key | |
239 | |
240 item_elt = pubsub.Item(id=item_id, payload=event_elt) | |
241 try: | |
242 # TODO: check auto-create, no need to create node first if available | |
243 node = yield self._p.createNode(client, service, nodeIdentifier=node) | |
244 except error.StanzaError as e: | |
245 if e.condition == u'conflict': | |
246 log.debug(_(u"requested node already exists")) | |
247 | |
248 yield self._p.publish(client, service, node, items=[item_elt]) | |
249 | |
250 defer.returnValue(node) | |
251 | |
252 def _eventModify(self, service, node, id_, timestamp_update, data_update, profile_key=C.PROF_KEY_NONE): | |
253 service = jid.JID(service) if service else None | |
254 node = node if node else NS_EVENT | |
255 client = self.host.getClient(profile_key) | |
256 return self.eventModify(client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update) | |
257 | |
258 @defer.inlineCallbacks | |
259 def eventModify(self, client, service, node, id_=NS_EVENT, timestamp_update=None, data_update=None): | |
260 """Update an event | |
261 | |
262 Similar as create instead that it update existing item instead of | |
263 creating or replacing it. Params are the same as for [eventCreate]. | |
264 """ | |
265 event_timestamp, event_metadata = yield self.eventGet(client, service, node, id_) | |
266 new_timestamp = event_timestamp if timestamp_update is None else timestamp_update | |
267 new_data = event_metadata | |
268 if data_update: | |
269 for k, v in data_update.iteritems(): | |
270 new_data[k] = v | |
271 yield self.eventCreate(client, new_timestamp, new_data, service, node, id_) | |
272 | |
273 def _eventInviteeGet(self, service, node, profile_key): | |
274 service = jid.JID(service) if service else None | |
275 node = node if node else NS_EVENT | |
276 client = self.host.getClient(profile_key) | |
277 return self.eventInviteeGet(client, service, node) | |
278 | |
279 @defer.inlineCallbacks | |
280 def eventInviteeGet(self, client, service, node): | |
281 """Retrieve attendance from event node | |
282 | |
283 @param service(unicode, None): PubSub service | |
284 @param node(unicode): PubSub node of the event | |
285 @return (dict): a dict with current attendance status, | |
286 an empty dict is returned if nothing has been answered yed | |
287 """ | |
288 items, metadata = yield self._p.getItems(client, service, node, item_ids=[client.jid.userhost()]) | |
289 try: | |
290 event_elt = next(items[0].elements(NS_EVENT, u'invitee')) | |
291 except IndexError: | |
292 # no item found, event data are not set yet | |
293 defer.returnValue({}) | |
294 data = {} | |
295 for key in (u'attend', u'guests'): | |
296 try: | |
297 data[key] = event_elt[key] | |
298 except KeyError: | |
299 continue | |
300 defer.returnValue(data) | |
301 | |
302 def _eventInviteeSet(self, service, node, event_data, profile_key): | |
303 service = jid.JID(service) if service else None | |
304 node = node if node else NS_EVENT | |
305 client = self.host.getClient(profile_key) | |
306 return self.eventInviteeSet(client, service, node, event_data) | |
307 | |
308 def eventInviteeSet(self, client, service, node, data): | |
309 """Set or update attendance data in event node | |
310 | |
311 @param service(unicode, None): PubSub service | |
312 @param node(unicode): PubSub node of the event | |
313 @param data(dict[unicode, unicode]): data to update | |
314 key can be: | |
315 attend: one of "yes", "no", "maybe" | |
316 guests: an int | |
317 """ | |
318 event_elt = domish.Element((NS_EVENT, 'invitee')) | |
319 for key in (u'attend', u'guests'): | |
320 try: | |
321 event_elt[key] = data.pop(key) | |
322 except KeyError: | |
323 pass | |
324 item_elt = pubsub.Item(id=client.jid.userhost(), payload=event_elt) | |
325 return self._p.publish(client, service, node, items=[item_elt]) | |
326 | |
327 def _eventInviteesList(self, service, node, profile_key): | |
328 service = jid.JID(service) if service else None | |
329 node = node if node else NS_EVENT | |
330 client = self.host.getClient(profile_key) | |
331 return self.eventInviteesList(client, service, node) | |
332 | |
333 @defer.inlineCallbacks | |
334 def eventInviteesList(self, client, service, node): | |
335 """Retrieve attendance from event node | |
336 | |
337 @param service(unicode, None): PubSub service | |
338 @param node(unicode): PubSub node of the event | |
339 @return (dict): a dict with current attendance status, | |
340 an empty dict is returned if nothing has been answered yed | |
341 """ | |
342 items, metadata = yield self._p.getItems(client, service, node) | |
343 invitees = {} | |
344 for item in items: | |
345 try: | |
346 event_elt = next(item.elements(NS_EVENT, u'invitee')) | |
347 except IndexError: | |
348 # no item found, event data are not set yet | |
349 log.warning(_(u"no data found for {item_id} (service: {service}, node: {node})".format( | |
350 item_id=item['id'], | |
351 service=service, | |
352 node=node | |
353 ))) | |
354 data = {} | |
355 for key in (u'attend', u'guests'): | |
356 try: | |
357 data[key] = event_elt[key] | |
358 except KeyError: | |
359 continue | |
360 invitees[item['id']] = data | |
361 defer.returnValue(invitees) | |
362 | |
363 def _invite(self, service, node, id_=NS_EVENT, email=u'', emails_extra=None, name=u'', host_name=u'', language=u'', url_template=u'', | |
364 message_subject=u'', message_body=u'', profile_key=C.PROF_KEY_NONE): | |
365 client = self.host.getClient(profile_key) | |
366 kwargs = {u'profile': client.profile, | |
367 u'emails_extra': [unicode(e) for e in emails_extra] | |
368 } | |
369 for key in ("email", "name", "host_name", "language", "url_template", "message_subject", "message_body"): | |
370 value = locals()[key] | |
371 kwargs[key] = unicode(value) | |
372 return self.invite(client, | |
373 jid.JID(service) if service else None, | |
374 node, | |
375 id_ or NS_EVENT, | |
376 **kwargs) | |
377 | |
378 @defer.inlineCallbacks | |
379 def invite(self, client, service, node, id_=NS_EVENT, **kwargs): | |
380 """High level method to create an email invitation to an event | |
381 | |
382 @param service(unicode, None): PubSub service | |
383 @param node(unicode): PubSub node of the event | |
384 @param id_(unicode): id_ with even data | |
385 """ | |
386 if self._i is None: | |
387 raise exceptions.FeatureNotFound(_(u'"Invitations" plugin is needed for this feature')) | |
388 if self._b is None: | |
389 raise exceptions.FeatureNotFound(_(u'"XEP-0277" (blog) plugin is needed for this feature')) | |
390 event_service = (service or client.jid.userhostJID()) | |
391 event_uri = uri_parse.buildXMPPUri('pubsub', | |
392 path=event_service.full(), | |
393 node=node, | |
394 item=id_) | |
395 kwargs['extra'] = {u'event_uri': event_uri} | |
396 invitation_data = yield self._i.create(**kwargs) | |
397 invitee_jid = invitation_data[u'jid'] | |
398 log.debug(_(u'invitation created')) | |
399 yield self._p.setNodeAffiliations(client, event_service, node, {invitee_jid: u'member'}) | |
400 log.debug(_(u'affiliation set on event node')) | |
401 dummy, event_data = yield self.eventGet(client, service, node, id_) | |
402 log.debug(_(u'got event data')) | |
403 invitees_service = jid.JID(event_data['invitees_service']) | |
404 invitees_node = event_data['invitees_node'] | |
405 blog_service = jid.JID(event_data['blog_service']) | |
406 blog_node = event_data['blog_node'] | |
407 yield self._p.setNodeAffiliations(client, invitees_service, invitees_node, {invitee_jid: u'publisher'}) | |
408 log.debug(_(u'affiliation set on invitee node')) | |
409 yield self._p.setNodeAffiliations(client, blog_service, blog_node, {invitee_jid: u'member'}) | |
410 # FIXME: what follow is crazy, we have no good way to handle comments affiliations for blog | |
411 blog_items, dummy = yield self._b.mbGet(client, blog_service, blog_node, None) | |
412 | |
413 for item in blog_items: | |
414 comments_service = jid.JID(item['comments_service']) | |
415 comments_node = item['comments_node'] | |
416 yield self._p.setNodeAffiliations(client, comments_service, comments_node, {invitee_jid: u'publisher'}) | |
417 log.debug(_(u'affiliation set on blog and comments nodes')) | |
418 | |
419 |