parent
c3caf26f22
commit
54e7755080
|
@ -928,14 +928,11 @@ class Post(StatorModel):
|
||||||
try:
|
try:
|
||||||
cls.by_object_uri(object_uri)
|
cls.by_object_uri(object_uri)
|
||||||
except cls.DoesNotExist:
|
except cls.DoesNotExist:
|
||||||
InboxMessage.objects.create(
|
InboxMessage.create_internal(
|
||||||
message={
|
{
|
||||||
"type": "__internal__",
|
"type": "FetchPost",
|
||||||
"object": {
|
"object": object_uri,
|
||||||
"type": "FetchPost",
|
"reason": reason,
|
||||||
"object": object_uri,
|
|
||||||
"reason": reason,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -995,7 +992,7 @@ class Post(StatorModel):
|
||||||
Handles an internal fetch-request inbox message
|
Handles an internal fetch-request inbox message
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
uri = data["object"]["object"]
|
uri = data["object"]
|
||||||
if "://" in uri:
|
if "://" in uri:
|
||||||
cls.by_object_uri(uri, fetch=True)
|
cls.by_object_uri(uri, fetch=True)
|
||||||
except (cls.DoesNotExist, KeyError):
|
except (cls.DoesNotExist, KeyError):
|
||||||
|
|
|
@ -166,6 +166,30 @@ class TimelineEvent(models.Model):
|
||||||
subject_identity_id=interaction.identity_id,
|
subject_identity_id=interaction.identity_id,
|
||||||
).delete()
|
).delete()
|
||||||
|
|
||||||
|
### Background tasks ###
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def handle_clear_timeline(cls, message):
|
||||||
|
"""
|
||||||
|
Internal stator handler for clearing all events by a user off another
|
||||||
|
user's timeline.
|
||||||
|
"""
|
||||||
|
actor_id = message["actor"]
|
||||||
|
object_id = message["object"]
|
||||||
|
full_erase = message.get("fullErase", False)
|
||||||
|
|
||||||
|
if full_erase:
|
||||||
|
q = (
|
||||||
|
models.Q(subject_post__author_id=object_id)
|
||||||
|
| models.Q(subject_post_interaction__identity_id=object_id)
|
||||||
|
| models.Q(subject_identity_id=object_id)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
q = models.Q(
|
||||||
|
type=cls.Types.post, subject_post__author_id=object_id
|
||||||
|
) | models.Q(type=cls.Types.boost, subject_identity_id=object_id)
|
||||||
|
TimelineEvent.objects.filter(q, identity_id=actor_id).delete()
|
||||||
|
|
||||||
### Mastodon Client API ###
|
### Mastodon Client API ###
|
||||||
|
|
||||||
def to_mastodon_notification_json(self, interactions=None):
|
def to_mastodon_notification_json(self, interactions=None):
|
||||||
|
|
|
@ -5,6 +5,7 @@ from activities.models import Post, TimelineEvent
|
||||||
from activities.services import PostService
|
from activities.services import PostService
|
||||||
from core.ld import format_ld_date
|
from core.ld import format_ld_date
|
||||||
from users.models import Block, Follow, Identity, InboxMessage
|
from users.models import Block, Follow, Identity, InboxMessage
|
||||||
|
from users.services import IdentityService
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
@ -192,3 +193,67 @@ def test_old_new_post(
|
||||||
).first()
|
).first()
|
||||||
assert event
|
assert event
|
||||||
assert "Hello " in event.subject_post.content
|
assert "Hello " in event.subject_post.content
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize("full", [True, False])
|
||||||
|
def test_clear_timeline(
|
||||||
|
identity: Identity,
|
||||||
|
remote_identity: Identity,
|
||||||
|
stator,
|
||||||
|
full: bool,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Ensures that timeline clearing works as expected.
|
||||||
|
"""
|
||||||
|
# Follow the remote user
|
||||||
|
service = IdentityService(remote_identity)
|
||||||
|
service.follow_from(identity)
|
||||||
|
# Create an inbound new post message mentioning us
|
||||||
|
message = {
|
||||||
|
"id": "test",
|
||||||
|
"type": "Create",
|
||||||
|
"actor": remote_identity.actor_uri,
|
||||||
|
"object": {
|
||||||
|
"id": "https://remote.test/test-post",
|
||||||
|
"type": "Note",
|
||||||
|
"published": format_ld_date(timezone.now()),
|
||||||
|
"attributedTo": remote_identity.actor_uri,
|
||||||
|
"content": f"Hello @{identity.handle}!",
|
||||||
|
"tag": {
|
||||||
|
"type": "Mention",
|
||||||
|
"href": identity.actor_uri,
|
||||||
|
"name": f"@{identity.handle}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
InboxMessage.objects.create(message=message)
|
||||||
|
|
||||||
|
# Run stator twice - to make fanouts and then process them
|
||||||
|
stator.run_single_cycle_sync()
|
||||||
|
stator.run_single_cycle_sync()
|
||||||
|
|
||||||
|
# Make sure it appeared on our timeline as a post and a mentioned
|
||||||
|
assert TimelineEvent.objects.filter(
|
||||||
|
type=TimelineEvent.Types.post, identity=identity
|
||||||
|
).exists()
|
||||||
|
assert TimelineEvent.objects.filter(
|
||||||
|
type=TimelineEvent.Types.mentioned, identity=identity
|
||||||
|
).exists()
|
||||||
|
|
||||||
|
# Now, submit either a user block (for full clear) or unfollow (for post clear)
|
||||||
|
if full:
|
||||||
|
service.block_from(identity)
|
||||||
|
else:
|
||||||
|
service.unfollow_from(identity)
|
||||||
|
|
||||||
|
# Run stator once to process the timeline clear message
|
||||||
|
stator.run_single_cycle_sync()
|
||||||
|
|
||||||
|
# Verify that the right things vanished
|
||||||
|
assert not TimelineEvent.objects.filter(
|
||||||
|
type=TimelineEvent.Types.post, identity=identity
|
||||||
|
).exists()
|
||||||
|
assert TimelineEvent.objects.filter(
|
||||||
|
type=TimelineEvent.Types.mentioned, identity=identity
|
||||||
|
).exists() == (not full)
|
||||||
|
|
|
@ -14,7 +14,7 @@ class InboxMessageStates(StateGraph):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def handle_received(cls, instance: "InboxMessage"):
|
async def handle_received(cls, instance: "InboxMessage"):
|
||||||
from activities.models import Post, PostInteraction
|
from activities.models import Post, PostInteraction, TimelineEvent
|
||||||
from users.models import Block, Follow, Identity, Report
|
from users.models import Block, Follow, Identity, Report
|
||||||
|
|
||||||
match instance.message_type:
|
match instance.message_type:
|
||||||
|
@ -148,7 +148,11 @@ class InboxMessageStates(StateGraph):
|
||||||
match instance.message_object_type:
|
match instance.message_object_type:
|
||||||
case "fetchpost":
|
case "fetchpost":
|
||||||
await sync_to_async(Post.handle_fetch_internal)(
|
await sync_to_async(Post.handle_fetch_internal)(
|
||||||
instance.message
|
instance.message["object"]
|
||||||
|
)
|
||||||
|
case "cleartimeline":
|
||||||
|
await sync_to_async(TimelineEvent.handle_clear_timeline)(
|
||||||
|
instance.message["object"]
|
||||||
)
|
)
|
||||||
case unknown:
|
case unknown:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
@ -171,6 +175,18 @@ class InboxMessage(StatorModel):
|
||||||
|
|
||||||
state = StateField(InboxMessageStates)
|
state = StateField(InboxMessageStates)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_internal(cls, payload):
|
||||||
|
"""
|
||||||
|
Creates an internal action message
|
||||||
|
"""
|
||||||
|
cls.objects.create(
|
||||||
|
message={
|
||||||
|
"type": "__internal__",
|
||||||
|
"object": payload,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def message_type(self):
|
def message_type(self):
|
||||||
return self.message["type"].lower()
|
return self.message["type"].lower()
|
||||||
|
|
|
@ -11,6 +11,7 @@ from users.models import (
|
||||||
Follow,
|
Follow,
|
||||||
FollowStates,
|
FollowStates,
|
||||||
Identity,
|
Identity,
|
||||||
|
InboxMessage,
|
||||||
User,
|
User,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -85,13 +86,29 @@ class IdentityService:
|
||||||
existing_follow = Follow.maybe_get(from_identity, self.identity)
|
existing_follow = Follow.maybe_get(from_identity, self.identity)
|
||||||
if existing_follow:
|
if existing_follow:
|
||||||
existing_follow.transition_perform(FollowStates.undone)
|
existing_follow.transition_perform(FollowStates.undone)
|
||||||
|
InboxMessage.create_internal(
|
||||||
|
{
|
||||||
|
"type": "ClearTimeline",
|
||||||
|
"actor": from_identity.pk,
|
||||||
|
"object": self.identity.pk,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def block_from(self, from_identity: Identity) -> Block:
|
def block_from(self, from_identity: Identity) -> Block:
|
||||||
"""
|
"""
|
||||||
Blocks a user.
|
Blocks a user.
|
||||||
"""
|
"""
|
||||||
self.unfollow_from(from_identity)
|
self.unfollow_from(from_identity)
|
||||||
return Block.create_local_block(from_identity, self.identity)
|
block = Block.create_local_block(from_identity, self.identity)
|
||||||
|
InboxMessage.create_internal(
|
||||||
|
{
|
||||||
|
"type": "ClearTimeline",
|
||||||
|
"actor": from_identity.pk,
|
||||||
|
"object": self.identity.pk,
|
||||||
|
"fullErase": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return block
|
||||||
|
|
||||||
def unblock_from(self, from_identity: Identity):
|
def unblock_from(self, from_identity: Identity):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue