diff sat/plugins/plugin_xep_0277.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 0b7ce5daee9b
children 73db9db8b9e1
line wrap: on
line diff
--- a/sat/plugins/plugin_xep_0277.py	Wed Jul 31 11:31:22 2019 +0200
+++ b/sat/plugins/plugin_xep_0277.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 microblogging over XMPP (xep-0277)
@@ -37,12 +37,12 @@
 # XXX: sat_tmp.wokkel.pubsub is actually used instead of wokkel version
 from wokkel import pubsub
 from wokkel import disco, iwokkel
-from zope.interface import implements
+from zope.interface import implementer
 import shortuuid
 import time
 import dateutil
 import calendar
-import urlparse
+import urllib.parse
 
 NS_MICROBLOG = "urn:xmpp:microblog:0"
 NS_ATOM = "http://www.w3.org/2005/Atom"
@@ -71,7 +71,7 @@
     namespace = NS_MICROBLOG
 
     def __init__(self, host):
-        log.info(_(u"Microblogging plugin initialization"))
+        log.info(_("Microblogging plugin initialization"))
         self.host = host
         host.registerNamespace("microblog", NS_MICROBLOG)
         self._p = self.host.plugins[
@@ -88,7 +88,7 @@
             in_sign="ssss",
             out_sign="",
             method=self._mbSend,
-            async=True,
+            async_=True,
         )
         host.bridge.addMethod(
             "mbRetract",
@@ -96,7 +96,7 @@
             in_sign="ssss",
             out_sign="",
             method=self._mbRetract,
-            async=True,
+            async_=True,
         )
         host.bridge.addMethod(
             "mbGet",
@@ -104,7 +104,7 @@
             in_sign="ssiasa{ss}s",
             out_sign="(asa{ss})",
             method=self._mbGet,
-            async=True,
+            async_=True,
         )
         host.bridge.addMethod(
             "mbSetAccess",
@@ -112,7 +112,7 @@
             in_sign="ss",
             out_sign="",
             method=self.mbSetAccess,
-            async=True,
+            async_=True,
         )
         host.bridge.addMethod(
             "mbSubscribeToMany",
@@ -127,7 +127,7 @@
             in_sign="ss",
             out_sign="(ua(sssasa{ss}))",
             method=self._mbGetFromManyRTResult,
-            async=True,
+            async_=True,
         )
         host.bridge.addMethod(
             "mbGetFromMany",
@@ -142,7 +142,7 @@
             in_sign="ss",
             out_sign="(ua(sssa(sa(sssasa{ss}))a{ss}))",
             method=self._mbGetFromManyWithCommentsRTResult,
-            async=True,
+            async_=True,
         )
         host.bridge.addMethod(
             "mbGetFromManyWithComments",
@@ -238,8 +238,8 @@
                 if data_elt is None:
                     raise failure.Failure(
                         exceptions.DataError(
-                            u"XHML content not wrapped in a <div/> element, this is not "
-                            u"standard !"
+                            "XHML content not wrapped in a <div/> element, this is not "
+                            "standard !"
                         )
                     )
                 if data_elt.uri != C.NS_XHTML:
@@ -248,46 +248,46 @@
                             _("Content of type XHTML must declare its namespace!")
                         )
                     )
-                key = check_conflict(u"{}_xhtml".format(elem.name))
+                key = check_conflict("{}_xhtml".format(elem.name))
                 data = data_elt.toXml()
                 microblog_data[key] = yield self.host.plugins["TEXT_SYNTAXES"].cleanXHTML(
                     data
                 )
             else:
                 key = check_conflict(elem.name)
-                microblog_data[key] = unicode(elem)
+                microblog_data[key] = str(elem)
 
         id_ = item_elt.getAttribute("id", "")  # there can be no id for transient nodes
-        microblog_data[u"id"] = id_
+        microblog_data["id"] = id_
         if item_elt.uri not in (pubsub.NS_PUBSUB, NS_PUBSUB_EVENT):
