changeset 4192:1d24ff583794

plugin forums: parsing fix + formatting: - reformatted with black - fix parsing of forums - minor improvements
author Goffi <goffi@goffi.org>
date Tue, 12 Dec 2023 12:17:15 +0100
parents 5d056d524298
children 730f542e4ad0
files libervia/backend/plugins/plugin_misc_forums.py
diffstat 1 files changed, 139 insertions(+), 96 deletions(-) [+]
line wrap: on
line diff
--- a/libervia/backend/plugins/plugin_misc_forums.py	Mon Dec 11 18:10:27 2023 +0100
+++ b/libervia/backend/plugins/plugin_misc_forums.py	Tue Dec 12 12:17:15 2023 +0100
@@ -1,8 +1,7 @@
 #!/usr/bin/env python3
 
-
-# SAT plugin for pubsub forums
-# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
+# Libervia plugin for pubsub forums
+# Copyright (C) 2009-2023 Jérôme Poisson (goffi@goffi.org)
 
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as published by
@@ -29,10 +28,11 @@
 from twisted.internet import defer
 import shortuuid
 import json
+
 log = getLogger(__name__)
 
-NS_FORUMS = 'org.salut-a-toi.forums:0'
-NS_FORUMS_TOPICS = NS_FORUMS + '#topics'
+NS_FORUMS = "org.salut-a-toi.forums:0"
+NS_FORUMS_TOPICS = NS_FORUMS + "#topics"
 
 PLUGIN_INFO = {
     C.PI_NAME: _("forums management"),
@@ -42,54 +42,69 @@
     C.PI_DEPENDENCIES: ["XEP-0060", "XEP-0277"],
     C.PI_MAIN: "forums",
     C.PI_HANDLER: "no",
-    C.PI_DESCRIPTION: _("""forums management plugin""")
+    C.PI_DESCRIPTION: _("""forums management plugin"""),
 }
-FORUM_ATTR = {'title', 'name', 'main-language', 'uri'}
-FORUM_SUB_ELTS = ('short-desc', 'desc')
-FORUM_TOPICS_NODE_TPL = '{node}#topics_{uuid}'
-FORUM_TOPIC_NODE_TPL = '{node}_{uuid}'
+FORUM_ATTR = {"title", "name", "main-language", "uri"}
+FORUM_SUB_ELTS = ("short-desc", "desc")
+FORUM_TOPICS_NODE_TPL = "{node}#topics_{uuid}"
+FORUM_TOPIC_NODE_TPL = "{node}_{uuid}"
 
 
-class forums(object):
-
+class forums:
     def __init__(self, host):
         log.info(_("forums plugin initialization"))
         self.host = host
