diff 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
line wrap: on
line diff
--- a/libervia/backend/plugins/plugin_xep_0045.py	Tue Jun 18 12:06:45 2024 +0200
+++ b/libervia/backend/plugins/plugin_xep_0045.py	Wed Jun 19 18:44:57 2024 +0200
@@ -53,14 +53,14 @@
     C.PI_RECOMMENDATIONS: [C.TEXT_CMDS, "XEP-0313"],
     C.PI_MAIN: "XEP_0045",
     C.PI_HANDLER: "yes",
-    C.PI_DESCRIPTION: _("""Implementation of Multi-User Chat""")
+    C.PI_DESCRIPTION: _("""Implementation of Multi-User Chat"""),
 }
 
-NS_MUC = 'http://jabber.org/protocol/muc'
-AFFILIATIONS = ('owner', 'admin', 'member', 'none', 'outcast')
-ROOM_USER_JOINED = 'ROOM_USER_JOINED'
-ROOM_USER_LEFT = 'ROOM_USER_LEFT'
-OCCUPANT_KEYS = ('nick', 'entity', 'affiliation', 'role')
+NS_MUC = "http://jabber.org/protocol/muc"
+AFFILIATIONS = ("owner", "admin", "member", "none", "outcast")
+ROOM_USER_JOINED = "ROOM_USER_JOINED"
+ROOM_USER_LEFT = "ROOM_USER_LEFT"
+OCCUPANT_KEYS = ("nick", "entity", "affiliation", "role")
 ROOM_STATE_OCCUPANTS = "occupants"
 ROOM_STATE_SELF_PRESENCE = "self-presence"
 ROOM_STATE_LIVE = "live"
@@ -69,9 +69,9 @@
 HISTORY_MAM = "mam"
 
 
-CONFIG_SECTION = 'plugin muc'
+CONFIG_SECTION = "plugin muc"
 
-default_conf = {"default_muc": 'sat@chat.jabberfr.org'}
+default_conf = {"default_muc": "sat@chat.jabberfr.org"}
 
 
 class AlreadyJoined(exceptions.ConflictError):
@@ -92,58 +92,101 @@
         # return same arguments as muc_room_joined + a boolean set to True is the room was
         # already joined (first argument)
         host.bridge.add_method(
-            "muc_join", ".plugin", in_sign='ssa{ss}s', out_sign='(bsa{sa{ss}}ssass)',
-            method=self._join, async_=True)
+            "muc_join",
+            ".plugin",
+            in_sign="ssa{ss}s",
+            out_sign="(bsa{sa{ss}}ssass)",
+            method=self._join,
+            async_=True,
+        )
         host.bridge.add_method(
-            "muc_nick", ".plugin", in_sign='sss', out_sign='', method=self._nick)
+            "muc_nick", ".plugin", in_sign="sss", out_sign="", method=self._nick
+        )
         host.bridge.add_method(
-            "muc_nick_get", ".plugin", in_sign='ss', out_sign='s', method=self._get_room_nick)
+            "muc_nick_get",
+            ".plugin",
+            in_sign="ss",
+            out_sign="s",
+            method=self._get_room_nick,
+        )
         host.bridge.add_method(
-            "muc_leave", ".plugin", in_sign='ss', out_sign='', method=self._leave,
-            async_=True)
+            "muc_leave",
+            ".plugin",
+            in_sign="ss",
+            out_sign="",
+            method=self._leave,
+            async_=True,
+        )
         host.bridge.add_method(
-            "muc_occupants_get", ".plugin", in_sign='ss', out_sign='a{sa{ss}}',
-            method=self._get_room_occupants)
+            "muc_occupants_get",
+            ".plugin",
+            in_sign="ss",
+            out_sign="a{sa{ss}}",
+            method=self._get_room_occupants,
+        )
         host.bridge.add_method(
-            "muc_subject", ".plugin", in_sign='sss', out_sign='', method=self._subject)
+            "muc_subject", ".plugin", in_sign="sss", out_sign="", method=self._subject
+        )
         host.bridge.add_method(
-            "muc_get_rooms_joined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ssas)',
-            method=self._get_rooms_joined)
+            "muc_get_rooms_joined",
+            ".plugin",
+            in_sign="s",
+            out_sign="a(sa{sa{ss}}ssas)",
+            method=self._get_rooms_joined,
+        )
         host.bridge.add_method(
-            "muc_get_unique_room_name", ".plugin", in_sign='ss', out_sign='s',
-            method=self._get_unique_name)
+            "muc_get_unique_room_name",
+            ".plugin",
+            in_sign="ss",
+            out_sign="s",
+            method=self._get_unique_name,
+        )
         host.bridge.add_method(
-            "muc_configure_room", ".plugin", in_sign='ss', out_sign='s',
-            method=self._configure_room, async_=True)
+            "muc_configure_room",
+            ".plugin",
+            in_sign="ss",
+            out_sign="s",
+            method=self._configure_room,
+            async_=True,
+        )
         host.bridge.add_method(
-            "muc_get_default_service", ".plugin", in_sign='', out_sign='s',
-            method=self.get_default_muc)
+            "muc_get_default_service",
+            ".plugin",
+            in_sign="",
+            out_sign="s",
+            method=self.get_default_muc,
+        )
         host.bridge.add_method(
-            "muc_get_service", ".plugin", in_sign='ss', out_sign='s',
-            method=self._get_muc_service, async_=True)
+            "muc_get_service",
+            ".plugin",
+            in_sign="ss",
+            out_sign="s",
+            method=self._get_muc_service,
+            async_=True,
+        )
         # called when a room will be joined but must be locked until join is received
         # (room is prepared, history is getting retrieved)
         # args: room_jid, profile
-        host.bridge.add_signal(
-            "muc_room_prepare_join", ".plugin", signature='ss')
+        host.bridge.add_signal("muc_room_prepare_join", ".plugin", signature="ss")
         # args: room_jid, occupants, user_nick, subject, profile
