Improved Block support

This commit is contained in:
Thomas Sileo 2022-10-18 21:39:09 +02:00
parent d909bf93a0
commit f50a233ce9
7 changed files with 147 additions and 3 deletions

View file

@ -259,9 +259,11 @@ class ActorMetadata:
is_following: bool is_following: bool
is_follower: bool is_follower: bool
is_follow_request_sent: bool is_follow_request_sent: bool
is_follow_request_rejected: bool
outbox_follow_ap_id: str | None outbox_follow_ap_id: str | None
inbox_follow_ap_id: str | None inbox_follow_ap_id: str | None
moved_to: typing.Optional["ActorModel"] moved_to: typing.Optional["ActorModel"]
has_blocked_local_actor: bool
ActorsMetadata = dict[str, ActorMetadata] ActorsMetadata = dict[str, ActorMetadata]
@ -304,6 +306,26 @@ async def get_actors_metadata(
) )
) )
} }
rejected_follow_requests = {
reject.activity_object_ap_id
for reject in await db_session.execute(
select(models.InboxObject.activity_object_ap_id).where(
models.InboxObject.ap_type == "Reject",
models.InboxObject.ap_actor_id.in_(ap_actor_ids),
)
)
}
blocks = {
block.ap_actor_id
for block in await db_session.execute(
select(models.InboxObject.ap_actor_id).where(
models.InboxObject.ap_type == "Block",
models.InboxObject.undone_by_inbox_object_id.is_(None),
models.InboxObject.ap_actor_id.in_(ap_actor_ids),
)
)
}
idx: ActorsMetadata = {} idx: ActorsMetadata = {}
for actor in actors: for actor in actors:
if not actor.ap_id: if not actor.ap_id:
@ -326,9 +348,15 @@ async def get_actors_metadata(
is_following=actor.ap_id in following, is_following=actor.ap_id in following,
is_follower=actor.ap_id in followers, is_follower=actor.ap_id in followers,
is_follow_request_sent=actor.ap_id in sent_follow_requests, is_follow_request_sent=actor.ap_id in sent_follow_requests,
is_follow_request_rejected=bool(
sent_follow_requests[actor.ap_id] in rejected_follow_requests
)
if actor.ap_id in sent_follow_requests
else False,
outbox_follow_ap_id=sent_follow_requests.get(actor.ap_id), outbox_follow_ap_id=sent_follow_requests.get(actor.ap_id),
inbox_follow_ap_id=followers.get(actor.ap_id), inbox_follow_ap_id=followers.get(actor.ap_id),
moved_to=moved_to, moved_to=moved_to,
has_blocked_local_actor=actor.ap_id in blocks,
) )
return idx return idx

View file

