comparison sat/plugins/plugin_exp_events.py @ 2616:1cc88adb5142

plugin events: invitations improvments + personal list - added invitation by <message> mechanism - added a personal list in PEP which keep events created by user, or in which she has been invited - new bridge methods: eventsList, and eventInviteByEmail. eventInvite is now used to invite a entity by jid (not email like before) - on invite, an invitation message is send to invitee's jid - when an invitation message is received, event is automatically linked in personal list - when creating a new event, event is automatically linked in personal list
author Goffi <goffi@goffi.org>
date Thu, 21 Jun 2018 01:21:44 +0200
parents 3e4e78de9cca
children 56f94936df1e
comparison
equal deleted inserted replaced
2615:b4ecbcc2fd08 2616:1cc88adb5142
21 from sat.core import exceptions 21 from sat.core import exceptions
22 from sat.core.constants import Const as C 22 from sat.core.constants import Const as C
23 from sat.core.log import getLogger 23 from sat.core.log import getLogger
24 log = getLogger(__name__) 24 log = getLogger(__name__)
25 from sat.tools import utils 25 from sat.tools import utils
26 from sat.tools.common import uri as uri_parse 26 from sat.tools.common import uri as xmpp_uri
27 from sat.tools.common import date_utils 27 from sat.tools.common import date_utils
28 from twisted.internet import defer 28 from twisted.internet import defer
29 from twisted.words.protocols.jabber import jid, error 29 from twisted.words.protocols.jabber import jid, error
30 from twisted.words.xish import domish 30 from twisted.words.xish import domish
31 from wokkel import disco, iwokkel
32 from zope.interface import implements
33 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
34
31 from wokkel import pubsub 35 from wokkel import pubsub
36 import shortuuid
32 37
33 38
34 PLUGIN_INFO = { 39 PLUGIN_INFO = {
35 C.PI_NAME: "Event plugin", 40 C.PI_NAME: "Event plugin",
36 C.PI_IMPORT_NAME: "EVENTS", 41 C.PI_IMPORT_NAME: "EVENTS",
37 C.PI_TYPE: "EXP", 42 C.PI_TYPE: "EXP",
38 C.PI_PROTOCOLS: [], 43 C.PI_PROTOCOLS: [],
39 C.PI_DEPENDENCIES: ["XEP-0060"], 44 C.PI_DEPENDENCIES: ["XEP-0060"],
40 C.PI_RECOMMENDATIONS: ["INVITATIONS", "XEP-0277"], 45 C.PI_RECOMMENDATIONS: ["INVITATIONS", "XEP-0277"],
41 C.PI_MAIN: "Events", 46 C.PI_MAIN: "Events",
42 C.PI_HANDLER: "no", 47 C.PI_HANDLER: "yes",
43 C.PI_DESCRIPTION: _("""Experimental implementation of XMPP events management""") 48 C.PI_DESCRIPTION: _("""Experimental implementation of XMPP events management""")
44 } 49 }
45 50
46 NS_EVENT = 'org.salut-a-toi.event:0' 51 NS_EVENT = 'org.salut-a-toi.event:0'
52 NS_EVENT_LIST = NS_EVENT + '#list'
53 NS_EVENT_INVIT = NS_EVENT + '#invitation'
54 INVITATION = '/message[@type="chat"]/invitation[@xmlns="{ns_invit}"]'.format(
55 ns_invit=NS_EVENT_INVIT)
47 56
48 57
49 class Events(object): 58 class Events(object):
50 """Q&D module to handle event attendance answer, experimentation only""" 59 """Q&D module to handle event attendance answer, experimentation only"""
51 60
65 async=True) 74 async=True)
66 host.bridge.addMethod("eventModify", ".plugin", 75 host.bridge.addMethod("eventModify", ".plugin",
67 in_sign='sssia{ss}s', out_sign='', 76 in_sign='sssia{ss}s', out_sign='',
68 method=self._eventModify, 77 method=self._eventModify,
69 async=True) 78 async=True)
79 host.bridge.addMethod("eventsList", ".plugin",
80 in_sign='sss', out_sign='aa{ss}',
81 method=self._eventsList,
82 async=True)
70 host.bridge.addMethod("eventInviteeGet", ".plugin", 83 host.bridge.addMethod("eventInviteeGet", ".plugin",
71 in_sign='sss', out_sign='a{ss}', 84 in_sign='sss', out_sign='a{ss}',
72 method=self._eventInviteeGet, 85 method=self._eventInviteeGet,
73 async=True) 86 async=True)
74 host.bridge.addMethod("eventInviteeSet", ".plugin", 87 host.bridge.addMethod("eventInviteeSet", ".plugin",
77 async=True) 90 async=True)
78 host.bridge.addMethod("eventInviteesList", ".plugin", 91 host.bridge.addMethod("eventInviteesList", ".plugin",
79 in_sign='sss', out_sign='a{sa{ss}}', 92 in_sign='sss', out_sign='a{sa{ss}}',
80 method=self._eventInviteesList, 93 method=self._eventInviteesList,
81 async=True), 94 async=True),
82 host.bridge.addMethod("eventInvite", ".plugin", in_sign='ssssassssssss', out_sign='', 95 host.bridge.addMethod("eventInvite", ".plugin", in_sign='sssss', out_sign='',
83 method=self._invite, 96 method=self._invite,
84 async=True) 97 async=True)
85 98 host.bridge.addMethod("eventInviteByEmail", ".plugin", in_sign='ssssassssssss', out_sign='',
86 def _eventGet(self, service, node, id_=u'', profile_key=C.PROF_KEY_NONE): 99 method=self._inviteByEmail,
87 service = jid.JID(service) if service else None 100 async=True)
88 node = node if node else NS_EVENT 101
89 client = self.host.getClient(profile_key) 102 def getHandler(self, client):
90 return self.eventGet(client, service, node, id_) 103 return EventsHandler(self)
91 104
92 @defer.inlineCallbacks 105 def _parseEventElt(self, event_elt):
93 def eventGet(self, client, service, node, id_=NS_EVENT): 106 """Helper method to parse event element
94 """Retrieve event data 107
95 108 @param (domish.Element): event_elt
96 @param service(unicode, None): PubSub service 109 @return (tupple[int, dict[unicode, unicode]): timestamp, event_data
97 @param node(unicode): PubSub node of the event 110 """
98 @param id_(unicode): id_ with even data
99 @return (tuple[int, dict[unicode, unicode]): event data:
100 - timestamp of the event
101 - event metadata where key can be:
102 location: location of the event
103 image: URL of a picture to use to represent event
104 background-image: URL of a picture to use in background
105 """
106 if not id_:
107 id_ = NS_EVENT
108 items, metadata = yield self._p.getItems(client, service, node, item_ids=[id_])
109 try:
110 event_elt = next(items[0].elements(NS_EVENT, u'event'))
111 except IndexError:
112 raise exceptions.NotFound(_(u"No event with this id has been found"))
113
114 try: 111 try:
115 timestamp = date_utils.date_parse(next(event_elt.elements(NS_EVENT, "date"))) 112 timestamp = date_utils.date_parse(next(event_elt.elements(NS_EVENT, "date")))
116 except StopIteration: 113 except StopIteration:
117 timestamp = -1 114 timestamp = -1
118 115
143 140
144 for uri_type in (u'invitees', u'blog'): 141 for uri_type in (u'invitees', u'blog'):
145 try: 142 try:
146 elt = next(event_elt.elements(NS_EVENT, uri_type)) 143 elt = next(event_elt.elements(NS_EVENT, uri_type))
147 uri = data[uri_type + u'_uri'] = elt['uri'] 144 uri = data[uri_type + u'_uri'] = elt['uri']
148 uri_data = uri_parse.parseXMPPUri(uri) 145 uri_data = xmpp_uri.parseXMPPUri(uri)
149 if uri_data[u'type'] != u'pubsub': 146 if uri_data[u'type'] != u'pubsub':
150 raise ValueError 147 raise ValueError
151 except StopIteration: 148 except StopIteration:
152 log.warning(_(u"no {uri_type} element found!").format(uri_type=uri_type)) 149 log.warning(_(u"no {uri_type} element found!").format(uri_type=uri_type))
153 except KeyError: 150 except KeyError:
162 key = meta_elt[u'name'] 159 key = meta_elt[u'name']
163 if key in data: 160 if key in data:
164 log.warning(u'Ignoring conflicting meta element: {xml}'.format(xml=meta_elt.toXml())) 161 log.warning(u'Ignoring conflicting meta element: {xml}'.format(xml=meta_elt.toXml()))
165 continue 162 continue
166 data[key] = unicode(meta_elt) 163 data[key] = unicode(meta_elt)
167 164 if event_elt.link:
168 defer.returnValue((timestamp, data)) 165 link_elt = event_elt.link
169 166 data['service'] = link_elt['service']
170 def _eventCreate(self, timestamp, data, service, node, id_=u'', profile_key=C.PROF_KEY_NONE): 167 data['node'] = link_elt['node']
168 data['item'] = link_elt['item']
169 if event_elt.getAttribute('creator') == 'true':
170 data['creator'] = True
171 return timestamp, data
172
173 @defer.inlineCallbacks
174 def getEventElement(self, client, service, node, id_):
175 """Retrieve event element
176
177 @param service(jid.JID): pubsub service
178 @param node(unicode): pubsub node
179 @param id_(unicode, None): event id
180 @return (domish.Element): event element
181 @raise exceptions.NotFound: no event element found
182 """
183 if not id_:
184 id_ = NS_EVENT
185 items, metadata = yield self._p.getItems(client, service, node, item_ids=[id_])
186 try:
187 event_elt = next(items[0].elements(NS_EVENT, u'event'))
188 except IndexError:
189 raise exceptions.NotFound(_(u"No event with this id has been found"))
190 defer.returnValue(event_elt)
191
192 @defer.inlineCallbacks
193 def register(self, client, service, node, event_id, event_elt, creator=False):
194 """register evenement in personal events list
195
196 @param service(jid.JID): pubsub service of the event
197 @param node(unicode): event node
198 @param event_id(unicode): event id
199 @param event_elt(domish.Element): event element
200 note that this element will be modified in place
201 @param creator(bool): True if client's profile is the creator of the node
202 """
203 # we save a link to the event in our local list
204 try:
205 # TODO: check auto-create, no need to create node first if available
206 options = {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST}
207 yield self._p.createNode(client,
208 client.jid.userhostJID(),
209 nodeIdentifier=NS_EVENT_LIST,
210 options=options)
211 except error.StanzaError as e:
212 if e.condition == u'conflict':
213 log.debug(_(u"requested node already exists"))
214 link_elt = event_elt.addElement((NS_EVENT_LIST, 'link'))
215 link_elt["service"] = service.full()
216 link_elt["node"] = node
217 link_elt["item"] = event_id
218 item_id = xmpp_uri.buildXMPPUri(u'pubsub',
219 path=service.full(),
220 node=node,
221 item=event_id)
222 if creator:
223 event_elt['creator'] = 'true'
224 item_elt = pubsub.Item(id=item_id, payload=event_elt)
225 yield self._p.publish(client,
226 client.jid.userhostJID(),
227 NS_EVENT_LIST,
228 items=[item_elt])
229
230 def _eventGet(self, service, node, id_=u'', profile_key=C.PROF_KEY_NONE):
171 service = jid.JID(service) if service else None 231 service = jid.JID(service) if service else None
172 node = node if node else NS_EVENT 232 node = node if node else NS_EVENT
173 client = self.host.getClient(profile_key) 233 client = self.host.getClient(profile_key)
234 return self.eventGet(client, service, node, id_)
235
236 @defer.inlineCallbacks
237 def eventGet(self, client, service, node, id_=NS_EVENT):
238 """Retrieve event data
239
240 @param service(unicode, None): PubSub service
241 @param node(unicode): PubSub node of the event
242 @param id_(unicode): id_ with even data
243 @return (tuple[int, dict[unicode, unicode]): event data:
244 - timestamp of the event
245 - event metadata where key can be:
246 location: location of the event
247 image: URL of a picture to use to represent event
248 background-image: URL of a picture to use in background
249 """
250 event_elt = yield self.getEventElement(client, service, node, id_)
251
252 defer.returnValue(self._parseEventElt(event_elt))
253
254 def _eventCreate(self, timestamp, data, service, node, id_=u'', profile_key=C.PROF_KEY_NONE):
255 service = jid.JID(service) if service else None
256 node = node or None
257 client = self.host.getClient(profile_key)
258 data[u'register'] = C.bool(data.get(u'register', C.BOOL_FALSE))
174 return self.eventCreate(client, timestamp, data, service, node, id_ or NS_EVENT) 259 return self.eventCreate(client, timestamp, data, service, node, id_ or NS_EVENT)
175 260
176 @defer.inlineCallbacks 261 @defer.inlineCallbacks
177 def eventCreate(self, client, timestamp, data, service, node=None, item_id=NS_EVENT): 262 def eventCreate(self, client, timestamp, data, service, node=None, event_id=NS_EVENT):
178 """Create or replace an event 263 """Create or replace an event
179 264
180 @param service(jid.JID, None): PubSub service 265 @param service(jid.JID, None): PubSub service
181 @param node(unicode, None): PubSub node of the event 266 @param node(unicode, None): PubSub node of the event
182 None will create instant node. 267 None will create instant node.
183 @param item_id(unicode): ID of the item to create. 268 @param event_id(unicode): ID of the item to create.
184 @param timestamp(timestamp, None) 269 @param timestamp(timestamp, None)
185 @param data(dict[unicode, unicode]): data to update 270 @param data(dict[unicode, unicode]): data to update
186 dict will be cleared, do a copy if data are still needed 271 dict will be cleared, do a copy if data are still needed
187 key can be: 272 key can be:
188 - name: name of the event 273 - name: name of the event
189 - description: details 274 - description: details
190 - image: main picture of the event 275 - image: main picture of the event
191 - background-image: image to use as background 276 - background-image: image to use as background
277 - register: bool, True if we want to register the event in our local list
192 @return (unicode): created node 278 @return (unicode): created node
193 """ 279 """
194 if not item_id: 280 if not event_id:
195 raise ValueError(_(u"item_id must be set")) 281 raise ValueError(_(u"event_id must be set"))
196 if not service: 282 if not service:
197 service = client.jid.userhostJID() 283 service = client.jid.userhostJID()
284 if not node:
285 node = NS_EVENT + u'__' + shortuuid.uuid()
198 event_elt = domish.Element((NS_EVENT, 'event')) 286 event_elt = domish.Element((NS_EVENT, 'event'))
199 if timestamp is not None and timestamp != -1: 287 if timestamp is not None and timestamp != -1:
200 formatted_date = utils.xmpp_date(timestamp) 288 formatted_date = utils.xmpp_date(timestamp)
201 event_elt.addElement((NS_EVENT, 'date'), content=formatted_date) 289 event_elt.addElement((NS_EVENT, 'date'), content=formatted_date)
290 register = data.pop('register', False)
202 for key in (u'name',): 291 for key in (u'name',):
203 if key in data: 292 if key in data:
204 event_elt[key] = data.pop(key) 293 event_elt[key] = data.pop(key)
205 for key in (u'description',): 294 for key in (u'description',):
206 if key in data: 295 if key in data:
222 uri_node = yield self._p.createNode(client, service) 311 uri_node = yield self._p.createNode(client, service)
223 yield self._p.setConfiguration(client, service, uri_node, {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST}) 312 yield self._p.setConfiguration(client, service, uri_node, {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST})
224 uri_service = service 313 uri_service = service
225 else: 314 else:
226 uri = data.pop(key) 315 uri = data.pop(key)
227 uri_data = uri_parse.parseXMPPUri(uri) 316 uri_data = xmpp_uri.parseXMPPUri(uri)
228 if uri_data[u'type'] != u'pubsub': 317 if uri_data[u'type'] != u'pubsub':
229 raise ValueError(_(u'The given URI is not valid: {uri}').format(uri=uri)) 318 raise ValueError(_(u'The given URI is not valid: {uri}').format(uri=uri))
230 uri_service = jid.JID(uri_data[u'path']) 319 uri_service = jid.JID(uri_data[u'path'])
231 uri_node = uri_data[u'node'] 320 uri_node = uri_data[u'node']
232 321
233 elt = event_elt.addElement((NS_EVENT, uri_type)) 322 elt = event_elt.addElement((NS_EVENT, uri_type))
234 elt['uri'] = uri_parse.buildXMPPUri('pubsub', path=uri_service.full(), node=uri_node) 323 elt['uri'] = xmpp_uri.buildXMPPUri('pubsub', path=uri_service.full(), node=uri_node)
235 324
236 # remaining data are put in <meta> elements 325 # remaining data are put in <meta> elements
237 for key in data.keys(): 326 for key in data.keys():
238 elt = event_elt.addElement((NS_EVENT, 'meta'), content = data.pop(key)) 327 elt = event_elt.addElement((NS_EVENT, 'meta'), content = data.pop(key))
239 elt['name'] = key 328 elt['name'] = key
240 329
241 item_elt = pubsub.Item(id=item_id, payload=event_elt) 330 item_elt = pubsub.Item(id=event_id, payload=event_elt)
242 try: 331 try:
243 # TODO: check auto-create, no need to create node first if available 332 # TODO: check auto-create, no need to create node first if available
244 node = yield self._p.createNode(client, service, nodeIdentifier=node) 333 node = yield self._p.createNode(client, service, nodeIdentifier=node)
245 except error.StanzaError as e: 334 except error.StanzaError as e:
246 if e.condition == u'conflict': 335 if e.condition == u'conflict':
247 log.debug(_(u"requested node already exists")) 336 log.debug(_(u"requested node already exists"))
248 337
249 yield self._p.publish(client, service, node, items=[item_elt]) 338 yield self._p.publish(client, service, node, items=[item_elt])
250 339
340 if register:
341 yield self.register(client, service, node, event_id, event_elt, creator=True)
251 defer.returnValue(node) 342 defer.returnValue(node)
252 343
253 def _eventModify(self, service, node, id_, timestamp_update, data_update, profile_key=C.PROF_KEY_NONE): 344 def _eventModify(self, service, node, id_, timestamp_update, data_update, profile_key=C.PROF_KEY_NONE):
254 service = jid.JID(service) if service else None 345 service = jid.JID(service) if service else None
255 node = node if node else NS_EVENT 346 node = node if node else NS_EVENT
268 new_data = event_metadata 359 new_data = event_metadata
269 if data_update: 360 if data_update:
270 for k, v in data_update.iteritems(): 361 for k, v in data_update.iteritems():
271 new_data[k] = v 362 new_data[k] = v
272 yield self.eventCreate(client, new_timestamp, new_data, service, node, id_) 363 yield self.eventCreate(client, new_timestamp, new_data, service, node, id_)
364
365 def _eventsListSerialise(self, events):
366 for timestamp, data in events:
367 data['date'] = unicode(timestamp)
368 data['creator'] = C.boolConst(data.get('creator', False))
369 return [e[1] for e in events]
370
371 def _eventsList(self, service, node, profile):
372 service = jid.JID(service) if service else None
373 node = node or None
374 client = self.host.getClient(profile)
375 d = self.eventsList(client, service, node)
376 d.addCallback(self._eventsListSerialise)
377 return d
378
379 @defer.inlineCallbacks
380 def eventsList(self, client, service, node):
381 """Retrieve list of registered events
382
383 @return list(tuple(int, dict)): list of events (timestamp + metadata)
384 """
385 if not node:
386 node = NS_EVENT_LIST
387 items = yield self._p.getItems(client, service, node)
388 events = []
389 for item in items[0]:
390 try:
391 event_elt = next(item.elements(NS_EVENT, u'event'))
392 except IndexError:
393 log.error(_(u"No event found in item {item_id}").format(
394 item_id = item['id']))
395 timestamp, data = self._parseEventElt(event_elt)
396 events.append((timestamp, data))
397 defer.returnValue(events)
273 398
274 def _eventInviteeGet(self, service, node, profile_key): 399 def _eventInviteeGet(self, service, node, profile_key):
275 service = jid.JID(service) if service else None 400 service = jid.JID(service) if service else None
276 node = node if node else NS_EVENT 401 node = node if node else NS_EVENT
277 client = self.host.getClient(profile_key) 402 client = self.host.getClient(profile_key)
343 items, metadata = yield self._p.getItems(client, service, node) 468 items, metadata = yield self._p.getItems(client, service, node)
344 invitees = {} 469 invitees = {}
345 for item in items: 470 for item in items:
346 try: 471 try:
347 event_elt = next(item.elements(NS_EVENT, u'invitee')) 472 event_elt = next(item.elements(NS_EVENT, u'invitee'))
348 except IndexError: 473 except StopIteration:
349 # no item found, event data are not set yet 474 # no item found, event data are not set yet
350 log.warning(_(u"no data found for {item_id} (service: {service}, node: {node})".format( 475 log.warning(_(u"no data found for {item_id} (service: {service}, node: {node})".format(
351 item_id=item['id'], 476 item_id=item['id'],
352 service=service, 477 service=service,
353 node=node 478 node=node
354 ))) 479 )))
355 data = {} 480 else:
356 for key in (u'attend', u'guests'): 481 data = {}
357 try: 482 for key in (u'attend', u'guests'):
358 data[key] = event_elt[key] 483 try:
359 except KeyError: 484 data[key] = event_elt[key]
360 continue 485 except KeyError:
361 invitees[item['id']] = data 486 continue
487 invitees[item['id']] = data
362 defer.returnValue(invitees) 488 defer.returnValue(invitees)
363 489
364 def _invite(self, service, node, id_=NS_EVENT, email=u'', emails_extra=None, name=u'', host_name=u'', language=u'', url_template=u'', 490 def sendMessageInvitation(self, client, invitee_jid, service, node, item_id):
491 """Send an invitation in a <message> stanza
492
493 @param invitee_jid(jid.JID): entitee to send invitation to
494 @param service(jid.JID): pubsub service of the event
495 @param node(unicode): node of the event
496 @param item_id(unicode): id of the event
497 """
498 mess_data = {
499 'from': client.jid,
500 'to': invitee_jid,
501 'uid': '',
502 'message': {},
503 'type': C.MESS_TYPE_CHAT,
504 'subject': {},
505 'extra': {},
506 }
507 client.generateMessageXML(mess_data)
508 event_elt = mess_data['xml'].addElement('invitation', NS_EVENT_INVIT)
509 event_elt['service'] = service.full()
510 event_elt['node'] = node
511 event_elt['item'] = item_id
512 client.send(mess_data['xml'])
513
514 def _invite(self, invitee_jid, service, node, item_id, profile):
515 client = self.host.getClient(profile)
516 service = jid.JID(service) if service else None
517 node = node or None
518 item_id = item_id or None
519 invitee_jid = jid.JID(invitee_jid)
520 return self.invite(client, invitee_jid, service, node, item_id)
521
522 @defer.inlineCallbacks
523 def invite(self, client, invitee_jid, service, node, item_id=NS_EVENT):
524 """Invite an entity to the event
525
526 This will set permission to let the entity access everything needed
527 @pararm invitee_jid(jid.JID): entity to invite
528 @param service(jid.JID, None): pubsub service
529 None to use client's PEP
530 @param node(unicode): event node
531 @param item_id(unicode): event id
532 """
533 if self._b is None:
534 raise exceptions.FeatureNotFound(_(u'"XEP-0277" (blog) plugin is needed for this feature'))
535 if item_id is None:
536 item_id = NS_EVENT
537
538 # first we authorize our invitee to see the nodes of interest
539 yield self._p.setNodeAffiliations(client, service, node, {invitee_jid: u'member'})
540 log.debug(_(u'affiliation set on event node'))
541 dummy, event_data = yield self.eventGet(client, service, node, item_id)
542 log.debug(_(u'got event data'))
543 invitees_service = jid.JID(event_data['invitees_service'])
544 invitees_node = event_data['invitees_node']
545 blog_service = jid.JID(event_data['blog_service'])
546 blog_node = event_data['blog_node']
547 yield self._p.setNodeAffiliations(client, invitees_service, invitees_node, {invitee_jid: u'publisher'})
548 log.debug(_(u'affiliation set on invitee node'))
549 yield self._p.setNodeAffiliations(client, blog_service, blog_node, {invitee_jid: u'member'})
550 blog_items, dummy = yield self._b.mbGet(client, blog_service, blog_node, None)
551
552 for item in blog_items:
553 try:
554 comments_service = jid.JID(item['comments_service'])
555 comments_node = item['comments_node']
556 except KeyError:
557 log.debug(u"no comment service set for item {item_id}".format(item_id=item['id']))
558 else:
559 yield self._p.setNodeAffiliations(client, comments_service, comments_node, {invitee_jid: u'publisher'})
560 log.debug(_(u'affiliation set on blog and comments nodes'))
561
562 # now we send the invitation
563 self.sendMessageInvitation(client, invitee_jid, service, node, item_id)
564
565 def _inviteByEmail(self, service, node, id_=NS_EVENT, email=u'', emails_extra=None, name=u'', host_name=u'', language=u'', url_template=u'',
365 message_subject=u'', message_body=u'', profile_key=C.PROF_KEY_NONE): 566 message_subject=u'', message_body=u'', profile_key=C.PROF_KEY_NONE):
366 client = self.host.getClient(profile_key) 567 client = self.host.getClient(profile_key)
367 kwargs = {u'profile': client.profile, 568 kwargs = {u'profile': client.profile,
368 u'emails_extra': [unicode(e) for e in emails_extra] 569 u'emails_extra': [unicode(e) for e in emails_extra]
369 } 570 }
370 for key in ("email", "name", "host_name", "language", "url_template", "message_subject", "message_body"): 571 for key in ("email", "name", "host_name", "language", "url_template", "message_subject", "message_body"):
371 value = locals()[key] 572 value = locals()[key]
372 kwargs[key] = unicode(value) 573 kwargs[key] = unicode(value)
373 return self.invite(client, 574 return self.inviteByEmail(client,
374 jid.JID(service) if service else None, 575 jid.JID(service) if service else None,
375 node, 576 node,
376 id_ or NS_EVENT, 577 id_ or NS_EVENT,
377 **kwargs) 578 **kwargs)
378 579
379 @defer.inlineCallbacks 580 @defer.inlineCallbacks
380 def invite(self, client, service, node, id_=NS_EVENT, **kwargs): 581 def inviteByEmail(self, client, service, node, id_=NS_EVENT, **kwargs):
381 """High level method to create an email invitation to an event 582 """High level method to create an email invitation to an event
382 583
383 @param service(unicode, None): PubSub service 584 @param service(unicode, None): PubSub service
384 @param node(unicode): PubSub node of the event 585 @param node(unicode): PubSub node of the event
385 @param id_(unicode): id_ with even data 586 @param id_(unicode): id_ with even data
386 """ 587 """
387 if self._i is None: 588 if self._i is None:
388 raise exceptions.FeatureNotFound(_(u'"Invitations" plugin is needed for this feature')) 589 raise exceptions.FeatureNotFound(_(u'"Invitations" plugin is needed for this feature'))
389 if self._b is None: 590 if self._b is None:
390 raise exceptions.FeatureNotFound(_(u'"XEP-0277" (blog) plugin is needed for this feature')) 591 raise exceptions.FeatureNotFound(_(u'"XEP-0277" (blog) plugin is needed for this feature'))
391 event_service = (service or client.jid.userhostJID()) 592 service = service or client.jid.userhostJID()
392 event_uri = uri_parse.buildXMPPUri('pubsub', 593 event_uri = xmpp_uri.buildXMPPUri('pubsub',
393 path=event_service.full(), 594 path=service.full(),
394 node=node, 595 node=node,
395 item=id_) 596 item=id_)
396 kwargs['extra'] = {u'event_uri': event_uri} 597 kwargs['extra'] = {u'event_uri': event_uri}
397 invitation_data = yield self._i.create(**kwargs) 598 invitation_data = yield self._i.create(**kwargs)
398 invitee_jid = invitation_data[u'jid'] 599 invitee_jid = invitation_data[u'jid']
399 log.debug(_(u'invitation created')) 600 log.debug(_(u'invitation created'))
400 yield self._p.setNodeAffiliations(client, event_service, node, {invitee_jid: u'member'}) 601 # now that we have a jid, we can send normal invitation
401 log.debug(_(u'affiliation set on event node')) 602 yield self.invite(client, invitee_jid, service, node, id_)
402 dummy, event_data = yield self.eventGet(client, service, node, id_) 603
403 log.debug(_(u'got event data')) 604 @defer.inlineCallbacks
404 invitees_service = jid.JID(event_data['invitees_service']) 605 def onInvitation(self, message_elt, client):
405 invitees_node = event_data['invitees_node'] 606 invitation_elt = message_elt.invitation
406 blog_service = jid.JID(event_data['blog_service']) 607 try:
407 blog_node = event_data['blog_node'] 608 service = jid.JID(invitation_elt['service'])
408 yield self._p.setNodeAffiliations(client, invitees_service, invitees_node, {invitee_jid: u'publisher'}) 609 node = invitation_elt['node']
409 log.debug(_(u'affiliation set on invitee node')) 610 event_id = invitation_elt['item']
410 yield self._p.setNodeAffiliations(client, blog_service, blog_node, {invitee_jid: u'member'}) 611 except (RuntimeError, KeyError):
411 # FIXME: what follow is crazy, we have no good way to handle comments affiliations for blog 612 log.warning(_(u"Bad invitation: {xml}").format(xml=message_elt.toXml()))
412 blog_items, dummy = yield self._b.mbGet(client, blog_service, blog_node, None) 613
413 614 event_elt = yield self.getEventElement(client, service, node, event_id)
414 for item in blog_items: 615 yield self.register(client, service, node, event_id, event_elt, creator=False)
415 comments_service = jid.JID(item['comments_service']) 616
416 comments_node = item['comments_node'] 617
417 yield self._p.setNodeAffiliations(client, comments_service, comments_node, {invitee_jid: u'publisher'}) 618 class EventsHandler(XMPPHandler):
418 log.debug(_(u'affiliation set on blog and comments nodes')) 619 implements(iwokkel.IDisco)
419 620
420 621 def __init__(self, plugin_parent):
622 self.plugin_parent = plugin_parent
623
624 @property
625 def host(self):
626 return self.plugin_parent.host
627
628 def connectionInitialized(self):
629 self.xmlstream.addObserver(INVITATION,
630 self.plugin_parent.onInvitation,
631 client=self.parent)
632
633 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
634 return [disco.DiscoFeature(NS_EVENT),
635 disco.DiscoFeature(NS_EVENT_LIST),
636 disco.DiscoFeature(NS_EVENT_INVIT)]
637
638 def getDiscoItems(self, requestor, target, nodeIdentifier=''):
639 return []