-        host.bridge.add_signal(
-            "muc_room_joined", ".plugin", signature='sa{sa{ss}}ssass')
+        host.bridge.add_signal("muc_room_joined", ".plugin", signature="sa{sa{ss}}ssass")
         # args: room_jid, profile
-        host.bridge.add_signal(
-            "muc_room_left", ".plugin", signature='ss')
+        host.bridge.add_signal("muc_room_left", ".plugin", signature="ss")
         # args: room_jid, old_nick, new_nick, profile
-        host.bridge.add_signal(
-            "muc_room_user_changed_nick", ".plugin", signature='ssss')
+        host.bridge.add_signal("muc_room_user_changed_nick", ".plugin", signature="ssss")
         # args: room_jid, subject, profile
-        host.bridge.add_signal(
-            "muc_room_new_subject", ".plugin", signature='sss')
+        host.bridge.add_signal("muc_room_new_subject", ".plugin", signature="sss")
         self.__submit_conf_id = host.register_callback(
-            self._submit_configuration, with_data=True)
+            self._submit_configuration, with_data=True
+        )
         self._room_join_id = host.register_callback(self._ui_room_join_cb, with_data=True)
         host.import_menu(
-            (D_("MUC"), D_("configure")), self._configure_room_menu, security_limit=0,
-            help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM)
+            (D_("MUC"), D_("configure")),
+            self._configure_room_menu,
+            security_limit=0,
+            help_string=D_("Configure Multi-User Chat room"),
+            type_=C.MENU_ROOM,
+        )
         try:
             self.text_cmds = self.host.plugins[C.TEXT_CMDS]
         except KeyError:
@@ -157,7 +200,9 @@
 
         host.trigger.add("presence_available", self.presence_trigger)
         host.trigger.add("presence_received", self.presence_received_trigger)
-        host.trigger.add("message_received", self.message_received_trigger, priority=1000000)
+        host.trigger.add(
+            "message_received", self.message_received_trigger, priority=1000000
+        )
         host.trigger.add("message_parse", self._message_parse_trigger)
 
     async def profile_connected(self, client):
@@ -179,7 +224,7 @@
         if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT:
             if message_elt.subject:
                 return False
-            from_jid = jid.JID(message_elt['from'])
+            from_jid = jid.JID(message_elt["from"])
             room_jid = from_jid.userhostJID()
             if room_jid in client._muc_client.joined_rooms:
                 room = client._muc_client.joined_rooms[room_jid]
@@ -189,16 +234,19 @@
                         # messages before history is complete, so this is not a warning
                         # but an expected case.
                         # On the other hand, with legacy history, it's not normal.
-                        log.warning(_(
-                            "Received non delayed message in a room before its "
-                            "initialisation: state={state}, msg={msg}").format(
-                        state=room.state,
-                        msg=message_elt.toXml()))
+                        log.warning(
+                            _(
+                                "Received non delayed message in a room before its "
+                                "initialisation: state={state}, msg={msg}"
+                            ).format(state=room.state, msg=message_elt.toXml())
+                        )
                     room._cache.append(message_elt)
                     return False
             else:
-                log.warning("Received groupchat message for a room which has not been "
-                            "joined, ignoring it: {}".format(message_elt.toXml()))
+                log.warning(
+                    "Received groupchat message for a room which has not been "
+                    "joined, ignoring it: {}".format(message_elt.toXml())
+                )
                 return False
         return True
 
@@ -284,34 +332,44 @@
             #    plugin should be refactored.
             getattr(room, "subject", ""),
             [s.name for s in room.statuses],
-            profile
-            ]
+            profile,
+        ]
 
     def _ui_room_join_cb(self, data, profile):
-        room_jid = jid.JID(data['index'])
+        room_jid = jid.JID(data["index"])
         client = self.host.get_client(profile)
-        defer.ensureDeferred(
-            self.join(client, room_jid)
-        )
+        defer.ensureDeferred(self.join(client, room_jid))
         return {}
 
     def _password_ui_cb(self, data, client, room_jid, nick):
         """Called when the user has given room password (or cancelled)"""
         if C.bool(data.get(C.XMLUI_DATA_CANCELLED, "false")):
             log.info("room join for {} is cancelled".format(room_jid.userhost()))
-            raise failure.Failure(exceptions.CancelError(D_("Room joining cancelled by user")))
-        password = data[xml_tools.form_escape('password')]
-        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))
+            raise failure.Failure(
+                exceptions.CancelError(D_("Room joining cancelled by user"))
+            )
+        password = data[xml_tools.form_escape("password")]
+        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),
+        )
 
     def _show_list_ui(self, items, client, service):
-        xmlui = xml_tools.XMLUI(title=D_('Rooms in {}'.format(service.full())))
-        adv_list = xmlui.change_container('advanced_list', columns=1, selectable='single', callback_id=self._room_join_id)
+        xmlui = xml_tools.XMLUI(title=D_("Rooms in {}".format(service.full())))
+        adv_list = xmlui.change_container(
+            "advanced_list",
+            columns=1,
+            selectable="single",
+            callback_id=self._room_join_id,
+        )
         items = sorted(items, key=lambda i: i.name.lower())
         for item in items:
             adv_list.set_row_index(item.entity.full())
             xmlui.addText(item.name)
         adv_list.end()
-        self.host.action_new({'xmlui': xmlui.toXml()}, profile=client.profile)
+        self.host.action_new({"xmlui": xmlui.toXml()}, profile=client.profile)
 
     def _join_cb(self, room, client, room_jid, nick):
         """Called when the user is in the requested room"""
@@ -321,8 +379,10 @@
             # a proper configuration management should be done
             log.debug(_("room locked !"))
             d = client._muc_client.configure(room.roomJID, {})
-            d.addErrback(self.host.log_errback,
-                         msg=_('Error while configuring the room: {failure_}'))
+            d.addErrback(
+                self.host.log_errback,
+                msg=_("Error while configuring the room: {failure_}"),
+            )
         return room.fully_joined
 
     def _join_eb(self, failure_, client, room_jid, nick, password):
@@ -330,33 +390,50 @@
         try:
             condition = failure_.value.condition
         except AttributeError:
-            msg_suffix = f': {failure_}'
+            msg_suffix = f": {failure_}"
         else:
-            if condition == 'conflict':
+            if condition == "conflict":
                 # we have a nickname conflict, we try again with "_" suffixed to current nickname
-                nick += '_'
-                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))
-            elif condition == 'not-allowed':
+                nick += "_"
+                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),
+                )
+            elif condition == "not-allowed":
                 # room is restricted, we need a password
-                password_ui = xml_tools.XMLUI("form", title=D_('Room {} is restricted').format(room_jid.userhost()), submit_id='')
-                password_ui.addText(D_("This room is restricted, please enter the password"))
-                password_ui.addPassword('password')
+                password_ui = xml_tools.XMLUI(
+                    "form",
+                    title=D_("Room {} is restricted").format(room_jid.userhost()),
+                    submit_id="",
+                )
+                password_ui.addText(
+                    D_("This room is restricted, please enter the password")
+                )
+                password_ui.addPassword("password")
                 d = xml_tools.defer_xmlui(self.host, password_ui, profile=client.profile)
                 d.addCallback(self._password_ui_cb, client, room_jid, nick)
                 return d
 
             msg_suffix = ' with condition "{}"'.format(failure_.value.condition)
 
-        mess = D_("Error while joining the room {room}{suffix}".format(
-            room = room_jid.userhost(), suffix = msg_suffix))
+        mess = D_(
+            "Error while joining the room {room}{suffix}".format(
+                room=room_jid.userhost(), suffix=msg_suffix
+            )
+        )
         log.warning(mess)
         xmlui = xml_tools.note(mess, D_("Group chat error"), level=C.XMLUI_DATA_LVL_ERROR)
-        self.host.action_new({'xmlui': xmlui.toXml()}, profile=client.profile)
+        self.host.action_new({"xmlui": xmlui.toXml()}, profile=client.profile)
 
     @staticmethod
     def _get_occupants(room):
         """Get occupants of a room in a form suitable for bridge"""
-        return {u.nick: {k:str(getattr(u,k) or '') for k in OCCUPANT_KEYS} for u in list(room.roster.values())}
+        return {
+            u.nick: {k: str(getattr(u, k) or "") for k in OCCUPANT_KEYS}
+            for u in list(room.roster.values())
+        }
 
     def _get_room_occupants(self, room_jid_s, profile_key):
         client = self.host.get_client(profile_key)
@@ -377,11 +454,12 @@
         for room in list(client._muc_client.joined_rooms.values()):
             if room.state == ROOM_STATE_LIVE:
                 result.append(
-                    (room.roomJID.userhost(),
-                     self._get_occupants(room),
-                     room.nick,
-                     room.subject,
-                     [s.name for s in room.statuses],
+                    (
+                        room.roomJID.userhost(),
+                        self._get_occupants(room),
+                        room.nick,
+                        room.subject,
+                        [s.name for s in room.statuses],
                     )
                 )
         return result
@@ -415,7 +493,7 @@
         """
         client = self.host.get_client(profile)
         try:
-            room_jid = jid.JID(menu_data['room_jid'])
+            room_jid = jid.JID(menu_data["room_jid"])
         except KeyError:
             log.error(_("room_jid key is not present !"))
             return defer.fail(exceptions.DataError)
@@ -425,6 +503,7 @@
                 msg = D_("No configuration available for this room")
                 return {"xmlui": xml_tools.note(msg).toXml()}
             return {"xmlui": xmlui.toXml()}
+
         return self.configure_room(client, room_jid).addCallback(xmlui_received)
 
     def configure_room(self, client, room_jid):
@@ -457,15 +536,15 @@
             session_data = self._sessions.profile_get(raw_data["session_id"], profile)
         except KeyError:
             log.warning(D_("Session ID doesn't exist, session has probably expired."))
-            _dialog = xml_tools.XMLUI('popup', title=D_('Room configuration failed'))
+            _dialog = xml_tools.XMLUI("popup", title=D_("Room configuration failed"))
             _dialog.addText(D_("Session ID doesn't exist, session has probably expired."))
-            return defer.succeed({'xmlui': _dialog.toXml()})
+            return defer.succeed({"xmlui": _dialog.toXml()})
 
         data = xml_tools.xmlui_result_2_data_form_result(raw_data)
-        d = client._muc_client.configure(session_data['room_jid'], data)
-        _dialog = xml_tools.XMLUI('popup', title=D_('Room configuration succeed'))
+        d = client._muc_client.configure(session_data["room_jid"], data)
+        _dialog = xml_tools.XMLUI("popup", title=D_("Room configuration succeed"))
         _dialog.addText(D_("The new settings have been saved."))
-        d.addCallback(lambda ignore: {'xmlui': _dialog.toXml()})
+        d.addCallback(lambda ignore: {"xmlui": _dialog.toXml()})
         del self._sessions[raw_data["session_id"]]
         return d
 
@@ -477,13 +556,14 @@
     def _get_muc_service(self, jid_=None, profile=C.PROF_KEY_NONE):
         client = self.host.get_client(profile)
         d = defer.ensureDeferred(self.get_muc_service(client, jid_ or None))
-        d.addCallback(lambda service_jid: service_jid.full() if service_jid is not None else '')
+        d.addCallback(
+            lambda service_jid: service_jid.full() if service_jid is not None else ""
+        )
         return d
 
     async def get_muc_service(
-        self,
-        client: SatXMPPEntity,
-        jid_: Optional[jid.JID] = None) -> Optional[jid.JID]:
+        self, client: SatXMPPEntity, jid_: Optional[jid.JID] = None
+    ) -> Optional[jid.JID]:
         """Return first found MUC service of an entity
 
         @param jid_: entity which may have a MUC service, or None for our own server
@@ -497,7 +577,9 @@
             else:
                 # we have a cached value, we return it
                 return muc_service
-        services = await self.host.find_service_entities(client, "conference", "text", jid_)
+        services = await self.host.find_service_entities(
+            client, "conference", "text", jid_
+        )
         for service in services:
             if ".irc." not in service.userhost():
                 # FIXME:
@@ -514,10 +596,7 @@
         return self.get_unique_name(client, muc_service or None).full()
 
     def get_unique_name(
-        self,
-        client: SatXMPPEntity,
-        muc_service: jid.JID|None = None,
-        prefix: str = ""
+        self, client: SatXMPPEntity, muc_service: jid.JID | None = None, prefix: str = ""
     ) -> jid.JID:
         """Return unique name for a room, avoiding collision
 
@@ -546,7 +625,9 @@
 
         @return: unicode
         """
-        return self.host.memory.config_get(CONFIG_SECTION, 'default_muc', default_conf['default_muc'])
+        return self.host.memory.config_get(
+            CONFIG_SECTION, "default_muc", default_conf["default_muc"]
+        )
 
     def _bridge_join_eb(self, failure_, client):
         failure_.trap(AlreadyJoined)
@@ -564,19 +645,23 @@
             try:
                 room_jid = jid.JID(room_jid_s)
             except (RuntimeError, jid.InvalidFormat, AttributeError):
-                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(
-                    room_id=room_jid_s,
-                    muc_service=str(muc_service))))
+                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(room_id=room_jid_s, muc_service=str(muc_service))
+                    )
+                )
             if not room_jid.user:
                 room_jid.user, room_jid.host = room_jid.host, muc_service
         else:
             room_jid = self.get_unique_name(client)
         # TODO: error management + signal in bridge
