Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_xep_0249.py @ 4071:4b842c1fb686
refactoring: renamed `sat` package to `libervia.backend`
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 02 Jun 2023 11:49:51 +0200 |
parents | sat/plugins/plugin_xep_0249.py@c23cad65ae99 |
children | a7d4007a8fa5 |
comparison
equal
deleted
inserted
replaced
4070:d10748475025 | 4071:4b842c1fb686 |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 | |
4 # SAT plugin for managing xep-0249 | |
5 # Copyright (C) 2009-2021 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 twisted.internet import defer | |
21 from twisted.words.protocols.jabber import jid | |
22 from twisted.words.xish import domish | |
23 from wokkel import disco, iwokkel | |
24 from zope.interface import implementer | |
25 | |
26 from libervia.backend.core import exceptions | |
27 from libervia.backend.core.constants import Const as C | |
28 from libervia.backend.core.i18n import D_, _ | |
29 from libervia.backend.core.log import getLogger | |
30 from libervia.backend.tools import xml_tools | |
31 | |
32 log = getLogger(__name__) | |
33 | |
34 | |
35 | |
36 try: | |
37 from twisted.words.protocols.xmlstream import XMPPHandler | |
38 except ImportError: | |
39 from wokkel.subprotocols import XMPPHandler | |
40 | |
41 MESSAGE = "/message" | |
42 NS_X_CONFERENCE = "jabber:x:conference" | |
43 AUTOJOIN_KEY = "Misc" | |
44 AUTOJOIN_NAME = "Auto-join MUC on invitation" | |
45 AUTOJOIN_VALUES = ["ask", "always", "never"] | |
46 | |
47 PLUGIN_INFO = { | |
48 C.PI_NAME: "XEP 0249 Plugin", | |
49 C.PI_IMPORT_NAME: "XEP-0249", | |
50 C.PI_TYPE: "XEP", | |
51 C.PI_PROTOCOLS: ["XEP-0249"], | |
52 C.PI_DEPENDENCIES: ["XEP-0045"], | |
53 C.PI_RECOMMENDATIONS: [C.TEXT_CMDS], | |
54 C.PI_MAIN: "XEP_0249", | |
55 C.PI_HANDLER: "yes", | |
56 C.PI_DESCRIPTION: _("""Implementation of Direct MUC Invitations"""), | |
57 } | |
58 | |
59 | |
60 class XEP_0249(object): | |
61 | |
62 params = """ | |
63 <params> | |
64 <individual> | |
65 <category name="%(category_name)s" label="%(category_label)s"> | |
66 <param name="%(param_name)s" label="%(param_label)s" type="list" security="0"> | |
67 %(param_options)s | |
68 </param> | |
69 </category> | |
70 </individual> | |
71 </params> | |
72 """ % { | |
73 "category_name": AUTOJOIN_KEY, | |
74 "category_label": _("Misc"), | |
75 "param_name": AUTOJOIN_NAME, | |
76 "param_label": _("Auto-join MUC on invitation"), | |
77 "param_options": "\n".join( | |
78 [ | |
79 '<option value="%s" %s/>' | |
80 % (value, 'selected="true"' if value == AUTOJOIN_VALUES[0] else "") | |
81 for value in AUTOJOIN_VALUES | |
82 ] | |
83 ), | |
84 } | |
85 | |
86 def __init__(self, host): | |
87 log.info(_("Plugin XEP_0249 initialization")) | |
88 self.host = host | |
89 host.memory.update_params(self.params) | |
90 host.bridge.add_method( | |
91 "muc_invite", ".plugin", in_sign="ssa{ss}s", out_sign="", method=self._invite | |
92 ) | |
93 try: | |
94 self.host.plugins[C.TEXT_CMDS].register_text_commands(self) | |
95 except KeyError: | |
96 log.info(_("Text commands not available")) | |
97 host.register_namespace('x-conference', NS_X_CONFERENCE) | |
98 host.trigger.add("message_received", self._message_received_trigger) | |
99 | |
100 def get_handler(self, client): | |
101 return XEP_0249_handler() | |
102 | |
103 def _invite(self, guest_jid_s, room_jid_s, options, profile_key): | |
104 """Invite an user to a room | |
105 | |
106 @param guest_jid_s: jid of the user to invite | |
107 @param service: jid of the MUC service | |
108 @param roomId: name of the room | |
109 @param profile_key: %(doc_profile_key)s | |
110 """ | |
111 # TODO: check parameters validity | |
112 client = self.host.get_client(profile_key) | |
113 self.invite(client, jid.JID(guest_jid_s), jid.JID(room_jid_s, options)) | |
114 | |
115 def invite(self, client, guest, room, options={}): | |
116 """Invite a user to a room | |
117 | |
118 @param guest(jid.JID): jid of the user to invite | |
119 @param room(jid.JID): jid of the room where the user is invited | |
120 @param options(dict): attribute with extra info (reason, password) as in #XEP-0249 | |
121 """ | |
122 message = domish.Element((None, "message")) | |
123 message["to"] = guest.full() | |
124 x_elt = message.addElement((NS_X_CONFERENCE, "x")) | |
125 x_elt["jid"] = room.userhost() | |
126 for key, value in options.items(): | |
127 if key not in ("password", "reason", "thread"): | |
128 log.warning("Ignoring invalid invite option: {}".format(key)) | |
129 continue | |
130 x_elt[key] = value | |
131 # there is not body in this message, so we can use directly send() | |
132 client.send(message) | |
133 | |
134 def _accept(self, room_jid, profile_key=C.PROF_KEY_NONE): | |
135 """Accept the invitation to join a MUC. | |
136 | |
137 @param room (jid.JID): JID of the room | |
138 """ | |
139 client = self.host.get_client(profile_key) | |
140 log.info( | |
141 _("Invitation accepted for room %(room)s [%(profile)s]") | |
142 % {"room": room_jid.userhost(), "profile": client.profile} | |
143 ) | |
144 d = defer.ensureDeferred( | |
145 self.host.plugins["XEP-0045"].join(client, room_jid, client.jid.user, {}) | |
146 ) | |
147 return d | |
148 | |
149 def _message_received_trigger(self, client, message_elt, post_treat): | |
150 """Check if a direct invitation is in the message, and handle it""" | |
151 x_elt = next(message_elt.elements(NS_X_CONFERENCE, 'x'), None) | |
152 if x_elt is None: | |
153 return True | |
154 | |
155 try: | |
156 room_jid_s = x_elt["jid"] | |
157 except KeyError: | |
158 log.warning(_("invalid invitation received: {xml}").format( | |
159 xml=message_elt.toXml())) | |
160 return False | |
161 log.info( | |
162 _("Invitation received for room %(room)s [%(profile)s]") | |
163 % {"room": room_jid_s, "profile": client.profile} | |
164 ) | |
165 from_jid_s = message_elt["from"] | |
166 room_jid = jid.JID(room_jid_s) | |
167 try: | |
168 self.host.plugins["XEP-0045"].check_room_joined(client, room_jid) | |
169 except exceptions.NotFound: | |
170 pass | |
171 else: | |
172 log.info( | |
173 _("Invitation silently discarded because user is already in the room.") | |
174 ) | |
175 return | |
176 | |
177 autojoin = self.host.memory.param_get_a( | |
178 AUTOJOIN_NAME, AUTOJOIN_KEY, profile_key=client.profile | |
179 ) | |
180 | |
181 if autojoin == "always": | |
182 self._accept(room_jid, client.profile) | |
183 elif autojoin == "never": | |
184 msg = D_( | |
185 "An invitation from %(user)s to join the room %(room)s has been " | |
186 "declined according to your personal settings." | |
187 ) % {"user": from_jid_s, "room": room_jid_s} | |
188 title = D_("MUC invitation") | |
189 xml_tools.quick_note(self.host, client, msg, title, C.XMLUI_DATA_LVL_INFO) | |
190 else: # leave the default value here | |
191 confirm_msg = D_( | |
192 "You have been invited by %(user)s to join the room %(room)s. " | |
193 "Do you accept?" | |
194 ) % {"user": from_jid_s, "room": room_jid_s} | |
195 confirm_title = D_("MUC invitation") | |
196 d = xml_tools.defer_confirm( | |
197 self.host, confirm_msg, confirm_title, profile=client.profile | |
198 ) | |
199 | |
200 def accept_cb(accepted): | |
201 if accepted: | |
202 self._accept(room_jid, client.profile) | |
203 | |
204 d.addCallback(accept_cb) | |
205 return False | |
206 | |
207 def cmd_invite(self, client, mess_data): | |
208 """invite someone in the room | |
209 | |
210 @command (group): JID | |
211 - JID: the JID of the person to invite | |
212 """ | |
213 contact_jid_s = mess_data["unparsed"].strip() | |
214 my_host = client.jid.host | |
215 try: | |
216 contact_jid = jid.JID(contact_jid_s) | |
217 except (RuntimeError, jid.InvalidFormat, AttributeError): | |
218 feedback = _( | |
219 "You must provide a valid JID to invite, like in '/invite " | |
220 "contact@{host}'" | |
221 ).format(host=my_host) | |
222 self.host.plugins[C.TEXT_CMDS].feed_back(client, feedback, mess_data) | |
223 return False | |
224 if not contact_jid.user: | |
225 contact_jid.user, contact_jid.host = contact_jid.host, my_host | |
226 self.invite(client, contact_jid, mess_data["to"]) | |
227 return False | |
228 | |
229 | |
230 @implementer(iwokkel.IDisco) | |
231 class XEP_0249_handler(XMPPHandler): | |
232 | |
233 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): | |
234 return [disco.DiscoFeature(NS_X_CONFERENCE)] | |
235 | |
236 def getDiscoItems(self, requestor, target, nodeIdentifier=""): | |
237 return [] |