Mercurial > libervia-backend
comparison sat/plugins/plugin_exp_events.py @ 2912:a3faf1c86596
plugin events: refactored invitation and personal lists logic:
- invitation logic has been moved to a new generic "plugin_exp_invitation" plugin
- plugin_misc_invitations has be rename "plugin_exp_email_invitation" to avoid confusion
- personal event list has be refactored to use a new experimental "list of interest", which regroup all interestings items, events or other ones
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 14 Apr 2019 08:21:51 +0200 |
parents | 003b8b4b56a7 |
children | b256e90612d0 |
comparison
equal
deleted
inserted
replaced
2911:cd391ea847cb | 2912:a3faf1c86596 |
---|---|
15 # GNU Affero General Public License for more details. | 15 # GNU Affero General Public License for more details. |
16 | 16 |
17 # You should have received a copy of the GNU Affero General Public License | 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/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 import shortuuid | |
20 from sat.core.i18n import _ | 21 from sat.core.i18n import _ |
21 from sat.core import exceptions | 22 from sat.core import exceptions |
22 from sat.core.constants import Const as C | 23 from sat.core.constants import Const as C |
23 from sat.core.log import getLogger | 24 from sat.core.log import getLogger |
24 | |
25 log = getLogger(__name__) | |
26 from sat.tools import utils | 25 from sat.tools import utils |
27 from sat.tools.common import uri as xmpp_uri | 26 from sat.tools.common import uri as xmpp_uri |
28 from sat.tools.common import date_utils | 27 from sat.tools.common import date_utils |
29 from twisted.internet import defer | 28 from twisted.internet import defer |
30 from twisted.words.protocols.jabber import jid, error | 29 from twisted.words.protocols.jabber import jid, error |
31 from twisted.words.xish import domish | 30 from twisted.words.xish import domish |
32 from wokkel import disco, iwokkel | 31 from wokkel import disco, iwokkel |
33 from zope.interface import implements | 32 from zope.interface import implements |
34 from twisted.words.protocols.jabber.xmlstream import XMPPHandler | 33 from twisted.words.protocols.jabber.xmlstream import XMPPHandler |
35 | |
36 from wokkel import pubsub | 34 from wokkel import pubsub |
37 import shortuuid | 35 |
36 log = getLogger(__name__) | |
38 | 37 |
39 | 38 |
40 PLUGIN_INFO = { | 39 PLUGIN_INFO = { |
41 C.PI_NAME: "Event plugin", | 40 C.PI_NAME: "Events", |
42 C.PI_IMPORT_NAME: "EVENTS", | 41 C.PI_IMPORT_NAME: "EVENTS", |
43 C.PI_TYPE: "EXP", | 42 C.PI_TYPE: "EXP", |
44 C.PI_PROTOCOLS: [], | 43 C.PI_PROTOCOLS: [], |
45 C.PI_DEPENDENCIES: ["XEP-0060"], | 44 C.PI_DEPENDENCIES: [u"XEP-0060", u"INVITATION", u"LIST_INTEREST"], |
46 C.PI_RECOMMENDATIONS: ["INVITATIONS", "XEP-0277"], | 45 C.PI_RECOMMENDATIONS: ["XEP-0277", "EMAIL_INVITATION"], |
47 C.PI_MAIN: "Events", | 46 C.PI_MAIN: "Events", |
48 C.PI_HANDLER: "yes", | 47 C.PI_HANDLER: "yes", |
49 C.PI_DESCRIPTION: _("""Experimental implementation of XMPP events management"""), | 48 C.PI_DESCRIPTION: _(u"""Experimental implementation of XMPP events management"""), |
50 } | 49 } |
51 | 50 |
52 NS_EVENT = "org.salut-a-toi.event:0" | 51 NS_EVENT = "org.salut-a-toi.event:0" |
53 NS_EVENT_LIST = NS_EVENT + "#list" | |
54 NS_EVENT_INVIT = NS_EVENT + "#invitation" | |
55 INVITATION = '/message[@type="chat"]/invitation[@xmlns="{ns_invit}"]'.format( | |
56 ns_invit=NS_EVENT_INVIT | |
57 ) | |
58 | 52 |
59 | 53 |
60 class Events(object): | 54 class Events(object): |
61 """Q&D module to handle event attendance answer, experimentation only""" | 55 """Q&D module to handle event attendance answer, experimentation only""" |
62 | 56 |
63 def __init__(self, host): | 57 def __init__(self, host): |
64 log.info(_(u"Event plugin initialization")) | 58 log.info(_(u"Event plugin initialization")) |
65 self.host = host | 59 self.host = host |
66 self._p = self.host.plugins["XEP-0060"] | 60 self._p = self.host.plugins["XEP-0060"] |
67 self._i = self.host.plugins.get("INVITATIONS") | 61 self._i = self.host.plugins.get("EMAIL_INVITATION") |
68 self._b = self.host.plugins.get("XEP-0277") | 62 self._b = self.host.plugins.get("XEP-0277") |
63 self.host.plugins[u"INVITATION"].registerNamespace(NS_EVENT, | |
64 self.register) | |
69 host.bridge.addMethod( | 65 host.bridge.addMethod( |
70 "eventGet", | 66 "eventGet", |
71 ".plugin", | 67 ".plugin", |
72 in_sign="ssss", | 68 in_sign="ssss", |
73 out_sign="(ia{ss})", | 69 out_sign="(ia{ss})", |
227 if not id_: | 223 if not id_: |
228 id_ = NS_EVENT | 224 id_ = NS_EVENT |
229 items, metadata = yield self._p.getItems(client, service, node, item_ids=[id_]) | 225 items, metadata = yield self._p.getItems(client, service, node, item_ids=[id_]) |
230 try: | 226 try: |
231 event_elt = next(items[0].elements(NS_EVENT, u"event")) | 227 event_elt = next(items[0].elements(NS_EVENT, u"event")) |
228 except StopIteration: | |
229 raise exceptions.NotFound(_(u"No event element has been found")) | |
232 except IndexError: | 230 except IndexError: |
233 raise exceptions.NotFound(_(u"No event with this id has been found")) | 231 raise exceptions.NotFound(_(u"No event with this id has been found")) |
234 defer.returnValue(event_elt) | 232 defer.returnValue(event_elt) |
235 | 233 |
236 @defer.inlineCallbacks | |
237 def register(self, client, service, node, event_id, event_elt, creator=False): | 234 def register(self, client, service, node, event_id, event_elt, creator=False): |
238 """register evenement in personal events list | 235 """register evenement in personal events list |
239 | 236 |
240 @param service(jid.JID): pubsub service of the event | 237 @param service(jid.JID): pubsub service of the event |
241 @param node(unicode): event node | 238 @param node(unicode): event node |
242 @param event_id(unicode): event id | 239 @param event_id(unicode): event id |
243 @param event_elt(domish.Element): event element | 240 @param event_elt(domish.Element): event element |
244 note that this element will be modified in place | 241 note that this element will be modified in place |
245 @param creator(bool): True if client's profile is the creator of the node | 242 @param creator(bool): True if client's profile is the creator of the node |
246 """ | 243 """ |
247 # we save a link to the event in our local list | 244 link_elt = event_elt.addElement("link") |
248 try: | |
249 # TODO: check auto-create, no need to create node first if available | |
250 options = {self._p.OPT_ACCESS_MODEL: self._p.ACCESS_WHITELIST} | |
251 yield self._p.createNode( | |
252 client, | |
253 client.jid.userhostJID(), | |
254 nodeIdentifier=NS_EVENT_LIST, | |
255 options=options, | |
256 ) | |
257 except error.StanzaError as e: | |
258 if e.condition == u"conflict": | |
259 log.debug(_(u"requested node already exists")) | |
260 link_elt = event_elt.addElement((NS_EVENT_LIST, "link")) | |
261 link_elt["service"] = service.full() | 245 link_elt["service"] = service.full() |
262 link_elt["node"] = node | 246 link_elt["node"] = node |
263 link_elt["item"] = event_id | 247 link_elt["item"] = event_id |
264 item_id = xmpp_uri.buildXMPPUri( | 248 return self.host.plugins[u'LIST_INTEREST'].registerPubsub( |
265 u"pubsub", path=service.full(), node=node, item=event_id | 249 client, NS_EVENT, service, node, event_id, creator, |
266 ) | 250 element=event_elt) |
267 if creator: | |
268 event_elt["creator"] = "true" | |
269 item_elt = pubsub.Item(id=item_id, payload=event_elt) | |
270 yield self._p.publish( | |
271 client, client.jid.userhostJID(), NS_EVENT_LIST, items=[item_elt] | |
272 ) | |
273 | 251 |
274 def _eventGet(self, service, node, id_=u"", profile_key=C.PROF_KEY_NONE): | 252 def _eventGet(self, service, node, id_=u"", profile_key=C.PROF_KEY_NONE): |
275 service = jid.JID(service) if service else None | 253 service = jid.JID(service) if service else None |
276 node = node if node else NS_EVENT | 254 node = node if node else NS_EVENT |
277 client = self.host.getClient(profile_key) | 255 client = self.host.getClient(profile_key) |
394 | 372 |
395 if register: | 373 if register: |
396 yield self.register(client, service, node, event_id, event_elt, creator=True) | 374 yield self.register(client, service, node, event_id, event_elt, creator=True) |
397 defer.returnValue(node) | 375 defer.returnValue(node) |
398 | 376 |
399 def _eventModify( | 377 def _eventModify(self, service, node, id_, timestamp_update, data_update, |
400 self, | 378 profile_key=C.PROF_KEY_NONE): |
401 service, | 379 service = jid.JID(service) if service else None |
402 node, | 380 if not node: |
403 id_, | 381 raise ValueError(_(u"missing node")) |
404 timestamp_update, | |
405 data_update, | |
406 profile_key=C.PROF_KEY_NONE, | |
407 ): | |
408 service = jid.JID(service) if service else None | |
409 node = node if node else NS_EVENT | |
410 client = self.host.getClient(profile_key) | 382 client = self.host.getClient(profile_key) |
411 return self.eventModify( | 383 return self.eventModify( |
412 client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update | 384 client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update |
413 ) | 385 ) |
414 | 386 |
442 d = self.eventsList(client, service, node) | 414 d = self.eventsList(client, service, node) |
443 d.addCallback(self._eventsListSerialise) | 415 d.addCallback(self._eventsListSerialise) |
444 return d | 416 return d |
445 | 417 |
446 @defer.inlineCallbacks | 418 @defer.inlineCallbacks |
447 def eventsList(self, client, service, node): | 419 def eventsList(self, client, service, node=None): |
448 """Retrieve list of registered events | 420 """Retrieve list of registered events |
449 | 421 |
450 @return list(tuple(int, dict)): list of events (timestamp + metadata) | 422 @return list(tuple(int, dict)): list of events (timestamp + metadata) |
451 """ | 423 """ |
452 if not node: | 424 items, metadata = yield self.host.plugins[u'LIST_INTEREST'].listInterests( |
453 node = NS_EVENT_LIST | 425 client, service, node, namespace=NS_EVENT) |
454 items = yield self._p.getItems(client, service, node) | |
455 events = [] | 426 events = [] |
456 for item in items[0]: | 427 for item in items: |
457 try: | 428 try: |
458 event_elt = next(item.elements(NS_EVENT, u"event")) | 429 event_elt = next(item.interest.pubsub.elements(NS_EVENT, u"event")) |
459 except IndexError: | 430 except IndexError: |
460 log.error( | 431 log.warning( |
461 _(u"No event found in item {item_id}").format(item_id=item["id"]) | 432 _(u"No event found in item {item_id}").format(item_id=item["id"]) |
462 ) | 433 ) |
463 timestamp, data = self._parseEventElt(event_elt) | 434 else: |
464 events.append((timestamp, data)) | 435 timestamp, data = self._parseEventElt(event_elt) |
436 events.append((timestamp, data)) | |
465 defer.returnValue(events) | 437 defer.returnValue(events) |
466 | 438 |
467 def _eventInviteeGet(self, service, node, profile_key): | 439 def _eventInviteeGet(self, service, node, profile_key): |
468 service = jid.JID(service) if service else None | 440 service = jid.JID(service) if service else None |
469 node = node if node else NS_EVENT | 441 node = node if node else NS_EVENT |
477 @param service(unicode, None): PubSub service | 449 @param service(unicode, None): PubSub service |
478 @param node(unicode): PubSub node of the event | 450 @param node(unicode): PubSub node of the event |
479 @return (dict): a dict with current attendance status, | 451 @return (dict): a dict with current attendance status, |
480 an empty dict is returned if nothing has been answered yed | 452 an empty dict is returned if nothing has been answered yed |
481 """ | 453 """ |
482 items, metadata = yield self._p.getItems( | |
483 client, service, node, item_ids=[client.jid.userhost()] | |
484 ) | |
485 try: | 454 try: |
455 items, metadata = yield self._p.getItems( | |
456 client, service, node, item_ids=[client.jid.userhost()] | |
457 ) | |
486 event_elt = next(items[0].elements(NS_EVENT, u"invitee")) | 458 event_elt = next(items[0].elements(NS_EVENT, u"invitee")) |
487 except IndexError: | 459 except (exceptions.NotFound, IndexError): |
488 # no item found, event data are not set yet | 460 # no item found, event data are not set yet |
489 defer.returnValue({}) | 461 defer.returnValue({}) |
490 data = {} | 462 data = {} |
491 for key in (u"attend", u"guests"): | 463 for key in (u"attend", u"guests"): |
492 try: | 464 try: |
540 for item in items: | 512 for item in items: |
541 try: | 513 try: |
542 event_elt = next(item.elements(NS_EVENT, u"invitee")) | 514 event_elt = next(item.elements(NS_EVENT, u"invitee")) |
543 except StopIteration: | 515 except StopIteration: |
544 # no item found, event data are not set yet | 516 # no item found, event data are not set yet |
545 log.warning( | 517 log.warning(_( |
546 _( | 518 u"no data found for {item_id} (service: {service}, node: {node})" |
547 u"no data found for {item_id} (service: {service}, node: {node})".format( | 519 .format(item_id=item["id"], service=service, node=node))) |
548 item_id=item["id"], service=service, node=node | |
549 ) | |
550 ) | |
551 ) | |
552 else: | 520 else: |
553 data = {} | 521 data = {} |
554 for key in (u"attend", u"guests"): | 522 for key in (u"attend", u"guests"): |
555 try: | 523 try: |
556 data[key] = event_elt[key] | 524 data[key] = event_elt[key] |
557 except KeyError: | 525 except KeyError: |
558 continue | 526 continue |
559 invitees[item["id"]] = data | 527 invitees[item["id"]] = data |
560 defer.returnValue(invitees) | 528 defer.returnValue(invitees) |
561 | |
562 def sendMessageInvitation(self, client, invitee_jid, service, node, item_id): | |
563 """Send an invitation in a <message> stanza | |
564 | |
565 @param invitee_jid(jid.JID): entitee to send invitation to | |
566 @param service(jid.JID): pubsub service of the event | |
567 @param node(unicode): node of the event | |
568 @param item_id(unicode): id of the event | |
569 """ | |
570 mess_data = { | |
571 "from": client.jid, | |
572 "to": invitee_jid, | |
573 "uid": "", | |
574 "message": {}, | |
575 "type": C.MESS_TYPE_CHAT, | |
576 "subject": {}, | |
577 "extra": {}, | |
578 } | |
579 client.generateMessageXML(mess_data) | |
580 event_elt = mess_data["xml"].addElement("invitation", NS_EVENT_INVIT) | |
581 event_elt["service"] = service.full() | |
582 event_elt["node"] = node | |
583 event_elt["item"] = item_id | |
584 client.send(mess_data["xml"]) | |
585 | 529 |
586 def _invite(self, invitee_jid, service, node, item_id, profile): | 530 def _invite(self, invitee_jid, service, node, item_id, profile): |
587 client = self.host.getClient(profile) | 531 client = self.host.getClient(profile) |
588 service = jid.JID(service) if service else None | 532 service = jid.JID(service) if service else None |
589 node = node or None | 533 node = node or None |
642 client, comments_service, comments_node, {invitee_jid: u"publisher"} | 586 client, comments_service, comments_node, {invitee_jid: u"publisher"} |
643 ) | 587 ) |
644 log.debug(_(u"affiliation set on blog and comments nodes")) | 588 log.debug(_(u"affiliation set on blog and comments nodes")) |
645 | 589 |
646 # now we send the invitation | 590 # now we send the invitation |
647 self.sendMessageInvitation(client, invitee_jid, service, node, item_id) | 591 pubsub_invitation = self.host.plugins[u'PUBSUB_INVITATION'] |
648 | 592 pubsub_invitation.sendPubsubInvitation(client, invitee_jid, service, node, |
649 def _inviteByEmail( | 593 item_id) |
650 self, | 594 |
651 service, | 595 def _inviteByEmail(self, service, node, id_=NS_EVENT, email=u"", emails_extra=None, |
652 node, | 596 name=u"", host_name=u"", language=u"", url_template=u"", |
653 id_=NS_EVENT, | 597 message_subject=u"", message_body=u"", |
654 email=u"", | 598 profile_key=C.PROF_KEY_NONE): |
655 emails_extra=None, | |
656 name=u"", | |
657 host_name=u"", | |
658 language=u"", | |
659 url_template=u"", | |
660 message_subject=u"", | |
661 message_body=u"", | |
662 profile_key=C.PROF_KEY_NONE, | |
663 ): | |
664 client = self.host.getClient(profile_key) | 599 client = self.host.getClient(profile_key) |
665 kwargs = { | 600 kwargs = { |
666 u"profile": client.profile, | 601 u"profile": client.profile, |
667 u"emails_extra": [unicode(e) for e in emails_extra], | 602 u"emails_extra": [unicode(e) for e in emails_extra], |
668 } | 603 } |
706 invitee_jid = invitation_data[u"jid"] | 641 invitee_jid = invitation_data[u"jid"] |
707 log.debug(_(u"invitation created")) | 642 log.debug(_(u"invitation created")) |
708 # now that we have a jid, we can send normal invitation | 643 # now that we have a jid, we can send normal invitation |
709 yield self.invite(client, invitee_jid, service, node, id_) | 644 yield self.invite(client, invitee_jid, service, node, id_) |
710 | 645 |
711 @defer.inlineCallbacks | |
712 def onInvitation(self, message_elt, client): | |
713 invitation_elt = message_elt.invitation | |
714 try: | |
715 service = jid.JID(invitation_elt["service"]) | |
716 node = invitation_elt["node"] | |
717 event_id = invitation_elt["item"] | |
718 except (RuntimeError, KeyError): | |
719 log.warning(_(u"Bad invitation: {xml}").format(xml=message_elt.toXml())) | |
720 | |
721 event_elt = yield self.getEventElement(client, service, node, event_id) | |
722 yield self.register(client, service, node, event_id, event_elt, creator=False) | |
723 | |
724 | 646 |
725 class EventsHandler(XMPPHandler): | 647 class EventsHandler(XMPPHandler): |
726 implements(iwokkel.IDisco) | 648 implements(iwokkel.IDisco) |
727 | 649 |
728 def __init__(self, plugin_parent): | 650 def __init__(self, plugin_parent): |
729 self.plugin_parent = plugin_parent | 651 self.plugin_parent = plugin_parent |
730 | |
731 @property | |
732 def host(self): | |
733 return self.plugin_parent.host | |
734 | |
735 def connectionInitialized(self): | |
736 self.xmlstream.addObserver( | |
737 INVITATION, self.plugin_parent.onInvitation, client=self.parent | |
738 ) | |
739 | 652 |
740 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): | 653 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): |
741 return [ | 654 return [ |
742 disco.DiscoFeature(NS_EVENT), | 655 disco.DiscoFeature(NS_EVENT), |
743 disco.DiscoFeature(NS_EVENT_LIST), | |
744 disco.DiscoFeature(NS_EVENT_INVIT), | |
745 ] | 656 ] |
746 | 657 |
747 def getDiscoItems(self, requestor, target, nodeIdentifier=""): | 658 def getDiscoItems(self, requestor, target, nodeIdentifier=""): |
748 return [] | 659 return [] |