diff sat/core/xmpp.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 94708a7d3ecf
children fee60f17ebac
line wrap: on
line diff
--- a/sat/core/xmpp.py	Wed Jul 31 11:31:22 2019 +0200
+++ b/sat/core/xmpp.py	Tue Aug 13 19:08:41 2019 +0200
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
 # SAT: a jabber client
@@ -40,18 +40,18 @@
 from sat.memory import encryption
 from sat.memory import persistent
 from sat.tools import xml_tools
-from zope.interface import implements
+from zope.interface import implementer
 
 log = getLogger(__name__)
 
 
-NS_X_DATA = u"jabber:x:data"
-NS_DISCO_INFO = u"http://jabber.org/protocol/disco#info"
-NS_XML_ELEMENT = u"urn:xmpp:xml-element"
-NS_ROSTER_VER = u"urn:xmpp:features:rosterver"
+NS_X_DATA = "jabber:x:data"
+NS_DISCO_INFO = "http://jabber.org/protocol/disco#info"
+NS_XML_ELEMENT = "urn:xmpp:xml-element"
+NS_ROSTER_VER = "urn:xmpp:features:rosterver"
 # we use 2 "@" which is illegal in a jid, to be sure we are not mixing keys
 # with roster jids
-ROSTER_VER_KEY = u"@version@"
+ROSTER_VER_KEY = "@version@"
 
 
 class SatXMPPEntity(object):
@@ -65,9 +65,9 @@
         clientConnectionFailed_ori = factory.clientConnectionFailed
         clientConnectionLost_ori = factory.clientConnectionLost
         factory.clientConnectionFailed = partial(
-            self.connectionTerminated, term_type=u"failed", cb=clientConnectionFailed_ori)
+            self.connectionTerminated, term_type="failed", cb=clientConnectionFailed_ori)
         factory.clientConnectionLost = partial(
-            self.connectionTerminated, term_type=u"lost", cb=clientConnectionLost_ori)
+            self.connectionTerminated, term_type="lost", cb=clientConnectionLost_ori)
 
         factory.maxRetries = max_retries
         factory.maxDelay = 30
@@ -87,7 +87,7 @@
         self.encryption = encryption.EncryptionHandler(self)
 
     def __unicode__(self):
-        return u"Client instance for profile {profile}".format(profile=self.profile)
+        return "Client instance for profile {profile}".format(profile=self.profile)
 
     def __str__(self):
         return self.__unicode__.encode('utf-8')
@@ -206,11 +206,11 @@
         def logPluginResults(results):
             all_succeed = all([success for success, result in results])
             if not all_succeed:
