# HG changeset patch # User Goffi # Date 1702379835 -3600 # Node ID 1d24ff58379480987b7cdf3eb8cf165504fce55f # Parent 5d056d524298f61d9358276c3895f2d80fb19169 plugin forums: parsing fix + formatting: - reformatted with black - fix parsing of forums - minor improvements diff -r 5d056d524298 -r 1d24ff583794 libervia/backend/plugins/plugin_misc_forums.py --- 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 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 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)