diff --git a/activitypub.py b/activitypub.py index feb6dae..547b957 100644 --- a/activitypub.py +++ b/activitypub.py @@ -3,7 +3,6 @@ import json import logging import os from datetime import datetime -from enum import Enum from typing import Any from typing import Dict from typing import List @@ -133,7 +132,9 @@ class MicroblogPubBackend(Backend): except Exception: # TODO(tsileo): should be ValueError, but replies trigger a KeyError on object pass object_visibility = None - if activity.has_type([ap.ActivityType.CREATE, ap.ActivityType.ANNOUNCE]): + if activity.has_type( + [ap.ActivityType.CREATE, ap.ActivityType.ANNOUNCE, ap.ActivityType.LIKE] + ): object_visibility = ap.get_visibility(activity.get_object()).name actor_id = activity.get_actor().id diff --git a/app.py b/app.py index 88bec37..bfa0d12 100644 --- a/app.py +++ b/app.py @@ -2005,6 +2005,8 @@ def inbox(): # POST/ inbox try: data = request.get_json(force=True) + if not isinstance(data, dict): + raise ValueError("not a dict") except Exception: return Response( status=422, @@ -2018,9 +2020,15 @@ def inbox(): and is_blacklisted(data["id"]) or ( "object" in data + and isinstance(data["object"], dict) and "id" in data["object"] and is_blacklisted(data["object"]["id"]) ) + or ( + "object" in data + and isinstance(data["object"], str) + and is_blacklisted(data["object"]) + ) ): logger.info(f"dropping activity from blacklisted host: {data['id']}") return Response(status=201) @@ -3067,13 +3075,6 @@ def task_cache_actor() -> str: if activity.has_type(ap.ActivityType.CREATE): Tasks.fetch_og_meta(iri) - # Cache the object if it's a `Like` or an `Announce` unrelated to the server outbox (because it will never get - # displayed) - if activity.has_type( - [ap.ActivityType.LIKE, ap.ActivityType.ANNOUNCE] - ) and not activity.get_object_id().startswith(BASE_URL): - Tasks.cache_object(iri) - actor = activity.get_actor() if actor.icon: if isinstance(actor.icon, dict) and "url" in actor.icon: @@ -3212,14 +3213,6 @@ def task_process_new_activity(): # If the activity was originally forwarded, forward the delete too should_forward = True - elif activity.has_type(ap.ActivityType.LIKE): - if activity.get_object_id().startswith(BASE_URL): - should_keep = True - else: - # We only want to keep a like if it's a like for a local activity - # (Pleroma relay the likes it received, we don't want to store them) - should_delete = True - if should_forward: app.logger.info(f"will forward {activity!r} to followers") Tasks.forward_activity(activity.id) diff --git a/templates/stream.html b/templates/stream.html index bc55421..178a190 100644 --- a/templates/stream.html +++ b/templates/stream.html @@ -7,7 +7,7 @@
{% if request.path == url_for('admin_notifications') and unread_notifications_count %} -
+
@@ -26,7 +26,14 @@ {% if item | has_type('Announce') %} {% set boost_actor = item.meta.actor %} {% if boost_actor %} -

{{ boost_actor.name or boost_actor.preferredUsername }} boosted

+
+ {{ boost_actor.name or boost_actor.preferredUsername }} boosted + {% if request.path == url_for('admin_notifications') %} + {% if item.meta.notification_unread %}new{% endif %} + {{ (item.activity.published or item.meta.published) | format_timeago }} + {% endif %} + +
{% endif %} {% if item.meta.object %} {{ utils.display_note(item.meta.object, ui=True, meta=item.meta) }} @@ -35,7 +42,11 @@ {% if item | has_type('Like') %} {% set boost_actor = item.meta.actor %} -

{{ boost_actor.name or boost_actor.preferredUsername }} liked

+
+ {{ boost_actor.name or boost_actor.preferredUsername }} liked + {% if item.meta.notification_unread %}new{% endif %} + {{ (item.activity.published or item.meta.published) | format_timeago }} +
{% if item.meta.object %} {{ utils.display_note(item.meta.object, ui=False, meta=item.meta) }} {% endif %} @@ -43,8 +54,9 @@ {% if item | has_type('Follow') %}
+ new follower {% if item.meta.notification_unread %}new{% endif %} - new follower + {{ (item.activity.published or item.meta.published) | format_timeago }} {% if item.meta.notification_follows_back %}already following {% else %} @@ -61,8 +73,9 @@ {% elif item | has_type('Accept') %}
+ you started following {% if item.meta.notification_unread %}new{% endif %} - you started following + {{ (item.activity.published or item.meta.published) | format_timeago }} {% if item.meta.notification_follows_back %}follows you back{% endif %}
@@ -71,7 +84,11 @@
{% elif item | has_type('Undo') %} -