-            msg = u"Unsupported namespace {ns} in pubsub item {id_}".format(
+            msg = "Unsupported namespace {ns} in pubsub item {id_}".format(
                 ns=item_elt.uri, id_=id_
             )
             log.warning(msg)
             raise failure.Failure(exceptions.DataError(msg))
 
         try:
-            entry_elt = item_elt.elements(NS_ATOM, "entry").next()
+            entry_elt = next(item_elt.elements(NS_ATOM, "entry"))
         except StopIteration:
-            msg = u"No atom entry found in the pubsub item {}".format(id_)
+            msg = "No atom entry found in the pubsub item {}".format(id_)
             raise failure.Failure(exceptions.DataError(msg))
 
         # language
         try:
-            microblog_data[u"language"] = entry_elt[(C.NS_XML, u"lang")].strip()
+            microblog_data["language"] = entry_elt[(C.NS_XML, "lang")].strip()
         except KeyError:
             pass
 
         # atom:id
         try:
-            id_elt = entry_elt.elements(NS_ATOM, "id").next()
+            id_elt = next(entry_elt.elements(NS_ATOM, "id"))
         except StopIteration:
-            msg = (u"No atom id found in the pubsub item {}, this is not standard !"
+            msg = ("No atom id found in the pubsub item {}, this is not standard !"
                    .format(id_))
             log.warning(msg)
-            microblog_data[u"atom_id"] = ""
+            microblog_data["atom_id"] = ""
         else:
-            microblog_data[u"atom_id"] = unicode(id_elt)
+            microblog_data["atom_id"] = str(id_elt)
 
         # title/content(s)
 
@@ -302,7 +302,7 @@
         #     raise failure.Failure(exceptions.DataError(msg))
         title_elts = list(entry_elt.elements(NS_ATOM, "title"))
         if not title_elts:
-            msg = u"No atom title found in the pubsub item {}".format(id_)
+            msg = "No atom title found in the pubsub item {}".format(id_)
             raise failure.Failure(exceptions.DataError(msg))
         for title_elt in title_elts:
             yield parseElement(title_elt)
@@ -317,13 +317,13 @@
         for key in ("title", "content"):
             if key not in microblog_data and ("{}_xhtml".format(key)) in microblog_data:
                 log.warning(
-                    u"item {id_} provide a {key}_xhtml data but not a text one".format(
+                    "item {id_} provide a {key}_xhtml data but not a text one".format(
                         id_=id_, key=key
                     )
                 )
                 # ... and do the conversion if it's not
                 microblog_data[key] = yield self.host.plugins["TEXT_SYNTAXES"].convert(
-                    microblog_data[u"{}_xhtml".format(key)],
+                    microblog_data["{}_xhtml".format(key)],
                     self.host.plugins["TEXT_SYNTAXES"].SYNTAX_XHTML,
                     self.host.plugins["TEXT_SYNTAXES"].SYNTAX_TEXT,
                     False,
@@ -331,28 +331,28 @@
 
         if "content" not in microblog_data:
             # use the atom title data as the microblog body content
-            microblog_data[u"content"] = microblog_data[u"title"]
-            del microblog_data[u"title"]
+            microblog_data["content"] = microblog_data["title"]
+            del microblog_data["title"]
             if "title_xhtml" in microblog_data:
-                microblog_data[u"content_xhtml"] = microblog_data[u"title_xhtml"]
-                del microblog_data[u"title_xhtml"]
+                microblog_data["content_xhtml"] = microblog_data["title_xhtml"]
+                del microblog_data["title_xhtml"]
 
         # published/updated dates
         try:
-            updated_elt = entry_elt.elements(NS_ATOM, "updated").next()
+            updated_elt = next(entry_elt.elements(NS_ATOM, "updated"))
         except StopIteration:
-            msg = u"No atom updated element found in the pubsub item {}".format(id_)
+            msg = "No atom updated element found in the pubsub item {}".format(id_)
             raise failure.Failure(exceptions.DataError(msg))
-        microblog_data[u"updated"] = calendar.timegm(
-            dateutil.parser.parse(unicode(updated_elt)).utctimetuple()
+        microblog_data["updated"] = calendar.timegm(
+            dateutil.parser.parse(str(updated_elt)).utctimetuple()
         )
         try:
-            published_elt = entry_elt.elements(NS_ATOM, "published").next()
+            published_elt = next(entry_elt.elements(NS_ATOM, "published"))
         except StopIteration:
-            microblog_data[u"published"] = microblog_data[u"updated"]
+            microblog_data["published"] = microblog_data["updated"]
         else:
-            microblog_data[u"published"] = calendar.timegm(
-                dateutil.parser.parse(unicode(published_elt)).utctimetuple()
+            microblog_data["published"] = calendar.timegm(
+                dateutil.parser.parse(str(published_elt)).utctimetuple()
             )
 
         # links
@@ -366,83 +366,83 @@
                 try:
                     service, node = self.parseCommentUrl(microblog_data[key])
                 except:
-                    log.warning(u"Can't parse url {}".format(microblog_data[key]))
+                    log.warning("Can't parse url {}".format(microblog_data[key]))
                     del microblog_data[key]
                 else:
-                    microblog_data[u"{}_service".format(key)] = service.full()
-                    microblog_data[u"{}_node".format(key)] = node
+                    microblog_data["{}_service".format(key)] = service.full()
+                    microblog_data["{}_node".format(key)] = node
             else:
                 rel = link_elt.getAttribute("rel", "")
                 title = link_elt.getAttribute("title", "")
                 href = link_elt.getAttribute("href", "")
                 log.warning(
-                    u"Unmanaged link element: rel={rel} title={title} href={href}".format(
+                    "Unmanaged link element: rel={rel} title={title} href={href}".format(
                         rel=rel, title=title, href=href
                     )
                 )
 
         # author
         try:
-            author_elt = entry_elt.elements(NS_ATOM, "author").next()
+            author_elt = next(entry_elt.elements(NS_ATOM, "author"))
         except StopIteration:
-            log.debug(u"Can't find author element in item {}".format(id_))
+            log.debug("Can't find author element in item {}".format(id_))
         else:
             publisher = item_elt.getAttribute("publisher")
             # name
             try:
-                name_elt = author_elt.elements(NS_ATOM, "name").next()
+                name_elt = next(author_elt.elements(NS_ATOM, "name"))
             except StopIteration:
                 log.warning(
-                    u"No name element found in author element of item {}".format(id_)
+                    "No name element found in author element of item {}".format(id_)
                 )
             else:
-                microblog_data[u"author"] = unicode(name_elt)
+                microblog_data["author"] = str(name_elt)
             # uri
             try:
-                uri_elt = author_elt.elements(NS_ATOM, "uri").next()
+                uri_elt = next(author_elt.elements(NS_ATOM, "uri"))
             except StopIteration:
                 log.debug(
-                    u"No uri element found in author element of item {}".format(id_)
+                    "No uri element found in author element of item {}".format(id_)
                 )
                 if publisher:
-                    microblog_data[u"author_jid"] = publisher
+                    microblog_data["author_jid"] = publisher
             else:
-                uri = unicode(uri_elt)
+                uri = str(uri_elt)
                 if uri.startswith("xmpp:"):
                     uri = uri[5:]
-                    microblog_data[u"author_jid"] = uri
+                    microblog_data["author_jid"] = uri
                 else:
-                    microblog_data[u"author_jid"] = (
-                        item_elt.getAttribute(u"publisher") or ""
+                    microblog_data["author_jid"] = (
+                        item_elt.getAttribute("publisher") or ""
                     )
 
                 if not publisher:
-                    log.debug(u"No publisher attribute, we can't verify author jid")
-                    microblog_data[u"author_jid_verified"] = False
+                    log.debug("No publisher attribute, we can't verify author jid")
+                    microblog_data["author_jid_verified"] = False
                 elif jid.JID(publisher).userhostJID() == jid.JID(uri).userhostJID():
-                    microblog_data[u"author_jid_verified"] = True
+                    microblog_data["author_jid_verified"] = True
                 else:
                     log.warning(
-                        u"item atom:uri differ from publisher attribute, spoofing "
-                        u"attempt ? atom:uri = {} publisher = {}".format(
+                        "item atom:uri differ from publisher attribute, spoofing "
+                        "attempt ? atom:uri = {} publisher = {}".format(
                             uri, item_elt.getAttribute("publisher")
                         )
                     )
-                    microblog_data[u"author_jid_verified"] = False
+                    microblog_data["author_jid_verified"] = False
             # email
             try:
-                email_elt = author_elt.elements(NS_ATOM, "email").next()
+                email_elt = next(author_elt.elements(NS_ATOM, "email"))
             except StopIteration:
                 pass
             else:
-                microblog_data[u"author_email"] = unicode(email_elt)
+                microblog_data["author_email"] = str(email_elt)
 
             # categories
             categories = [
                 category_elt.getAttribute("term", "")
                 for category_elt in entry_elt.elements(NS_ATOM, "category")
             ]
-            microblog_data[u"tags"] = categories
+            microblog_data["tags"] = categories
 
         ## the trigger ##
         # if other plugins have things to add or change
@@ -467,8 +467,8 @@
         entry_elt = domish.Element((NS_ATOM, "entry"))
 
         ## language ##
-        if u"language" in data:
-            entry_elt[(C.NS_XML, u"lang")] = data[u"language"].strip()
+        if "language" in data:
+            entry_elt[(C.NS_XML, "lang")] = data["language"].strip()
 
         ## content and title ##
         synt = self.host.plugins["TEXT_SYNTAXES"]
@@ -528,7 +528,7 @@
                         elem["type"] = "text"
 
         try:
-            entry_elt.elements(NS_ATOM, "title").next()
+            next(entry_elt.elements(NS_ATOM, "title"))
         except StopIteration:
             # we have no title element which is mandatory
             # so we transform content element to title
@@ -579,7 +579,7 @@
         entry_id = data.get(
             "id",
             xmpp_uri.buildXMPPUri(
-                u"pubsub",
+                "pubsub",
                 path=service.full() if service is not None else client.jid.userhost(),
                 node=node,
                 item=item_id,
@@ -613,7 +613,7 @@
         @param item_id(unicode): id of the parent item
         @return (unicode): comment node to use
         """
-        return u"{}{}".format(NS_COMMENT_PREFIX, item_id)
+        return "{}{}".format(NS_COMMENT_PREFIX, item_id)
 
     def getCommentsService(self, client, parent_service=None):
         """Get prefered PubSub service to create comment node
@@ -662,8 +662,8 @@
         elif allow_comments == False:
             if "comments" in mb_data:
                 log.warning(
-                    u"comments are not allowed but there is already a comments node, "
-                    u"it may be lost: {uri}".format(
+                    "comments are not allowed but there is already a comments node, "
+                    "it may be lost: {uri}".format(
                         uri=mb_data["comments"]
                     )
                 )
@@ -695,7 +695,7 @@
         else:
             if not comments_node:
                 raise exceptions.DataError(
-                    u"if comments_node is present, it must not be empty"
+                    "if comments_node is present, it must not be empty"
                 )
 
         try:
@@ -708,7 +708,7 @@
         except error.StanzaError as e:
             if e.condition == "conflict":
                 log.info(
-                    u"node {} already exists on service {}".format(
+                    "node {} already exists on service {}".format(
                         comments_node, comments_service
                     )
                 )
@@ -722,7 +722,7 @@
                 )
                 # …except for "member", that we transform to publisher
                 # because we wants members to be able to write to comments
-                for jid_, affiliation in comments_affiliations.items():
+                for jid_, affiliation in list(comments_affiliations.items()):
                     if affiliation == "member":
                         comments_affiliations[jid_] == "publisher"
 
@@ -736,12 +736,12 @@
         if "comments" in mb_data:
             if not mb_data["comments"]:
                 raise exceptions.DataError(
-                    u"if comments is present, it must not be empty"
+                    "if comments is present, it must not be empty"
                 )
             if "comments_node" in mb_data or "comments_service" in mb_data:
                 raise exceptions.DataError(
-                    u"You can't use comments_service/comments_node and comments at the "
-                    u"same time"
+                    "You can't use comments_service/comments_node and comments at the "
+                    "same time"
                 )
         else:
             mb_data["comments"] = self._p.getNodeURI(comments_service, comments_node)
@@ -769,12 +769,12 @@
         if node is None:
             node = NS_MICROBLOG
 
-        item_id = data.get("id") or unicode(shortuuid.uuid())
+        item_id = data.get("id") or str(shortuuid.uuid())
 
         try:
             yield self._manageComments(client, data, service, node, item_id, access=None)
         except error.StanzaError:
-            log.warning(u"Can't create comments node for item {}".format(item_id))
+            log.warning("Can't create comments node for item {}".format(item_id))
         item = yield self.data2entry(client, data, item_id, service, node)
         ret = yield self._p.publish(client, service, node, [item])
         defer.returnValue(ret)
@@ -851,10 +851,10 @@
         will return(JID(u'sat-pubsub.example.net'), 'urn:xmpp:comments:_af43b363-3259-4b2a-ba4c-1bc33aa87634__urn:xmpp:groupblog:somebody@example.net')
         @return (tuple[jid.JID, unicode]): service and node
         """
-        parsed_url = urlparse.urlparse(node_url, "xmpp")
+        parsed_url = urllib.parse.urlparse(node_url, "xmpp")
         service = jid.JID(parsed_url.path)
-        parsed_queries = urlparse.parse_qs(parsed_url.query.encode("utf-8"))
-        node = parsed_queries.get("node", [""])[0].decode("utf-8")
+        parsed_queries = urllib.parse.parse_qs(parsed_url.query.encode("utf-8"))
+        node = parsed_queries.get("node", [""])[0]
 
         if not node:
             raise failure.Failure(exceptions.DataError("Invalid comments link"))
@@ -883,11 +883,11 @@
 
         def cb(result):
             # Node is created with right permission
-            log.debug(_(u"Microblog node has now access %s") % access)
+            log.debug(_("Microblog node has now access %s") % access)
 
         def fatal_err(s_error):
             # Something went wrong
-            log.error(_(u"Can't set microblog access"))
+            log.error(_("Can't set microblog access"))
             raise NodeAccessChangeException()
 
         def err_cb(s_error):
@@ -948,7 +948,7 @@
                     if services:
                         log.debug(
                             "Extra PEP followed entities: %s"
-                            % ", ".join([unicode(service) for service in services])
+                            % ", ".join([str(service) for service in services])
                         )
                         jids_set.update(services)
 
@@ -1033,7 +1033,7 @@
         d = self._p.getRTResults(
             session_id,
             on_success=onSuccess,
-            on_error=lambda failure: (unicode(failure.value), ([], {})),
+            on_error=lambda failure: (str(failure.value), ([], {})),
             profile=profile,
         )
         d.addCallback(
@@ -1043,7 +1043,7 @@
                     (service.full(), node, failure, items, metadata)
                     for (service, node), (success, (failure, (items, metadata))) in ret[
                         1
-                    ].iteritems()
+                    ].items()
                 ],
             )
         )
@@ -1098,7 +1098,7 @@
         @return (tuple): see [_mbGetFromManyWithCommentsRTResult]
         """
         ret = []
-        data_iter = data[1].iteritems()
+        data_iter = iter(data[1].items())
         for (service, node), (success, (failure_, (items_data, metadata))) in data_iter:
             items = []
             for item, item_metadata in items_data:
@@ -1203,7 +1203,7 @@
             items_dlist = []  # deferred list for items
             for item in items:
                 dlist = []  # deferred list for comments
-                for key, value in item.iteritems():
+                for key, value in item.items():
                     # we look for comments
                     if key.startswith("comments") and key.endswith("_service"):
                         prefix = key[: key.find("_")]
@@ -1228,7 +1228,7 @@
                         d.addCallback(
                             lambda serialised_items_data: ("",) + serialised_items_data
                         )
-                        d.addErrback(lambda failure: (unicode(failure.value), [], {}))
+                        d.addErrback(lambda failure: (str(failure.value), [], {}))
                         # and associate with service/node (needed if there are several
                         # comments nodes)
                         d.addCallback(
@@ -1262,13 +1262,13 @@
             )
             d.addCallback(getComments)
             d.addCallback(lambda items_comments_data: ("", items_comments_data))
-            d.addErrback(lambda failure: (unicode(failure.value), ([], {})))
+            d.addErrback(lambda failure: (str(failure.value), ([], {})))
 
         return self.rt_sessions.newSession(deferreds, client.profile)
 
 
+@implementer(iwokkel.IDisco)
 class XEP_0277_handler(XMPPHandler):
-    implements(iwokkel.IDisco)
 
     def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
         return [disco.DiscoFeature(NS_MICROBLOG)]