componenent AP Gateway, plugin XEP-0277: fix MbData use:

Updated legacy use of dict microblog data for the new Pydantic based model MbData:
This commit is contained in:
Goffi 2025-12-03 17:41:10 +01:00
commit 3a2a5f787d
3 changed files with 218 additions and 223 deletions

View file

@ -61,12 +61,20 @@ from libervia.backend.core.i18n import _
from libervia.backend.core.log import getLogger
from libervia.backend.memory import persistent
from libervia.backend.memory.sqla_mapping import History, SubscriptionState
from libervia.backend.plugins.plugin_xep_0277 import XEP_0277
from libervia.backend.plugins.plugin_xep_0277 import (
XEP_0277,
MbData,
Comment,
Attachment,
Repeated,
MbExtra,
)
from libervia.backend.tools import utils
from libervia.backend.tools.common import data_format, tls, uri
from libervia.backend.tools.common.async_utils import async_lru
from .ad_hoc import APAdHocService
# We import PLUGIN_INFO as it's needed for the plugin, and we put it in .constants because
# it's necessary to create the ComponentSettings subclass.
from .constants import PLUGIN_INFO
@ -524,7 +532,7 @@ class APGateway:
return ap_item["object"]
else:
# this is a blog item
mb_data = await self._m.item_2_mb_data(
mb_data = await MbData.from_element(
self.client, found_item, author_jid, node
)
ap_item = await self.mb_data_2_ap_item(self.client, mb_data)
@ -1132,8 +1140,8 @@ class APGateway:
)
else:
# blog item
mb_data = await self._m.item_2_mb_data(client, item, service, node)
author_jid = jid.JID(mb_data["author_jid"])
mb_data = await MbData.from_element(client, item, service, node)
author_jid = mb_data.author_jid
if subscribe_extra_nodes and not self.is_virtual_jid(author_jid):
# we subscribe automatically to comment nodes if any
recipient_jid = self.get_local_jid_from_account(ap_account)
@ -1151,11 +1159,11 @@ class APGateway:
)
try:
await self._pa.subscribe(
recipient_client, service, node, mb_data["id"]
recipient_client, service, node, mb_data.id
)
except exceptions.NotFound:
log.debug(
f"no attachment node found for item {mb_data['id']!r} on "
f"no attachment node found for item {mb_data.id!r} on "
f"{node!r} at {service}"
)
ap_item = await self.mb_data_2_ap_item(client, mb_data, is_new=is_new)
@ -1645,21 +1653,20 @@ class APGateway:
async def ap_item_2_mb_data_and_elt(
self, requestor_actor_id: str, ap_item: dict
) -> tuple[dict, domish.Element]:
) -> tuple[MbData, domish.Element]:
"""Convert AP item to parsed microblog data and corresponding item element
@param requestor_actor_id: ID of the actor requesting the conversion.
@param ap_item: AP item to convert.
@return: microblog and correspondign <item> element.
"""
assert self.client is not None
mb_data = await self.ap_item_2_mb_data(requestor_actor_id, ap_item)
item_elt = await self._m.mb_data_2_entry_elt(
self.client, mb_data, mb_data["id"], None, self._m.namespace
)
if "repeated" in mb_data["extra"]:
item_elt["publisher"] = mb_data["extra"]["repeated"]["by"]
item_elt = await self._m.mb_data_2_entry_elt(self.client, mb_data)
if mb_data.extra.repeated:
item_elt["publisher"] = mb_data.extra.repeated.by.full()
else:
item_elt["publisher"] = mb_data["author_jid"]
item_elt["publisher"] = mb_data.author_jid.full()
return mb_data, item_elt
async def ap_item_2_mb_elt(
@ -1756,7 +1763,7 @@ class APGateway:
last_level_item = parents[parent_limit]
return (self._m.get_comments_node(last_level_item["id"]), None)
async def ap_item_2_mb_data(self, requestor_actor_id: str, ap_item: dict) -> dict:
async def ap_item_2_mb_data(self, requestor_actor_id: str, ap_item: dict) -> MbData:
"""Convert AP activity or object to microblog data
@param actor_id: ID of the actor doing the request.
@ -1779,30 +1786,32 @@ class APGateway:
if not item_id:
log.warning(f'No "id" found in AP item: {ap_object!r}')
raise exceptions.DataError
mb_data = {"id": item_id, "extra": {}}
# Create MbData instance with required fields
mb_data = MbData(id=item_id, service=None, extra=MbExtra())
# content
try:
language, content_xhtml = ap_object["contentMap"].popitem()
except (KeyError, AttributeError):
try:
mb_data["content_xhtml"] = ap_object["content"]
mb_data.content_xhtml = ap_object["content"]
except KeyError:
log.warning(f"no content found:\n{ap_object!r}")
raise exceptions.DataError
else:
mb_data["language"] = language
mb_data["content_xhtml"] = content_xhtml
mb_data.language = language
mb_data.content_xhtml = content_xhtml
mb_data["content"] = await self._t.convert(
mb_data["content_xhtml"],
mb_data.content = await self._t.convert(
mb_data.content_xhtml,
self._t.SYNTAX_XHTML,
self._t.SYNTAX_TEXT,
False,
)
# attachments
if "attachment" in ap_object:
attachments = mb_data["extra"][C.KEY_ATTACHMENTS] = []
for ap_attachment in ap_object["attachment"]:
try:
url = ap_attachment["url"]
@ -1815,17 +1824,20 @@ class APGateway:
if not url.startswith("http"):
log.warning(f"non HTTP URL in attachment, ignoring: {ap_attachment}")
continue
attachment = {"url": url}
for ap_key, key in (
("mediaType", "media_type"),
# XXX: as weird as it seems, "name" is actually used for description
# in AP world
("name", "desc"),
):
value = ap_attachment.get(ap_key)
if value:
attachment[key] = value
attachments.append(attachment)
attachment = Attachment(
sources=[{"url": url}],
)
if media_type := ap_attachment.get("mediaType"):
attachment.media_type = media_type
# XXX: as weird as it seems, "name" is actually used for description
# in AP world
if desc := ap_attachment.get("name"):
attachment.desc = desc
mb_data.extra.attachments.append(attachment)
# author
if is_activity:
@ -1840,10 +1852,10 @@ class APGateway:
log.warning("multiple actors are not managed")
account = authors[0]
author_jid = self.get_local_jid_from_account(account).full()
author_jid = self.get_local_jid_from_account(account)
mb_data["author"] = account.split("@", 1)[0]
mb_data["author_jid"] = author_jid
mb_data.author = account.split("@", 1)[0]
mb_data.author_jid = author_jid
# published/updated
for field in ("published", "updated"):
@ -1852,15 +1864,18 @@ class APGateway:
value = ap_object.get("published")
if value:
try:
mb_data[field] = calendar.timegm(
timestamp = calendar.timegm(
dateutil.parser.parse(str(value)).utctimetuple()
)
setattr(mb_data, field, timestamp)
except dateutil.parser.ParserError as e:
log.warning(f"Can't parse {field!r} field: {e}")
# repeat
if "_repeated" in ap_item:
mb_data["extra"]["repeated"] = ap_item["_repeated"]
mb_data.extra.repeated = Repeated(
by=jid.JID(ap_item["_repeated"]["by"]), uri=ap_item["_repeated"]["uri"]
)
# comments
in_reply_to = ap_object.get("inReplyTo")
@ -1868,12 +1883,14 @@ class APGateway:
requestor_actor_id, item_id, in_reply_to
)
if comments_node is not None:
comments_data = {
"service": author_jid,
"node": comments_node,
"uri": uri.build_xmpp_uri("pubsub", path=author_jid, node=comments_node),
}
mb_data["comments"] = [comments_data]
comments_data = Comment(
service=author_jid,
node=comments_node,
uri=uri.build_xmpp_uri(
"pubsub", path=author_jid.full(), node=comments_node
),
)
mb_data.comments = [comments_data]
return mb_data
@ -1910,7 +1927,7 @@ class APGateway:
# we found several cached item with given ID, we check if there is one
# corresponding to this author
try:
author = jid.JID(mb_data["author_jid"]).userhostJID()
author = mb_data.author_jid.userhostJID()
cached_item = next(
i
for i in found_items
@ -1930,17 +1947,19 @@ class APGateway:
return self.build_apurl(TYPE_ITEM, parent_ap_account, parent_item)
async def repeated_mb_2_ap_item(self, mb_data: dict) -> dict:
async def repeated_mb_2_ap_item(self, mb_data: MbData) -> dict:
"""Convert repeated blog item to suitable AP Announce activity
@param mb_data: microblog metadata of an item repeating an other blog post
@return: Announce activity linking to the repeated item
"""
repeated = mb_data["extra"]["repeated"]
repeater = jid.JID(repeated["by"])
assert mb_data.id is not None
repeated = mb_data.extra.repeated
assert repeated is not None
repeater = repeated.by
repeater_account = await self.get_ap_account_from_jid_and_node(repeater, None)
repeater_id = self.build_apurl(TYPE_ACTOR, repeater_account)
repeated_uri = repeated["uri"]
repeated_uri = repeated.uri
if not repeated_uri.startswith("xmpp:"):
log.warning(
@ -1957,7 +1976,7 @@ class APGateway:
raise NotImplementedError
rep_service = jid.JID(parsed_url["path"])
rep_item = parsed_url["item"]
activity_id = self.build_apurl("item", repeater.userhost(), mb_data["id"])
activity_id = self.build_apurl("item", repeater.userhost(), mb_data.id)
if self.is_virtual_jid(rep_service):
# it's an AP actor linked through this gateway
@ -1990,7 +2009,7 @@ class APGateway:
async def mb_data_2_ap_item(
self,
client: SatXMPPEntity,
mb_data: dict,
mb_data: MbData,
public: bool = True,
is_new: bool = True,
) -> dict:
@ -2011,36 +2030,31 @@ class APGateway:
be.
@return: Activity item
"""
extra = mb_data.get("extra", {})
if "repeated" in extra:
if mb_data.extra.repeated:
return await self.repeated_mb_2_ap_item(mb_data)
if not mb_data.get("id"):
mb_data["id"] = shortuuid.uuid()
if not mb_data.get("author_jid"):
mb_data["author_jid"] = client.jid.userhost()
ap_account = await self.get_ap_account_from_jid_and_node(
jid.JID(mb_data["author_jid"]), None
)
if not mb_data.id:
mb_data.id = shortuuid.uuid()
if not mb_data.author_jid:
mb_data.author_jid = client.jid.userhostJID()
ap_account = await self.get_ap_account_from_jid_and_node(mb_data.author_jid, None)
url_actor = self.build_apurl(TYPE_ACTOR, ap_account)
url_item = self.build_apurl(TYPE_ITEM, ap_account, mb_data["id"])
url_item = self.build_apurl(TYPE_ITEM, ap_account, mb_data.id)
ap_object = {
"id": url_item,
"type": "Note",
"published": utils.xmpp_date(mb_data.get("published")),
"published": utils.xmpp_date(mb_data.published),
"attributedTo": url_actor,
"content": mb_data.get("content_xhtml") or mb_data["content"],
"content": mb_data.content_xhtml or mb_data.content,
}
language = mb_data.get("language")
if language:
ap_object["contentMap"] = {language: ap_object["content"]}
if mb_data.language:
ap_object["contentMap"] = {mb_data.language: ap_object["content"]}
attachments = extra.get(C.KEY_ATTACHMENTS)
if attachments:
if mb_data.extra.attachments:
ap_attachments = ap_object["attachment"] = []
for attachment in attachments:
for attachment in mb_data.extra.attachments:
try:
url = next(s["url"] for s in attachment["sources"] if "url" in s)
url = next(s["url"] for s in attachment.sources if "url" in s)
except (StopIteration, KeyError):
log.warning(f"Ignoring attachment without URL: {attachment}")
continue
@ -2050,7 +2064,7 @@ class APGateway:
# XXX: yes "name", cf. [ap_item_2_mb_data]
("desc", "name"),
):
value = attachment.get(key)
value = getattr(attachment, key, None)
if value:
ap_attachment[ap_key] = value
ap_attachments.append(ap_attachment)
@ -2080,8 +2094,8 @@ class APGateway:
}
)
try:
node = mb_data["node"]
service = jid.JID(mb_data["service"])
node = mb_data.node
service = mb_data.service
except KeyError:
# node and service must always be specified when this method is used
raise exceptions.InternalError("node or service is missing in mb_data")
@ -2092,8 +2106,8 @@ class APGateway:
except MissingLocalPartError:
# we have no local part, this is probably a comment
try:
service = jid.JID(mb_data["author_jid"])
if not mb_data["author_jid_verified"]:
service = mb_data.author_jid
if not mb_data.author_jid_verified:
raise KeyError
except KeyError:
raise exceptions.DataError(
@ -2195,10 +2209,10 @@ class APGateway:
)
else:
try:
mb_data = await self._m.item_2_mb_data(
mb_data = await MbData.from_element(
self.client, items[0].data, jid_, node
)
if "repeated" in mb_data["extra"]:
if mb_data.extra.repeated:
# we are deleting a repeated item, we must translate this to an
# "Undo" of the "Announce" activity instead of a "Delete" one
announce = await self.repeated_mb_2_ap_item(mb_data)
@ -2238,6 +2252,7 @@ class APGateway:
this method handle the conversion to AP items and posting
"""
assert self.client is not None
if client != self.client:
return mess_data
if mess_data["type"] not in ("chat", "normal"):
@ -2268,18 +2283,19 @@ class APGateway:
log.warning(f"ignoring empty message: {mess_data}")
return mess_data
mb_data = {
"content": message,
}
mb_data = MbData(
content = message,
service = None
)
if language:
mb_data["language"] = language
mb_data.language = language
origin_id = mess_data["extra"].get("origin_id")
if origin_id:
# we need to use origin ID when present to be able to retract the message
mb_data["id"] = origin_id
mb_data.id = origin_id
attachments = mess_data["extra"].get(C.KEY_ATTACHMENTS)
if attachments:
mb_data["extra"] = {C.KEY_ATTACHMENTS: attachments}
mb_data.extra = MbExtra(attachments=attachments)
client = self.client.get_virtual_client(mess_data["from"])
ap_item = await self.mb_data_2_ap_item(client, mb_data, public=False)
@ -2402,7 +2418,7 @@ class APGateway:
cached_item = cached_items[0]
mb_data = await self._m.item_2_mb_data(
mb_data = await MbData.from_element(
client, cached_item.data, pubsub_service, pubsub_node
)
ap_item = await self.mb_data_2_ap_item(client, mb_data)
@ -2465,13 +2481,15 @@ class APGateway:
f"{parent_item_node!r})\n{pformat(ap_item)}"
)
return
parent_item_parsed = await self._m.item_2_mb_data(
parent_item_parsed = await MbData.from_element(
client, parent_item_elt, parent_item_service, parent_item_node
)
try:
comment_service = jid.JID(parent_item_parsed["comments"][0]["service"])
comment_node = parent_item_parsed["comments"][0]["node"]
except (KeyError, IndexError):
comment_service = parent_item_parsed.comments[0].service
comment_node = parent_item_parsed.comments[0].node
if comment_node is None or comment_service is None:
raise IndexError
except IndexError:
# we don't have a comment node set for this item
from libervia.backend.tools.xml_tools import pp_elt
@ -2612,8 +2630,8 @@ class APGateway:
if destinee is not None:
targets_jids.add(destinee)
mb_data = await self.ap_item_2_mb_data(requestor_actor_id, item)
extra = {"origin_id": mb_data["id"]}
attachments = mb_data["extra"].get(C.KEY_ATTACHMENTS)
extra: dict[str, Any] = {"origin_id": mb_data.id}
attachments = mb_data.extra.attachments
if attachments:
extra[C.KEY_ATTACHMENTS] = attachments
@ -2622,8 +2640,8 @@ class APGateway:
defer_l.append(
client.sendMessage(
target_jid,
{"": mb_data.get("content", "")},
mb_data.get("title"),
{"": mb_data.content or ""},
mb_data.title,
extra=extra,
)
)

View file

@ -16,60 +16,61 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time
import html
from typing import TYPE_CHECKING, Optional, Dict, List, Any
import json
from urllib import parse
from collections import deque
import html
import json
import time
from typing import Any, Dict, List, Optional, TYPE_CHECKING
import unicodedata
from urllib import parse
from twisted.internet import defer
from twisted.python import failure
from twisted.web import http, resource as web_resource, server
from twisted.web import static
from twisted.web import util as web_util
from twisted.python import failure
from twisted.internet import defer
from twisted.words.protocols.jabber import jid, error
from twisted.words.protocols.jabber import error, jid
from wokkel import pubsub, rsm
from libervia.backend.core import exceptions
from libervia.backend.core.constants import Const as C
from libervia.backend.core.i18n import _
from libervia.backend.core.core_types import SatXMPPEntity
from libervia.backend.core.i18n import _
from libervia.backend.core.log import getLogger
from libervia.backend.tools.common import date_utils, uri
from libervia.backend.memory.sqla_mapping import SubscriptionState
from libervia.backend.plugins.plugin_xep_0277 import MbData
from libervia.backend.tools.common import date_utils, uri
from .constants import (
ACTIVITY_TYPES_LOWER,
ACTIVIY_NO_ACCOUNT_ALLOWED,
AP_REQUEST_TYPES,
CONTENT_TYPE_AP,
CONTENT_TYPE_WEBFINGER,
HS2019,
MEDIA_TYPE_AP,
MEDIA_TYPE_AP_ALT,
NS_AP,
PAGE_SIZE,
SIGN_EXP,
SIGN_HEADERS,
ST_AP_CACHE,
TYPE_ACTOR,
TYPE_EVENT,
TYPE_FOLLOWERS,
TYPE_FOLLOWING,
TYPE_INBOX,
TYPE_ITEM,
TYPE_LIKE,
TYPE_OUTBOX,
TYPE_REACTION,
TYPE_SHARED_INBOX,
)
from .regex import RE_SIG_PARAM
if TYPE_CHECKING:
from . import APGateway
from .constants import (
NS_AP,
MEDIA_TYPE_AP,
MEDIA_TYPE_AP_ALT,
CONTENT_TYPE_WEBFINGER,
CONTENT_TYPE_AP,
TYPE_ACTOR,
TYPE_INBOX,
TYPE_SHARED_INBOX,
TYPE_OUTBOX,
TYPE_EVENT,
AP_REQUEST_TYPES,
PAGE_SIZE,
ACTIVITY_TYPES_LOWER,
ACTIVIY_NO_ACCOUNT_ALLOWED,
SIGN_HEADERS,
HS2019,
SIGN_EXP,
TYPE_FOLLOWERS,
TYPE_FOLLOWING,
TYPE_ITEM,
TYPE_LIKE,
TYPE_REACTION,
ST_AP_CACHE,
)
from .regex import RE_SIG_PARAM
log = getLogger(__name__)
@ -730,9 +731,7 @@ class HTTPAPGServer(web_resource.Resource):
ordered_items = [
await self.apg.mb_data_2_ap_item(
self.apg.client,
await self.apg._m.item_2_mb_data(
self.apg.client, item, account_jid, node
),
await MbData.from_element(self.apg.client, item, account_jid, node),
)
for item in reversed(items)
]

View file

@ -940,24 +940,19 @@ class XEP_0277:
async def mb_data_2_entry_elt(
self,
client: SatXMPPEntity,
mb_data: dict,
mb_data: MbData,
) -> pubsub.Item:
"""Convert a data dict to en entry usable to create an item
"""Convert microblog data to an entry usable to create an item.
@param mb_data: data dict as given by bridge method.
@param item_id: id of the item to use
@param service: pubsub service where the item is sent
Needed to construct Atom id
@param node: pubsub node where the item is sent
Needed to construct Atom id
@param mb_data: Microblog Data
@return: deferred which fire domish.Element
"""
entry_elt = domish.Element((NS_ATOM, "entry"))
extra = mb_data.get("extra", {})
extra = mb_data.extra
## language ##
if "language" in mb_data:
entry_elt[(C.NS_XML, "lang")] = mb_data["language"].strip()
if mb_data.language:
entry_elt[(C.NS_XML, "lang")] = mb_data.language.strip()
## content and title ##
text_syntaxes = cast(TextSyntaxes, self.host.plugins["TEXT_SYNTAXES"])
@ -965,16 +960,17 @@ class XEP_0277:
for elem_name in ("title", "content"):
for type_ in ["", "_rich", "_xhtml"]:
attr = f"{elem_name}{type_}"
if attr in mb_data:
value = getattr(mb_data, attr)
if value is not None:
elem = entry_elt.addElement(elem_name)
if type_:
if type_ == "_rich": # convert input from current syntax to XHTML
xml_content = await text_syntaxes.convert(
mb_data[attr],
value,
text_syntaxes.get_current_syntax(client.profile),
"XHTML",
)
if f"{elem_name}_xhtml" in mb_data:
if getattr(mb_data, f"{elem_name}_xhtml") is not None:
raise failure.Failure(
exceptions.DataError(
_(
@ -983,7 +979,7 @@ class XEP_0277:
)
)
else:
xml_content = mb_data[attr]
xml_content = value
div_elt = xml_tools.ElementParser()(
xml_content, namespace=C.NS_XHTML
@ -999,7 +995,7 @@ class XEP_0277:
div_elt = wrap_div_elt
elem.addChild(div_elt)
elem["type"] = "xhtml"
if elem_name not in mb_data:
if getattr(mb_data, elem_name) is None:
# there is raw text content, which is mandatory
# so we create one from xhtml content
elem_txt = entry_elt.addElement(elem_name)
@ -1015,7 +1011,7 @@ class XEP_0277:
elem_txt["type"] = "text"
else: # raw text only needs to be escaped to get HTML-safe sequence
elem.addContent(mb_data[attr])
elem.addContent(value)
elem["type"] = "text"
try:
@ -1032,138 +1028,120 @@ class XEP_0277:
elem.name = "title"
## attachments ##
attachments = extra.get(C.KEY_ATTACHMENTS)
if attachments:
for attachment in attachments:
try:
url = attachment["url"]
except KeyError:
try:
url = next(s["url"] for s in attachment["sources"] if "url" in s)
except (StopIteration, KeyError):
log.warning(
f'"url" missing in attachment, ignoring: {attachment}'
)
continue
for attachment in extra.attachments:
try:
url = attachment.sources[0]["url"]
except (IndexError, KeyError):
log.warning(f'"url" missing in attachment, ignoring: {attachment}')
continue
if not url.startswith("http"):
log.warning(f"non HTTP URL in attachment, ignoring: {attachment}")
continue
link_elt = entry_elt.addElement("link")
# XXX: "uri" is set in self._manage_comments if not already existing
link_elt["href"] = url
if attachment.get("external", False):
# this is a link to an external data such as a website
link_elt["rel"] = "related"
else:
# this is an attached file
link_elt["rel"] = "enclosure"
for key, attr in (
("media_type", "type"),
("desc", "title"),
("size", "lenght"),
):
value = attachment.get(key)
if value:
link_elt[attr] = str(value)
if not url.startswith("http"):
log.warning(f"non HTTP URL in attachment, ignoring: {attachment}")
continue
link_elt = entry_elt.addElement("link")
# XXX: "uri" is set in self._manage_comments if not already existing
link_elt["href"] = url
if attachment.external:
# this is a link to an external data such as a website
link_elt["rel"] = "related"
else:
# this is an attached file
link_elt["rel"] = "enclosure"
for key, attr in (
("media_type", "type"),
("desc", "title"),
("size", "lenght"),
):
value = getattr(attachment, key, None)
if value:
link_elt[attr] = str(value)
## alternate links ##
alt_links = extra.get("alt_links")
if alt_links:
for link_data in alt_links:
url_template = link_data["url"]
url = url_template.format(
service=quote(service.full(), safe=""),
node=quote(node, safe=""),
item=quote(item_id, safe=""),
)
for alt_link in extra.alt_links:
url_template = alt_link.url
url = url_template.format(
service=quote(mb_data.service.full(), safe=""),
node=quote(mb_data.node, safe=""),
item=quote(mb_data.id, safe=""),
)
link_elt = entry_elt.addElement("link")
link_elt["href"] = url
link_elt["rel"] = "alternate"
link_elt = entry_elt.addElement("link")
link_elt["href"] = url
link_elt["rel"] = "alternate"
media_type = link_data.get("media_type")
if not media_type:
parsed_url = urlparse(url)
if parsed_url.scheme in ["http", "https"]:
media_type = "text/html"
else:
media_type = guess_type(url)[0] or "application/octet-stream"
media_type = alt_link.media_type
if not media_type:
parsed_url = urlparse(url)
if parsed_url.scheme in ["http", "https"]:
media_type = "text/html"
else:
media_type = guess_type(url)[0] or "application/octet-stream"
link_elt["type"] = media_type
link_elt["type"] = media_type
## author ##
author_elt = entry_elt.addElement("author")
try:
author_name = mb_data["author"]
except KeyError:
# FIXME: must use better name
author_name = client.jid.user
# Use the author name from MbData, fallback to client's user if not set
author_name = mb_data.author or client.jid.user
author_elt.addElement("name", content=author_name)
try:
author_jid_s = mb_data["author_jid"]
except KeyError:
author_jid_s = client.jid.userhost()
# Use the author JID from MbData, fallback to client's JID if not set
author_jid_s = mb_data.author_jid or client.jid.userhost()
author_elt.addElement("uri", content="xmpp:{}".format(author_jid_s))
try:
author_jid_s = mb_data["author_email"]
except KeyError:
pass
## published/updated time ##
current_time = time.time()
entry_elt.addElement(
"updated",
content=utils.xmpp_date(float(mb_data.get("updated", current_time))),
content=utils.xmpp_date(float(mb_data.updated or current_time)),
)
entry_elt.addElement(
"published",
content=utils.xmpp_date(float(mb_data.get("published", current_time))),
content=utils.xmpp_date(float(mb_data.published or current_time)),
)
## categories ##
for tag in mb_data.get("tags", []):
for tag in mb_data.tags:
category_elt = entry_elt.addElement("category")
category_elt["term"] = tag
## id ##
entry_id = mb_data.get(
"id",
xmpp_uri.build_xmpp_uri(
"pubsub",
path=service.full() if service is not None else client.jid.userhost(),
node=node,
item=item_id,
entry_id = mb_data.id or xmpp_uri.build_xmpp_uri(
"pubsub",
path=(
mb_data.service.full()
if mb_data.service is not None
else client.jid.userhost()
),
node=mb_data.node,
item=mb_data.id,
)
entry_elt.addElement("id", content=entry_id) #
## comments ##
for comments_data in mb_data.get("comments", []):
for comment in mb_data.comments:
link_elt = entry_elt.addElement("link")
# XXX: "uri" is set in self._manage_comments if not already existing
try:
link_elt["href"] = comments_data["uri"]
link_elt["href"] = comment.uri
except KeyError:
log.warning(f"missing URI in comments data: {comments_data}")
log.warning(f"missing URI in comments data: {comment}")
entry_elt.children.remove(link_elt)
else:
link_elt["rel"] = "replies"
link_elt["title"] = "comments"
if "repeated" in extra:
if extra.repeated:
try:
repeated = extra["repeated"]
repeated = extra.repeated
link_elt = entry_elt.addElement("link")
link_elt["rel"] = "via"
link_elt["href"] = repeated["uri"]
link_elt["href"] = repeated.uri
except KeyError as e:
log.warning(f"invalid repeated element({e}): {extra['repeated']}")
log.warning(f"invalid repeated element({e}): {extra.repeated}")
## final item building ##
item_elt = pubsub.Item(id=item_id, payload=entry_elt)
item_elt = pubsub.Item(id=mb_data.id, payload=entry_elt)
## the trigger ##
# if other plugins have things to add or change