unfollowed you

+
+ unfollowed you + {% if item.meta.notification_unread %}new{% endif %} + {{ (item.activity.published or item.meta.published) | format_timeago }} +
{{ utils.display_actor_inline(item.meta.actor, size=50) }}
diff --git a/utils/__init__.py b/utils/__init__.py index e64f407..09517e3 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -29,4 +29,4 @@ def parse_datetime(s: str) -> datetime: def now() -> str: - ap.format_datetime(datetime.now(timezone.utc)) + return ap.format_datetime(datetime.now(timezone.utc)) diff --git a/utils/meta.py b/utils/meta.py index dd55866..4b32283 100644 --- a/utils/meta.py +++ b/utils/meta.py @@ -20,6 +20,9 @@ class MetaKey(Enum): ACTOR_ID = "actor_id" UNDO = "undo" PUBLISHED = "published" + GC_KEEP = "gc_keep" + OBJECT = "object" + OBJECT_ACTOR = "object_actor" def _meta(mk: MetaKey) -> str: diff --git a/utils/notifications.py b/utils/notifications.py index 54f9fb8..301e928 100644 --- a/utils/notifications.py +++ b/utils/notifications.py @@ -5,9 +5,11 @@ from typing import Dict from little_boxes import activitypub as ap +from config import BASE_URL from config import DB from config import MetaKey from config import _meta +from tasks import Tasks from utils.meta import by_actor from utils.meta import by_type from utils.meta import in_inbox @@ -18,6 +20,17 @@ _logger = logging.getLogger(__name__) _NewMeta = Dict[str, Any] +def _is_from_outbox(activity: ap.BaseActivity) -> bool: + return activity.id.startswith(BASE_URL) + + +def _flag_as_notification(activity: ap.BaseActivity, new_meta: _NewMeta) -> None: + new_meta.update( + **{_meta(MetaKey.NOTIFICATION): True, _meta(MetaKey.NOTIFICATION_UNREAD): True} + ) + return None + + @singledispatch def set_inbox_flags(activity: ap.BaseActivity, new_meta: _NewMeta) -> None: return None @@ -44,13 +57,8 @@ def _accept_set_inbox_flags(activity: ap.Accept, new_meta: _NewMeta) -> None: ) # This Accept will be a "You started following $actor" notification - new_meta.update( - **{ - _meta(MetaKey.NOTIFICATION): True, - _meta(MetaKey.NOTIFICATION_UNREAD): True, - _meta(MetaKey.NOTIFICATION_FOLLOWS_BACK): follows_back, - } - ) + _flag_as_notification(activity, new_meta) + new_meta.update(**{_meta(MetaKey.NOTIFICATION_FOLLOWS_BACK): follows_back}) return None @@ -74,11 +82,38 @@ def _follow_set_inbox_flags(activity: ap.Follow, new_meta: _NewMeta) -> None: ) # This Follow will be a "$actor started following you" notification - new_meta.update( - **{ - _meta(MetaKey.NOTIFICATION): True, - _meta(MetaKey.NOTIFICATION_UNREAD): True, - _meta(MetaKey.NOTIFICATION_FOLLOWS_BACK): follows_back, - } - ) + _flag_as_notification(activity, new_meta) + new_meta.update(**{_meta(MetaKey.NOTIFICATION_FOLLOWS_BACK): follows_back}) + return None + + +@set_inbox_flags.register +def _like_set_inbox_flags(activity: ap.Like, new_meta: _NewMeta) -> None: + # Is it a Like of local acitivty/from the outbox + if _is_from_outbox(activity.get_object()): + # Flag it as a notification + _flag_as_notification(activity, new_meta) + + # Cache the object (for display on the notifcation page) + Tasks.cache_object(activity.id) + + # Also set the "keep mark" for the GC (as we want to keep it forever) + new_meta.update(**{_meta(MetaKey.GC_KEEP): True}) + + return None + + +@set_inbox_flags.register +def _announce_set_inbox_flags(activity: ap.Announce, new_meta: _NewMeta) -> None: + # Is it a Like of local acitivty/from the outbox + if _is_from_outbox(activity.get_object()): + # Flag it as a notification + _flag_as_notification(activity, new_meta) + + # Also set the "keep mark" for the GC (as we want to keep it forever) + new_meta.update(**{_meta(MetaKey.GC_KEEP): True}) + + # Cache the object in all case (for display on the notifcation page) + Tasks.cache_object(activity.id) + return None