comparison sat/plugins/plugin_exp_invitation.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 sat/plugins/plugin_exp_events.py@003b8b4b56a7
children 672e6be3290f
comparison
equal deleted inserted replaced
2911:cd391ea847cb 2912:a3faf1c86596
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
3
4 # SAT plugin to detect language (experimental)
5 # Copyright (C) 2009-2019 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 from twisted.internet import defer
25 from twisted.words.protocols.jabber import jid
26 from wokkel import disco, iwokkel
27 from zope.interface import implements
28 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
29
30 log = getLogger(__name__)
31
32
33 PLUGIN_INFO = {
34 C.PI_NAME: "Invitation",
35 C.PI_IMPORT_NAME: "INVITATION",
36 C.PI_TYPE: "EXP",
37 C.PI_PROTOCOLS: [],
38 C.PI_DEPENDENCIES: ["XEP-0060"],
39 C.PI_RECOMMENDATIONS: [],
40 C.PI_MAIN: "Invitation",
41 C.PI_HANDLER: "yes",
42 C.PI_DESCRIPTION: _(u"Experimental handling of invitations"),
43 }
44
45 NS_INVITATION = u"https://salut-a-toi/protocol/invitation:0"
46 INVITATION = '/message/invitation[@xmlns="{ns_invit}"]'.format(
47 ns_invit=NS_INVITATION
48 )
49 NS_INVITATION_LIST = NS_INVITATION + u"#list"
50
51
52 class Invitation(object):
53
54 def __init__(self, host):
55 log.info(_(u"Invitation plugin initialization"))
56 self.host = host
57 self._p = self.host.plugins["XEP-0060"]
58 # map from namespace of the invitation to callback handling it
59 self._ns_cb = {}
60
61 def getHandler(self, client):
62 return PubsubInvitationHandler(self)
63
64 def registerNamespace(self, namespace, callback):
65 """Register a callback for a namespace
66
67 @param namespace(unicode): namespace handled
68 @param callback(callbable): method handling the invitation
69 For pubsub invitation, it will be called with following arguments:
70 - client
71 - service(jid.JID): pubsub service jid
72 - node(unicode): pubsub node
73 - item_id(unicode, None): pubsub item id
74 - item_elt(domish.Element): item of the invitation
75 @raise exceptions.ConflictError: this namespace is already registered
76 """
77 if namespace in self._ns_cb:
78 raise exceptions.ConflictError(
79 u"invitation namespace {namespace} is already register with {callback}"
80 .format(namespace=namespace, callback=self._ns_cb[namespace]))
81 self._ns_cb[namespace] = callback
82
83 def sendPubsubInvitation(self, client, invitee_jid, service, node,
84 item_id):
85 """Send an invitation in a <message> stanza
86
87 @param invitee_jid(jid.JID): entitee to send invitation to
88 @param service(jid.JID): pubsub service
89 @param node(unicode): pubsub node
90 @param item_id(unicode): pubsub id
91 """
92 mess_data = {
93 "from": client.jid,
94 "to": invitee_jid,
95 "uid": "",
96 "message": {},
97 "type": C.MESS_TYPE_CHAT,
98 "subject": {},
99 "extra": {},
100 }
101 client.generateMessageXML(mess_data)
102 invitation_elt = mess_data["xml"].addElement("invitation", NS_INVITATION)
103 pubsub_elt = invitation_elt.addElement(u"pubsub")
104 pubsub_elt[u"service"] = service.full()
105 pubsub_elt[u"node"] = node
106 pubsub_elt[u"item"] = item_id
107 client.send(mess_data[u"xml"])
108
109 @defer.inlineCallbacks
110 def _parsePubsubElt(self, client, pubsub_elt):
111 try:
112 service = jid.JID(pubsub_elt["service"])
113 node = pubsub_elt["node"]
114 item_id = pubsub_elt.getAttribute("item")
115 except (RuntimeError, KeyError):
116 log.warning(_(u"Bad invitation, ignoring"))
117 raise exceptions.DataError
118
119 try:
120 items, metadata = yield self._p.getItems(client, service, node,
121 item_ids=[item_id])
122 except Exception as e:
123 log.warning(_(u"Can't get item linked with invitation: {reason}").format(
124 reason=e))
125 try:
126 item_elt = items[0]
127 except IndexError:
128 log.warning(_(u"Invitation was linking to a non existing item"))
129 raise exceptions.DataError
130
131 try:
132 namespace = item_elt.firstChildElement().uri
133 except Exception as e:
134 log.warning(_(u"Can't retrieve namespace of invitation: {reason}").format(
135 reason = e))
136 raise exceptions.DataError
137
138 args = [service, node, item_id, item_elt]
139 defer.returnValue((namespace, args))
140
141 @defer.inlineCallbacks
142 def onInvitation(self, message_elt, client):
143 invitation_elt = message_elt.invitation
144 for elt in invitation_elt.elements():
145 if elt.uri != NS_INVITATION:
146 log.warning(u"unexpected element: {xml}".format(xml=elt.toXml()))
147 continue
148 if elt.name == u"pubsub":
149 method = self._parsePubsubElt
150 else:
151 log.warning(u"not implemented invitation element: {xml}".format(
152 xml = elt.toXml()))
153 continue
154 try:
155 namespace, args = yield method(client, elt)
156 except exceptions.DataError:
157 log.warning(u"Can't parse invitation element: {xml}".format(
158 xml = elt.toXml()))
159 continue
160
161 try:
162 cb = self._ns_cb[namespace]
163 except KeyError:
164 log.warning(_(u'No handler for namespace "{namespace}", invitation ignored')
165 .format(namespace=namespace))
166 else:
167 cb(client, *args)
168
169
170 class PubsubInvitationHandler(XMPPHandler):
171 implements(iwokkel.IDisco)
172
173 def __init__(self, plugin_parent):
174 self.plugin_parent = plugin_parent
175
176 def connectionInitialized(self):
177 self.xmlstream.addObserver(
178 INVITATION, self.plugin_parent.onInvitation, client=self.parent
179 )
180
181 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
182 return [
183 disco.DiscoFeature(NS_INVITATION),
184 ]
185
186 def getDiscoItems(self, requestor, target, nodeIdentifier=""):
187 return []