forked from forks/microblog.pub
Improved Block support
This commit is contained in:
parent
d909bf93a0
commit
f50a233ce9
7 changed files with 147 additions and 3 deletions
28
app/actor.py
28
app/actor.py
|
@ -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
|
||||||
|
|
||||||
|
|
35
app/boxes.py
35
app/boxes.py
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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 %}
|
||||||
<li>follow request sent</li>
|
{% if metadata.is_follow_request_rejected %}
|
||||||
<li>{{ admin_undo_button(metadata.outbox_follow_ap_id, "undo follow") }}</li>
|
<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>{{ 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 %}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue