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 []