diff sat/plugins/plugin_xep_0054.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 e624550d5c24
children fee60f17ebac
line wrap: on
line diff
--- a/sat/plugins/plugin_xep_0054.py	Wed Jul 31 11:31:22 2019 +0200
+++ b/sat/plugins/plugin_xep_0054.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 plugin for managing xep-0054
@@ -28,7 +28,7 @@
 from twisted.words.xish import domish
 from twisted.python.failure import Failure
 
-from zope.interface import implements
+from zope.interface import implementer
 
 from wokkel import disco, iwokkel
 
@@ -42,9 +42,9 @@
     from PIL import Image
 except:
     raise exceptions.MissingModule(
-        u"Missing module pillow, please download/install it from https://python-pillow.github.io"
+        "Missing module pillow, please download/install it from https://python-pillow.github.io"
     )
-from cStringIO import StringIO
+from io import StringIO
 
 try:
     from twisted.words.protocols.xmlstream import XMPPHandler
@@ -84,27 +84,27 @@
     #      - get missing values
 
     def __init__(self, host):
-        log.info(_(u"Plugin XEP_0054 initialization"))
+        log.info(_("Plugin XEP_0054 initialization"))
         self.host = host
         host.bridge.addMethod(
-            u"avatarGet",
-            u".plugin",
-            in_sign=u"sbbs",
-            out_sign=u"s",
+            "avatarGet",
+            ".plugin",
+            in_sign="sbbs",
+            out_sign="s",
             method=self._getAvatar,
-            async=True,
+            async_=True,
         )
         host.bridge.addMethod(
-            u"avatarSet",
-            u".plugin",
-            in_sign=u"ss",
-            out_sign=u"",
+            "avatarSet",
+            ".plugin",
+            in_sign="ss",
+            out_sign="",
             method=self._setAvatar,
-            async=True,
+            async_=True,
         )
-        host.trigger.add(u"presence_available", self.presenceAvailableTrigger)
-        host.memory.setSignalOnUpdate(u"avatar")
-        host.memory.setSignalOnUpdate(u"nick")
+        host.trigger.add("presence_available", self.presenceAvailableTrigger)
+        host.memory.setSignalOnUpdate("avatar")
+        host.memory.setSignalOnUpdate("nick")
 
     def getHandler(self, client):
         return XEP_0054_handler(self)
@@ -143,7 +143,7 @@
             try:
                 avatar_hash = client._cache_0054[client.jid.userhost()]["avatar"]
             except KeyError:
-                log.info(u"No avatar in cache for {}".format(client.jid.userhost()))
+                log.info("No avatar in cache for {}".format(client.jid.userhost()))
                 return True
             x_elt = domish.Element((NS_VCARD_UPDATE, "x"))
             x_elt.addElement("photo", content=avatar_hash)
@@ -165,14 +165,14 @@
         #       this is not possible with vcard-tmp, but it is with XEP-0084).
         #       Loading avatar on demand per jid may be an option to investigate.
         client = self.host.getClient(profile)
-        for jid_s, data in client._cache_0054.iteritems():
+        for jid_s, data in client._cache_0054.items():
             jid_ = jid.JID(jid_s)
             for name in CACHED_DATA:
                 try:
                     value = data[name]
                     if value is None:
                         log.error(
-                            u"{name} value for {jid_} is None, ignoring".format(
+                            "{name} value for {jid_} is None, ignoring".format(
                                 name=name, jid_=jid_
                             )
                         )
@@ -232,7 +232,7 @@
         """Parse a <PHOTO> photo_elt and save the picture"""
         # XXX: this method is launched in a separate thread
         try:
-            mime_type = unicode(photo_elt.elements(NS_VCARD, "TYPE").next())
+            mime_type = str(next(photo_elt.elements(NS_VCARD, "TYPE")))
         except StopIteration:
             mime_type = None
         else:
@@ -248,7 +248,7 @@
                 else:
                     # TODO: handle other image formats (svg?)
                     log.warning(
-                        u"following avatar image format is not handled: {type} [{jid}]".format(
+                        "following avatar image format is not handled: {type} [{jid}]".format(
                             type=mime_type, jid=entity_jid.full()
                         )
                     )
@@ -256,31 +256,31 @@
 
             ext = mimetypes.guess_extension(mime_type, strict=False)
             assert ext is not None
-            if ext == u".jpe":
-                ext = u".jpg"
+            if ext == ".jpe":
+                ext = ".jpg"
             log.debug(
-                u"photo of type {type} with extension {ext} found [{jid}]".format(
+                "photo of type {type} with extension {ext} found [{jid}]".format(
                     type=mime_type, ext=ext, jid=entity_jid.full()
                 )
             )
         try:
-            buf = str(photo_elt.elements(NS_VCARD, "BINVAL").next())
+            buf = str(next(photo_elt.elements(NS_VCARD, "BINVAL")))
         except StopIteration:
-            log.warning(u"BINVAL element not found")
+            log.warning("BINVAL element not found")
             raise Failure(exceptions.NotFound())
         if not buf:
-            log.warning(u"empty avatar for {jid}".format(jid=entity_jid.full()))
+            log.warning("empty avatar for {jid}".format(jid=entity_jid.full()))
             raise Failure(exceptions.NotFound())
         if mime_type is None:
-            log.warning(_(u"no MIME type found for {entity}'s avatar, assuming image/png")
+            log.warning(_("no MIME type found for {entity}'s avatar, assuming image/png")
                 .format(entity=entity_jid.full()))
             if buf[:8] != b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a':
-                log.warning(u"this is not a PNG file, ignoring it")
+                log.warning("this is not a PNG file, ignoring it")
                 raise Failure(exceptions.DataError())
             else:
-                mime_type = u"image/png"
+                mime_type = "image/png"
 
-        log.debug(_(u"Decoding binary"))
+        log.debug(_("Decoding binary"))
         decoded = b64decode(buf)
         del buf
         image_hash = sha1(decoded).hexdigest()
@@ -297,21 +297,21 @@
     @defer.inlineCallbacks
     def vCard2Dict(self, client, vcard, entity_jid):
         """Convert a VCard to a dict, and save binaries"""
-        log.debug((u"parsing vcard"))
+        log.debug(("parsing vcard"))
         vcard_dict = {}
 
         for elem in vcard.elements():
             if elem.name == "FN":
-                vcard_dict["fullname"] = unicode(elem)
+                vcard_dict["fullname"] = str(elem)
             elif elem.name == "NICKNAME":
-                vcard_dict["nick"] = unicode(elem)
+                vcard_dict["nick"] = str(elem)
                 self.updateCache(client, entity_jid, "nick", vcard_dict["nick"])
             elif elem.name == "URL":
-                vcard_dict["website"] = unicode(elem)
+                vcard_dict["website"] = str(elem)
             elif elem.name == "EMAIL":
-                vcard_dict["email"] = unicode(elem)
+                vcard_dict["email"] = str(elem)
             elif elem.name == "BDAY":
-                vcard_dict["birthday"] = unicode(elem)
+                vcard_dict["birthday"] = str(elem)
             elif elem.name == "PHOTO":
                 # TODO: handle EXTVAL
                 try:
@@ -322,20 +322,20 @@
                     avatar_hash = ""
                     vcard_dict["avatar"] = avatar_hash
                 except Exception as e:
-                    log.error(u"avatar saving error: {}".format(e))
+                    log.error("avatar saving error: {}".format(e))
                     avatar_hash = None
                 else:
                     vcard_dict["avatar"] = avatar_hash
                 self.updateCache(client, entity_jid, "avatar", avatar_hash)
             else:
-                log.debug(u"FIXME: [{}] VCard tag is not managed yet".format(elem.name))
+                log.debug("FIXME: [{}] VCard tag is not managed yet".format(elem.name))
 
         # if a data in cache doesn't exist anymore, we need to delete it
         # so we check CACHED_DATA no gotten (i.e. not in vcard_dict keys)
         # and we reset them
-        for datum in CACHED_DATA.difference(vcard_dict.keys()):
+        for datum in CACHED_DATA.difference(list(vcard_dict.keys())):
             log.debug(
-                u"reseting vcard datum [{datum}] for {entity}".format(
+                "reseting vcard datum [{datum}] for {entity}".format(
                     datum=datum, entity=entity_jid.full()
                 )
             )
@@ -357,14 +357,14 @@
     def _vCardEb(self, failure_, to_jid, client):
         """Called when something is wrong with registration"""
         log.warning(
-            u"Can't get vCard for {jid}: {failure}".format(
+            "Can't get vCard for {jid}: {failure}".format(
                 jid=to_jid.full, failure=failure_
             )
         )
         self.updateCache(client, to_jid, "avatar", None)
 
     def _getVcardElt(self, iq_elt):
-        return iq_elt.elements(NS_VCARD, "vCard").next()
+        return next(iq_elt.elements(NS_VCARD, "vCard"))
 
     def getCardRaw(self, client, entity_jid):
         """get raw vCard XML
@@ -372,7 +372,7 @@
         params are as in [getCard]
         """
         entity_jid = self.getBareOrFull(client, entity_jid)
-        log.debug(u"Asking for {}'s VCard".format(entity_jid.full()))
+        log.debug("Asking for {}'s VCard".format(entity_jid.full()))
         reg_request = client.IQ("get")
         reg_request["from"] = client.jid.full()
         reg_request["to"] = entity_jid.full()
@@ -435,7 +435,7 @@
                     raise KeyError
             else:
                 # avatar has already been checked but it is not set
-                full_path = u""
+                full_path = ""
         except KeyError:
             # avatar is not in cache
             if cache_only:
@@ -462,11 +462,11 @@
         @param entity(jid.JID): entity to get nick from
         @return(unicode, None): nick or None if not found
         """
-        nick = self.getCache(client, entity, u"nick")
+        nick = self.getCache(client, entity, "nick")
         if nick is not None:
             defer.returnValue(nick)
         yield self.getCard(client, entity)
-        defer.returnValue(self.getCache(client, entity, u"nick"))
+        defer.returnValue(self.getCache(client, entity, "nick"))
 
     @defer.inlineCallbacks
     def setNick(self, client, nick):
@@ -483,24 +483,24 @@
             else:
                 raise e
         try:
-            nickname_elt = next(vcard_elt.elements(NS_VCARD, u"NICKNAME"))
+            nickname_elt = next(vcard_elt.elements(NS_VCARD, "NICKNAME"))
         except StopIteration:
             pass
         else:
             vcard_elt.children.remove(nickname_elt)
 
-        nickname_elt = vcard_elt.addElement((NS_VCARD, u"NICKNAME"), content=nick)
+        nickname_elt = vcard_elt.addElement((NS_VCARD, "NICKNAME"), content=nick)
         iq_elt = client.IQ()
         vcard_elt = iq_elt.addChild(vcard_elt)
         yield iq_elt.send()
-        self.updateCache(client, jid_, u"nick", unicode(nick))
+        self.updateCache(client, jid_, "nick", str(nick))
 
     def _buildSetAvatar(self, client, vcard_elt, file_path):
         # XXX: this method is executed in a separate thread
         try:
             img = Image.open(file_path)
         except IOError:
-            return Failure(exceptions.DataError(u"Can't open image"))
+            return Failure(exceptions.DataError("Can't open image"))
 
         if img.size != AVATAR_DIM:
             img.thumbnail(AVATAR_DIM)
@@ -549,7 +549,7 @@
         else:
             # the vcard exists, we need to remove PHOTO element as we'll make a new one
             try:
-                photo_elt = next(vcard_elt.elements(NS_VCARD, u"PHOTO"))
+                photo_elt = next(vcard_elt.elements(NS_VCARD, "PHOTO"))
             except StopIteration:
                 pass
             else:
@@ -567,8 +567,8 @@
         client.presence.available()  # FIXME: should send the current presence, not always "available" !
 
 
+@implementer(iwokkel.IDisco)
 class XEP_0054_handler(XMPPHandler):
-    implements(iwokkel.IDisco)
 
     def __init__(self, plugin_parent):
         self.plugin_parent = plugin_parent
@@ -590,7 +590,7 @@
         computed_hash = self.plugin_parent.getCache(client, entity, "avatar")
         if computed_hash != given_hash:
             log.warning(
-                u"computed hash differs from given hash for {entity}:\n"
+                "computed hash differs from given hash for {entity}:\n"
                 "computed: {computed}\ngiven: {given}".format(
                     entity=entity, computed=computed_hash, given=given_hash
                 )
@@ -606,18 +606,18 @@
         entity_jid = self.plugin_parent.getBareOrFull(client, jid.JID(presence["from"]))
         # FIXME: wokkel's data_form should be used here
         try:
-            x_elt = presence.elements(NS_VCARD_UPDATE, "x").next()
+            x_elt = next(presence.elements(NS_VCARD_UPDATE, "x"))
         except StopIteration:
             return
 
         try:
-            photo_elt = x_elt.elements(NS_VCARD_UPDATE, "photo").next()
+            photo_elt = next(x_elt.elements(NS_VCARD_UPDATE, "photo"))
         except StopIteration:
             return
 
-        hash_ = unicode(photo_elt).strip()
+        hash_ = str(photo_elt).strip()
         if hash_ == C.HASH_SHA1_EMPTY:
-            hash_ = u""
+            hash_ = ""
         old_avatar = self.plugin_parent.getCache(client, entity_jid, "avatar")
 
         if old_avatar == hash_:
@@ -627,13 +627,13 @@
                 file_path = client.cache.getFilePath(hash_)
                 if file_path is None:
                     log.error(
-                        u"Avatar for [{}] should be in cache but it is not! We get it".format(
+                        "Avatar for [{}] should be in cache but it is not! We get it".format(
                             entity_jid.full()
                         )
                     )
                     self.plugin_parent.getCard(client, entity_jid)
             else:
-                log.debug(u"avatar for {} already in cache".format(entity_jid.full()))
+                log.debug("avatar for {} already in cache".format(entity_jid.full()))
             return
 
         if not hash_:
@@ -646,14 +646,14 @@
         file_path = client.cache.getFilePath(hash_)
         if file_path is not None:
             log.debug(
-                u"New avatar found for [{}], it's already in cache, we use it".format(
+                "New avatar found for [{}], it's already in cache, we use it".format(
                     entity_jid.full()
                 )
             )
             self.plugin_parent.updateCache(client, entity_jid, "avatar", hash_)
         else:
             log.debug(
-                u"New avatar found for [{}], requesting vcard".format(entity_jid.full())
+                "New avatar found for [{}], requesting vcard".format(entity_jid.full())
             )
             d = self.plugin_parent.getCard(client, entity_jid)
             d.addCallback(self._checkAvatarHash, client, entity_jid, hash_)