-        d = defer.ensureDeferred(
-            self.join(client, room_jid, nick, options or None)
+        d = defer.ensureDeferred(self.join(client, room_jid, nick, options or None))
+
+        d.addCallback(
+            lambda room: [False] + self._get_room_joined_args(room, client.profile)
         )
-
-        d.addCallback(lambda room: [False] + self._get_room_joined_args(room, client.profile))
         d.addErrback(self._bridge_join_eb, client)
         return d
 
@@ -585,7 +670,7 @@
         client: SatXMPPEntity,
         room_jid: jid.JID,
         nick: Optional[str] = None,
-        options: Optional[dict] = None
+        options: Optional[dict] = None,
     ) -> Optional[muc.Room]:
         if not nick:
             nick = client.jid.user
@@ -593,11 +678,17 @@
             options = {}
         if room_jid in client._muc_client.joined_rooms:
             room = client._muc_client.joined_rooms[room_jid]
-            log.info(_('{profile} is already in room {room_jid}').format(
-                profile=client.profile, room_jid = room_jid.userhost()))
+            log.info(
+                _("{profile} is already in room {room_jid}").format(
+                    profile=client.profile, room_jid=room_jid.userhost()
+                )
+            )
             raise AlreadyJoined(room)
-        log.info(_("[{profile}] is joining room {room} with nick {nick}").format(
-            profile=client.profile, room=room_jid.userhost(), nick=nick))
+        log.info(
+            _("[{profile}] is joining room {room} with nick {nick}").format(
+                profile=client.profile, room=room_jid.userhost(), nick=nick
+            )
+        )
         self.host.bridge.muc_room_prepare_join(room_jid.userhost(), client.profile)
 
         password = options.get("password")
@@ -611,9 +702,7 @@
         else:
             room.on_joined_callbacks = []
             room.on_left_callbacks = []
-            await defer.ensureDeferred(
-                self._join_cb(room, client, room_jid, nick)
-            )
+            await defer.ensureDeferred(self._join_cb(room, client, room_jid, nick))
         return room
 
     def pop_rooms(self, client):
@@ -669,7 +758,7 @@
         if options is None:
             options = {}
         self.check_room_joined(client, room_jid)
-        return client._muc_client.kick(room_jid, nick, reason=options.get('reason', None))
+        return client._muc_client.kick(room_jid, nick, reason=options.get("reason", None))
 
     def ban(self, client, entity_jid, room_jid, options=None):
         """Ban an entity from the room
@@ -683,7 +772,9 @@
             options = {}
         assert not entity_jid.resource
         assert not room_jid.resource
-        return client._muc_client.ban(room_jid, entity_jid, reason=options.get('reason', None))
+        return client._muc_client.ban(
+            room_jid, entity_jid, reason=options.get("reason", None)
+        )
 
     def affiliate(self, client, entity_jid, room_jid, options):
         """Change the affiliation of an entity
@@ -695,9 +786,11 @@
         self.check_room_joined(client, room_jid)
         assert not entity_jid.resource
         assert not room_jid.resource
-        assert 'affiliation' in options
+        assert "affiliation" in options
         # TODO: handles reason and nick
-        return client._muc_client.modifyAffiliationList(room_jid, [entity_jid], options['affiliation'])
+        return client._muc_client.modifyAffiliationList(
+            room_jid, [entity_jid], options["affiliation"]
+        )
 
     # Text commands #
 
@@ -733,9 +826,7 @@
                 muc_service = client.muc_service or ""
                 nick = client.jid.user
             room_jid = self.text_cmds.get_room_jid(room_raw, muc_service)
-            defer.ensureDeferred(
-                self.join(client, room_jid, nick, {})
-            )
+            defer.ensureDeferred(self.join(client, room_jid, nick, {}))
 
         return False
 
@@ -778,18 +869,19 @@
             self.text_cmds.feed_back(client, feedback, mess_data)
             return False
 
-        reason = ' '.join(options[1:]) if len(options) > 1 else None
+        reason = " ".join(options[1:]) if len(options) > 1 else None
 
         d = self.kick(client, nick, mess_data["to"], {"reason": reason})
 
         def cb(__):
