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:
parent
6a7b7bc36a
commit
3a2a5f787d
3 changed files with 218 additions and 223 deletions
|
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue