comparison sat/plugins/plugin_xep_0249.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_xep_0249.py@0046283a285d
children 56f94936df1e
comparison
equal deleted inserted replaced
2561:bd30dc3ffe5a 2562:26edcf3a30eb
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
3
4 # SAT plugin for managing xep-0249
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 _, D_
21 from sat.core.constants import Const as C
22 from sat.core import exceptions
23 from sat.core.log import getLogger
24 log = getLogger(__name__)
25 from sat.tools import xml_tools
26 from twisted.words.xish import domish
27 from twisted.words.protocols.jabber import jid
28
29 from zope.interface import implements
30
31 from wokkel import disco, iwokkel
32
33
34 try:
35 from twisted.words.protocols.xmlstream import XMPPHandler
36 except ImportError:
37 from wokkel.subprotocols import XMPPHandler
38
39 MESSAGE = '/message'
40 NS_DIRECT_MUC_INVITATION = 'jabber:x:conference'
41 DIRECT_MUC_INVITATION_REQUEST = MESSAGE + '/x[@xmlns="' + NS_DIRECT_MUC_INVITATION + '"]'
42 AUTOJOIN_KEY = "Misc"
43 AUTOJOIN_NAME = "Auto-join MUC on invitation"
44 AUTOJOIN_VALUES = ["ask", "always", "never"]
45
46 PLUGIN_INFO = {
47 C.PI_NAME: "XEP 0249 Plugin",
48 C.PI_IMPORT_NAME: "XEP-0249",
49 C.PI_TYPE: "XEP",
50 C.PI_PROTOCOLS: ["XEP-0249"],
51 C.PI_DEPENDENCIES: ["XEP-0045"],
52 C.PI_RECOMMENDATIONS: [C.TEXT_CMDS],
53 C.PI_MAIN: "XEP_0249",
54 C.PI_HANDLER: "yes",
55 C.PI_DESCRIPTION: _("""Implementation of Direct MUC Invitations""")
56 }
57
58
59 class XEP_0249(object):
60
61 params = """
62 <params>
63 <individual>
64 <category name="%(category_name)s" label="%(category_label)s">
65 <param name="%(param_name)s" label="%(param_label)s" type="list" security="0">
66 %(param_options)s
67 </param>
68 </category>
69 </individual>
70 </params>
71 """ % {
72 'category_name': AUTOJOIN_KEY,
73 'category_label': _("Misc"),
74 'param_name': AUTOJOIN_NAME,
75 'param_label': _("Auto-join MUC on invitation"),
76 'param_options': '\n'.join(['<option value="%s" %s/>' % \
77 (value, 'selected="true"' if value == AUTOJOIN_VALUES[0] else '') \
78 for value in AUTOJOIN_VALUES])
79 }
80
81 def __init__(self, host):
82 log.info(_("Plugin XEP_0249 initialization"))
83 self.host = host
84 host.memory.updateParams(self.params)
85 host.bridge.addMethod("inviteMUC", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._invite)
86 try:
87 self.host.plugins[C.TEXT_CMDS].registerTextCommands(self)
88 except KeyError:
89 log.info(_("Text commands not available"))
90
91 def getHandler(self, client):
92 return XEP_0249_handler(self)
93
94 def _invite(self, guest_jid_s, room_jid_s, options, profile_key):
95 """Invite an user to a room
96
97 @param guest_jid_s: jid of the user to invite
98 @param service: jid of the MUC service
99 @param roomId: name of the room
100 @param profile_key: %(doc_profile_key)s
101 """
102 #TODO: check parameters validity
103 client = self.host.getClient(profile_key)
104 self.invite(client, jid.JID(guest_jid_s), jid.JID(room_jid_s, options))
105
106 def invite(self, client, guest, room, options={}):
107 """Invite a user to a room
108
109 @param guest(jid.JID): jid of the user to invite
110 @param room(jid.JID): jid of the room where the user is invited
111 @param options(dict): attribute with extra info (reason, password) as in #XEP-0249
112 """
113 message = domish.Element((None, 'message'))
114 message["to"] = guest.full()
115 x_elt = message.addElement((NS_DIRECT_MUC_INVITATION, 'x'))
116 x_elt['jid'] = room.userhost()
117 for key, value in options.iteritems():
118 if key not in ('password', 'reason', 'thread'):
119 log.warning(u"Ignoring invalid invite option: {}".format(key))
120 continue
121 x_elt[key] = value
122 # there is not body in this message, so we can use directly send()
123 client.send(message)
124
125 def _accept(self, room_jid, profile_key=C.PROF_KEY_NONE):
126 """Accept the invitation to join a MUC.
127
128 @param room (jid.JID): JID of the room
129 """
130 client = self.host.getClient(profile_key)
131 log.info(_(u'Invitation accepted for room %(room)s [%(profile)s]') % {'room': room_jid.userhost(), 'profile': client.profile})
132 d = self.host.plugins["XEP-0045"].join(client, room_jid, client.jid.user, {})
133 return d
134
135 def onInvitation(self, message, profile):
136 """
137 called when an invitation is received
138 @param message: message element
139 @profile: %(doc_profile)s
140 """
141 client = self.host.getClient(profile)
142 try:
143 room_jid_s = message.firstChildElement()['jid']
144 log.info(_(u'Invitation received for room %(room)s [%(profile)s]') % {'room': room_jid_s, 'profile': profile})
145 except:
146 log.error(_('Error while parsing invitation'))
147 return
148 from_jid_s = message["from"]
149 room_jid = jid.JID(room_jid_s)
150 try:
151 self.host.plugins["XEP-0045"].checkRoomJoined(client, room_jid)
152 except exceptions.NotFound:
153 pass
154 else:
155 log.info(_(u"Invitation silently discarded because user is already in the room."))
156 return
157
158 autojoin = self.host.memory.getParamA(AUTOJOIN_NAME, AUTOJOIN_KEY, profile_key=profile)
159
160 if autojoin == "always":
161 self._accept(room_jid, profile)
162 elif autojoin == "never":
163 msg = D_("An invitation from %(user)s to join the room %(room)s has been declined according to your personal settings.") % {'user': from_jid_s, 'room': room_jid_s}
164 title = D_("MUC invitation")
165 xml_tools.quickNote(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO)
166 else: # leave the default value here
167 confirm_msg = D_("You have been invited by %(user)s to join the room %(room)s. Do you accept?") % {'user': from_jid_s, 'room': room_jid_s}
168 confirm_title = D_("MUC invitation")
169 d = xml_tools.deferConfirm(self.host, confirm_msg, confirm_title, profile=profile)
170 def accept_cb(accepted):
171 if accepted:
172 self._accept(room_jid, profile)
173
174 d.addCallback(accept_cb)
175
176 def cmd_invite(self, client, mess_data):
177 """invite someone in the room
178
179 @command (group): JID
180 - JID: the JID of the person to invite
181 """
182 contact_jid_s = mess_data["unparsed"].strip()
183 my_host = client.jid.host
184 try:
185 contact_jid = jid.JID(contact_jid_s)
186 except (RuntimeError, jid.InvalidFormat, AttributeError):
187 feedback = _(u"You must provide a valid JID to invite, like in '/invite contact@{host}'").format(host=my_host)
188 self.host.plugins[C.TEXT_CMDS].feedBack(client, feedback, mess_data)
189 return False
190 if not contact_jid.user:
191 contact_jid.user, contact_jid.host = contact_jid.host, my_host
192 self.invite(client, contact_jid, mess_data["to"])
193 return False
194
195
196 class XEP_0249_handler(XMPPHandler):
197 implements(iwokkel.IDisco)
198
199 def __init__(self, plugin_parent):
200 self.plugin_parent = plugin_parent
201 self.host = plugin_parent.host
202
203 def connectionInitialized(self):
204 self.xmlstream.addObserver(DIRECT_MUC_INVITATION_REQUEST, self.plugin_parent.onInvitation, profile=self.parent.profile)
205
206 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
207 return [disco.DiscoFeature(NS_DIRECT_MUC_INVITATION)]
208
209 def getDiscoItems(self, requestor, target, nodeIdentifier=''):
210 return []