-            feedback_msg = _('You have kicked {}').format(nick)
+            feedback_msg = _("You have kicked {}").format(nick)
             if reason is not None:
-                feedback_msg += _(' for the following reason: {reason}').format(
+                feedback_msg += _(" for the following reason: {reason}").format(
                     reason=reason
                 )
             self.text_cmds.feed_back(client, feedback_msg, mess_data)
             return True
+
         d.addCallback(cb)
         return d
 
@@ -804,28 +896,34 @@
         try:
             jid_s = options[0]
             entity_jid = jid.JID(jid_s).userhostJID()
-            assert(entity_jid.user)
-            assert(entity_jid.host)
-        except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError,
-                AssertionError):
+            assert entity_jid.user
+            assert entity_jid.host
+        except (
+            RuntimeError,
+            jid.InvalidFormat,
+            AttributeError,
+            IndexError,
+            AssertionError,
+        ):
             feedback = _(
                 "You must provide a valid JID to ban, like in '/ban contact@example.net'"
             )
             self.text_cmds.feed_back(client, feedback, mess_data)
             return False
 
-        reason = ' '.join(options[1:]) if len(options) > 1 else None
+        reason = " ".join(options[1:]) if len(options) > 1 else None
 
         d = self.ban(client, entity_jid, mess_data["to"], {"reason": reason})
 
         def cb(__):
-            feedback_msg = _('You have banned {}').format(entity_jid)
+            feedback_msg = _("You have banned {}").format(entity_jid)
             if reason is not None:
-                feedback_msg += _(' for the following reason: {reason}').format(
+                feedback_msg += _(" for the following reason: {reason}").format(
                     reason=reason
                 )
             self.text_cmds.feed_back(client, feedback_msg, mess_data)
             return True
+
         d.addCallback(cb)
         return d
 
@@ -844,26 +942,40 @@
         try:
             jid_s = options[0]
             entity_jid = jid.JID(jid_s).userhostJID()
-            assert(entity_jid.user)
-            assert(entity_jid.host)
-        except (RuntimeError, jid.InvalidFormat, AttributeError, IndexError, AssertionError):
-            feedback = _("You must provide a valid JID to affiliate, like in '/affiliate contact@example.net member'")
+            assert entity_jid.user
+            assert entity_jid.host
+        except (
+            RuntimeError,
+            jid.InvalidFormat,
+            AttributeError,
+            IndexError,
+            AssertionError,
+        ):
+            feedback = _(
+                "You must provide a valid JID to affiliate, like in '/affiliate contact@example.net member'"
+            )
             self.text_cmds.feed_back(client, feedback, mess_data)
             return False
 
-        affiliation = options[1] if len(options) > 1 else 'none'
+        affiliation = options[1] if len(options) > 1 else "none"
         if affiliation not in AFFILIATIONS:
-            feedback = _("You must provide a valid affiliation: %s") % ' '.join(AFFILIATIONS)
+            feedback = _("You must provide a valid affiliation: %s") % " ".join(
+                AFFILIATIONS
+            )
             self.text_cmds.feed_back(client, feedback, mess_data)
             return False
 
-        d = self.affiliate(client, entity_jid, mess_data["to"], {'affiliation': affiliation})
+        d = self.affiliate(
+            client, entity_jid, mess_data["to"], {"affiliation": affiliation}
+        )
 
         def cb(__):
-            feedback_msg = _('New affiliation for {entity}: {affiliation}').format(
-                entity=entity_jid, affiliation=affiliation)
+            feedback_msg = _("New affiliation for {entity}: {affiliation}").format(
+                entity=entity_jid, affiliation=affiliation
+            )
             self.text_cmds.feed_back(client, feedback_msg, mess_data)
             return True
+
         d.addCallback(cb)
         return d
 
@@ -901,14 +1013,15 @@
         try:
             service = jid.JID(unparsed)
         except RuntimeError:
-            if mess_data['type'] == C.MESS_TYPE_GROUPCHAT:
+            if mess_data["type"] == C.MESS_TYPE_GROUPCHAT:
                 room_jid = mess_data["to"]
                 service = jid.JID(room_jid.host)
             elif client.muc_service is not None:
                 service = client.muc_service
             else:
                 msg = D_("No known default MUC service {unparsed}").format(
-                    unparsed=unparsed)
+                    unparsed=unparsed
+                )
                 self.text_cmds.feed_back(client, msg, mess_data)
                 return False
         except jid.InvalidFormat:
@@ -921,21 +1034,23 @@
         return False
 
     def _whois(self, client, whois_msg, mess_data, target_jid):
-        """ Add MUC user information to whois """
-        if mess_data['type'] != "groupchat":
+        """Add MUC user information to whois"""
+        if mess_data["type"] != "groupchat":
             return
         if target_jid.userhostJID() not in client._muc_client.joined_rooms:
             log.warning(_("This room has not been joined"))
             return
         if not target_jid.resource:
             return
-        user = client._muc_client.joined_rooms[target_jid.userhostJID()].getUser(target_jid.resource)
+        user = client._muc_client.joined_rooms[target_jid.userhostJID()].getUser(
+            target_jid.resource
+        )
         whois_msg.append(_("Nickname: %s") % user.nick)
         if user.entity:
             whois_msg.append(_("Entity: %s") % user.entity)
-        if user.affiliation != 'none':
+        if user.affiliation != "none":
             whois_msg.append(_("Affiliation: %s") % user.affiliation)
-        if user.role != 'none':
+        if user.role != "none":
             whois_msg.append(_("Role: %s") % user.role)
         if user.status:
             whois_msg.append(_("Status: %s") % user.status)
@@ -948,7 +1063,7 @@
         muc_client = client._muc_client
         for room_jid, room in muc_client.joined_rooms.items():
             elt = xml_tools.element_copy(presence_elt)
-            elt['to'] = room_jid.userhost() + '/' + room.nick
+            elt["to"] = room_jid.userhost() + "/" + room.nick
             client.presence.send(elt)
         return True
 
