forked from forks/microblog.pub
Merge IndieWeb likes/reposts with their AP counterpart
This commit is contained in:
parent
89c90fba56
commit
434fd98cd9
3 changed files with 144 additions and 12 deletions
65
app/main.py
65
app/main.py
|
@ -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),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
83
app/utils/facepile.py
Normal file
83
app/utils/facepile.py
Normal 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]
|
Loading…
Reference in a new issue