mirror of
				https://git.sr.ht/~tsileo/microblog.pub
				synced 2025-06-05 21:59:23 +02:00 
			
		
		
		
	Merge IndieWeb likes/reposts with their AP counterpart
This commit is contained in:
		
							
								
								
									
										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.utils import pagination | ||||
| 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.url import check_url | ||||
| 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.is_deleted.is_(False), | ||||
|             ) | ||||
|             .limit(10) | ||||
|             .limit(50) | ||||
|         ) | ||||
|     ).all() | ||||
|  | ||||
| @@ -774,9 +776,9 @@ async def outbox_by_public_id( | ||||
|         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) | ||||
|     shares = await _fetch_shares(db_session, maybe_object) | ||||
|     webmentions = await _fetch_webmentions(db_session, maybe_object) | ||||
|     return await templates.render_template( | ||||
|         db_session, | ||||
|         request, | ||||
| @@ -784,13 +786,52 @@ async def outbox_by_public_id( | ||||
|         { | ||||
|             "replies_tree": replies_tree, | ||||
|             "outbox_object": maybe_object, | ||||
|             "likes": likes, | ||||
|             "shares": shares, | ||||
|             "webmentions": webmentions, | ||||
|             "likes": _merge_faces_from_inbox_object_and_webmentions( | ||||
|                 likes, | ||||
|                 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}") | ||||
| async def article_by_slug( | ||||
|     short_id: str, | ||||
| @@ -826,9 +867,17 @@ async def article_by_slug( | ||||
|         { | ||||
|             "replies_tree": replies_tree, | ||||
|             "outbox_object": maybe_object, | ||||
|             "likes": likes, | ||||
|             "shares": shares, | ||||
|             "webmentions": webmentions, | ||||
|             "likes": _merge_faces_from_inbox_object_and_webmentions( | ||||
|                 likes, | ||||
|                 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="facepile-wrapper"> | ||||
|                 {% 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"> | ||||
|                         <img src="{{ like.actor.resized_icon_url }}" alt="{{ like.actor.handle}}"> | ||||
|                     <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.picture_url }}" alt="{{ like.name }}"> | ||||
|                     </a> | ||||
|                 {% endfor %} | ||||
|                 {% if object.likes_count > likes | length %} | ||||
| @@ -728,8 +728,8 @@ | ||||
|             <div class="interactions-block">Shares | ||||
|                 <div class="facepile-wrapper"> | ||||
|                 {% 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"> | ||||
|                         <img src="{{ share.actor.resized_icon_url }}" alt="{{ share.actor.handle}}"> | ||||
|                     <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.picture_url }}" alt="{{ share.name }}"> | ||||
|                     </a> | ||||
|                 {% endfor %} | ||||
|                 {% 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] | ||||
		Reference in New Issue
	
	Block a user