@@ -968,7 +1083,7 @@
         self.plugin_parent = plugin_parent
         muc.MUCClient.__init__(self)
         self._changing_nicks = set()  # used to keep trace of who is changing nick,
-                                      # and to discard userJoinedRoom signal in this case
+        # and to discard userJoinedRoom signal in this case
         print("init SatMUCClient OK")
 
     @property
@@ -1000,17 +1115,23 @@
         if new_state_idx == -1:
             raise exceptions.InternalError("unknown room state")
         if new_state_idx < 1:
-            raise exceptions.InternalError("unexpected new room state ({room}): {state}".format(
-                room=room.userhost(),
-                state=new_state))
-        expected_state = ROOM_STATES[new_state_idx-1]
+            raise exceptions.InternalError(
+                "unexpected new room state ({room}): {state}".format(
+                    room=room.userhost(), state=new_state
+                )
+            )
+        expected_state = ROOM_STATES[new_state_idx - 1]
         if room.state != expected_state:
-            log.error(_(
-                "room {room} is not in expected state: room is in state {current_state} "
-                "while we were expecting {expected_state}").format(
-                room=room.roomJID.userhost(),
-                current_state=room.state,
-                expected_state=expected_state))
+            log.error(
+                _(
+                    "room {room} is not in expected state: room is in state {current_state} "
+                    "while we were expecting {expected_state}"
+                ).format(
+                    room=room.roomJID.userhost(),
+                    current_state=room.state,
+                    expected_state=expected_state,
+                )
+            )
         room.state = new_state
 
     def _addRoom(self, room):
@@ -1026,11 +1147,7 @@
         room._cache_presence = {}
 
     async def _join_legacy(
-        self,
-        client: SatXMPPEntity,
-        room_jid: jid.JID,
-        nick: str,
-        password: Optional[str]
+        self, client: SatXMPPEntity, room_jid: jid.JID, nick: str, password: Optional[str]
     ) -> muc.Room:
         """Join room an retrieve history with legacy method"""
         mess_data_list = await self.host.memory.history_get(
@@ -1038,7 +1155,7 @@
             client.jid.userhostJID(),
             limit=1,
             between=True,
-            profile=client.profile
+            profile=client.profile,
         )
         if mess_data_list:
             timestamp = mess_data_list[0][1]
@@ -1049,7 +1166,8 @@
             seconds = None
 
         room = await super(LiberviaMUCClient, self).join(
-            room_jid, nick, muc.HistoryOptions(seconds=seconds), password)
+            room_jid, nick, muc.HistoryOptions(seconds=seconds), password
+        )
         # used to send bridge signal once backlog are written in history
         room._history_type = HISTORY_LEGACY
         room._history_d = defer.Deferred()
@@ -1057,10 +1175,7 @@
         return room
 
     async def _get_mam_history(
-        self,
-        client: SatXMPPEntity,
-        room: muc.Room,
-        room_jid: jid.JID
+        self, client: SatXMPPEntity, room: muc.Room, room_jid: jid.JID
     ) -> None:
         """Retrieve history for rooms handling MAM"""
         history_d = room._history_d = defer.Deferred()
@@ -1073,39 +1188,39 @@
             None,
             limit=1,
             between=False,
-            filters={
-                'types': C.MESS_TYPE_GROUPCHAT,
-                'last_stanza_id': True},
-            profile=client.profile)
+            filters={"types": C.MESS_TYPE_GROUPCHAT, "last_stanza_id": True},
+            profile=client.profile,
+        )
         if last_mess:
-            stanza_id = last_mess[0][-1]['stanza_id']
+            stanza_id = last_mess[0][-1]["stanza_id"]
             rsm_req = rsm.RSMRequest(max_=20, after=stanza_id)
-            no_loop=False
+            no_loop = False
         else:
-            log.info("We have no MAM archive for room {room_jid}.".format(
-                room_jid=room_jid))
+            log.info(
+                "We have no MAM archive for room {room_jid}.".format(room_jid=room_jid)
+            )
             # we don't want the whole archive if we have no archive yet
             # as it can be huge
-            rsm_req = rsm.RSMRequest(max_=50, before='')
-            no_loop=True
+            rsm_req = rsm.RSMRequest(max_=50, before="")
+            no_loop = True
 
         mam_req = mam.MAMRequest(rsm_=rsm_req)
         complete = False
         count = 0
         while not complete:
             try:
-                mam_data = await self._mam.get_archives(client, mam_req,
-                                                       service=room_jid)
+                mam_data = await self._mam.get_archives(client, mam_req, service=room_jid)
             except xmpp_error.StanzaError as e:
-                if last_mess and e.condition == 'item-not-found':
+                if last_mess and e.condition == "item-not-found":
                     log.warning(
                         f"requested item (with id {stanza_id!r}) can't be found in "
                         f"history of {room_jid}, history has probably been purged on "
-                        f"server.")
+                        f"server."
+                    )
                     # we get last items like for a new room
-                    rsm_req = rsm.RSMRequest(max_=50, before='')
+                    rsm_req = rsm.RSMRequest(max_=50, before="")
                     mam_req = mam.MAMRequest(rsm_=rsm_req)
-                    no_loop=True
+                    no_loop = True
                     continue
                 else:
                     raise e
@@ -1122,47 +1237,56 @@
                 for mess_elt in elt_list:
                     try:
                         fwd_message_elt = self._mam.get_message_from_result(
-                            client, mess_elt, mam_req, service=room_jid)
+                            client, mess_elt, mam_req, service=room_jid
+                        )
                     except exceptions.DataError:
                         continue
                     if fwd_message_elt.getAttribute("to"):
                         log.warning(
                             'Forwarded message element has a "to" attribute while it is '
-                            'forbidden by specifications')
+                            "forbidden by specifications"
+                        )
                     fwd_message_elt["to"] = client.jid.full()
                     client.messageProt.onMessage(fwd_message_elt)
                     client._muc_client._onGroupChat(fwd_message_elt)
 
         if not count:
