microblog.pub/app/utils/facepile.py

162 lines
5.6 KiB
Python
Raw Normal View History

import datetime
from dataclasses import dataclass
from typing import Any
from typing import Optional
from loguru import logger
from app import media
from app.models import InboxObject
from app.models import Webmention
from app.models import WebmentionType
from app.utils.datetime import parse_isoformat
2022-11-19 07:12:33 +00:00
from app.utils.url import make_abs
@dataclass
class Face:
ap_actor_id: str | None
url: str
name: str
picture_url: str
created_at: datetime.datetime
@classmethod
def from_inbox_object(cls, like: InboxObject) -> "Face":
return cls(
ap_actor_id=like.actor.ap_id,
url=like.actor.url, # type: ignore
name=like.actor.handle, # type: ignore
picture_url=like.actor.resized_icon_url,
created_at=like.created_at, # type: ignore
)
@classmethod
def from_webmention(cls, webmention: Webmention) -> Optional["Face"]:
items = webmention.source_microformats.get("items", []) # type: ignore
for item in items:
if item["type"][0] == "h-card":
try:
return cls(
ap_actor_id=None,
url=(
2022-11-19 07:12:33 +00:00
item["properties"]["url"][0]
if item["properties"].get("url")
else webmention.source
),
name=item["properties"]["name"][0],
picture_url=media.resized_media_url(
make_abs(
item["properties"]["photo"][0], webmention.source
), # type: ignore
50,
),
created_at=webmention.created_at, # type: ignore
)
except Exception:
logger.exception(
f"Failed to build Face for webmention id={webmention.id}"
)
break
elif item["type"][0] == "h-entry":
author = item["properties"]["author"][0]
try:
return cls(
ap_actor_id=None,
url=webmention.source,
name=author["properties"]["name"][0],
picture_url=media.resized_media_url(
make_abs(
author["properties"]["photo"][0], webmention.source
), # type: ignore
50,
),
created_at=webmention.created_at, # type: ignore
)
except Exception:
logger.exception(
f"Failed to build Face for webmention id={webmention.id}"
)
break
return None
def merge_faces(faces: list[Face]) -> list[Face]:
return sorted(
faces,
key=lambda f: f.created_at,
reverse=True,
)[:10]
def _parse_face(webmention: Webmention, items: list[dict[str, Any]]) -> Face | None:
for item in items:
if item["type"][0] == "h-card":
try:
return Face(
ap_actor_id=None,
url=(
2022-11-19 07:12:33 +00:00
item["properties"]["url"][0]
if item["properties"].get("url")
else webmention.source
),
name=item["properties"]["name"][0],
picture_url=media.resized_media_url(
make_abs(
item["properties"]["photo"][0], webmention.source
), # type: ignore
50,
),
created_at=webmention.created_at, # type: ignore
)
except Exception:
logger.exception(
f"Failed to build Face for webmention id={webmention.id}"
)
break
2022-11-19 07:12:33 +00:00
return None
@dataclass
class WebmentionReply:
face: Face
content: str
url: str
published_at: datetime.datetime
2022-11-19 07:12:33 +00:00
in_reply_to: str
@classmethod
2022-11-19 07:12:33 +00:00
def from_webmention(cls, webmention: Webmention) -> Optional["WebmentionReply"]:
if webmention.webmention_type != WebmentionType.REPLY:
raise ValueError(f"Unexpected webmention {webmention.id}")
items = webmention.source_microformats.get("items", []) # type: ignore
for item in items:
if item["type"][0] == "h-entry":
try:
face = _parse_face(webmention, item["properties"].get("author", []))
if not face:
logger.info(
"Failed to build WebmentionReply/Face for "
f"webmention id={webmention.id}"
)
break
return cls(
face=face,
content=item["properties"]["content"][0]["html"],
url=item["properties"]["url"][0],
published_at=parse_isoformat(
item["properties"]["published"][0]
).replace(tzinfo=None),
2022-11-19 07:12:33 +00:00
in_reply_to=webmention.target, # type: ignore
)
except Exception:
logger.exception(
f"Failed to build Face for webmention id={webmention.id}"
)
break
2022-11-19 07:12:33 +00:00
return None