diff --git a/federation/entities/activitypub/entities.py b/federation/entities/activitypub/entities.py index 00cf521..cb3f00e 100644 --- a/federation/entities/activitypub/entities.py +++ b/federation/entities/activitypub/entities.py @@ -10,7 +10,7 @@ from federation.entities.activitypub.constants import ( from federation.entities.activitypub.enums import ActorType, ObjectType, ActivityType from federation.entities.activitypub.mixins import ActivitypubObjectMixin, ActivitypubActorMixin from federation.entities.activitypub.objects import ImageObject -from federation.entities.base import Profile, Post, Follow, Accept +from federation.entities.base import Profile, Post, Follow, Accept, Comment from federation.outbound import handle_send from federation.types import UserType from federation.utils.text import with_slash @@ -138,6 +138,13 @@ class ActivitypubPost(ActivitypubObjectMixin, Post): return as2 +class ActivitypubComment(ActivitypubPost, Comment): + def to_as2(self) -> Dict: + as2 = super().to_as2() + as2["object"]["inReplyTo"] = self.target_id + return as2 + + class ActivitypubProfile(ActivitypubActorMixin, Profile): _type = ActorType.PERSON.value public = True diff --git a/federation/entities/activitypub/mappers.py b/federation/entities/activitypub/mappers.py index be991f0..8c73e83 100644 --- a/federation/entities/activitypub/mappers.py +++ b/federation/entities/activitypub/mappers.py @@ -3,7 +3,7 @@ from typing import List, Callable, Dict, Union from federation.entities.activitypub.constants import NAMESPACE_PUBLIC from federation.entities.activitypub.entities import ( - ActivitypubFollow, ActivitypubProfile, ActivitypubAccept, ActivitypubPost) + ActivitypubFollow, ActivitypubProfile, ActivitypubAccept, ActivitypubPost, ActivitypubComment) from federation.entities.base import Follow, Profile, Accept, Post from federation.entities.mixins import BaseEntity from federation.types import UserType @@ -28,10 +28,12 @@ def element_to_objects(payload: Dict) -> List: """ entities = [] if isinstance(payload.get('object'), dict) and payload["object"].get('type'): - as2_type = payload["object"]["type"] + if payload["object"]["type"] == "Note" and payload["object"].get("inReplyTo"): + cls = ActivitypubComment + else: + cls = MAPPINGS.get(payload["object"]["type"]) else: - as2_type = payload.get('type') - cls = MAPPINGS.get(as2_type) + cls = MAPPINGS.get(payload.get('type')) if not cls: return [] @@ -151,6 +153,8 @@ def transform_attribute(key: str, value: Union[str, Dict, int], transformed: Dic transformed["inboxes"]["private"] = value if not transformed["inboxes"]["public"]: transformed["inboxes"]["public"] = value + elif key == "inReplyTo": + transformed["target_id"] = value elif key == "name": transformed["name"] = value elif key == "object": diff --git a/federation/tests/entities/activitypub/test_mappers.py b/federation/tests/entities/activitypub/test_mappers.py index 6858247..fdc3414 100644 --- a/federation/tests/entities/activitypub/test_mappers.py +++ b/federation/tests/entities/activitypub/test_mappers.py @@ -3,11 +3,12 @@ from unittest.mock import patch import pytest from federation.entities.activitypub.entities import ( - ActivitypubFollow, ActivitypubAccept, ActivitypubProfile, ActivitypubPost) + ActivitypubFollow, ActivitypubAccept, ActivitypubProfile, ActivitypubPost, ActivitypubComment) from federation.entities.activitypub.mappers import message_to_objects, get_outbound_entity -from federation.entities.base import Accept, Follow, Profile, Post +from federation.entities.base import Accept, Follow, Profile, Post, Comment from federation.tests.fixtures.payloads import ( - ACTIVITYPUB_FOLLOW, ACTIVITYPUB_PROFILE, ACTIVITYPUB_PROFILE_INVALID, ACTIVITYPUB_UNDO_FOLLOW, ACTIVITYPUB_POST) + ACTIVITYPUB_FOLLOW, ACTIVITYPUB_PROFILE, ACTIVITYPUB_PROFILE_INVALID, ACTIVITYPUB_UNDO_FOLLOW, ACTIVITYPUB_POST, + ACTIVITYPUB_COMMENT) class TestActivitypubEntityMappersReceive: @@ -53,7 +54,8 @@ class TestActivitypubEntityMappersReceive: 'class="u-url mention">@jaywink boom

' assert post.id == "https://diaspodon.fr/users/jaywink/statuses/102356911717767237" assert post.actor_id == "https://diaspodon.fr/users/jaywink" - assert post.public == True + assert post.public is True + assert getattr(post, "target_id", None) is None @pytest.mark.skip def test_message_to_objects_post_with_photos(self): @@ -75,24 +77,18 @@ class TestActivitypubEntityMappersReceive: assert photo.public == False assert photo.created_at == datetime(2011, 7, 20, 1, 36, 7) - @pytest.mark.skip - def test_message_to_objects_comment(self, mock_validate): - entities = message_to_objects(DIASPORA_POST_COMMENT, "alice@alice.diaspora.example.org", - sender_key_fetcher=Mock()) + def test_message_to_objects_comment(self): + entities = message_to_objects(ACTIVITYPUB_COMMENT, "https://diaspodon.fr/users/jaywink") assert len(entities) == 1 comment = entities[0] - assert isinstance(comment, DiasporaComment) + assert isinstance(comment, ActivitypubComment) assert isinstance(comment, Comment) - assert comment.target_guid == "((parent_guidparent_guidparent_guidparent_guid))" - assert comment.guid == "((guidguidguidguidguidguid))" - assert comment.handle == "alice@alice.diaspora.example.org" - assert comment.participation == "comment" - assert comment.raw_content == "((text))" - assert comment.signature == "((signature))" - assert comment._xml_tags == [ - "guid", "parent_guid", "text", "author", - ] - mock_validate.assert_called_once_with() + assert comment.raw_content == '

@jaywink boom

' + assert comment.id == "https://diaspodon.fr/users/jaywink/statuses/102356911717767237" + assert comment.actor_id == "https://diaspodon.fr/users/jaywink" + assert comment.public is True + assert comment.target_id == "https://dev.jasonrobinson.me/content/653bad70-41b3-42c9-89cb-c4ee587e68e4/" @pytest.mark.skip def test_message_to_objects_like(self, mock_validate): diff --git a/federation/tests/fixtures/payloads/activitypub.py b/federation/tests/fixtures/payloads/activitypub.py index c99546a..0fa6a71 100644 --- a/federation/tests/fixtures/payloads/activitypub.py +++ b/federation/tests/fixtures/payloads/activitypub.py @@ -1,3 +1,53 @@ +ACTIVITYPUB_COMMENT = { + '@context': ['https://www.w3.org/ns/activitystreams', + {'ostatus': 'http://ostatus.org#', + 'atomUri': 'ostatus:atomUri', + 'inReplyToAtomUri': 'ostatus:inReplyToAtomUri', + 'conversation': 'ostatus:conversation', + 'sensitive': 'as:sensitive', + 'Hashtag': 'as:Hashtag', + 'toot': 'http://joinmastodon.org/ns#', + 'Emoji': 'toot:Emoji', + 'focalPoint': {'@container': '@list', '@id': 'toot:focalPoint'}, + 'blurhash': 'toot:blurhash'}], + 'id': 'https://diaspodon.fr/users/jaywink/statuses/102356911717767237/activity', + 'type': 'Create', + 'actor': 'https://diaspodon.fr/users/jaywink', + 'published': '2019-06-29T21:08:45Z', + 'to': ['https://www.w3.org/ns/activitystreams#Public'], + 'cc': ['https://diaspodon.fr/users/jaywink/followers', + 'https://dev.jasonrobinson.me/p/d4574854-a5d7-42be-bfac-f70c16fcaa97/'], + 'object': {'id': 'https://diaspodon.fr/users/jaywink/statuses/102356911717767237', + 'type': 'Note', + 'summary': None, + 'inReplyTo': 'https://dev.jasonrobinson.me/content/653bad70-41b3-42c9-89cb-c4ee587e68e4/', + 'published': '2019-06-29T21:08:45Z', + 'url': 'https://diaspodon.fr/@jaywink/102356911717767237', + 'attributedTo': 'https://diaspodon.fr/users/jaywink', + 'to': ['https://www.w3.org/ns/activitystreams#Public'], + 'cc': ['https://diaspodon.fr/users/jaywink/followers', + 'https://dev.jasonrobinson.me/p/d4574854-a5d7-42be-bfac-f70c16fcaa97/'], + 'sensitive': False, + 'atomUri': 'https://diaspodon.fr/users/jaywink/statuses/102356911717767237', + 'inReplyToAtomUri': 'https://dev.jasonrobinson.me/content/653bad70-41b3-42c9-89cb-c4ee587e68e4/', + 'conversation': 'tag:diaspodon.fr,2019-06-28:objectId=2347687:objectType=Conversation', + 'content': '

@jaywink boom

', + 'contentMap': {'en': '

@jaywink boom

'}, + 'attachment': [], + 'tag': [{'type': 'Mention', + 'href': 'https://dev.jasonrobinson.me/p/d4574854-a5d7-42be-bfac-f70c16fcaa97/', + 'name': '@jaywink@dev.jasonrobinson.me'}], + 'replies': {'id': 'https://diaspodon.fr/users/jaywink/statuses/102356911717767237/replies', + 'type': 'Collection', + 'first': {'type': 'CollectionPage', + 'partOf': 'https://diaspodon.fr/users/jaywink/statuses/102356911717767237/replies', + 'items': []}}}, + 'signature': {'type': 'RsaSignature2017', + 'creator': 'https://diaspodon.fr/users/jaywink#main-key', + 'created': '2019-06-29T21:08:45Z', + 'signatureValue': 'SjDACS7Z/Cb1SEC3AtxEokID5SHAYl7kpys/hhmaRbpXuFKCxfj2P9BmH8QhLnuam3sENZlrnBOcB5NlcBhIfwo/Xh242RZBmPQf+edTVYVCe1j19dihcftNCHtnqAcKwp/51dNM/OlKu2730FrwvOUXVIPtB7iVqkseO9TRzDYIDj+zBTksnR/NAYtq6SUpmefXfON0uW3N3Uq6PGfExJaS+aeqRf8cPGkZFSIUQZwOLXbIpb7BFjJ1+y1OMOAJueqvikUprAit3v6BiNWurAvSQpC7WWMFUKyA79/xtkO9kIPA/Q4C9ryqdzxZJ0jDhXiaIIQj2JZfIADdjLZHJA=='} +} + ACTIVITYPUB_FOLLOW = { "@context": [ "https://www.w3.org/ns/activitystreams", @@ -132,7 +182,7 @@ ACTIVITYPUB_POST = { 'object': {'id': 'https://diaspodon.fr/users/jaywink/statuses/102356911717767237', 'type': 'Note', 'summary': None, - 'inReplyTo': 'https://dev.jasonrobinson.me/content/653bad70-41b3-42c9-89cb-c4ee587e68e4/', + 'inReplyTo': None, 'published': '2019-06-29T21:08:45Z', 'url': 'https://diaspodon.fr/@jaywink/102356911717767237', 'attributedTo': 'https://diaspodon.fr/users/jaywink', @@ -141,7 +191,7 @@ ACTIVITYPUB_POST = { 'https://dev.jasonrobinson.me/p/d4574854-a5d7-42be-bfac-f70c16fcaa97/'], 'sensitive': False, 'atomUri': 'https://diaspodon.fr/users/jaywink/statuses/102356911717767237', - 'inReplyToAtomUri': 'https://dev.jasonrobinson.me/content/653bad70-41b3-42c9-89cb-c4ee587e68e4/', + 'inReplyToAtomUri': None, 'conversation': 'tag:diaspodon.fr,2019-06-28:objectId=2347687:objectType=Conversation', 'content': '

@jaywink boom

', 'contentMap': {'en': '

@jaywink boom

'},