@ -1406,6 +1406,13 @@ async def _handle_undo_activity(
inbox_object_id=ap_activity_to_undo.id, inbox_object_id=ap_activity_to_undo.id,
) )
db_session.add(notif) db_session.add(notif)
elif ap_activity_to_undo.ap_type == "Block":
notif = models.Notification(
notification_type=models.NotificationType.UNBLOCKED,
actor_id=from_actor.id,
inbox_object_id=ap_activity_to_undo.id,
)
db_session.add(notif)
else: else:
logger.warning(f"Don't know how to undo {ap_activity_to_undo.ap_type} activity") logger.warning(f"Don't know how to undo {ap_activity_to_undo.ap_type} activity")
@ -1945,6 +1952,28 @@ async def _handle_like_activity(
db_session.add(notif) db_session.add(notif)
async def _handle_block_activity(
db_session: AsyncSession,
actor: models.Actor,
block_activity: models.InboxObject,
):
if block_activity.activity_object_ap_id != LOCAL_ACTOR.ap_id:
logger.warning(
"Received invalid Block activity "
f"{block_activity.activity_object_ap_id=}"
)
await db_session.delete(block_activity)
return
# Create a notification
notif = models.Notification(
notification_type=models.NotificationType.BLOCKED,
actor_id=actor.id,
inbox_object_id=block_activity.id,
)
db_session.add(notif)
async def _process_transient_object( async def _process_transient_object(
db_session: AsyncSession, db_session: AsyncSession,
raw_object: ap.RawObject, raw_object: ap.RawObject,
@ -2210,7 +2239,11 @@ async def save_to_inbox(
# View is used by Peertube, there's nothing useful we can do with it # View is used by Peertube, there's nothing useful we can do with it
await db_session.delete(inbox_object) await db_session.delete(inbox_object)
elif activity_ro.ap_type == "Block": elif activity_ro.ap_type == "Block":
pass await _handle_block_activity(
db_session,
actor,
inbox_object,
)
else: else:
logger.warning(f"Received an unknown {inbox_object.ap_type} object") logger.warning(f"Received an unknown {inbox_object.ap_type} object")

View file

@ -551,6 +551,9 @@ class NotificationType(str, enum.Enum):
UPDATED_WEBMENTION = "updated_webmention" UPDATED_WEBMENTION = "updated_webmention"
DELETED_WEBMENTION = "deleted_webmention" DELETED_WEBMENTION = "deleted_webmention"
BLOCKED = "blocked"
UNBLOCKED = "unblocked"
class Notification(Base): class Notification(Base):
__tablename__ = "notifications" __tablename__ = "notifications"

View file

@ -36,6 +36,12 @@
{%- elif notif.notification_type.value == "follow_request_rejected" %} {%- elif notif.notification_type.value == "follow_request_rejected" %}
{{ notif_actor_action(notif, "rejected your follow request") }} {{ notif_actor_action(notif, "rejected your follow request") }}
{{ utils.display_actor(notif.actor, actors_metadata) }} {{ utils.display_actor(notif.actor, actors_metadata) }}
{% elif notif.notification_type.value == "blocked" %}
{{ notif_actor_action(notif, "blocked you") }}
{{ utils.display_actor(notif.actor, actors_metadata) }}
{% elif notif.notification_type.value == "unblocked" %}
{{ notif_actor_action(notif, "unblocked you") }}
{{ utils.display_actor(notif.actor, actors_metadata) }}
{%- elif notif.notification_type.value == "move" %} {%- elif notif.notification_type.value == "move" %}
{# for move notif, the actor is the target and the inbox object the Move activity #} {# for move notif, the actor is the target and the inbox object the Move activity #}
<div class="actor-action"> <div class="actor-action">

View file

@ -216,6 +216,9 @@
<div> <div>
<nav class="flexbox actor-metadata"> <nav class="flexbox actor-metadata">
<ul> <ul>
{% if metadata.has_blocked_local_actor %}
<li>blocked you</li>
{% endif %}
{% if metadata.is_following %} {% if metadata.is_following %}
<li>already following</li> <li>already following</li>
<li>{{ admin_undo_button(metadata.outbox_follow_ap_id, "unfollow")}}</li> <li>{{ admin_undo_button(metadata.outbox_follow_ap_id, "unfollow")}}</li>
@ -223,8 +226,15 @@
<li>{{ admin_profile_button(actor.ap_id) }}</li> <li>{{ admin_profile_button(actor.ap_id) }}</li>
{% endif %} {% endif %}
{% elif metadata.is_follow_request_sent %} {% elif metadata.is_follow_request_sent %}
{% if metadata.is_follow_request_rejected %}
<li>follow request rejected</li>
{% if not metadata.has_blocked_local_actor %}
<li>{{ admin_follow_button(actor) }}</li>
{% endif %}
{% else %}
<li>follow request sent</li> <li>follow request sent</li>
<li>{{ admin_undo_button(metadata.outbox_follow_ap_id, "undo follow") }}</li> <li>{{ admin_undo_button(metadata.outbox_follow_ap_id, "undo follow") }}</li>
{% endif %}
{% elif not actor.moved_to %} {% elif not actor.moved_to %}
<li>{{ admin_follow_button(actor) }}</li> <li>{{ admin_follow_button(actor) }}</li>
{% endif %} {% endif %}

View file

@ -68,6 +68,20 @@ def build_accept_activity(
} }
def build_block_activity(
from_remote_actor: actor.RemoteActor,
for_remote_actor: actor.RemoteActor,
outbox_public_id: str | None = None,
) -> ap.RawObject:
return {
"@context": ap.AS_CTX,
"type": "Block",
"id": from_remote_actor.ap_id + "/block/" + (outbox_public_id or uuid4().hex),
"actor": from_remote_actor.ap_id,
"object": for_remote_actor.ap_id,
}
def build_move_activity( def build_move_activity(
from_remote_actor: actor.RemoteActor, from_remote_actor: actor.RemoteActor,
for_remote_object: actor.RemoteActor, for_remote_object: actor.RemoteActor,

View file

@ -423,3 +423,53 @@ def test_inbox__move_activity(
).scalar_one() ).scalar_one()
assert notif.actor.ap_id == new_ra.ap_id assert notif.actor.ap_id == new_ra.ap_id
assert notif.inbox_object_id == inbox_activity.id assert notif.inbox_object_id == inbox_activity.id
def test_inbox__block_activity(
db: Session,
client: TestClient,
respx_mock: respx.MockRouter,
) -> None:
# Given a remote actor
ra = setup_remote_actor(respx_mock)
# Which is followed by the local actor
setup_remote_actor_as_following(ra)
# When receiving a Block activity
follow_activity = RemoteObject(
factories.build_block_activity(
from_remote_actor=ra,
for_remote_actor=LOCAL_ACTOR,
),
ra,
)
with mock_httpsig_checker(ra):
response = client.post(
"/inbox",
headers={"Content-Type": ap.AS_CTX},
json=follow_activity.ap_object,
)
# Then the server returns a 202
assert response.status_code == 202
run_process_next_incoming_activity()
# And the actor was saved in DB
saved_actor = db.execute(select(models.Actor)).scalar_one()
assert saved_actor.ap_id == ra.ap_id
# And the Block activity was saved in the inbox
inbox_activity = db.execute(
select(models.InboxObject).where(models.InboxObject.ap_type == "Block")
).scalar_one()
# And a notification was created
notif = db.execute(
select(models.Notification).where(
models.Notification.notification_type == models.NotificationType.BLOCKED
)
).scalar_one()
assert notif.actor.ap_id == ra.ap_id
assert notif.inbox_object_id == inbox_activity.id