-                log.error(_(u"Plugins initialisation error"))
+                log.error(_("Plugins initialisation error"))
                 for idx, (success, result) in enumerate(results):
                     if not success:
                         log.error(
-                            u"error (plugin %(name)s): %(failure)s"
+                            "error (plugin %(name)s): %(failure)s"
                             % {
                                 "name": plugin_conn_cb[idx][0]._info["import_name"],
                                 "failure": result,
@@ -226,11 +226,11 @@
         self._connected_d = None
 
     def _disconnectionEb(self, failure_):
-        log.error(_(u"Error while disconnecting: {}".format(failure_)))
+        log.error(_("Error while disconnecting: {}".format(failure_)))
 
     def _authd(self, xmlstream):
         super(SatXMPPEntity, self)._authd(xmlstream)
-        log.debug(_(u"{profile} identified").format(profile=self.profile))
+        log.debug(_("{profile} identified").format(profile=self.profile))
         self.streamInitialized()
 
     def _finish_connection(self, __):
@@ -238,7 +238,7 @@
 
     def streamInitialized(self):
         """Called after _authd"""
-        log.debug(_(u"XML stream is initialized"))
+        log.debug(_("XML stream is initialized"))
         if not self.host_app.trigger.point("xml_init", self):
             return
         self.postStreamInit()
@@ -246,7 +246,7 @@
     def postStreamInit(self):
         """Workflow after stream initalisation."""
         log.info(
-            _(u"********** [{profile}] CONNECTED **********").format(profile=self.profile)
+            _("********** [{profile}] CONNECTED **********").format(profile=self.profile)
         )
 
         # the following Deferred is used to know when we are connected
@@ -273,7 +273,7 @@
     def initializationFailed(self, reason):
         log.error(
             _(
-                u"ERROR: XMPP connection failed for profile '%(profile)s': %(reason)s"
+                "ERROR: XMPP connection failed for profile '%(profile)s': %(reason)s"
                 % {"profile": self.profile, "reason": reason}
             )
         )
@@ -306,17 +306,17 @@
         if reason is not None and not isinstance(reason.value,
                                                  internet_error.ConnectionDone):
             try:
-                reason_str = unicode(reason.value)
+                reason_str = str(reason.value)
             except Exception:
                 # FIXME: workaround for Android were p4a strips docstrings
                 #        while Twisted use docstring in __str__
                 # TODO: create a ticket upstream, Twisted should work when optimization
                 #       is used
-                reason_str = unicode(reason.value.__class__)
-            log.warning(u"Connection {term_type}: {reason}".format(
+                reason_str = str(reason.value.__class__)
+            log.warning("Connection {term_type}: {reason}".format(
                 term_type = term_type,
                 reason=reason_str))
-        if not self.host_app.trigger.point(u"connection_" + term_type, connector, reason):
+        if not self.host_app.trigger.point("connection_" + term_type, connector, reason):
             return
         return cb(connector, reason)
 
@@ -327,7 +327,7 @@
         Retrying is disabled too, as it makes no sense to try without network, and it may
         use resources (notably battery on mobiles).
         """
-        log.info(_(u"stopping connection because of network disabled"))
+        log.info(_("stopping connection because of network disabled"))
         self.factory.continueTrying = 0
         self._network_disabled = True
         if self.xmlstream is not None:
@@ -344,13 +344,13 @@
         except AttributeError:
             # connection has not been stopped by networkDisabled
             # we don't have to restart it
-            log.debug(u"no connection to restart")
+            log.debug("no connection to restart")
             return
         else:
             del self._network_disabled
             if not network_disabled:
-                raise exceptions.InternalError(u"network_disabled should be True")
-        log.info(_(u"network is available, trying to connect"))
+                raise exceptions.InternalError("network_disabled should be True")
+        log.info(_("network is available, trying to connect"))
         # we want to be sure to start fresh
         self.factory.resetDelay()
         # we have a saved connector, meaning the connection has been stopped previously
@@ -378,23 +378,23 @@
                 self.profile
             )  # and we remove references to this client
             log.info(
-                _(u"********** [{profile}] DISCONNECTED **********").format(
+                _("********** [{profile}] DISCONNECTED **********").format(
                     profile=self.profile
                 )
             )
         if not self.conn_deferred.called:
             if reason is None:
-                err = error.StreamError(u"Server unexpectedly closed the connection")
+                err = error.StreamError("Server unexpectedly closed the connection")
             else:
                 err = reason
                 try:
                     if err.value.args[0][0][2] == "certificate verify failed":
                         err = exceptions.InvalidCertificate(
-                            _(u"Your server certificate is not valid "
-                              u"(its identity can't be checked).\n\n"
-                              u"This should never happen and may indicate that "
-                              u"somebody is trying to spy on you.\n"
-                              u"Please contact your server administrator."))
+                            _("Your server certificate is not valid "
+                              "(its identity can't be checked).\n\n"
+                              "This should never happen and may indicate that "
+                              "somebody is trying to spy on you.\n"
+                              "Please contact your server administrator."))
                         self.factory.stopTrying()
                         try:
                             # with invalid certificate, we should not retry to connect
@@ -434,7 +434,7 @@
     def entityDisconnect(self):
         if not self.host_app.trigger.point("disconnecting", self):
             return
-        log.info(_(u"Disconnecting..."))
+        log.info(_("Disconnecting..."))
         self.stopService()
         if self._connected_d is not None:
             return self._connected_d
@@ -443,7 +443,7 @@
 
     ## sending ##
 
-    def IQ(self, type_=u"set", timeout=60):
+    def IQ(self, type_="set", timeout=60):
         """shortcut to create an IQ element managing deferred
 
         @param type_(unicode): IQ type ('set' or 'get')
@@ -486,11 +486,11 @@
         if data["uid"]:  # key must be present but can be set to ''
             # by a plugin to avoid id on purpose
             message_elt["id"] = data["uid"]
-        for lang, subject in data["subject"].iteritems():
+        for lang, subject in data["subject"].items():
             subject_elt = message_elt.addElement("subject", content=subject)
             if lang:
                 subject_elt[(C.NS_XML, "lang")] = lang
-        for lang, message in data["message"].iteritems():
+        for lang, message in data["message"].items():
             body_elt = message_elt.addElement("body", content=message)
             if lang:
                 body_elt[(C.NS_XML, "lang")] = lang
@@ -499,7 +499,7 @@
         except KeyError:
             if "thread_parent" in data["extra"]:
                 raise exceptions.InternalError(
-                    u"thread_parent found while there is not associated thread"
+                    "thread_parent found while there is not associated thread"
                 )
         else:
             thread_elt = message_elt.addElement("thread", content=thread)
@@ -546,7 +546,7 @@
         data = {  # dict is similar to the one used in client.onMessage
             "from": self.jid,
             "to": to_jid,
-            "uid": uid or unicode(uuid.uuid4()),
+            "uid": uid or str(uuid.uuid4()),
             "message": message,
             "subject": subject,
             "type": mess_type,
@@ -599,15 +599,15 @@
             ):
                 return defer.succeed(None)
 
-        log.debug(_(u"Sending message (type {type}, to {to})")
+        log.debug(_("Sending message (type {type}, to {to})")
                     .format(type=data["type"], to=to_jid.full()))
 
         pre_xml_treatments.addCallback(lambda __: self.generateMessageXML(data))
         pre_xml_treatments.chainDeferred(post_xml_treatments)
         post_xml_treatments.addCallback(self.sendMessageData)
         if send_only:
-            log.debug(_(u"Triggers, storage and echo have been inhibited by the "
-                        u"'send_only' parameter"))
+            log.debug(_("Triggers, storage and echo have been inhibited by the "
+                        "'send_only' parameter"))
         else:
             self.addPostXmlCallbacks(post_xml_treatments)
             post_xml_treatments.addErrback(self._cancelErrorTrap)
@@ -625,22 +625,22 @@
         @param data: message data dictionnary
         @param client: profile's client
         """
-        if data[u"type"] != C.MESS_TYPE_GROUPCHAT:
+        if data["type"] != C.MESS_TYPE_GROUPCHAT:
             # we don't add groupchat message to history, as we get them back
             # and they will be added then
-            if data[u"message"] or data[u"subject"]:  # we need a message to store
+            if data["message"] or data["subject"]:  # we need a message to store
                 self.host_app.memory.addToHistory(self, data)
             else:
                 log.warning(
-                    u"No message found"
+                    "No message found"
                 )  # empty body should be managed by plugins before this point
         return data
 
     def messageGetBridgeArgs(self, data):
         """Generate args to use with bridge from data dict"""
-        return (data[u"uid"], data[u"timestamp"], data[u"from"].full(),
-                data[u"to"].full(), data[u"message"], data[u"subject"],
-                data[u"type"], data[u"extra"])
+        return (data["uid"], data["timestamp"], data["from"].full(),
+                data["to"].full(), data["message"], data["subject"],
+                data["type"], data["extra"])
 
 
     def messageSendToBridge(self, data):
@@ -649,10 +649,10 @@
         @param data: message data dictionnary
         @param client: profile's client
         """
-        if data[u"type"] != C.MESS_TYPE_GROUPCHAT:
+        if data["type"] != C.MESS_TYPE_GROUPCHAT:
             # we don't send groupchat message to bridge, as we get them back
             # and they will be added the
-            if (data[u"message"] or data[u"subject"]):  # we need a message to send
+            if (data["message"] or data["subject"]):  # we need a message to send
                                                         # something
 
                 # We send back the message, so all frontends are aware of it
@@ -661,12 +661,12 @@
                     profile=self.profile
                 )
             else:
-                log.warning(_(u"No message found"))
+                log.warning(_("No message found"))
         return data
 
 
+@implementer(iwokkel.IDisco)
 class SatXMPPClient(SatXMPPEntity, wokkel_client.XMPPClient):
-    implements(iwokkel.IDisco)
     trigger_suffix = ""
     is_component = False
 
@@ -681,34 +681,34 @@
         # with a web frontend,
         # etc., we should implement a way to dynamically update identities through the
         # bridge
-        self.identities = [disco.DiscoIdentity(u"client", u"pc", C.APP_NAME)]
+        self.identities = [disco.DiscoIdentity("client", "pc", C.APP_NAME)]
         if sys.platform == "android":
             # FIXME: temporary hack as SRV is not working on android
             # TODO: remove this hack and fix SRV
-            log.info(u"FIXME: Android hack, ignoring SRV")
+            log.info("FIXME: Android hack, ignoring SRV")
             if host is None:
                 host = user_jid.host
             # for now we consider Android devices to be always phones
-            self.identities = [disco.DiscoIdentity(u"client", u"phone", C.APP_NAME)]
+            self.identities = [disco.DiscoIdentity("client", "phone", C.APP_NAME)]
 
         hosts_map = host_app.memory.getConfig(None, "hosts_dict", {})
         if host is None and user_jid.host in hosts_map:
             host_data = hosts_map[user_jid.host]
-            if isinstance(host_data, basestring):
+            if isinstance(host_data, str):
                 host = host_data
             elif isinstance(host_data, dict):
-                if u"host" in host_data:
-                    host = host_data[u"host"]
-                if u"port" in host_data:
-                    port = host_data[u"port"]
+                if "host" in host_data:
+                    host = host_data["host"]
+                if "port" in host_data:
+                    port = host_data["port"]
             else:
                 log.warning(
-                    _(u"invalid data used for host: {data}").format(data=host_data)
+                    _("invalid data used for host: {data}").format(data=host_data)
                 )
                 host_data = None
             if host_data is not None:
                 log.info(
-                    u"using {host}:{port} for host {host_ori} as requested in config"
+                    "using {host}:{port} for host {host_ori} as requested in config"
                     .format(host_ori=user_jid.host, host=host, port=port)
                 )
 
@@ -717,22 +717,22 @@
 
         wokkel_client.XMPPClient.__init__(
             self, user_jid, password, host or None, port or C.XMPP_C2S_PORT,
-            check_certificate = self.check_certificate
+            # check_certificate = self.check_certificate  # FIXME: currently disabled with Python 3 port
         )
         SatXMPPEntity.__init__(self, host_app, profile, max_retries)
 
         if not self.check_certificate:
-            msg = (_(u"Certificate validation is deactivated, this is unsecure and "
-                u"somebody may be spying on you. If you have no good reason to disable "
-                u"certificate validation, please activate \"Check certificate\" in your "
-                u"settings in \"Connection\" tab."))
-            xml_tools.quickNote(host_app, self, msg, _(u"Security notice"),
+            msg = (_("Certificate validation is deactivated, this is unsecure and "
+                "somebody may be spying on you. If you have no good reason to disable "
+                "certificate validation, please activate \"Check certificate\" in your "
+                "settings in \"Connection\" tab."))
+            xml_tools.quickNote(host_app, self, msg, _("Security notice"),
                 level = C.XMLUI_DATA_LVL_WARNING)
 
 
     def _getPluginsList(self):
-        for p in self.host_app.plugins.itervalues():
-            if C.PLUG_MODE_CLIENT in p._info[u"modes"]:
+        for p in self.host_app.plugins.values():
+            if C.PLUG_MODE_CLIENT in p._info["modes"]:
                 yield p
 
     def _createSubProtocols(self):
@@ -795,7 +795,7 @@
         #      This trigger point can't cancel the method
         yield self.host_app.trigger.asyncPoint("sendMessageData", self, mess_data,
             triggers_no_cancel=True)
-        self.send(mess_data[u"xml"])
+        self.send(mess_data["xml"])
         defer.returnValue(mess_data)
 
     def feedback(self, to_jid, message, extra=None):
@@ -811,11 +811,11 @@
         if extra is None:
             extra = {}
         self.host_app.bridge.messageNew(
-            uid=unicode(uuid.uuid4()),
+            uid=str(uuid.uuid4()),
             timestamp=time.time(),
             from_jid=self.jid.full(),
             to_jid=to_jid.full(),
-            message={u"": message},
+            message={"": message},
             subject={},
             mess_type=C.MESS_TYPE_INFO,
             extra=extra,
@@ -827,6 +827,7 @@
         d.addCallback(lambda __: super(SatXMPPClient, self)._finish_connection(__))
 
 
+@implementer(iwokkel.IDisco)
 class SatXMPPComponent(SatXMPPEntity, component.Component):
     """XMPP component
 
@@ -835,7 +836,6 @@
     Component need to instantiate MessageProtocol itself
     """
 
-    implements(iwokkel.IDisco)
     trigger_suffix = (
         "Component"
     )  # used for to distinguish some trigger points set in SatXMPPEntity
@@ -857,19 +857,19 @@
             self.entry_plugin = host_app.plugins[entry_point]
         except KeyError:
             raise exceptions.NotFound(
-                _(u"The requested entry point ({entry_point}) is not available").format(
+                _("The requested entry point ({entry_point}) is not available").format(
                     entry_point=entry_point
                 )
             )
 
-        self.identities = [disco.DiscoIdentity(u"component", u"generic", C.APP_NAME)]
+        self.identities = [disco.DiscoIdentity("component", "generic", C.APP_NAME)]
         # jid is set automatically on bind by Twisted for Client, but not for Component
         self.jid = component_jid
         if host is None:
             try:
-                host = component_jid.host.split(u".", 1)[1]
+                host = component_jid.host.split(".", 1)[1]
             except IndexError:
-                raise ValueError(u"Can't guess host from jid, please specify a host")
+                raise ValueError("Can't guess host from jid, please specify a host")
         # XXX: component.Component expect unicode jid, while Client expect jid.JID.
         #      this is not consistent, so we use jid.JID for SatXMPP*
         component.Component.__init__(self, host, port, component_jid.full(), password)
@@ -890,20 +890,20 @@
         @raise KeyError: one plugin should be present in self.host_app.plugins but it
                          is not
         """
-        if C.PLUG_MODE_COMPONENT not in current._info[u"modes"]:
+        if C.PLUG_MODE_COMPONENT not in current._info["modes"]:
             if not required:
                 return
             else:
                 log.error(
                     _(
-                        u"Plugin {current_name} is needed for {entry_name}, "
-                        u"but it doesn't handle component mode"
+                        "Plugin {current_name} is needed for {entry_name}, "
+                        "but it doesn't handle component mode"
                     ).format(
-                        current_name=current._info[u"import_name"],
-                        entry_name=self.entry_plugin._info[u"import_name"],
+                        current_name=current._info["import_name"],
+                        entry_name=self.entry_plugin._info["import_name"],
                     )
                 )
-                raise exceptions.InternalError(_(u"invalid plugin mode"))
+                raise exceptions.InternalError(_("invalid plugin mode"))
 
         for import_name in current._info.get(C.PI_DEPENDENCIES, []):
             # plugins are already loaded as dependencies
@@ -960,9 +960,9 @@
             if None, mapping will not be done
         @return(dict): message data
         """
-        if message_elt.name != u"message":
+        if message_elt.name != "message":
             log.warning(_(
-                u"parseMessage used with a non <message/> stanza, ignoring: {xml}"
+                "parseMessage used with a non <message/> stanza, ignoring: {xml}"
                 .format(xml=message_elt.toXml())))
             return {}
 
@@ -974,31 +974,31 @@
                     c.uri = C.NS_CLIENT
         elif message_elt.uri != C.NS_CLIENT:
             log.warning(_(
-                u"received <message> with a wrong namespace: {xml}"
+                "received <message> with a wrong namespace: {xml}"
                 .format(xml=message_elt.toXml())))
 
         client = self.parent
 
-        if not message_elt.hasAttribute(u'to'):
+        if not message_elt.hasAttribute('to'):
             message_elt['to'] = client.jid.full()
 
         message = {}
         subject = {}
         extra = {}
         data = {
-            u"from": jid.JID(message_elt["from"]),
-            u"to": jid.JID(message_elt["to"]),
-            u"uid": message_elt.getAttribute(
-                u"uid", unicode(uuid.uuid4())
+            "from": jid.JID(message_elt["from"]),
+            "to": jid.JID(message_elt["to"]),
+            "uid": message_elt.getAttribute(
+                "uid", str(uuid.uuid4())
             ),  # XXX: uid is not a standard attribute but may be added by plugins
-            u"message": message,
-            u"subject": subject,
-            u"type": message_elt.getAttribute(u"type", u"normal"),
-            u"extra": extra,
+            "message": message,
+            "subject": subject,
+            "type": message_elt.getAttribute("type", "normal"),
+            "extra": extra,
         }
 
         try:
-            message_id = data[u"extra"][u"message_id"] = message_elt[u"id"]
+            message_id = data["extra"]["message_id"] = message_elt["id"]
         except KeyError:
             pass
         else:
@@ -1006,11 +1006,11 @@
 
         # message
         for e in message_elt.elements(C.NS_CLIENT, "body"):
-            message[e.getAttribute((C.NS_XML, "lang"), "")] = unicode(e)
+            message[e.getAttribute((C.NS_XML, "lang"), "")] = str(e)
 
         # subject
         for e in message_elt.elements(C.NS_CLIENT, "subject"):
-            subject[e.getAttribute((C.NS_XML, "lang"), "")] = unicode(e)
+            subject[e.getAttribute((C.NS_XML, "lang"), "")] = str(e)
 
         # delay and timestamp
         try:
@@ -1018,12 +1018,12 @@
         except AttributeError:
             # message_elt._received_timestamp should have been set in onMessage
             # but if parseMessage is called directly, it can be missing
-            log.debug(u"missing received timestamp for {message_elt}".format(
+            log.debug("missing received timestamp for {message_elt}".format(
                 message_elt=message_elt))
             received_timestamp = time.time()
 
         try:
-            delay_elt = message_elt.elements(delay.NS_DELAY, "delay").next()
+            delay_elt = next(message_elt.elements(delay.NS_DELAY, "delay"))
         except StopIteration:
             data["timestamp"] = received_timestamp
         else:
@@ -1060,7 +1060,7 @@
         client = self.parent
         if not "from" in message_elt.attributes:
             message_elt["from"] = client.jid.host
-        log.debug(_(u"got message from: {from_}").format(from_=message_elt["from"]))
+        log.debug(_("got message from: {from_}").format(from_=message_elt["from"]))
 
         # plugin can add their treatments to this deferred
         post_treat = defer.Deferred()
@@ -1077,24 +1077,24 @@
         return data
 
     def addToHistory(self, data):
-        if data.pop(u"history", None) == C.HISTORY_SKIP:
-            log.info(u"history is skipped as requested")
-            data[u"extra"][u"history"] = C.HISTORY_SKIP
+        if data.pop("history", None) == C.HISTORY_SKIP:
+            log.info("history is skipped as requested")
+            data["extra"]["history"] = C.HISTORY_SKIP
         else:
-            if data[u"message"] or data[u"subject"]:  # we need a message to store
+            if data["message"] or data["subject"]:  # we need a message to store
                 return self.host.memory.addToHistory(self.parent, data)
             else:
-                log.debug(u"not storing empty message to history: {data}"
+                log.debug("not storing empty message to history: {data}"
                     .format(data=data))
 
     def bridgeSignal(self, __, data):
         try:
-            data["extra"]["received_timestamp"] = unicode(data["received_timestamp"])
+            data["extra"]["received_timestamp"] = str(data["received_timestamp"])
             data["extra"]["delay_sender"] = data["delay_sender"]
         except KeyError:
             pass
         if C.MESS_KEY_ENCRYPTION in data:
-            data[u"extra"][u"encrypted"] = C.BOOL_TRUE
+            data["extra"]["encrypted"] = C.BOOL_TRUE
         if data is not None:
             if data["message"] or data["subject"] or data["type"] == C.MESS_TYPE_INFO:
                 self.host.bridge.messageNew(
@@ -1109,7 +1109,7 @@
                     profile=self.parent.profile,
                 )
             else:
-                log.debug(u"Discarding bridge signal for empty message: {data}".format(
+                log.debug("Discarding bridge signal for empty message: {data}".format(
                     data=data))
         return data
 
@@ -1131,7 +1131,7 @@
     @property
     def versioning(self):
         """True if server support roster versioning"""
-        return (NS_ROSTER_VER, u'ver') in self.parent.xmlstream.features
+        return (NS_ROSTER_VER, 'ver') in self.parent.xmlstream.features
 
     @property
     def roster_cache(self):
@@ -1148,23 +1148,23 @@
         item must be already registered in self._jids before this method is called
         @param item (RosterIem): item added
         """
-        log.debug(u"registering item: {}".format(item.entity.full()))
+        log.debug("registering item: {}".format(item.entity.full()))
         if item.entity.resource:
             log.warning(
-                u"Received a roster item with a resource, this is not common but not "
-                u"restricted by RFC 6121, this case may be not well tested."
+                "Received a roster item with a resource, this is not common but not "
+                "restricted by RFC 6121, this case may be not well tested."
             )
         if not item.subscriptionTo:
             if not item.subscriptionFrom:
                 log.info(
-                    _(u"There's no subscription between you and [{}]!").format(
+                    _("There's no subscription between you and [{}]!").format(
                         item.entity.full()
                     )
                 )
             else:
-                log.info(_(u"You are not subscribed to [{}]!").format(item.entity.full()))
+                log.info(_("You are not subscribed to [{}]!").format(item.entity.full()))
         if not item.subscriptionFrom:
-            log.info(_(u"[{}] is not subscribed to you!").format(item.entity.full()))
+            log.info(_("[{}] is not subscribed to you!").format(item.entity.full()))
 
         for group in item.groups:
             self._groups.setdefault(group, set()).add(item.entity)
@@ -1178,7 +1178,7 @@
         roster_cache = self.roster_cache
         yield roster_cache.clear()
         roster_cache[ROSTER_VER_KEY] = version
-        for roster_jid, roster_item in self._jids.iteritems():
+        for roster_jid, roster_item in self._jids.items():
             roster_jid_s = roster_jid.full()
             roster_item_elt = roster_item.toElement().toXml()
             roster_cache[roster_jid_s] = roster_item_elt
@@ -1200,19 +1200,19 @@
     def requestRoster(self):
         """Ask the server for Roster list """
         if self.versioning:
-            log.info(_(u"our server support roster versioning, we use it"))
+            log.info(_("our server support roster versioning, we use it"))
             roster_cache = self.roster_cache
             yield roster_cache.load()
             try:
                 version = roster_cache[ROSTER_VER_KEY]
             except KeyError:
-                log.info(_(u"no roster in cache, we start fresh"))
+                log.info(_("no roster in cache, we start fresh"))
                 # u"" means we use versioning without valid roster in cache
-                version = u""
+                version = ""
             else:
-                log.info(_(u"We have roster v{version} in cache").format(version=version))
+                log.info(_("We have roster v{version} in cache").format(version=version))
                 # we deserialise cached roster to our local cache
-                for roster_jid_s, roster_item_elt_s in roster_cache.iteritems():
+                for roster_jid_s, roster_item_elt_s in roster_cache.items():
                     if roster_jid_s == ROSTER_VER_KEY:
                         continue
                     roster_jid = jid.JID(roster_jid_s)
@@ -1221,26 +1221,26 @@
                     self._jids[roster_jid] = roster_item
                     self._registerItem(roster_item)
         else:
-            log.warning(_(u"our server doesn't support roster versioning"))
+            log.warning(_("our server doesn't support roster versioning"))
             version = None
 
         log.debug("requesting roster")
         roster = yield self.getRoster(version=version)
         if roster is None:
-            log.debug(u"empty roster result received, we'll get roster item with roster "
-                      u"pushes")
+            log.debug("empty roster result received, we'll get roster item with roster "
+                      "pushes")
         else:
             # a full roster is received
             self._groups.clear()
             self._jids = roster
-            for item in roster.itervalues():
+            for item in roster.values():
                 if not item.subscriptionTo and not item.subscriptionFrom and not item.ask:
                     # XXX: current behaviour: we don't want contact in our roster list
                     # if there is no presence subscription
                     # may change in the future
                     log.info(
-                        u"Removing contact {} from roster because there is no presence "
-                        u"subscription".format(
+                        "Removing contact {} from roster because there is no presence "
+                        "subscription".format(
                             item.jid
                         )
                     )
@@ -1267,9 +1267,9 @@
         @return: dictionary of attributes
         """
         item_attr = {
-            "to": unicode(item.subscriptionTo),
-            "from": unicode(item.subscriptionFrom),
-            "ask": unicode(item.ask),
+            "to": str(item.subscriptionTo),
+            "from": str(item.subscriptionFrom),
+            "ask": str(item.ask),
         }
         if item.name:
             item_attr["name"] = item.name
@@ -1278,7 +1278,7 @@
     def setReceived(self, request):
         item = request.item
         entity = item.entity
-        log.info(_(u"adding {entity} to roster").format(entity=entity.full()))
+        log.info(_("adding {entity} to roster").format(entity=entity.full()))
         if request.version is not None:
             # we update the cache in storage
             roster_cache = self.roster_cache
@@ -1302,7 +1302,7 @@
 
     def removeReceived(self, request):
         entity = request.item.entity
-        log.info(_(u"removing {entity} from roster").format(entity=entity.full()))
+        log.info(_("removing {entity} from roster").format(entity=entity.full()))
         if request.version is not None:
             # we update the cache in storage
             roster_cache = self.roster_cache
@@ -1319,7 +1319,7 @@
             item = self._jids.pop(entity)
         except KeyError:
             log.error(
-                u"Received a roster remove event for an item not in cache ({})".format(
+                "Received a roster remove event for an item not in cache ({})".format(
                     entity
                 )
             )
@@ -1332,8 +1332,8 @@
                     del self._groups[group]
             except KeyError:
                 log.warning(
-                    u"there is no cache for the group [{group}] of the removed roster "
-                    u"item [{jid_}]".format(group=group, jid=entity)
+                    "there is no cache for the group [{group}] of the removed roster "
+                    "item [{jid_}]".format(group=group, jid=entity)
                 )
 
         # then we send the bridge signal
@@ -1341,7 +1341,7 @@
 
     def getGroups(self):
         """Return a list of groups"""
-        return self._groups.keys()
+        return list(self._groups.keys())
 
     def getItem(self, entity_jid):
         """Return RosterItem for a given jid
@@ -1354,7 +1354,7 @@
 
     def getJids(self):
         """Return all jids of the roster"""
-        return self._jids.keys()
+        return list(self._jids.keys())
 
     def isJidInRoster(self, entity_jid):
         """Return True if jid is in roster"""
@@ -1370,7 +1370,7 @@
 
     def getItems(self):
         """Return all items of the roster"""
-        return self._jids.values()
+        return list(self._jids.values())
 
     def getJidsFromGroup(self, group):
         try:
@@ -1398,7 +1398,7 @@
                 jids.update(self.getJidsFromGroup(group))
             return jids
         else:
-            raise ValueError(u"Unexpected type_ {}".format(type_))
+            raise ValueError("Unexpected type_ {}".format(type_))
 
     def getNick(self, entity_jid):
         """Return a nick name for an entity
@@ -1447,7 +1447,7 @@
 
     def unavailableReceived(self, entity, statuses=None):
         log.debug(
-            _(u"presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)")
+            _("presence update for [%(entity)s] (unavailable, statuses=%(statuses)s)")
             % {"entity": entity, C.PRESENCE_STATUSES: statuses}
         )
 
@@ -1539,16 +1539,16 @@
         self.host.memory.delWaitingSub(entity.userhost(), self.parent.profile)
 
     def subscribedReceived(self, entity):
-        log.debug(_(u"subscription approved for [%s]") % entity.userhost())
+        log.debug(_("subscription approved for [%s]") % entity.userhost())
         self.host.bridge.subscribe("subscribed", entity.userhost(), self.parent.profile)
 
     def unsubscribedReceived(self, entity):
-        log.debug(_(u"unsubscription confirmed for [%s]") % entity.userhost())
+        log.debug(_("unsubscription confirmed for [%s]") % entity.userhost())
         self.host.bridge.subscribe("unsubscribed", entity.userhost(), self.parent.profile)
 
     @defer.inlineCallbacks
     def subscribeReceived(self, entity):
-        log.debug(_(u"subscription request from [%s]") % entity.userhost())
+        log.debug(_("subscription request from [%s]") % entity.userhost())
         yield self.parent.roster.got_roster
         item = self.parent.roster.getItem(entity)
         if item and item.subscriptionTo:
@@ -1566,7 +1566,7 @@
 
     @defer.inlineCallbacks
     def unsubscribeReceived(self, entity):
-        log.debug(_(u"unsubscription asked for [%s]") % entity.userhost())
+        log.debug(_("unsubscription asked for [%s]") % entity.userhost())
         yield self.parent.roster.got_roster
         item = self.parent.roster.getItem(entity)
         if item and item.subscriptionFrom:  # we automatically remove contact
@@ -1575,8 +1575,8 @@
         self.host.bridge.subscribe("unsubscribe", entity.userhost(), self.parent.profile)
 
 
+@implementer(iwokkel.IDisco)
 class SatDiscoProtocol(disco.DiscoClientProtocol):
-    implements(iwokkel.IDisco)
 
     def __init__(self, host):
         disco.DiscoClientProtocol.__init__(self)
@@ -1599,7 +1599,7 @@
     def iqFallback(self, iq):
         if iq.handled is True:
             return
-        log.debug(u"iqFallback: xml = [%s]" % (iq.toXml()))
+        log.debug("iqFallback: xml = [%s]" % (iq.toXml()))
         generic.FallbackHandler.iqFallback(self, iq)
 
 
@@ -1615,9 +1615,9 @@
         return generic.VersionHandler.getDiscoInfo(self, requestor, target, None)
 
 
+@implementer(iwokkel.IDisco)
 class SatIdentityHandler(XMPPHandler):
     """Manage disco Identity of SàT."""
-    implements(iwokkel.IDisco)
     # TODO: dynamic identity update (see docstring). Note that a XMPP entity can have
     #       several identities