-        self._m = self.host.plugins['XEP-0277']
-        self._p = self.host.plugins['XEP-0060']
+        self._m = self.host.plugins["XEP-0277"]
+        self._p = self.host.plugins["XEP-0060"]
         self._node_options = {
             self._p.OPT_ACCESS_MODEL: self._p.ACCESS_OPEN,
             self._p.OPT_PERSIST_ITEMS: 1,
             self._p.OPT_DELIVER_PAYLOADS: 1,
             self._p.OPT_SEND_ITEM_SUBSCRIBE: 1,
             self._p.OPT_PUBLISH_MODEL: self._p.ACCESS_OPEN,
-            }
-        host.register_namespace('forums', NS_FORUMS)
-        host.bridge.add_method("forums_get", ".plugin",
-                              in_sign='ssss', out_sign='s',
-                              method=self._get,
-                              async_=True)
-        host.bridge.add_method("forums_set", ".plugin",
-                              in_sign='sssss', out_sign='',
-                              method=self._set,
-                              async_=True)
-        host.bridge.add_method("forum_topics_get", ".plugin",
-                              in_sign='ssa{ss}s', out_sign='(aa{ss}s)',
-                              method=self._get_topics,
-                              async_=True)
-        host.bridge.add_method("forum_topic_create", ".plugin",
-                              in_sign='ssa{ss}s', out_sign='',
-                              method=self._create_topic,
-                              async_=True)
+        }
+        host.register_namespace("forums", NS_FORUMS)
+        host.bridge.add_method(
+            "forums_get",
+            ".plugin",
+            in_sign="ssss",
+            out_sign="s",
+            method=self._get,
+            async_=True,
+        )
+        host.bridge.add_method(
+            "forums_set",
+            ".plugin",
+            in_sign="sssss",
+            out_sign="",
+            method=self._set,
+            async_=True,
+        )
+        host.bridge.add_method(
+            "forum_topics_get",
+            ".plugin",
+            in_sign="ssa{ss}s",
+            out_sign="(aa{ss}s)",
+            method=self._get_topics,
+            async_=True,
+        )
+        host.bridge.add_method(
+            "forum_topic_create",
+            ".plugin",
+            in_sign="ssa{ss}s",
+            out_sign="",
+            method=self._create_topic,
+            async_=True,
+        )
 
     async def _create_forums(
-            self,
-            client: SatXMPPEntity,
-            forums: list[dict],
-            service: jid.JID,
-            node: str,
-            forums_elt: domish.Element|None = None,
-            names: Iterable = None
+        self,
+        client: SatXMPPEntity,
+        forums: list[dict],
+        service: jid.JID,
+        node: str,
+        forums_elt: domish.Element | None = None,
+        names: Iterable = None,
     ) -> domish.Element:
         """Recursively create <forums> element(s)
 
@@ -104,46 +119,54 @@
         if not isinstance(forums, list):
             raise ValueError(_("forums arguments must be a list of forums"))
         if forums_elt is None:
-            forums_elt = domish.Element((NS_FORUMS, 'forums'))
+            forums_elt = domish.Element((NS_FORUMS, "forums"))
             assert names is None
             names = set()
         else:
-            if names is None or forums_elt.name != 'forums':
-                raise exceptions.InternalError('invalid forums or names')
+            if names is None or forums_elt.name != "forums":
+                raise exceptions.InternalError("invalid forums or names")
             assert names is not None
 
         for forum in forums:
             if not isinstance(forum, dict):
                 raise ValueError(_("A forum item must be a dictionary"))
-            forum_elt = forums_elt.addElement('forum')
+            forum_elt = forums_elt.addElement("forum")
 
             for key, value in forum.items():
-                if key == 'name' and key in names:
-                    raise exceptions.ConflictError(_("following forum name is not unique: {name}").format(name=key))
-                if key == 'uri' and value is None or not value.strip():
+                if key == "name" and key in names:
+                    raise exceptions.ConflictError(
+                        _("following forum name is not unique: {name}").format(name=key)
+                    )
+                if key == "uri" and (value is None or not value.strip()):
                     log.info(_("creating missing forum node"))
-                    forum_node = FORUM_TOPICS_NODE_TPL.format(node=node, uuid=shortuuid.uuid())
-                    await self._p.createNode(client, service, forum_node, self._node_options)
-                    value = uri.build_xmpp_uri('pubsub',
-                                             path=service.full(),
-                                             node=forum_node)
+                    forum_node = FORUM_TOPICS_NODE_TPL.format(
+                        node=node, uuid=shortuuid.uuid()
+                    )
+                    await self._p.createNode(
+                        client, service, forum_node, self._node_options
+                    )
+                    value = uri.build_xmpp_uri(
+                        "pubsub", path=service.full(), node=forum_node
+                    )
                 if key in FORUM_ATTR:
                     forum_elt[key] = value.strip()
                 elif key in FORUM_SUB_ELTS:
                     forum_elt.addElement(key, content=value)
-                elif key == 'sub-forums':
+                elif key == "sub-forums":
                     assert isinstance(value, list)
-                    sub_forums_elt = forum_elt.addElement('forums')
-                    await self._create_forums(client, value, service, node, sub_forums_elt, names=names)
+                    sub_forums_elt = forum_elt.addElement("forums")
+                    await self._create_forums(
+                        client, value, service, node, sub_forums_elt, names=names
+                    )
                 else:
                     log.warning(_("Unknown forum attribute: {key}").format(key=key))
-            if not forum_elt.getAttribute('title'):
-                name = forum_elt.getAttribute('name')
+            if not forum_elt.getAttribute("title"):
+                name = forum_elt.getAttribute("name")
                 if name:
-                    forum_elt['title'] = name
+                    forum_elt["title"] = name
                 else:
                     raise ValueError(_("forum need a title or a name"))
-            if not forum_elt.getAttribute('uri') and not forum_elt.children:
+            if not forum_elt.getAttribute("uri") and not forum_elt.children:
                 raise ValueError(_("forum need uri or sub-forums"))
         return forums_elt
 
@@ -155,34 +178,42 @@
         @return (list): parsed data
         @raise ValueError: item is invalid
         """
-        if parent_elt.name == 'item':
+        if parent_elt.name == "item":
             forums = []
             try:
-                forums_elt = next(parent_elt.elements(NS_FORUMS, 'forums'))
+                forums_elt = next(parent_elt.elements(NS_FORUMS, "forums"))
             except StopIteration:
                 raise ValueError(_("missing <forums> element"))
         else:
             forums_elt = parent_elt
             if forums is None:
-                raise exceptions.InternalError('expected forums')
-            if forums_elt.name != 'forums':
-                raise ValueError(_('Unexpected element: {xml}').format(xml=forums_elt.toXml()))
+                raise exceptions.InternalError("expected forums")
+            if forums_elt.name != "forums":
+                raise ValueError(
+                    _("Unexpected element: {xml}").format(xml=forums_elt.toXml())
+                )
         for forum_elt in forums_elt.elements():
-            if forum_elt.name == 'forum':
+            if forum_elt.name == "forum":
                 data = {}
                 for attrib in FORUM_ATTR.intersection(forum_elt.attributes):
                     data[attrib] = forum_elt[attrib]
                 unknown = set(forum_elt.attributes).difference(FORUM_ATTR)
                 if unknown:
-                    log.warning(_("Following attributes are unknown: {unknown}").format(unknown=unknown))
+                    log.warning(
+                        _("Following attributes are unknown: {unknown}").format(
+                            unknown=unknown
+                        )
+                    )
                 for elt in forum_elt.elements():
                     if elt.name in FORUM_SUB_ELTS:
                         data[elt.name] = str(elt)
-                    elif elt.name == 'forums':
-                        sub_forums = data['sub-forums'] = []
+                    elif elt.name == "forums":
+                        sub_forums = data["sub-forums"] = []
                         self._parse_forums(elt, sub_forums)
-                if not 'title' in data or not {'uri', 'sub-forums'}.intersection(data):
-                    log.warning(_("invalid forum, ignoring: {xml}").format(xml=forum_elt.toXml()))
+                if not "title" in data or not {"uri", "sub-forums"}.intersection(data):
+                    log.warning(
+                        _("invalid forum, ignoring: {xml}").format(xml=forum_elt.toXml())
+                    )
                 else:
                     forums.append(data)
             else:
@@ -208,22 +239,28 @@
         if node is None:
             node = NS_FORUMS
         if forums_key is None:
-            forums_key = 'default'
+            forums_key = "default"
         items_data = await self._p.get_items(client, service, node, item_ids=[forums_key])
         item = items_data[0][0]
         # we have the item and need to convert it to json
         forums = self._parse_forums(item)
         return forums
 
-    def _set(self, forums, service=None, node=None, forums_key=None, profile_key=C.PROF_KEY_NONE):
+    def _set(
+        self,
+        forums: str,
+        service_s: str = "",
+        node_s: str = "",
+        forums_key: str = "",
+        profile_key: str = C.PROF_KEY_NONE,
+    ) -> defer.Deferred:
         client = self.host.get_client(profile_key)
         forums = json.loads(forums)
-        if service.strip():
-            service = jid.JID(service)
+        if not service_s.strip():
+            service = None
         else:
-            service = None
-        if not node.strip():
-            node = None
+            service = jid.JID(service_s)
+        node = None if not node_s.strip() else node_s
         return defer.ensureDeferred(
             self.set(client, forums, service, node, forums_key or None)
         )
@@ -246,11 +283,11 @@
             None to use "default"
         """
         if service is None:
-             service = client.pubsub_service
+            service = client.pubsub_service
         if node is None:
             node = NS_FORUMS
         if forums_key is None:
-            forums_key = 'default'
+            forums_key = "default"
         forums_elt = await self._create_forums(client, forums, service, node)
         return await self._p.send_item(
             client, service, node, forums_elt, item_id=forums_key
@@ -261,8 +298,11 @@
         extra = self._p.parse_extra(extra)
         d = defer.ensureDeferred(
             self.get_topics(
-                client, jid.JID(service), node, rsm_request=extra.rsm_request,
-                extra=extra.extra
+                client,
+                jid.JID(service),
+                node,
+                rsm_request=extra.rsm_request,
+                extra=extra.extra,
             )
         )
         d.addCallback(
@@ -281,11 +321,13 @@
         topics = []
         item_elts, metadata = topics_data
         for item_elt in item_elts:
-            topic_elt = next(item_elt.elements(NS_FORUMS, 'topic'))
-            title_elt = next(topic_elt.elements(NS_FORUMS, 'title'))
-            topic = {'uri': topic_elt['uri'],
-                     'author': topic_elt['author'],
-                     'title': str(title_elt)}
+            topic_elt = next(item_elt.elements(NS_FORUMS, "topic"))
+            title_elt = next(topic_elt.elements(NS_FORUMS, "title"))
+            topic = {
+                "uri": topic_elt["uri"],
+                "author": topic_elt["author"],
+                "title": str(title_elt),
+            }
             topics.append(topic)
         return (topics, metadata)
 
@@ -297,21 +339,22 @@
 
     async def create_topic(self, client, service, node, mb_data):
         try:
-            title = mb_data['title']
-            content = mb_data.pop('content')
+            title = mb_data["title"]
+            content = mb_data.pop("content")
         except KeyError as e:
-            raise exceptions.DataError("missing mandatory data: {key}".format(key=e.args[0]))
+            raise exceptions.DataError(
+                "missing mandatory data: {key}".format(key=e.args[0])
+            )
         else:
             mb_data["content_rich"] = content
         topic_node = FORUM_TOPIC_NODE_TPL.format(node=node, uuid=shortuuid.uuid())
         await self._p.createNode(client, service, topic_node, self._node_options)
         await self._m.send(client, mb_data, service, topic_node)
-        topic_uri = uri.build_xmpp_uri('pubsub',
-                                     subtype='microblog',
-                                     path=service.full(),
-                                     node=topic_node)
-        topic_elt = domish.Element((NS_FORUMS, 'topic'))
-        topic_elt['uri'] = topic_uri
-        topic_elt['author'] = client.jid.userhost()
-        topic_elt.addElement('title', content = title)
+        topic_uri = uri.build_xmpp_uri(
+            "pubsub", subtype="microblog", path=service.full(), node=topic_node
+        )
+        topic_elt = domish.Element((NS_FORUMS, "topic"))
+        topic_elt["uri"] = topic_uri
+        topic_elt["author"] = client.jid.userhost()
+        topic_elt.addElement("title", content=title)
         await self._p.send_item(client, service, node, topic_elt)