-            log.info(_("No message received while offline in {room_jid}".format(
-                room_jid=room_jid)))
+            log.info(
+                _(
+                    "No message received while offline in {room_jid}".format(
+                        room_jid=room_jid
+                    )
+                )
+            )
         else:
             log.info(
-                _("We have received {num_mess} message(s) in {room_jid} while "
-                  "offline.")
-                .format(num_mess=count, room_jid=room_jid))
+                _(
+                    "We have received {num_mess} message(s) in {room_jid} while "
+                    "offline."
+                ).format(num_mess=count, room_jid=room_jid)
+            )
 
         # for legacy history, the following steps are done in receivedSubject but for MAM
         # the order is different (we have to join then get MAM archive, so subject
         # is received before archive), so we change state and add the callbacks here.
         self.change_room_state(room, ROOM_STATE_LIVE)
-        history_d.addCallbacks(self._history_cb, self._history_eb, [room],
-                                     errbackArgs=[room])
+        history_d.addCallbacks(
+            self._history_cb, self._history_eb, [room], errbackArgs=[room]
+        )
 
         # we wait for all callbacks to be processed
         await history_d
 
     async def _join_mam(
-        self,
-        client: SatXMPPEntity,
-        room_jid: jid.JID,
-        nick: str,
-        password: Optional[str]
+        self, client: SatXMPPEntity, room_jid: jid.JID, nick: str, password: Optional[str]
     ) -> muc.Room:
         """Join room and retrieve history using MAM"""
         room = await super(LiberviaMUCClient, self).join(
             # we don't want any history from room as we'll get it with MAM
-            room_jid, nick, muc.HistoryOptions(maxStanzas=0), password=password
+            room_jid,
+            nick,
+            muc.HistoryOptions(maxStanzas=0),
+            password=password,
         )
         room._history_type = HISTORY_MAM
         # MAM history retrieval can be very long, and doesn't need to be sync, so we don't
@@ -1197,8 +1321,11 @@
         if user is None:
             nick = presence.sender.resource
             if not nick:
-                log.warning(_("missing nick in presence: {xml}").format(
-                    xml = presence.toElement().toXml()))
+                log.warning(
+                    _("missing nick in presence: {xml}").format(
+                        xml=presence.toElement().toXml()
+                    )
+                )
                 return
             user = muc.User(nick, presence.entity)
 
@@ -1207,8 +1334,10 @@
         #      like 110 (REALJID_PUBLIC) after first <presence/> received
         #      so we keep only the initial <presence> (with SELF_PRESENCE),
         #      thus we check if attribute already exists
-        if (not hasattr(room, 'statuses')
-            and muc.STATUS_CODE.SELF_PRESENCE in presence.mucStatuses):
+        if (
+            not hasattr(room, "statuses")
+            and muc.STATUS_CODE.SELF_PRESENCE in presence.mucStatuses
+        ):
             room.statuses = presence.mucStatuses
 
         # Update user data
@@ -1250,8 +1379,11 @@
             # we have received our own nick,
             # this mean that the full room roster was received
             self.change_room_state(room, ROOM_STATE_SELF_PRESENCE)
-            log.debug("room {room} joined with nick {nick}".format(
-                room=room.occupantJID.userhost(), nick=user.nick))
+            log.debug(
+                "room {room} joined with nick {nick}".format(
+                    room=room.occupantJID.userhost(), nick=user.nick
+                )
+            )
             # we set type so we don't have to use a deferred
             # with disco to check entity type
             self.host.memory.update_entity_data(
@@ -1262,9 +1394,9 @@
                 "Received user presence data in a room before its initialisation "
                 "(current state: {state}),"
                 "this is not standard! Ignoring it: {room} ({nick})".format(
-                    state=room.state,
-                    room=room.roomJID.userhost(),
-                    nick=user.nick))
+                    state=room.state, room=room.roomJID.userhost(), nick=user.nick
+                )
+            )
             return
         else:
             if not room.fully_joined.called:
@@ -1275,24 +1407,29 @@
                 self._changing_nicks.remove(user.nick)
             except KeyError:
                 # this is a new user
-                log.debug(_("user {nick} has joined room {room_id}").format(
-                    nick=user.nick, room_id=room.occupantJID.userhost()))
+                log.debug(
+                    _("user {nick} has joined room {room_id}").format(
+                        nick=user.nick, room_id=room.occupantJID.userhost()
+                    )
+                )
                 if not self.host.trigger.point(
-                        "MUC user joined", room, user, self.client.profile):
+                    "MUC user joined", room, user, self.client.profile
+                ):
                     return
 
-                extra = {'info_type': ROOM_USER_JOINED,
-                         'user_affiliation': user.affiliation,
-                         'user_role': user.role,
-                         'user_nick': user.nick
-                         }
+                extra = {
+                    "info_type": ROOM_USER_JOINED,
+                    "user_affiliation": user.affiliation,
+                    "user_role": user.role,
+                    "user_nick": user.nick,
+                }
                 if user.entity is not None:
-                    extra['user_entity'] = user.entity.full()
+                    extra["user_entity"] = user.entity.full()
                 mess_data = {  # dict is similar to the one used in client.onMessage
                     "from": room.roomJID,
                     "to": self.client.jid,
                     "uid": str(uuid.uuid4()),
-                    "message": {'': D_("=> {} has joined the room").format(user.nick)},
+                    "message": {"": D_("=> {} has joined the room").format(user.nick)},
                     "subject": {},
                     "type": C.MESS_TYPE_INFO,
                     "extra": extra,
@@ -1304,42 +1441,52 @@
                 # self.client.message_add_to_history(mess_data)
                 self.client.message_send_to_bridge(mess_data)
 
-
     def userLeftRoom(self, room, user):
         if not self.host.trigger.point("MUC user left", room, user, self.client.profile):
             return
         if user.nick == room.nick:
             # we left the room
             room_jid_s = room.roomJID.userhost()
-            log.info(_("Room ({room}) left ({profile})").format(
-                room = room_jid_s, profile = self.client.profile))
-            self.host.memory.del_entity_cache(room.roomJID, profile_key=self.client.profile)
+            log.info(
+                _("Room ({room}) left ({profile})").format(
+                    room=room_jid_s, profile=self.client.profile
+                )
+            )
+            self.host.memory.del_entity_cache(
+                room.roomJID, profile_key=self.client.profile
+            )
             self.host.bridge.muc_room_left(room.roomJID.userhost(), self.client.profile)
         elif room.state != ROOM_STATE_LIVE:
-            log.warning("Received user presence data in a room before its initialisation (current state: {state}),"
+            log.warning(
+                "Received user presence data in a room before its initialisation (current state: {state}),"
                 "this is not standard! Ignoring it: {room} ({nick})".format(
-                state=room.state,
-                room=room.roomJID.userhost(),
-                nick=user.nick))
+                    state=room.state, room=room.roomJID.userhost(), nick=user.nick
+                )
+            )
             return
         else:
             if not room.fully_joined.called:
                 return
-            log.debug(_("user {nick} left room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost()))
+            log.debug(
+                _("user {nick} left room {room_id}").format(
+                    nick=user.nick, room_id=room.occupantJID.userhost()
+                )
+            )
             for cb in room.on_left_callbacks:
                 defer.ensureDeferred(cb(room, user))
-            extra = {'info_type': ROOM_USER_LEFT,
-                     'user_affiliation': user.affiliation,
-                     'user_role': user.role,
-                     'user_nick': user.nick
-                     }
+            extra = {
+                "info_type": ROOM_USER_LEFT,
+                "user_affiliation": user.affiliation,
+                "user_role": user.role,
+                "user_nick": user.nick,
+            }
             if user.entity is not None:
-                extra['user_entity'] = user.entity.full()
+                extra["user_entity"] = user.entity.full()
             mess_data = {  # dict is similar to the one used in client.onMessage
                 "from": room.roomJID,
                 "to": self.client.jid,
                 "uid": str(uuid.uuid4()),
-                "message": {'': D_("<= {} has left the room").format(user.nick)},
+                "message": {"": D_("<= {} has left the room").format(user.nick)},
                 "subject": {},
                 "type": C.MESS_TYPE_INFO,
                 "extra": extra,
@@ -1350,7 +1497,9 @@
             self.client.message_send_to_bridge(mess_data)
 
     def user_changed_nick(self, room, user, new_nick):
-        self.host.bridge.muc_room_user_changed_nick(room.roomJID.userhost(), user.nick, new_nick, self.client.profile)
+        self.host.bridge.muc_room_user_changed_nick(
+            room.roomJID.userhost(), user.nick, new_nick, self.client.profile
+        )
 
     def userUpdatedStatus(self, room, user, show, status):
         entity = jid.JID(tuple=(room.roomJID.user, room.roomJID.host, user.nick))
@@ -1365,16 +1514,20 @@
                 "user": user,
                 "show": show,
                 "status": status,
-                }
+            }
             return
-        statuses = {C.PRESENCE_STATUSES_DEFAULT: status or ''}
+        statuses = {C.PRESENCE_STATUSES_DEFAULT: status or ""}
         self.host.bridge.presence_update(
-            entity.full(), show or '', 0, statuses, self.client.profile)
+            entity.full(), show or "", 0, statuses, self.client.profile
+        )
 
     ## messages ##
 
     def receivedGroupChat(self, room, user, body):
-        log.debug('receivedGroupChat: room=%s user=%s body=%s' % (room.roomJID.full(), user, body))
+        log.debug(
+            "receivedGroupChat: room=%s user=%s body=%s"
+            % (room.roomJID.full(), user, body)
+        )
 
     ## subject ##
 
@@ -1390,8 +1543,11 @@
         room, user = self._getRoomUser(message)
 
         if room is None:
-            log.warning("No room found for message: {message}"
-                        .format(message=message.toElement().toXml()))
+            log.warning(
+                "No room found for message: {message}".format(
+                    message=message.toElement().toXml()
+                )
+            )
             return
 
         if message.subject is not None:
@@ -1424,7 +1580,7 @@
         for elem in cache:
             self.client.xmlstream.dispatch(elem)
         for presence_data in cache_presence.values():
-            if not presence_data['show'] and not presence_data['status']:
+            if not presence_data["show"] and not presence_data["status"]:
                 # occupants are already sent in muc_room_joined, so if we don't have
                 # extra information like show or statuses, we can discard the signal
                 continue
@@ -1442,18 +1598,26 @@
         if room.state != ROOM_STATE_LIVE:
             if room._history_type == HISTORY_LEGACY:
                 self.change_room_state(room, ROOM_STATE_LIVE)
-                room._history_d.addCallbacks(self._history_cb, self._history_eb, [room], errbackArgs=[room])
+                room._history_d.addCallbacks(
+                    self._history_cb, self._history_eb, [room], errbackArgs=[room]
+                )
         else:
             # the subject has been changed
-            log.debug(_("New subject for room ({room_id}): {subject}").format(room_id = room.roomJID.full(), subject = subject))
-            self.host.bridge.muc_room_new_subject(room.roomJID.userhost(), subject, self.client.profile)
+            log.debug(
+                _("New subject for room ({room_id}): {subject}").format(
+                    room_id=room.roomJID.full(), subject=subject
+                )
+            )
+            self.host.bridge.muc_room_new_subject(
+                room.roomJID.userhost(), subject, self.client.profile
+            )
 
     ## disco ##
 
-    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
+    def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_MUC)]
 
-    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
+    def getDiscoItems(self, requestor, target, nodeIdentifier=""):
         # TODO: manage room queries ? Bad for privacy, must be disabled by default
         #       see XEP-0045 ยง 6.7
         return []