comparison libervia/backend/plugins/plugin_xep_0045.py @ 4270:0d7bb4df2343

Reformatted code base using black.
author Goffi <goffi@goffi.org>
date Wed, 19 Jun 2024 18:44:57 +0200
parents a7d4007a8fa5
children
comparison
equal deleted inserted replaced
4269:64a85ce8be70 4270:0d7bb4df2343
51 C.PI_PROTOCOLS: ["XEP-0045"], 51 C.PI_PROTOCOLS: ["XEP-0045"],
52 C.PI_DEPENDENCIES: ["XEP-0359"], 52 C.PI_DEPENDENCIES: ["XEP-0359"],
53 C.PI_RECOMMENDATIONS: [C.TEXT_CMDS, "XEP-0313"], 53 C.PI_RECOMMENDATIONS: [C.TEXT_CMDS, "XEP-0313"],
54 C.PI_MAIN: "XEP_0045", 54 C.PI_MAIN: "XEP_0045",
55 C.PI_HANDLER: "yes", 55 C.PI_HANDLER: "yes",
56 C.PI_DESCRIPTION: _("""Implementation of Multi-User Chat""") 56 C.PI_DESCRIPTION: _("""Implementation of Multi-User Chat"""),
57 } 57 }
58 58
59 NS_MUC = 'http://jabber.org/protocol/muc' 59 NS_MUC = "http://jabber.org/protocol/muc"
60 AFFILIATIONS = ('owner', 'admin', 'member', 'none', 'outcast') 60 AFFILIATIONS = ("owner", "admin", "member", "none", "outcast")
61 ROOM_USER_JOINED = 'ROOM_USER_JOINED' 61 ROOM_USER_JOINED = "ROOM_USER_JOINED"
62 ROOM_USER_LEFT = 'ROOM_USER_LEFT' 62 ROOM_USER_LEFT = "ROOM_USER_LEFT"
63 OCCUPANT_KEYS = ('nick', 'entity', 'affiliation', 'role') 63 OCCUPANT_KEYS = ("nick", "entity", "affiliation", "role")
64 ROOM_STATE_OCCUPANTS = "occupants" 64 ROOM_STATE_OCCUPANTS = "occupants"
65 ROOM_STATE_SELF_PRESENCE = "self-presence" 65 ROOM_STATE_SELF_PRESENCE = "self-presence"
66 ROOM_STATE_LIVE = "live" 66 ROOM_STATE_LIVE = "live"
67 ROOM_STATES = (ROOM_STATE_OCCUPANTS, ROOM_STATE_SELF_PRESENCE, ROOM_STATE_LIVE) 67 ROOM_STATES = (ROOM_STATE_OCCUPANTS, ROOM_STATE_SELF_PRESENCE, ROOM_STATE_LIVE)
68 HISTORY_LEGACY = "legacy" 68 HISTORY_LEGACY = "legacy"
69 HISTORY_MAM = "mam" 69 HISTORY_MAM = "mam"
70 70
71 71
72 CONFIG_SECTION = 'plugin muc' 72 CONFIG_SECTION = "plugin muc"
73 73
74 default_conf = {"default_muc": 'sat@chat.jabberfr.org'} 74 default_conf = {"default_muc": "sat@chat.jabberfr.org"}
75 75
76 76
77 class AlreadyJoined(exceptions.ConflictError): 77 class AlreadyJoined(exceptions.ConflictError):
78 78
79 def __init__(self, room): 79 def __init__(self, room):
90 self.host = host 90 self.host = host
91 self._sessions = memory.Sessions() 91 self._sessions = memory.Sessions()
92 # return same arguments as muc_room_joined + a boolean set to True is the room was 92 # return same arguments as muc_room_joined + a boolean set to True is the room was
93 # already joined (first argument) 93 # already joined (first argument)
94 host.bridge.add_method( 94 host.bridge.add_method(
95 "muc_join", ".plugin", in_sign='ssa{ss}s', out_sign='(bsa{sa{ss}}ssass)', 95 "muc_join",
96 method=self._join, async_=True) 96 ".plugin",
97 in_sign="ssa{ss}s",
98 out_sign="(bsa{sa{ss}}ssass)",
99 method=self._join,
100 async_=True,
101 )
97 host.bridge.add_method( 102 host.bridge.add_method(
98 "muc_nick", ".plugin", in_sign='sss', out_sign='', method=self._nick) 103 "muc_nick", ".plugin", in_sign="sss", out_sign="", method=self._nick
104 )
99 host.bridge.add_method( 105 host.bridge.add_method(
100 "muc_nick_get", ".plugin", in_sign='ss', out_sign='s', method=self._get_room_nick) 106 "muc_nick_get",
107 ".plugin",
108 in_sign="ss",
109 out_sign="s",
110 method=self._get_room_nick,
111 )
101 host.bridge.add_method( 112 host.bridge.add_method(
102 "muc_leave", ".plugin", in_sign='ss', out_sign='', method=self._leave, 113 "muc_leave",
103 async_=True) 114 ".plugin",
115 in_sign="ss",
116 out_sign="",
117 method=self._leave,
118 async_=True,
119 )
104 host.bridge.add_method( 120 host.bridge.add_method(
105 "muc_occupants_get", ".plugin", in_sign='ss', out_sign='a{sa{ss}}', 121 "muc_occupants_get",
106 method=self._get_room_occupants) 122 ".plugin",
123 in_sign="ss",
124 out_sign="a{sa{ss}}",
125 method=self._get_room_occupants,
126 )
107 host.bridge.add_method( 127 host.bridge.add_method(
108 "muc_subject", ".plugin", in_sign='sss', out_sign='', method=self._subject) 128 "muc_subject", ".plugin", in_sign="sss", out_sign="", method=self._subject
129 )
109 host.bridge.add_method( 130 host.bridge.add_method(
110 "muc_get_rooms_joined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ssas)', 131 "muc_get_rooms_joined",
111 method=self._get_rooms_joined) 132 ".plugin",
133 in_sign="s",
134 out_sign="a(sa{sa{ss}}ssas)",
135 method=self._get_rooms_joined,
136 )
112 host.bridge.add_method( 137 host.bridge.add_method(
113 "muc_get_unique_room_name", ".plugin", in_sign='ss', out_sign='s', 138 "muc_get_unique_room_name",
114 method=self._get_unique_name) 139 ".plugin",
140 in_sign="ss",
141 out_sign="s",
142 method=self._get_unique_name,
143 )
115 host.bridge.add_method( 144 host.bridge.add_method(
116 "muc_configure_room", ".plugin", in_sign='ss', out_sign='s', 145 "muc_configure_room",
117 method=self._configure_room, async_=True) 146 ".plugin",
147 in_sign="ss",
148 out_sign="s",
149 method=self._configure_room,
150 async_=True,
151 )
118 host.bridge.add_method( 152 host.bridge.add_method(
119 "muc_get_default_service", ".plugin", in_sign='', out_sign='s', 153 "muc_get_default_service",
120 method=self.get_default_muc) 154 ".plugin",
155 in_sign="",
156 out_sign="s",
157 method=self.get_default_muc,
158 )
121 host.bridge.add_method( 159 host.bridge.add_method(
122 "muc_get_service", ".plugin", in_sign='ss', out_sign='s', 160 "muc_get_service",
123 method=self._get_muc_service, async_=True) 161 ".plugin",
162 in_sign="ss",
163 out_sign="s",
164 method=self._get_muc_service,
165 async_=True,
166 )
124 # called when a room will be joined but must be locked until join is received 167 # called when a room will be joined but must be locked until join is received
125 # (room is prepared, history is getting retrieved) 168 # (room is prepared, history is getting retrieved)
126 # args: room_jid, profile 169 # args: room_jid, profile
127 host.bridge.add_signal( 170 host.bridge.add_signal("muc_room_prepare_join", ".plugin", signature="ss")
128 "muc_room_prepare_join", ".plugin", signature='ss')
129 # args: room_jid, occupants, user_nick, subject, profile 171 # args: room_jid, occupants, user_nick, subject, profile
130 host.bridge.add_signal( 172 host.bridge.add_signal("muc_room_joined", ".plugin", signature="sa{sa{ss}}ssass")
131 "muc_room_joined", ".plugin", signature='sa{sa{ss}}ssass')
132 # args: room_jid, profile 173 # args: room_jid, profile
133 host.bridge.add_signal( 174 host.bridge.add_signal("muc_room_left", ".plugin", signature="ss")
134 "muc_room_left", ".plugin", signature='ss')
135 # args: room_jid, old_nick, new_nick, profile 175 # args: room_jid, old_nick, new_nick, profile
136 host.bridge.add_signal( 176 host.bridge.add_signal("muc_room_user_changed_nick", ".plugin", signature="ssss")
137 "muc_room_user_changed_nick", ".plugin", signature='ssss')
138 # args: room_jid, subject, profile 177 # args: room_jid, subject, profile
139 host.bridge.add_signal( 178 host.bridge.add_signal("muc_room_new_subject", ".plugin", signature="sss")
140 "muc_room_new_subject", ".plugin", signature='sss')
141 self.__submit_conf_id = host.register_callback( 179 self.__submit_conf_id = host.register_callback(
142 self._submit_configuration, with_data=True) 180 self._submit_configuration, with_data=True
181 )
143 self._room_join_id = host.register_callback(self._ui_room_join_cb, with_data=True) 182 self._room_join_id = host.register_callback(self._ui_room_join_cb, with_data=True)
144 host.import_menu( 183 host.import_menu(
145 (D_("MUC"), D_("configure")), self._configure_room_menu, security_limit=0, 184 (D_("MUC"), D_("configure")),
146 help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM) 185 self._configure_room_menu,
186 security_limit=0,
187 help_string=D_("Configure Multi-User Chat room"),
188 type_=C.MENU_ROOM,
189 )
147 try: 190 try:
148 self.text_cmds = self.host.plugins[C.TEXT_CMDS] 191 self.text_cmds = self.host.plugins[C.TEXT_CMDS]
149 except KeyError: 192 except KeyError:
150 log.info(_("Text commands not available")) 193 log.info(_("Text commands not available"))
151 else: 194 else:
155 self._mam = self.host.plugins.get("XEP-0313") 198 self._mam = self.host.plugins.get("XEP-0313")
156 self._si = self.host.plugins["XEP-0359"] 199 self._si = self.host.plugins["XEP-0359"]
157 200
158 host.trigger.add("presence_available", self.presence_trigger) 201 host.trigger.add("presence_available", self.presence_trigger)
159 host.trigger.add("presence_received", self.presence_received_trigger) 202 host.trigger.add("presence_received", self.presence_received_trigger)
160 host.trigger.add("message_received", self.message_received_trigger, priority=1000000) 203 host.trigger.add(
204 "message_received", self.message_received_trigger, priority=1000000
205 )
161 host.trigger.add("message_parse", self._message_parse_trigger) 206 host.trigger.add("message_parse", self._message_parse_trigger)
162 207
163 async def profile_connected(self, client): 208 async def profile_connected(self, client):
164 client.muc_service = await self.get_muc_service(client) 209 client.muc_service = await self.get_muc_service(client)
165 210
177 222
178 def message_received_trigger(self, client, message_elt, post_treat): 223 def message_received_trigger(self, client, message_elt, post_treat):
179 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: 224 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT:
180 if message_elt.subject: 225 if message_elt.subject:
181 return False 226 return False
182 from_jid = jid.JID(message_elt['from']) 227 from_jid = jid.JID(message_elt["from"])
183 room_jid = from_jid.userhostJID() 228 room_jid = from_jid.userhostJID()
184 if room_jid in client._muc_client.joined_rooms: 229 if room_jid in client._muc_client.joined_rooms:
185 room = client._muc_client.joined_rooms[room_jid] 230 room = client._muc_client.joined_rooms[room_jid]
186 if room.state != ROOM_STATE_LIVE: 231 if room.state != ROOM_STATE_LIVE:
187 if getattr(room, "_history_type", HISTORY_LEGACY) == HISTORY_LEGACY: 232 if getattr(room, "_history_type", HISTORY_LEGACY) == HISTORY_LEGACY:
188 # With MAM history, order is different, and we can get live 233 # With MAM history, order is different, and we can get live
189 # messages before history is complete, so this is not a warning 234 # messages before history is complete, so this is not a warning
190 # but an expected case. 235 # but an expected case.
191 # On the other hand, with legacy history, it's not normal. 236 # On the other hand, with legacy history, it's not normal.
192 log.warning(_( 237 log.warning(
193 "Received non delayed message in a room before its " 238 _(
194 "initialisation: state={state}, msg={msg}").format( 239 "Received non delayed message in a room before its "
195 state=room.state, 240 "initialisation: state={state}, msg={msg}"
196 msg=message_elt.toXml())) 241 ).format(state=room.state, msg=message_elt.toXml())
242 )
197 room._cache.append(message_elt) 243 room._cache.append(message_elt)
198 return False 244 return False
199 else: 245 else:
200 log.warning("Received groupchat message for a room which has not been " 246 log.warning(
201 "joined, ignoring it: {}".format(message_elt.toXml())) 247 "Received groupchat message for a room which has not been "
248 "joined, ignoring it: {}".format(message_elt.toXml())
249 )
202 return False 250 return False
203 return True 251 return True
204 252
205 def get_room(self, client: SatXMPPEntity, room_jid: jid.JID) -> muc.Room: 253 def get_room(self, client: SatXMPPEntity, room_jid: jid.JID) -> muc.Room:
206 """Retrieve Room instance from its jid 254 """Retrieve Room instance from its jid
282 # FIXME: getattr below is a Q&D fix as `subject` may not be set in early call 330 # FIXME: getattr below is a Q&D fix as `subject` may not be set in early call
283 # of _get_rooms_joined (during `join` call). The whole workflow of this 331 # of _get_rooms_joined (during `join` call). The whole workflow of this
284 # plugin should be refactored. 332 # plugin should be refactored.
285 getattr(room, "subject", ""), 333 getattr(room, "subject", ""),
286 [s.name for s in room.statuses], 334 [s.name for s in room.statuses],
287 profile 335 profile,
288 ] 336 ]
289 337
290 def _ui_room_join_cb(self, data, profile): 338 def _ui_room_join_cb(self, data, profile):
291 room_jid = jid.JID(data['index']) 339 room_jid = jid.JID(data["index"])
292 client = self.host.get_client(profile) 340 client = self.host.get_client(profile)
293 defer.ensureDeferred( 341 defer.ensureDeferred(self.join(client, room_jid))
294 self.join(client, room_jid)
295 )
296 return {} 342 return {}
297 343
298 def _password_ui_cb(self, data, client, room_jid, nick): 344 def _password_ui_cb(self, data, client, room_jid, nick):
299 """Called when the user has given room password (or cancelled)""" 345 """Called when the user has given room password (or cancelled)"""
300 if C.bool(data.get(C.XMLUI_DATA_CANCELLED, "false")): 346 if C.bool(data.get(C.XMLUI_DATA_CANCELLED, "false")):
301 log.info("room join for {} is cancelled".format(room_jid.userhost())) 347 log.info("room join for {} is cancelled".format(room_jid.userhost()))
302 raise failure.Failure(exceptions.CancelError(D_("Room joining cancelled by user"))) 348 raise failure.Failure(
303 password = data[xml_tools.form_escape('password')] 349 exceptions.CancelError(D_("Room joining cancelled by user"))
304 return client._muc_client.join(room_jid, nick, password).addCallbacks(self._join_cb, self._join_eb, (client, room_jid, nick), errbackArgs=(client, room_jid, nick, password)) 350 )
351 password = data[xml_tools.form_escape("password")]
352 return client._muc_client.join(room_jid, nick, password).addCallbacks(
353 self._join_cb,
354 self._join_eb,
355 (client, room_jid, nick),
356 errbackArgs=(client, room_jid, nick, password),
357 )
305 358
306 def _show_list_ui(self, items, client, service): 359 def _show_list_ui(self, items, client, service):
307 xmlui = xml_tools.XMLUI(title=D_('Rooms in {}'.format(service.full()))) 360 xmlui = xml_tools.XMLUI(title=D_("Rooms in {}".format(service.full())))
308 adv_list = xmlui.change_container('advanced_list', columns=1, selectable='single', callback_id=self._room_join_id) 361 adv_list = xmlui.change_container(
362 "advanced_list",
363 columns=1,
364 selectable="single",
365 callback_id=self._room_join_id,
366 )
309 items = sorted(items, key=lambda i: i.name.lower()) 367 items = sorted(items, key=lambda i: i.name.lower())
310 for item in items: 368 for item in items:
311 adv_list.set_row_index(item.entity.full()) 369 adv_list.set_row_index(item.entity.full())
312 xmlui.addText(item.name) 370 xmlui.addText(item.name)
313 adv_list.end() 371 adv_list.end()
314 self.host.action_new({'xmlui': xmlui.toXml()}, profile=client.profile) 372 self.host.action_new({"xmlui": xmlui.toXml()}, profile=client.profile)
315 373
316 def _join_cb(self, room, client, room_jid, nick): 374 def _join_cb(self, room, client, room_jid, nick):
317 """Called when the user is in the requested room""" 375 """Called when the user is in the requested room"""
318 if room.locked: 376 if room.locked:
319 # FIXME: the current behaviour is to create an instant room 377 # FIXME: the current behaviour is to create an instant room
320 # and send the signal only when the room is unlocked 378 # and send the signal only when the room is unlocked
321 # a proper configuration management should be done 379 # a proper configuration management should be done
322 log.debug(_("room locked !")) 380 log.debug(_("room locked !"))
323 d = client._muc_client.configure(room.roomJID, {}) 381 d = client._muc_client.configure(room.roomJID, {})
324 d.addErrback(self.host.log_errback, 382 d.addErrback(
325 msg=_('Error while configuring the room: {failure_}')) 383 self.host.log_errback,
384 msg=_("Error while configuring the room: {failure_}"),
385 )
326 return room.fully_joined 386 return room.fully_joined
327 387
328 def _join_eb(self, failure_, client, room_jid, nick, password): 388 def _join_eb(self, failure_, client, room_jid, nick, password):
329 """Called when something is going wrong when joining the room""" 389 """Called when something is going wrong when joining the room"""
330 try: 390 try:
331 condition = failure_.value.condition 391 condition = failure_.value.condition
332 except AttributeError: 392 except AttributeError:
333 msg_suffix = f': {failure_}' 393 msg_suffix = f": {failure_}"
334 else: 394 else:
335 if condition == 'conflict': 395 if condition == "conflict":
336 # we have a nickname conflict, we try again with "_" suffixed to current nickname 396 # we have a nickname conflict, we try again with "_" suffixed to current nickname
337 nick += '_' 397 nick += "_"
338 return client._muc_client.join(room_jid, nick, password).addCallbacks(self._join_cb, self._join_eb, (client, room_jid, nick), errbackArgs=(client, room_jid, nick, password)) 398 return client._muc_client.join(room_jid, nick, password).addCallbacks(
339 elif condition == 'not-allowed': 399 self._join_cb,
400 self._join_eb,
401 (client, room_jid, nick),
402 errbackArgs=(client, room_jid, nick, password),
403 )
404 elif condition == "not-allowed":
340 # room is restricted, we need a password 405 # room is restricted, we need a password
341 password_ui = xml_tools.XMLUI("form", title=D_('Room {} is restricted').format(room_jid.userhost()), submit_id='') 406 password_ui = xml_tools.XMLUI(
342 password_ui.addText(D_("This room is restricted, please enter the password")) 407 "form",
343 password_ui.addPassword('password') 408 title=D_("Room {} is restricted").format(room_jid.userhost()),
409 submit_id="",
410 )
411 password_ui.addText(
412 D_("This room is restricted, please enter the password")
413 )
414 password_ui.addPassword("password")
344 d = xml_tools.defer_xmlui(self.host, password_ui, profile=client.profile) 415 d = xml_tools.defer_xmlui(self.host, password_ui, profile=client.profile)
345 d.addCallback(self._password_ui_cb, client, room_jid, nick) 416 d.addCallback(self._password_ui_cb, client, room_jid, nick)
346 return d 417 return d
347 418
348 msg_suffix = ' with condition "{}"'.format(failure_.value.condition) 419 msg_suffix = ' with condition "{}"'.format(failure_.value.condition)
349 420
350 mess = D_("Error while joining the room {room}{suffix}".format( 421 mess = D_(
351 room = room_jid.userhost(), suffix = msg_suffix)) 422 "Error while joining the room {room}{suffix}".format(
423 room=room_jid.userhost(), suffix=msg_suffix
424 )
425 )
352 log.warning(mess) 426 log.warning(mess)
353 xmlui = xml_tools.note(mess, D_("Group chat error"), level=C.XMLUI_DATA_LVL_ERROR) 427 xmlui = xml_tools.note(mess, D_("Group chat error"), level=C.XMLUI_DATA_LVL_ERROR)
354 self.host.action_new({'xmlui': xmlui.toXml()}, profile=client.profile) 428 self.host.action_new({"xmlui": xmlui.toXml()}, profile=client.profile)
355 429
356 @staticmethod 430 @staticmethod
357 def _get_occupants(room): 431 def _get_occupants(room):
358 """Get occupants of a room in a form suitable for bridge""" 432 """Get occupants of a room in a form suitable for bridge"""
359 return {u.nick: {k:str(getattr(u,k) or '') for k in OCCUPANT_KEYS} for u in list(room.roster.values())} 433 return {
434 u.nick: {k: str(getattr(u, k) or "") for k in OCCUPANT_KEYS}
435 for u in list(room.roster.values())
436 }
360 437
361 def _get_room_occupants(self, room_jid_s, profile_key): 438 def _get_room_occupants(self, room_jid_s, profile_key):
362 client = self.host.get_client(profile_key) 439 client = self.host.get_client(profile_key)
363 room_jid = jid.JID(room_jid_s) 440 room_jid = jid.JID(room_jid_s)
364 return self.get_room_occupants(client, room_jid) 441 return self.get_room_occupants(client, room_jid)
375 """Return rooms where user is""" 452 """Return rooms where user is"""
376 result = [] 453 result = []
377 for room in list(client._muc_client.joined_rooms.values()): 454 for room in list(client._muc_client.joined_rooms.values()):
378 if room.state == ROOM_STATE_LIVE: 455 if room.state == ROOM_STATE_LIVE:
379 result.append( 456 result.append(
380 (room.roomJID.userhost(), 457 (
381 self._get_occupants(room), 458 room.roomJID.userhost(),
382 room.nick, 459 self._get_occupants(room),
383 room.subject, 460 room.nick,
384 [s.name for s in room.statuses], 461 room.subject,
462 [s.name for s in room.statuses],
385 ) 463 )
386 ) 464 )
387 return result 465 return result
388 466
389 def _get_room_nick(self, room_jid_s, profile_key=C.PROF_KEY_NONE): 467 def _get_room_nick(self, room_jid_s, profile_key=C.PROF_KEY_NONE):
413 @param menu_data: %(menu_data)s 491 @param menu_data: %(menu_data)s
414 @param profile: %(doc_profile)s 492 @param profile: %(doc_profile)s
415 """ 493 """
416 client = self.host.get_client(profile) 494 client = self.host.get_client(profile)
417 try: 495 try:
418 room_jid = jid.JID(menu_data['room_jid']) 496 room_jid = jid.JID(menu_data["room_jid"])
419 except KeyError: 497 except KeyError:
420 log.error(_("room_jid key is not present !")) 498 log.error(_("room_jid key is not present !"))
421 return defer.fail(exceptions.DataError) 499 return defer.fail(exceptions.DataError)
422 500
423 def xmlui_received(xmlui): 501 def xmlui_received(xmlui):
424 if not xmlui: 502 if not xmlui:
425 msg = D_("No configuration available for this room") 503 msg = D_("No configuration available for this room")
426 return {"xmlui": xml_tools.note(msg).toXml()} 504 return {"xmlui": xml_tools.note(msg).toXml()}
427 return {"xmlui": xmlui.toXml()} 505 return {"xmlui": xmlui.toXml()}
506
428 return self.configure_room(client, room_jid).addCallback(xmlui_received) 507 return self.configure_room(client, room_jid).addCallback(xmlui_received)
429 508
430 def configure_room(self, client, room_jid): 509 def configure_room(self, client, room_jid):
431 """return the room configuration form 510 """return the room configuration form
432 511
455 client = self.host.get_client(profile) 534 client = self.host.get_client(profile)
456 try: 535 try:
457 session_data = self._sessions.profile_get(raw_data["session_id"], profile) 536 session_data = self._sessions.profile_get(raw_data["session_id"], profile)
458 except KeyError: 537 except KeyError:
459 log.warning(D_("Session ID doesn't exist, session has probably expired.")) 538 log.warning(D_("Session ID doesn't exist, session has probably expired."))
460 _dialog = xml_tools.XMLUI('popup', title=D_('Room configuration failed')) 539 _dialog = xml_tools.XMLUI("popup", title=D_("Room configuration failed"))
461 _dialog.addText(D_("Session ID doesn't exist, session has probably expired.")) 540 _dialog.addText(D_("Session ID doesn't exist, session has probably expired."))
462 return defer.succeed({'xmlui': _dialog.toXml()}) 541 return defer.succeed({"xmlui": _dialog.toXml()})
463 542
464 data = xml_tools.xmlui_result_2_data_form_result(raw_data) 543 data = xml_tools.xmlui_result_2_data_form_result(raw_data)
465 d = client._muc_client.configure(session_data['room_jid'], data) 544 d = client._muc_client.configure(session_data["room_jid"], data)
466 _dialog = xml_tools.XMLUI('popup', title=D_('Room configuration succeed')) 545 _dialog = xml_tools.XMLUI("popup", title=D_("Room configuration succeed"))
467 _dialog.addText(D_("The new settings have been saved.")) 546 _dialog.addText(D_("The new settings have been saved."))
468 d.addCallback(lambda ignore: {'xmlui': _dialog.toXml()}) 547 d.addCallback(lambda ignore: {"xmlui": _dialog.toXml()})
469 del self._sessions[raw_data["session_id"]] 548 del self._sessions[raw_data["session_id"]]
470 return d 549 return d
471 550
472 def is_nick_in_room(self, client, room_jid, nick): 551 def is_nick_in_room(self, client, room_jid, nick):
473 """Tell if a nick is currently present in a room""" 552 """Tell if a nick is currently present in a room"""
475 return client._muc_client.joined_rooms[room_jid].inRoster(muc.User(nick)) 554 return client._muc_client.joined_rooms[room_jid].inRoster(muc.User(nick))
476 555
477 def _get_muc_service(self, jid_=None, profile=C.PROF_KEY_NONE): 556 def _get_muc_service(self, jid_=None, profile=C.PROF_KEY_NONE):
478 client = self.host.get_client(profile) 557 client = self.host.get_client(profile)
479 d = defer.ensureDeferred(self.get_muc_service(client, jid_ or None)) 558 d = defer.ensureDeferred(self.get_muc_service(client, jid_ or None))
480 d.addCallback(lambda service_jid: service_jid.full() if service_jid is not None else '') 559 d.addCallback(
560 lambda service_jid: service_jid.full() if service_jid is not None else ""
561 )
481 return d 562 return d
482 563
483 async def get_muc_service( 564 async def get_muc_service(
484 self, 565 self, client: SatXMPPEntity, jid_: Optional[jid.JID] = None
485 client: SatXMPPEntity, 566 ) -> Optional[jid.JID]:
486 jid_: Optional[jid.JID] = None) -> Optional[jid.JID]:
487 """Return first found MUC service of an entity 567 """Return first found MUC service of an entity
488 568
489 @param jid_: entity which may have a MUC service, or None for our own server 569 @param jid_: entity which may have a MUC service, or None for our own server
490 @return: found service jid or None 570 @return: found service jid or None
491 """ 571 """
495 except AttributeError: 575 except AttributeError:
496 pass 576 pass
497 else: 577 else:
498 # we have a cached value, we return it 578 # we have a cached value, we return it
499 return muc_service 579 return muc_service
500 services = await self.host.find_service_entities(client, "conference", "text", jid_) 580 services = await self.host.find_service_entities(
581 client, "conference", "text", jid_
582 )
501 for service in services: 583 for service in services:
502 if ".irc." not in service.userhost(): 584 if ".irc." not in service.userhost():
503 # FIXME: 585 # FIXME:
504 # This ugly hack is here to avoid an issue with openfire: the IRC gateway 586 # This ugly hack is here to avoid an issue with openfire: the IRC gateway
505 # use "conference/text" identity (instead of "conference/irc") 587 # use "conference/text" identity (instead of "conference/irc")
512 def _get_unique_name(self, muc_service="", profile_key=C.PROF_KEY_NONE): 594 def _get_unique_name(self, muc_service="", profile_key=C.PROF_KEY_NONE):
513 client = self.host.get_client(profile_key) 595 client = self.host.get_client(profile_key)
514 return self.get_unique_name(client, muc_service or None).full() 596 return self.get_unique_name(client, muc_service or None).full()
515 597
516 def get_unique_name( 598 def get_unique_name(
517 self, 599 self, client: SatXMPPEntity, muc_service: jid.JID | None = None, prefix: str = ""
518 client: SatXMPPEntity,
519 muc_service: jid.JID|None = None,
520 prefix: str = ""
521 ) -> jid.JID: 600 ) -> jid.JID:
522 """Return unique name for a room, avoiding collision 601 """Return unique name for a room, avoiding collision
523 602
524 @param client: Client instance. 603 @param client: Client instance.
525 @param muc_service: leave empty string to use the default service 604 @param muc_service: leave empty string to use the default service
544 def get_default_muc(self): 623 def get_default_muc(self):
545 """Return the default MUC. 624 """Return the default MUC.
546 625
547 @return: unicode 626 @return: unicode
548 """ 627 """
549 return self.host.memory.config_get(CONFIG_SECTION, 'default_muc', default_conf['default_muc']) 628 return self.host.memory.config_get(
629 CONFIG_SECTION, "default_muc", default_conf["default_muc"]
630 )
550 631
551 def _bridge_join_eb(self, failure_, client): 632 def _bridge_join_eb(self, failure_, client):
552 failure_.trap(AlreadyJoined) 633 failure_.trap(AlreadyJoined)
553 room = failure_.value.room 634 room = failure_.value.room
554 return [True] + self._get_room_joined_args(room, client.profile) 635 return [True] + self._get_room_joined_args(room, client.profile)
562 if room_jid_s: 643 if room_jid_s:
563 muc_service = client.muc_service 644 muc_service = client.muc_service
564 try: 645 try:
565 room_jid = jid.JID(room_jid_s) 646 room_jid = jid.JID(room_jid_s)
566 except (RuntimeError, jid.InvalidFormat, AttributeError): 647 except (RuntimeError, jid.InvalidFormat, AttributeError):
567 return defer.fail(jid.InvalidFormat(_("Invalid room identifier: {room_id}'. Please give a room short or full identifier like 'room' or 'room@{muc_service}'.").format( 648 return defer.fail(
568 room_id=room_jid_s, 649 jid.InvalidFormat(
569 muc_service=str(muc_service)))) 650 _(
651 "Invalid room identifier: {room_id}'. Please give a room short or full identifier like 'room' or 'room@{muc_service}'."
652 ).format(room_id=room_jid_s, muc_service=str(muc_service))
653 )
654 )
570 if not room_jid.user: 655 if not room_jid.user:
571 room_jid.user, room_jid.host = room_jid.host, muc_service 656 room_jid.user, room_jid.host = room_jid.host, muc_service
572 else: 657 else:
573 room_jid = self.get_unique_name(client) 658 room_jid = self.get_unique_name(client)
574 # TODO: error management + signal in bridge 659 # TODO: error management + signal in bridge
575 d = defer.ensureDeferred( 660 d = defer.ensureDeferred(self.join(client, room_jid, nick, options or None))
576 self.join(client, room_jid, nick, options or None) 661
577 ) 662 d.addCallback(
578 663 lambda room: [False] + self._get_room_joined_args(room, client.profile)
579 d.addCallback(lambda room: [False] + self._get_room_joined_args(room, client.profile)) 664 )
580 d.addErrback(self._bridge_join_eb, client) 665 d.addErrback(self._bridge_join_eb, client)
581 return d 666 return d
582 667
583 async def join( 668 async def join(
584 self, 669 self,
585 client: SatXMPPEntity, 670 client: SatXMPPEntity,
586 room_jid: jid.JID, 671 room_jid: jid.JID,
587 nick: Optional[str] = None, 672 nick: Optional[str] = None,
588 options: Optional[dict] = None 673 options: Optional[dict] = None,
589 ) -> Optional[muc.Room]: 674 ) -> Optional[muc.Room]:
590 if not nick: 675 if not nick:
591 nick = client.jid.user 676 nick = client.jid.user
592 if options is None: 677 if options is None:
593 options = {} 678 options = {}
594 if room_jid in client._muc_client.joined_rooms: 679 if room_jid in client._muc_client.joined_rooms:
595 room = client._muc_client.joined_rooms[room_jid] 680 room = client._muc_client.joined_rooms[room_jid]
596 log.info(_('{profile} is already in room {room_jid}').format( 681 log.info(
597 profile=client.profile, room_jid = room_jid.userhost())) 682 _("{profile} is already in room {room_jid}").format(
683 profile=client.profile, room_jid=room_jid.userhost()
684 )
685 )
598 raise AlreadyJoined(room) 686 raise AlreadyJoined(room)
599 log.info(_("[{profile}] is joining room {room} with nick {nick}").format( 687 log.info(
600 profile=client.profile, room=room_jid.userhost(), nick=nick)) 688 _("[{profile}] is joining room {room} with nick {nick}").format(
689 profile=client.profile, room=room_jid.userhost(), nick=nick
690 )
691 )
601 self.host.bridge.muc_room_prepare_join(room_jid.userhost(), client.profile) 692 self.host.bridge.muc_room_prepare_join(room_jid.userhost(), client.profile)
602 693
603 password = options.get("password") 694 password = options.get("password")
604 695
605 try: 696 try:
609 self._join_eb(failure.Failure(e), client, room_jid, nick, password) 700 self._join_eb(failure.Failure(e), client, room_jid, nick, password)
610 ) 701 )
611 else: 702 else:
612 room.on_joined_callbacks = [] 703 room.on_joined_callbacks = []
613 room.on_left_callbacks = [] 704 room.on_left_callbacks = []
614 await defer.ensureDeferred( 705 await defer.ensureDeferred(self._join_cb(room, client, room_jid, nick))
615 self._join_cb(room, client, room_jid, nick)
616 )
617 return room 706 return room
618 707
619 def pop_rooms(self, client): 708 def pop_rooms(self, client):
620 """Remove rooms and return data needed to re-join them 709 """Remove rooms and return data needed to re-join them
621 710
667 @param options (dict): attribute with extra info (reason, password) as in #XEP-0045 756 @param options (dict): attribute with extra info (reason, password) as in #XEP-0045
668 """ 757 """
669 if options is None: 758 if options is None:
670 options = {} 759 options = {}
671 self.check_room_joined(client, room_jid) 760 self.check_room_joined(client, room_jid)
672 return client._muc_client.kick(room_jid, nick, reason=options.get('reason', None)) 761 return client._muc_client.kick(room_jid, nick, reason=options.get("reason", None))
673 762
674 def ban(self, client, entity_jid, room_jid, options=None): 763 def ban(self, client, entity_jid, room_jid, options=None):
675 """Ban an entity from the room 764 """Ban an entity from the room
676 765
677 @param entity_jid (JID): bare jid of the entity to be banned 766 @param entity_jid (JID): bare jid of the entity to be banned
681 self.check_room_joined(client, room_jid) 770 self.check_room_joined(client, room_jid)
682 if options is None: 771 if options is None:
683 options = {} 772 options = {}
684 assert not entity_jid.resource 773 assert not entity_jid.resource
685 assert not room_jid.resource 774 assert not room_jid.resource
686 return client._muc_client.ban(room_jid, entity_jid, reason=options.get('reason', None)) 775 return client._muc_client.ban(
776 room_jid, entity_jid, reason=options.get("reason", None)
777 )
687 778
688 def affiliate(self, client, entity_jid, room_jid, options): 779 def affiliate(self, client, entity_jid, room_jid, options):
689 """Change the affiliation of an entity 780 """Change the affiliation of an entity
690 781
691 @param entity_jid (JID): bare jid of the entity 782 @param entity_jid (JID): bare jid of the entity
693 @param options: attribute with extra info (reason, nick) as in #XEP-0045 784 @param options: attribute with extra info (reason, nick) as in #XEP-0045
694 """ 785 """
695 self.check_room_joined(client, room_jid) 786 self.check_room_joined(client, room_jid)
696 assert not entity_jid.resource 787 assert not entity_jid.resource
697 assert not room_jid.resource 788 assert not room_jid.resource
698 assert 'affiliation' in options 789 assert "affiliation" in options
699 # TODO: handles reason and nick 790 # TODO: handles reason and nick
700 return client._muc_client.modifyAffiliationList(room_jid, [entity_jid], options['affiliation']) 791 return client._muc_client.modifyAffiliationList(
792 room_jid, [entity_jid], options["affiliation"]
793 )
701 794
702 # Text commands # 795 # Text commands #
703 796
704 def cmd_nick(self, client, mess_data): 797 def cmd_nick(self, client, mess_data):
705 """change nickname 798 """change nickname
731 # the command has been entered in a one2one conversation, so we use 824 # the command has been entered in a one2one conversation, so we use
732 # our server MUC service as default service 825 # our server MUC service as default service
733 muc_service = client.muc_service or "" 826 muc_service = client.muc_service or ""
734 nick = client.jid.user 827 nick = client.jid.user
735 room_jid = self.text_cmds.get_room_jid(room_raw, muc_service) 828 room_jid = self.text_cmds.get_room_jid(room_raw, muc_service)
736 defer.ensureDeferred( 829 defer.ensureDeferred(self.join(client, room_jid, nick, {}))
737 self.join(client, room_jid, nick, {})
738 )
739 830
740 return False 831 return False
741 832
742 def cmd_leave(self, client, mess_data): 833 def cmd_leave(self, client, mess_data):
743 """quit a room 834 """quit a room
776 except (IndexError, AssertionError): 867 except (IndexError, AssertionError):
777 feedback = _("You must provide a member's nick to kick.") 868 feedback = _("You must provide a member's nick to kick.")
778 self.text_cmds.feed_back(client, feedback, mess_data) 869 self.text_cmds.feed_back(client, feedback, mess_data)
779 return False 870 return False
780 871
781 reason = ' '.join(options[1:]) if len(options) > 1 else None 872 reason = " ".join(options[1:]) if len(options) > 1 else None
782 873
783 d = self.kick(client, nick, mess_data["to"], {"reason": reason}) 874 d = self.kick(client, nick, mess_data["to"], {"reason": reason})
784 875
785 def cb(__): 876 def cb(__):
786 feedback_msg = _('You have kicked {}').format(nick) 877 feedback_msg = _("You have kicked {}").format(nick)
787 if reason is not None: 878 if reason is not None:
788 feedback_msg += _(' for the following reason: {reason}').format( 879 feedback_msg += _(" for the following reason: {reason}").format(
789 reason=reason 880 reason=reason
790 ) 881 )
791 self.text_cmds.feed_back(client, feedback_msg, mess_data) 882 self.text_cmds.feed_back(client, feedback_msg, mess_data)
792 return True 883 return True
884
793 d.addCallback(cb) 885 d.addCallback(cb)
794 return d 886 return d
795 887
796 def cmd_ban(self, client, mess_data): 888 def cmd_ban(self, client, mess_data):
797 """ban an entity from the room 889 """ban an entity from the room
802 """ 894 """
803 options = mess_data["unparsed"].strip().split() 895 options = mess_data["unparsed"].strip().split()
804 try: 896 try:
805 jid_s = options[0] 897 jid_s = options[0]
806 entity_jid = jid.JID(jid_s).userhostJID() 898 entity_jid = jid.JID(jid_s).userhostJID()
807 assert(entity_jid.user) 899 assert entity_jid.user
808 assert(entity_jid.host) 900 assert entity_jid.host
809 except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, 901 except (
810 AssertionError): 902 RuntimeError,
903 jid.InvalidFormat,
904 AttributeError,
905 IndexError,
906 AssertionError,
907 ):
811 feedback = _( 908 feedback = _(
812 "You must provide a valid JID to ban, like in '/ban contact@example.net'" 909 "You must provide a valid JID to ban, like in '/ban contact@example.net'"
813 ) 910 )
814 self.text_cmds.feed_back(client, feedback, mess_data) 911 self.text_cmds.feed_back(client, feedback, mess_data)
815 return False 912 return False
816 913
817 reason = ' '.join(options[1:]) if len(options) > 1 else None 914 reason = " ".join(options[1:]) if len(options) > 1 else None
818 915
819 d = self.ban(client, entity_jid, mess_data["to"], {"reason": reason}) 916 d = self.ban(client, entity_jid, mess_data["to"], {"reason": reason})
820 917
821 def cb(__): 918 def cb(__):
822 feedback_msg = _('You have banned {}').format(entity_jid) 919 feedback_msg = _("You have banned {}").format(entity_jid)
823 if reason is not None: 920 if reason is not None:
824 feedback_msg += _(' for the following reason: {reason}').format( 921 feedback_msg += _(" for the following reason: {reason}").format(
825 reason=reason 922 reason=reason
826 ) 923 )
827 self.text_cmds.feed_back(client, feedback_msg, mess_data) 924 self.text_cmds.feed_back(client, feedback_msg, mess_data)
828 return True 925 return True
926
829 d.addCallback(cb) 927 d.addCallback(cb)
830 return d 928 return d
831 929
832 def cmd_affiliate(self, client, mess_data): 930 def cmd_affiliate(self, client, mess_data):
833 """affiliate an entity to the room 931 """affiliate an entity to the room
842 """ 940 """
843 options = mess_data["unparsed"].strip().split() 941 options = mess_data["unparsed"].strip().split()
844 try: 942 try:
845 jid_s = options[0] 943 jid_s = options[0]
846 entity_jid = jid.JID(jid_s).userhostJID() 944 entity_jid = jid.JID(jid_s).userhostJID()
847 assert(entity_jid.user) 945 assert entity_jid.user
848 assert(entity_jid.host) 946 assert entity_jid.host
849 except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError): 947 except (
850 feedback = _("You must provide a valid JID to affiliate, like in '/affiliate contact@example.net member'") 948 RuntimeError,
949 jid.InvalidFormat,
950 AttributeError,
951 IndexError,
952 AssertionError,
953 ):
954 feedback = _(
955 "You must provide a valid JID to affiliate, like in '/affiliate contact@example.net member'"
956 )
851 self.text_cmds.feed_back(client, feedback, mess_data) 957 self.text_cmds.feed_back(client, feedback, mess_data)
852 return False 958 return False
853 959
854 affiliation = options[1] if len(options) > 1 else 'none' 960 affiliation = options[1] if len(options) > 1 else "none"
855 if affiliation not in AFFILIATIONS: 961 if affiliation not in AFFILIATIONS:
856 feedback = _("You must provide a valid affiliation: %s") % ' '.join(AFFILIATIONS) 962 feedback = _("You must provide a valid affiliation: %s") % " ".join(
963 AFFILIATIONS
964 )
857 self.text_cmds.feed_back(client, feedback, mess_data) 965 self.text_cmds.feed_back(client, feedback, mess_data)
858 return False 966 return False
859 967
860 d = self.affiliate(client, entity_jid, mess_data["to"], {'affiliation': affiliation}) 968 d = self.affiliate(
969 client, entity_jid, mess_data["to"], {"affiliation": affiliation}
970 )
861 971
862 def cb(__): 972 def cb(__):
863 feedback_msg = _('New affiliation for {entity}: {affiliation}').format( 973 feedback_msg = _("New affiliation for {entity}: {affiliation}").format(
864 entity=entity_jid, affiliation=affiliation) 974 entity=entity_jid, affiliation=affiliation
975 )
865 self.text_cmds.feed_back(client, feedback_msg, mess_data) 976 self.text_cmds.feed_back(client, feedback_msg, mess_data)
866 return True 977 return True
978
867 d.addCallback(cb) 979 d.addCallback(cb)
868 return d 980 return d
869 981
870 def cmd_title(self, client, mess_data): 982 def cmd_title(self, client, mess_data):
871 """change room's subject 983 """change room's subject
899 """ 1011 """
900 unparsed = mess_data["unparsed"].strip() 1012 unparsed = mess_data["unparsed"].strip()
901 try: 1013 try:
902 service = jid.JID(unparsed) 1014 service = jid.JID(unparsed)
903 except RuntimeError: 1015 except RuntimeError:
904 if mess_data['type'] == C.MESS_TYPE_GROUPCHAT: 1016 if mess_data["type"] == C.MESS_TYPE_GROUPCHAT:
905 room_jid = mess_data["to"] 1017 room_jid = mess_data["to"]
906 service = jid.JID(room_jid.host) 1018 service = jid.JID(room_jid.host)
907 elif client.muc_service is not None: 1019 elif client.muc_service is not None:
908 service = client.muc_service 1020 service = client.muc_service
909 else: 1021 else:
910 msg = D_("No known default MUC service {unparsed}").format( 1022 msg = D_("No known default MUC service {unparsed}").format(
911 unparsed=unparsed) 1023 unparsed=unparsed
1024 )
912 self.text_cmds.feed_back(client, msg, mess_data) 1025 self.text_cmds.feed_back(client, msg, mess_data)
913 return False 1026 return False
914 except jid.InvalidFormat: 1027 except jid.InvalidFormat:
915 msg = D_("{} is not a valid JID!".format(unparsed)) 1028 msg = D_("{} is not a valid JID!".format(unparsed))
916 self.text_cmds.feed_back(client, msg, mess_data) 1029 self.text_cmds.feed_back(client, msg, mess_data)
919 d.addCallback(self._show_list_ui, client, service) 1032 d.addCallback(self._show_list_ui, client, service)
920 1033
921 return False 1034 return False
922 1035
923 def _whois(self, client, whois_msg, mess_data, target_jid): 1036 def _whois(self, client, whois_msg, mess_data, target_jid):
924 """ Add MUC user information to whois """ 1037 """Add MUC user information to whois"""
925 if mess_data['type'] != "groupchat": 1038 if mess_data["type"] != "groupchat":
926 return 1039 return
927 if target_jid.userhostJID() not in client._muc_client.joined_rooms: 1040 if target_jid.userhostJID() not in client._muc_client.joined_rooms:
928 log.warning(_("This room has not been joined")) 1041 log.warning(_("This room has not been joined"))
929 return 1042 return
930 if not target_jid.resource: 1043 if not target_jid.resource:
931 return 1044 return
932 user = client._muc_client.joined_rooms[target_jid.userhostJID()].getUser(target_jid.resource) 1045 user = client._muc_client.joined_rooms[target_jid.userhostJID()].getUser(
1046 target_jid.resource
1047 )
933 whois_msg.append(_("Nickname: %s") % user.nick) 1048 whois_msg.append(_("Nickname: %s") % user.nick)
934 if user.entity: 1049 if user.entity:
935 whois_msg.append(_("Entity: %s") % user.entity) 1050 whois_msg.append(_("Entity: %s") % user.entity)
936 if user.affiliation != 'none': 1051 if user.affiliation != "none":
937 whois_msg.append(_("Affiliation: %s") % user.affiliation) 1052 whois_msg.append(_("Affiliation: %s") % user.affiliation)
938 if user.role != 'none': 1053 if user.role != "none":
939 whois_msg.append(_("Role: %s") % user.role) 1054 whois_msg.append(_("Role: %s") % user.role)
940 if user.status: 1055 if user.status:
941 whois_msg.append(_("Status: %s") % user.status) 1056 whois_msg.append(_("Status: %s") % user.status)
942 if user.show: 1057 if user.show:
943 whois_msg.append(_("Show: %s") % user.show) 1058 whois_msg.append(_("Show: %s") % user.show)
946 # FIXME: should we add a privacy parameters in settings to activate before 1061 # FIXME: should we add a privacy parameters in settings to activate before
947 # broadcasting the presence to all MUC rooms ? 1062 # broadcasting the presence to all MUC rooms ?
948 muc_client = client._muc_client 1063 muc_client = client._muc_client
949 for room_jid, room in muc_client.joined_rooms.items(): 1064 for room_jid, room in muc_client.joined_rooms.items():
950 elt = xml_tools.element_copy(presence_elt) 1065 elt = xml_tools.element_copy(presence_elt)
951 elt['to'] = room_jid.userhost() + '/' + room.nick 1066 elt["to"] = room_jid.userhost() + "/" + room.nick
952 client.presence.send(elt) 1067 client.presence.send(elt)
953 return True 1068 return True
954 1069
955 def presence_received_trigger(self, client, entity, show, priority, statuses): 1070 def presence_received_trigger(self, client, entity, show, priority, statuses):
956 entity_bare = entity.userhostJID() 1071 entity_bare = entity.userhostJID()
966 1081
967 def __init__(self, plugin_parent): 1082 def __init__(self, plugin_parent):
968 self.plugin_parent = plugin_parent 1083 self.plugin_parent = plugin_parent
969 muc.MUCClient.__init__(self) 1084 muc.MUCClient.__init__(self)
970 self._changing_nicks = set() # used to keep trace of who is changing nick, 1085 self._changing_nicks = set() # used to keep trace of who is changing nick,
971 # and to discard userJoinedRoom signal in this case 1086 # and to discard userJoinedRoom signal in this case
972 print("init SatMUCClient OK") 1087 print("init SatMUCClient OK")
973 1088
974 @property 1089 @property
975 def joined_rooms(self): 1090 def joined_rooms(self):
976 return self._rooms 1091 return self._rooms
998 """ 1113 """
999 new_state_idx = ROOM_STATES.index(new_state) 1114 new_state_idx = ROOM_STATES.index(new_state)
1000 if new_state_idx == -1: 1115 if new_state_idx == -1:
1001 raise exceptions.InternalError("unknown room state") 1116 raise exceptions.InternalError("unknown room state")
1002 if new_state_idx < 1: 1117 if new_state_idx < 1:
1003 raise exceptions.InternalError("unexpected new room state ({room}): {state}".format( 1118 raise exceptions.InternalError(
1004 room=room.userhost(), 1119 "unexpected new room state ({room}): {state}".format(
1005 state=new_state)) 1120 room=room.userhost(), state=new_state
1006 expected_state = ROOM_STATES[new_state_idx-1] 1121 )
1122 )
1123 expected_state = ROOM_STATES[new_state_idx - 1]
1007 if room.state != expected_state: 1124 if room.state != expected_state:
1008 log.error(_( 1125 log.error(
1009 "room {room} is not in expected state: room is in state {current_state} " 1126 _(
1010 "while we were expecting {expected_state}").format( 1127 "room {room} is not in expected state: room is in state {current_state} "
1011 room=room.roomJID.userhost(), 1128 "while we were expecting {expected_state}"
1012 current_state=room.state, 1129 ).format(
1013 expected_state=expected_state)) 1130 room=room.roomJID.userhost(),
1131 current_state=room.state,
1132 expected_state=expected_state,
1133 )
1134 )
1014 room.state = new_state 1135 room.state = new_state
1015 1136
1016 def _addRoom(self, room): 1137 def _addRoom(self, room):
1017 super(LiberviaMUCClient, self)._addRoom(room) 1138 super(LiberviaMUCClient, self)._addRoom(room)
1018 room._roster_ok = False # True when occupants list has been fully received 1139 room._roster_ok = False # True when occupants list has been fully received
1024 room._cache = [] 1145 room._cache = []
1025 # we only need to keep last presence status for each jid, so a dict is suitable 1146 # we only need to keep last presence status for each jid, so a dict is suitable
1026 room._cache_presence = {} 1147 room._cache_presence = {}
1027 1148
1028 async def _join_legacy( 1149 async def _join_legacy(
1029 self, 1150 self, client: SatXMPPEntity, room_jid: jid.JID, nick: str, password: Optional[str]
1030 client: SatXMPPEntity,
1031 room_jid: jid.JID,
1032 nick: str,
1033 password: Optional[str]
1034 ) -> muc.Room: 1151 ) -> muc.Room:
1035 """Join room an retrieve history with legacy method""" 1152 """Join room an retrieve history with legacy method"""
1036 mess_data_list = await self.host.memory.history_get( 1153 mess_data_list = await self.host.memory.history_get(
1037 room_jid, 1154 room_jid,
1038 client.jid.userhostJID(), 1155 client.jid.userhostJID(),
1039 limit=1, 1156 limit=1,
1040 between=True, 1157 between=True,
1041 profile=client.profile 1158 profile=client.profile,
1042 ) 1159 )
1043 if mess_data_list: 1160 if mess_data_list:
1044 timestamp = mess_data_list[0][1] 1161 timestamp = mess_data_list[0][1]
1045 # we use seconds since last message to get backlog without duplicates 1162 # we use seconds since last message to get backlog without duplicates
1046 # and we remove 1 second to avoid getting the last message again 1163 # and we remove 1 second to avoid getting the last message again
1047 seconds = int(time.time() - timestamp) - 1 1164 seconds = int(time.time() - timestamp) - 1
1048 else: 1165 else:
1049 seconds = None 1166 seconds = None
1050 1167
1051 room = await super(LiberviaMUCClient, self).join( 1168 room = await super(LiberviaMUCClient, self).join(
1052 room_jid, nick, muc.HistoryOptions(seconds=seconds), password) 1169 room_jid, nick, muc.HistoryOptions(seconds=seconds), password
1170 )
1053 # used to send bridge signal once backlog are written in history 1171 # used to send bridge signal once backlog are written in history
1054 room._history_type = HISTORY_LEGACY 1172 room._history_type = HISTORY_LEGACY
1055 room._history_d = defer.Deferred() 1173 room._history_d = defer.Deferred()
1056 room._history_d.callback(None) 1174 room._history_d.callback(None)
1057 return room 1175 return room
1058 1176
1059 async def _get_mam_history( 1177 async def _get_mam_history(
1060 self, 1178 self, client: SatXMPPEntity, room: muc.Room, room_jid: jid.JID
1061 client: SatXMPPEntity,
1062 room: muc.Room,
1063 room_jid: jid.JID
1064 ) -> None: 1179 ) -> None:
1065 """Retrieve history for rooms handling MAM""" 1180 """Retrieve history for rooms handling MAM"""
1066 history_d = room._history_d = defer.Deferred() 1181 history_d = room._history_d = defer.Deferred()
1067 # we trigger now the deferred so all callback are processed as soon as possible 1182 # we trigger now the deferred so all callback are processed as soon as possible
1068 # and in order 1183 # and in order
1071 last_mess = await self.host.memory.history_get( 1186 last_mess = await self.host.memory.history_get(
1072 room_jid, 1187 room_jid,
1073 None, 1188 None,
1074 limit=1, 1189 limit=1,
1075 between=False, 1190 between=False,
1076 filters={ 1191 filters={"types": C.MESS_TYPE_GROUPCHAT, "last_stanza_id": True},
1077 'types': C.MESS_TYPE_GROUPCHAT, 1192 profile=client.profile,
1078 'last_stanza_id': True}, 1193 )
1079 profile=client.profile)
1080 if last_mess: 1194 if last_mess:
1081 stanza_id = last_mess[0][-1]['stanza_id'] 1195 stanza_id = last_mess[0][-1]["stanza_id"]
1082 rsm_req = rsm.RSMRequest(max_=20, after=stanza_id) 1196 rsm_req = rsm.RSMRequest(max_=20, after=stanza_id)
1083 no_loop=False 1197 no_loop = False
1084 else: 1198 else:
1085 log.info("We have no MAM archive for room {room_jid}.".format( 1199 log.info(
1086 room_jid=room_jid)) 1200 "We have no MAM archive for room {room_jid}.".format(room_jid=room_jid)
1201 )
1087 # we don't want the whole archive if we have no archive yet 1202 # we don't want the whole archive if we have no archive yet
1088 # as it can be huge 1203 # as it can be huge
1089 rsm_req = rsm.RSMRequest(max_=50, before='') 1204 rsm_req = rsm.RSMRequest(max_=50, before="")
1090 no_loop=True 1205 no_loop = True
1091 1206
1092 mam_req = mam.MAMRequest(rsm_=rsm_req) 1207 mam_req = mam.MAMRequest(rsm_=rsm_req)
1093 complete = False 1208 complete = False
1094 count = 0 1209 count = 0
1095 while not complete: 1210 while not complete:
1096 try: 1211 try:
1097 mam_data = await self._mam.get_archives(client, mam_req, 1212 mam_data = await self._mam.get_archives(client, mam_req, service=room_jid)
1098 service=room_jid)
1099 except xmpp_error.StanzaError as e: 1213 except xmpp_error.StanzaError as e:
1100 if last_mess and e.condition == 'item-not-found': 1214 if last_mess and e.condition == "item-not-found":
1101 log.warning( 1215 log.warning(
1102 f"requested item (with id {stanza_id!r}) can't be found in " 1216 f"requested item (with id {stanza_id!r}) can't be found in "
1103 f"history of {room_jid}, history has probably been purged on " 1217 f"history of {room_jid}, history has probably been purged on "
1104 f"server.") 1218 f"server."
1219 )
1105 # we get last items like for a new room 1220 # we get last items like for a new room
1106 rsm_req = rsm.RSMRequest(max_=50, before='') 1221 rsm_req = rsm.RSMRequest(max_=50, before="")
1107 mam_req = mam.MAMRequest(rsm_=rsm_req) 1222 mam_req = mam.MAMRequest(rsm_=rsm_req)
1108 no_loop=True 1223 no_loop = True
1109 continue 1224 continue
1110 else: 1225 else:
1111 raise e 1226 raise e
1112 elt_list, rsm_response, mam_response = mam_data 1227 elt_list, rsm_response, mam_response = mam_data
1113 complete = True if no_loop else mam_response["complete"] 1228 complete = True if no_loop else mam_response["complete"]
1120 count += len(elt_list) 1235 count += len(elt_list)
1121 1236
1122 for mess_elt in elt_list: 1237 for mess_elt in elt_list:
1123 try: 1238 try:
1124 fwd_message_elt = self._mam.get_message_from_result( 1239 fwd_message_elt = self._mam.get_message_from_result(
1125 client, mess_elt, mam_req, service=room_jid) 1240 client, mess_elt, mam_req, service=room_jid
1241 )
1126 except exceptions.DataError: 1242 except exceptions.DataError:
1127 continue 1243 continue
1128 if fwd_message_elt.getAttribute("to"): 1244 if fwd_message_elt.getAttribute("to"):
1129 log.warning( 1245 log.warning(
1130 'Forwarded message element has a "to" attribute while it is ' 1246 'Forwarded message element has a "to" attribute while it is '
1131 'forbidden by specifications') 1247 "forbidden by specifications"
1248 )
1132 fwd_message_elt["to"] = client.jid.full() 1249 fwd_message_elt["to"] = client.jid.full()
1133 client.messageProt.onMessage(fwd_message_elt) 1250 client.messageProt.onMessage(fwd_message_elt)
1134 client._muc_client._onGroupChat(fwd_message_elt) 1251 client._muc_client._onGroupChat(fwd_message_elt)
1135 1252
1136 if not count: 1253 if not count:
1137 log.info(_("No message received while offline in {room_jid}".format( 1254 log.info(
1138 room_jid=room_jid))) 1255 _(
1256 "No message received while offline in {room_jid}".format(
1257 room_jid=room_jid
1258 )
1259 )
1260 )
1139 else: 1261 else:
1140 log.info( 1262 log.info(
1141 _("We have received {num_mess} message(s) in {room_jid} while " 1263 _(
1142 "offline.") 1264 "We have received {num_mess} message(s) in {room_jid} while "
1143 .format(num_mess=count, room_jid=room_jid)) 1265 "offline."
1266 ).format(num_mess=count, room_jid=room_jid)
1267 )
1144 1268
1145 # for legacy history, the following steps are done in receivedSubject but for MAM 1269 # for legacy history, the following steps are done in receivedSubject but for MAM
1146 # the order is different (we have to join then get MAM archive, so subject 1270 # the order is different (we have to join then get MAM archive, so subject
1147 # is received before archive), so we change state and add the callbacks here. 1271 # is received before archive), so we change state and add the callbacks here.
1148 self.change_room_state(room, ROOM_STATE_LIVE) 1272 self.change_room_state(room, ROOM_STATE_LIVE)
1149 history_d.addCallbacks(self._history_cb, self._history_eb, [room], 1273 history_d.addCallbacks(
1150 errbackArgs=[room]) 1274 self._history_cb, self._history_eb, [room], errbackArgs=[room]
1275 )
1151 1276
1152 # we wait for all callbacks to be processed 1277 # we wait for all callbacks to be processed
1153 await history_d 1278 await history_d
1154 1279
1155 async def _join_mam( 1280 async def _join_mam(
1156 self, 1281 self, client: SatXMPPEntity, room_jid: jid.JID, nick: str, password: Optional[str]
1157 client: SatXMPPEntity,
1158 room_jid: jid.JID,
1159 nick: str,
1160 password: Optional[str]
1161 ) -> muc.Room: 1282 ) -> muc.Room:
1162 """Join room and retrieve history using MAM""" 1283 """Join room and retrieve history using MAM"""
1163 room = await super(LiberviaMUCClient, self).join( 1284 room = await super(LiberviaMUCClient, self).join(
1164 # we don't want any history from room as we'll get it with MAM 1285 # we don't want any history from room as we'll get it with MAM
1165 room_jid, nick, muc.HistoryOptions(maxStanzas=0), password=password 1286 room_jid,
1287 nick,
1288 muc.HistoryOptions(maxStanzas=0),
1289 password=password,
1166 ) 1290 )
1167 room._history_type = HISTORY_MAM 1291 room._history_type = HISTORY_MAM
1168 # MAM history retrieval can be very long, and doesn't need to be sync, so we don't 1292 # MAM history retrieval can be very long, and doesn't need to be sync, so we don't
1169 # wait for it 1293 # wait for it
1170 defer.ensureDeferred(self._get_mam_history(client, room, room_jid)) 1294 defer.ensureDeferred(self._get_mam_history(client, room, room_jid))
1195 return 1319 return
1196 1320
1197 if user is None: 1321 if user is None:
1198 nick = presence.sender.resource 1322 nick = presence.sender.resource
1199 if not nick: 1323 if not nick:
1200 log.warning(_("missing nick in presence: {xml}").format( 1324 log.warning(
1201 xml = presence.toElement().toXml())) 1325 _("missing nick in presence: {xml}").format(
1326 xml=presence.toElement().toXml()
1327 )
1328 )
1202 return 1329 return
1203 user = muc.User(nick, presence.entity) 1330 user = muc.User(nick, presence.entity)
1204 1331
1205 # we want to keep statuses with room 1332 # we want to keep statuses with room
1206 # XXX: presence if broadcasted, and we won't have status code 1333 # XXX: presence if broadcasted, and we won't have status code
1207 # like 110 (REALJID_PUBLIC) after first <presence/> received 1334 # like 110 (REALJID_PUBLIC) after first <presence/> received
1208 # so we keep only the initial <presence> (with SELF_PRESENCE), 1335 # so we keep only the initial <presence> (with SELF_PRESENCE),
1209 # thus we check if attribute already exists 1336 # thus we check if attribute already exists
1210 if (not hasattr(room, 'statuses') 1337 if (
1211 and muc.STATUS_CODE.SELF_PRESENCE in presence.mucStatuses): 1338 not hasattr(room, "statuses")
1339 and muc.STATUS_CODE.SELF_PRESENCE in presence.mucStatuses
1340 ):
1212 room.statuses = presence.mucStatuses 1341 room.statuses = presence.mucStatuses
1213 1342
1214 # Update user data 1343 # Update user data
1215 user.role = presence.role 1344 user.role = presence.role
1216 user.affiliation = presence.affiliation 1345 user.affiliation = presence.affiliation
1248 def userJoinedRoom(self, room, user): 1377 def userJoinedRoom(self, room, user):
1249 if user.nick == room.nick: 1378 if user.nick == room.nick:
1250 # we have received our own nick, 1379 # we have received our own nick,
1251 # this mean that the full room roster was received 1380 # this mean that the full room roster was received
1252 self.change_room_state(room, ROOM_STATE_SELF_PRESENCE) 1381 self.change_room_state(room, ROOM_STATE_SELF_PRESENCE)
1253 log.debug("room {room} joined with nick {nick}".format( 1382 log.debug(
1254 room=room.occupantJID.userhost(), nick=user.nick)) 1383 "room {room} joined with nick {nick}".format(
1384 room=room.occupantJID.userhost(), nick=user.nick
1385 )
1386 )
1255 # we set type so we don't have to use a deferred 1387 # we set type so we don't have to use a deferred
1256 # with disco to check entity type 1388 # with disco to check entity type
1257 self.host.memory.update_entity_data( 1389 self.host.memory.update_entity_data(
1258 self.client, room.roomJID, C.ENTITY_TYPE, C.ENTITY_TYPE_MUC 1390 self.client, room.roomJID, C.ENTITY_TYPE, C.ENTITY_TYPE_MUC
1259 ) 1391 )
1260 elif room.state not in (ROOM_STATE_OCCUPANTS, ROOM_STATE_LIVE): 1392 elif room.state not in (ROOM_STATE_OCCUPANTS, ROOM_STATE_LIVE):
1261 log.warning( 1393 log.warning(
1262 "Received user presence data in a room before its initialisation " 1394 "Received user presence data in a room before its initialisation "
1263 "(current state: {state})," 1395 "(current state: {state}),"
1264 "this is not standard! Ignoring it: {room} ({nick})".format( 1396 "this is not standard! Ignoring it: {room} ({nick})".format(
1265 state=room.state, 1397 state=room.state, room=room.roomJID.userhost(), nick=user.nick
1266 room=room.roomJID.userhost(), 1398 )
1267 nick=user.nick)) 1399 )
1268 return 1400 return
1269 else: 1401 else:
1270 if not room.fully_joined.called: 1402 if not room.fully_joined.called:
1271 return 1403 return
1272 for cb in room.on_joined_callbacks: 1404 for cb in room.on_joined_callbacks:
1273 defer.ensureDeferred(cb(room, user)) 1405 defer.ensureDeferred(cb(room, user))
1274 try: 1406 try:
1275 self._changing_nicks.remove(user.nick) 1407 self._changing_nicks.remove(user.nick)
1276 except KeyError: 1408 except KeyError:
1277 # this is a new user 1409 # this is a new user
1278 log.debug(_("user {nick} has joined room {room_id}").format( 1410 log.debug(
1279 nick=user.nick, room_id=room.occupantJID.userhost())) 1411 _("user {nick} has joined room {room_id}").format(
1412 nick=user.nick, room_id=room.occupantJID.userhost()
1413 )
1414 )
1280 if not self.host.trigger.point( 1415 if not self.host.trigger.point(
1281 "MUC user joined", room, user, self.client.profile): 1416 "MUC user joined", room, user, self.client.profile
1417 ):
1282 return 1418 return
1283 1419
1284 extra = {'info_type': ROOM_USER_JOINED, 1420 extra = {
1285 'user_affiliation': user.affiliation, 1421 "info_type": ROOM_USER_JOINED,
1286 'user_role': user.role, 1422 "user_affiliation": user.affiliation,
1287 'user_nick': user.nick 1423 "user_role": user.role,
1288 } 1424 "user_nick": user.nick,
1425 }
1289 if user.entity is not None: 1426 if user.entity is not None:
1290 extra['user_entity'] = user.entity.full() 1427 extra["user_entity"] = user.entity.full()
1291 mess_data = { # dict is similar to the one used in client.onMessage 1428 mess_data = { # dict is similar to the one used in client.onMessage
1292 "from": room.roomJID, 1429 "from": room.roomJID,
1293 "to": self.client.jid, 1430 "to": self.client.jid,
1294 "uid": str(uuid.uuid4()), 1431 "uid": str(uuid.uuid4()),
1295 "message": {'': D_("=> {} has joined the room").format(user.nick)}, 1432 "message": {"": D_("=> {} has joined the room").format(user.nick)},
1296 "subject": {}, 1433 "subject": {},
1297 "type": C.MESS_TYPE_INFO, 1434 "type": C.MESS_TYPE_INFO,
1298 "extra": extra, 1435 "extra": extra,
1299 "timestamp": time.time(), 1436 "timestamp": time.time(),
1300 } 1437 }
1302 # while not indispensable. In the future an option may allow 1439 # while not indispensable. In the future an option may allow
1303 # to re-enable it 1440 # to re-enable it
1304 # self.client.message_add_to_history(mess_data) 1441 # self.client.message_add_to_history(mess_data)
1305 self.client.message_send_to_bridge(mess_data) 1442 self.client.message_send_to_bridge(mess_data)
1306 1443
1307
1308 def userLeftRoom(self, room, user): 1444 def userLeftRoom(self, room, user):
1309 if not self.host.trigger.point("MUC user left", room, user, self.client.profile): 1445 if not self.host.trigger.point("MUC user left", room, user, self.client.profile):
1310 return 1446 return
1311 if user.nick == room.nick: 1447 if user.nick == room.nick:
1312 # we left the room 1448 # we left the room
1313 room_jid_s = room.roomJID.userhost() 1449 room_jid_s = room.roomJID.userhost()
1314 log.info(_("Room ({room}) left ({profile})").format( 1450 log.info(
1315 room = room_jid_s, profile = self.client.profile)) 1451 _("Room ({room}) left ({profile})").format(
1316 self.host.memory.del_entity_cache(room.roomJID, profile_key=self.client.profile) 1452 room=room_jid_s, profile=self.client.profile
1453 )
1454 )
1455 self.host.memory.del_entity_cache(
1456 room.roomJID, profile_key=self.client.profile
1457 )
1317 self.host.bridge.muc_room_left(room.roomJID.userhost(), self.client.profile) 1458 self.host.bridge.muc_room_left(room.roomJID.userhost(), self.client.profile)
1318 elif room.state != ROOM_STATE_LIVE: 1459 elif room.state != ROOM_STATE_LIVE:
1319 log.warning("Received user presence data in a room before its initialisation (current state: {state})," 1460 log.warning(
1461 "Received user presence data in a room before its initialisation (current state: {state}),"
1320 "this is not standard! Ignoring it: {room} ({nick})".format( 1462 "this is not standard! Ignoring it: {room} ({nick})".format(
1321 state=room.state, 1463 state=room.state, room=room.roomJID.userhost(), nick=user.nick
1322 room=room.roomJID.userhost(), 1464 )
1323 nick=user.nick)) 1465 )
1324 return 1466 return
1325 else: 1467 else:
1326 if not room.fully_joined.called: 1468 if not room.fully_joined.called:
1327 return 1469 return
1328 log.debug(_("user {nick} left room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost())) 1470 log.debug(
1471 _("user {nick} left room {room_id}").format(
1472 nick=user.nick, room_id=room.occupantJID.userhost()
1473 )
1474 )
1329 for cb in room.on_left_callbacks: 1475 for cb in room.on_left_callbacks:
1330 defer.ensureDeferred(cb(room, user)) 1476 defer.ensureDeferred(cb(room, user))
1331 extra = {'info_type': ROOM_USER_LEFT, 1477 extra = {
1332 'user_affiliation': user.affiliation, 1478 "info_type": ROOM_USER_LEFT,
1333 'user_role': user.role, 1479 "user_affiliation": user.affiliation,
1334 'user_nick': user.nick 1480 "user_role": user.role,
1335 } 1481 "user_nick": user.nick,
1482 }
1336 if user.entity is not None: 1483 if user.entity is not None:
1337 extra['user_entity'] = user.entity.full() 1484 extra["user_entity"] = user.entity.full()
1338 mess_data = { # dict is similar to the one used in client.onMessage 1485 mess_data = { # dict is similar to the one used in client.onMessage
1339 "from": room.roomJID, 1486 "from": room.roomJID,
1340 "to": self.client.jid, 1487 "to": self.client.jid,
1341 "uid": str(uuid.uuid4()), 1488 "uid": str(uuid.uuid4()),
1342 "message": {'': D_("<= {} has left the room").format(user.nick)}, 1489 "message": {"": D_("<= {} has left the room").format(user.nick)},
1343 "subject": {}, 1490 "subject": {},
1344 "type": C.MESS_TYPE_INFO, 1491 "type": C.MESS_TYPE_INFO,
1345 "extra": extra, 1492 "extra": extra,
1346 "timestamp": time.time(), 1493 "timestamp": time.time(),
1347 } 1494 }
1348 # FIXME: disable history, see userJoinRoom comment 1495 # FIXME: disable history, see userJoinRoom comment
1349 # self.client.message_add_to_history(mess_data) 1496 # self.client.message_add_to_history(mess_data)
1350 self.client.message_send_to_bridge(mess_data) 1497 self.client.message_send_to_bridge(mess_data)
1351 1498
1352 def user_changed_nick(self, room, user, new_nick): 1499 def user_changed_nick(self, room, user, new_nick):
1353 self.host.bridge.muc_room_user_changed_nick(room.roomJID.userhost(), user.nick, new_nick, self.client.profile) 1500 self.host.bridge.muc_room_user_changed_nick(
1501 room.roomJID.userhost(), user.nick, new_nick, self.client.profile
1502 )
1354 1503
1355 def userUpdatedStatus(self, room, user, show, status): 1504 def userUpdatedStatus(self, room, user, show, status):
1356 entity = jid.JID(tuple=(room.roomJID.user, room.roomJID.host, user.nick)) 1505 entity = jid.JID(tuple=(room.roomJID.user, room.roomJID.host, user.nick))
1357 if hasattr(room, "_cache_presence"): 1506 if hasattr(room, "_cache_presence"):
1358 # room has a cache for presence, meaning it has not been fully 1507 # room has a cache for presence, meaning it has not been fully
1363 cache[entity] = { 1512 cache[entity] = {
1364 "room": room, 1513 "room": room,
1365 "user": user, 1514 "user": user,
1366 "show": show, 1515 "show": show,
1367 "status": status, 1516 "status": status,
1368 } 1517 }
1369 return 1518 return
1370 statuses = {C.PRESENCE_STATUSES_DEFAULT: status or ''} 1519 statuses = {C.PRESENCE_STATUSES_DEFAULT: status or ""}
1371 self.host.bridge.presence_update( 1520 self.host.bridge.presence_update(
1372 entity.full(), show or '', 0, statuses, self.client.profile) 1521 entity.full(), show or "", 0, statuses, self.client.profile
1522 )
1373 1523
1374 ## messages ## 1524 ## messages ##
1375 1525
1376 def receivedGroupChat(self, room, user, body): 1526 def receivedGroupChat(self, room, user, body):
1377 log.debug('receivedGroupChat: room=%s user=%s body=%s' % (room.roomJID.full(), user, body)) 1527 log.debug(
1528 "receivedGroupChat: room=%s user=%s body=%s"
1529 % (room.roomJID.full(), user, body)
1530 )
1378 1531
1379 ## subject ## 1532 ## subject ##
1380 1533
1381 def groupChatReceived(self, message): 1534 def groupChatReceived(self, message):
1382 """ 1535 """
1388 # We override this method to fix subject handling (empty strings were discarded) 1541 # We override this method to fix subject handling (empty strings were discarded)
1389 # FIXME: remove this once fixed upstream 1542 # FIXME: remove this once fixed upstream
1390 room, user = self._getRoomUser(message) 1543 room, user = self._getRoomUser(message)
1391 1544
1392 if room is None: 1545 if room is None:
1393 log.warning("No room found for message: {message}" 1546 log.warning(
1394 .format(message=message.toElement().toXml())) 1547 "No room found for message: {message}".format(
1548 message=message.toElement().toXml()
1549 )
1550 )
1395 return 1551 return
1396 1552
1397 if message.subject is not None: 1553 if message.subject is not None:
1398 self.receivedSubject(room, user, message.subject) 1554 self.receivedSubject(room, user, message.subject)
1399 elif message.delay is None: 1555 elif message.delay is None:
1422 cache_presence = room._cache_presence 1578 cache_presence = room._cache_presence
1423 del room._cache_presence 1579 del room._cache_presence
1424 for elem in cache: 1580 for elem in cache:
1425 self.client.xmlstream.dispatch(elem) 1581 self.client.xmlstream.dispatch(elem)
1426 for presence_data in cache_presence.values(): 1582 for presence_data in cache_presence.values():
1427 if not presence_data['show'] and not presence_data['status']: 1583 if not presence_data["show"] and not presence_data["status"]:
1428 # occupants are already sent in muc_room_joined, so if we don't have 1584 # occupants are already sent in muc_room_joined, so if we don't have
1429 # extra information like show or statuses, we can discard the signal 1585 # extra information like show or statuses, we can discard the signal
1430 continue 1586 continue
1431 else: 1587 else:
1432 self.userUpdatedStatus(**presence_data) 1588 self.userUpdatedStatus(**presence_data)
1440 # cf. http://xmpp.org/extensions/xep-0045.html#enter-subject 1596 # cf. http://xmpp.org/extensions/xep-0045.html#enter-subject
1441 room.subject = subject # FIXME: subject doesn't handle xml:lang 1597 room.subject = subject # FIXME: subject doesn't handle xml:lang
1442 if room.state != ROOM_STATE_LIVE: 1598 if room.state != ROOM_STATE_LIVE:
1443 if room._history_type == HISTORY_LEGACY: 1599 if room._history_type == HISTORY_LEGACY:
1444 self.change_room_state(room, ROOM_STATE_LIVE) 1600 self.change_room_state(room, ROOM_STATE_LIVE)
1445 room._history_d.addCallbacks(self._history_cb, self._history_eb, [room], errbackArgs=[room]) 1601 room._history_d.addCallbacks(
1602 self._history_cb, self._history_eb, [room], errbackArgs=[room]
1603 )
1446 else: 1604 else:
1447 # the subject has been changed 1605 # the subject has been changed
1448 log.debug(_("New subject for room ({room_id}): {subject}").format(room_id = room.roomJID.full(), subject = subject)) 1606 log.debug(
1449 self.host.bridge.muc_room_new_subject(room.roomJID.userhost(), subject, self.client.profile) 1607 _("New subject for room ({room_id}): {subject}").format(
1608 room_id=room.roomJID.full(), subject=subject
1609 )
1610 )
1611 self.host.bridge.muc_room_new_subject(
1612 room.roomJID.userhost(), subject, self.client.profile
1613 )
1450 1614
1451 ## disco ## 1615 ## disco ##
1452 1616
1453 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): 1617 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
1454 return [disco.DiscoFeature(NS_MUC)] 1618 return [disco.DiscoFeature(NS_MUC)]
1455 1619
1456 def getDiscoItems(self, requestor, target, nodeIdentifier=''): 1620 def getDiscoItems(self, requestor, target, nodeIdentifier=""):
1457 # TODO: manage room queries ? Bad for privacy, must be disabled by default 1621 # TODO: manage room queries ? Bad for privacy, must be disabled by default
1458 # see XEP-0045 § 6.7 1622 # see XEP-0045 § 6.7
1459 return [] 1623 return []