Merge IndieWeb likes/reposts with their AP counterpart

main
Thomas Sileo 2022-11-17 21:03:24 +01:00
parent 89c90fba56
commit 434fd98cd9
3 changed files with 144 additions and 12 deletions

View File

@ -73,6 +73,8 @@ from app.templates import is_current_user_admin
from app.uploads import UPLOAD_DIR from app.uploads import UPLOAD_DIR
from app.utils import pagination from app.utils import pagination
from app.utils.emoji import EMOJIS_BY_NAME from app.utils.emoji import EMOJIS_BY_NAME
from app.utils.facepile import Face
from app.utils.facepile import merge_faces
from app.utils.highlight import HIGHLIGHT_CSS_HASH from app.utils.highlight import HIGHLIGHT_CSS_HASH
from app.utils.url import check_url from app.utils.url import check_url
from app.webfinger import get_remote_follow_template from app.webfinger import get_remote_follow_template
@ -724,7 +726,7 @@ async def _fetch_webmentions(
models.Webmention.outbox_object_id == outbox_object.id, models.Webmention.outbox_object_id == outbox_object.id,
models.Webmention.is_deleted.is_(False), models.Webmention.is_deleted.is_(False),
) )
.limit(10) .limit(50)
) )
).all() ).all()
@ -774,9 +776,9 @@ async def outbox_by_public_id(
is_current_user_admin=is_current_user_admin(request), is_current_user_admin=is_current_user_admin(request),
) )
webmentions = await _fetch_webmentions(db_session, maybe_object)
likes = await _fetch_likes(db_session, maybe_object) likes = await _fetch_likes(db_session, maybe_object)
shares = await _fetch_shares(db_session, maybe_object) shares = await _fetch_shares(db_session, maybe_object)
webmentions = await _fetch_webmentions(db_session, maybe_object)
return await templates.render_template( return await templates.render_template(
db_session, db_session,
request, request,
@ -784,13 +786,52 @@ async def outbox_by_public_id(
{ {
"replies_tree": replies_tree, "replies_tree": replies_tree,
"outbox_object": maybe_object, "outbox_object": maybe_object,
"likes": likes, "likes": _merge_faces_from_inbox_object_and_webmentions(
"shares": shares, likes,
"webmentions": webmentions, webmentions,
models.WebmentionType.LIKE,
),
"shares": _merge_faces_from_inbox_object_and_webmentions(
shares,
webmentions,
models.WebmentionType.REPOST,
),
"webmentions": _filter_webmentions(webmentions),
}, },
) )
def _filter_webmentions(
webmentions: list[models.Webmention],
) -> list[models.Webmention]:
return [
wm
for wm in webmentions
if wm.webmention_type
not in [
models.WebmentionType.LIKE,
models.WebmentionType.REPOST,
]
]
def _merge_faces_from_inbox_object_and_webmentions(
inbox_objects: list[models.InboxObject],
webmentions: list[models.Webmention],
webmention_type: models.WebmentionType,
) -> list[Face]:
wm_faces = []
for wm in webmentions:
if wm.webmention_type != webmention_type:
continue
if face := Face.from_webmention(wm):
wm_faces.append(face)
return merge_faces(
[Face.from_inbox_object(obj) for obj in inbox_objects] + wm_faces
)
@app.get("/articles/{short_id}/{slug}") @app.get("/articles/{short_id}/{slug}")
async def article_by_slug( async def article_by_slug(
short_id: str, short_id: str,
@ -826,9 +867,17 @@ async def article_by_slug(
{ {
"replies_tree": replies_tree, "replies_tree": replies_tree,
"outbox_object": maybe_object, "outbox_object": maybe_object,
"likes": likes, "likes": _merge_faces_from_inbox_object_and_webmentions(
"shares": shares, likes,
"webmentions": webmentions, webmentions,
models.WebmentionType.LIKE,
),
"shares": _merge_faces_from_inbox_object_and_webmentions(
shares,
webmentions,
models.WebmentionType.REPOST,
),
"webmentions": _filter_webmentions(webmentions),
}, },
) )

View File

@ -711,8 +711,8 @@
<div class="interactions-block">Likes <div class="interactions-block">Likes
<div class="facepile-wrapper"> <div class="facepile-wrapper">
{% for like in likes %} {% for like in likes %}
<a href="{% if is_admin %}{{ url_for("admin_profile") }}?actor_id={{ like.actor.ap_id }}{% else %}{{ like.actor.url }}{% endif %}" title="{{ like.actor.handle }}" rel="noreferrer"> <a href="{% if is_admin and like.ap_actor_id %}{{ url_for("admin_profile") }}?actor_id={{ like.ap_actor_id }}{% else %}{{ like.url }}{% endif %}" title="{{ like.name }}" rel="noreferrer">
<img src="{{ like.actor.resized_icon_url }}" alt="{{ like.actor.handle}}"> <img src="{{ like.picture_url }}" alt="{{ like.name }}">
</a> </a>
{% endfor %} {% endfor %}
{% if object.likes_count > likes | length %} {% if object.likes_count > likes | length %}
@ -728,8 +728,8 @@
<div class="interactions-block">Shares <div class="interactions-block">Shares
<div class="facepile-wrapper"> <div class="facepile-wrapper">
{% for share in shares %} {% for share in shares %}
<a href="{% if is_admin %}{{ url_for("admin_profile") }}?actor_id={{ share.actor.ap_id }}{% else %}{{ share.actor.url }}{% endif %}" title="{{ share.actor.handle }}" rel="noreferrer"> <a href="{% if is_admin and share.ap_actor_id %}{{ url_for("admin_profile") }}?actor_id={{ share.actor.ap_actor_id }}{% else %}{{ share.url }}{% endif %}" title="{{ share.name }}" rel="noreferrer">
<img src="{{ share.actor.resized_icon_url }}" alt="{{ share.actor.handle}}"> <img src="{{ share.picture_url }}" alt="{{ share.name }}">
</a> </a>
{% endfor %} {% endfor %}
{% if object.announces_count > shares | length %} {% if object.announces_count > shares | length %}

View File

@ -0,0 +1,83 @@
import datetime
from dataclasses import dataclass
from typing import Optional
from loguru import logger
from app import media
from app.models import InboxObject
from app.models import Webmention
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=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]