diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 1abf9a75..12bbda96 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -191,7 +191,7 @@ class ObjectMixin(ActivitypubMixin): try: software = None - # do we have a "pure" activitypub version of this for mastodon? + # do we have a "pure" activitypub version of this for mastodon? if hasattr(self, 'pure_content'): pure_activity = self.to_create_activity(user, pure=True) self.broadcast(pure_activity, user, software='other') @@ -199,7 +199,7 @@ class ObjectMixin(ActivitypubMixin): # sends to BW only if we just did a pure version for masto activity = self.to_create_activity(user) self.broadcast(activity, user, software=software) - except KeyError: + except AttributeError: # janky as heck, this catches the mutliple inheritence chain # for boosts and ignores this auxilliary broadcast return @@ -228,27 +228,27 @@ class ObjectMixin(ActivitypubMixin): def to_create_activity(self, user, **kwargs): ''' returns the object wrapped in a Create activity ''' - activity_object = self.to_activity(**kwargs) + activity_object = self.to_activity_dataclass(**kwargs) signature = None create_id = self.remote_id + '/activity' - if 'content' in activity_object and activity_object['content']: + if hasattr(activity_object, 'content') and activity_object.content: signer = pkcs1_15.new(RSA.import_key(user.key_pair.private_key)) - content = activity_object['content'] + content = activity_object.content signed_message = signer.sign(SHA256.new(content.encode('utf8'))) signature = activitypub.Signature( creator='%s#main-key' % user.remote_id, - created=activity_object['published'], + created=activity_object.published, signatureValue=b64encode(signed_message).decode('utf8') ) return activitypub.Create( id=create_id, actor=user.remote_id, - to=activity_object['to'], - cc=activity_object['cc'], - object=self, + to=activity_object.to, + cc=activity_object.cc, + object=activity_object, signature=signature, ).serialize() @@ -312,7 +312,7 @@ class OrderedCollectionPageMixin(ObjectMixin): activity['first'] = '%s?page=1' % remote_id activity['last'] = '%s?page=%d' % (remote_id, paginated.num_pages) - return serializer(**activity).serialize() + return serializer(**activity) class OrderedCollectionMixin(OrderedCollectionPageMixin): @@ -324,9 +324,12 @@ class OrderedCollectionMixin(OrderedCollectionPageMixin): activity_serializer = activitypub.OrderedCollection + def to_activity_dataclass(self, **kwargs): + return self.to_ordered_collection(self.collection_queryset, **kwargs) + def to_activity(self, **kwargs): ''' an ordered collection of the specified model queryset ''' - return self.to_ordered_collection(self.collection_queryset, **kwargs) + return self.to_ordered_collection(self.collection_queryset, **kwargs).serialize() class CollectionItemMixin(ActivitypubMixin): diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 2fb70801..aff028a5 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -157,7 +157,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): **kwargs ) - def to_activity(self, pure=False):# pylint: disable=arguments-differ + def to_activity_dataclass(self, pure=False):# pylint: disable=arguments-differ ''' return tombstone if the status is deleted ''' if self.deleted: return activitypub.Tombstone( @@ -165,25 +165,29 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): url=self.remote_id, deleted=self.deleted_date.isoformat(), published=self.deleted_date.isoformat() - ).serialize() - activity = ActivitypubMixin.to_activity(self) - activity['replies'] = self.to_replies() + ) + activity = ActivitypubMixin.to_activity_dataclass(self) + activity.replies = self.to_replies() # "pure" serialization for non-bookwyrm instances if pure and hasattr(self, 'pure_content'): - activity['content'] = self.pure_content - if 'name' in activity: - activity['name'] = self.pure_name - activity['type'] = self.pure_type - activity['attachment'] = [ + activity.content = self.pure_content + if hasattr(activity, 'name'): + activity.name = self.pure_name + activity.type = self.pure_type + activity.attachment = [ image_serializer(b.cover, b.alt_text) \ for b in self.mention_books.all()[:4] if b.cover] if hasattr(self, 'book') and self.book.cover: - activity['attachment'].append( + activity.attachment.append( image_serializer(self.book.cover, self.book.alt_text) ) return activity + def to_activity(self, pure=False):# pylint: disable=arguments-differ + ''' json serialized activitypub class ''' + return self.to_activity_dataclass(pure=pure).serialize() + class GeneratedNote(Status): ''' these are app-generated messages about user activity ''' diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index a6f069d4..11b944d9 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -296,29 +296,6 @@ class ActivitypubMixins(TestCase): id=1, user=self.local_user, deleted=True).save() - def test_to_create_activity(self): - ''' wrapper for ActivityPub "create" action ''' - MockSelf = namedtuple('Self', ('remote_id', 'to_activity')) - mock_self = MockSelf( - 'https://example.com/status/1', - lambda *args: self.object_mock - ) - activity = ObjectMixin.to_create_activity( - mock_self, self.local_user) - self.assertEqual( - activity['id'], - 'https://example.com/status/1/activity' - ) - self.assertEqual(activity['actor'], self.local_user.remote_id) - self.assertEqual(activity['type'], 'Create') - self.assertEqual(activity['to'], 'to field') - self.assertEqual(activity['cc'], 'cc field') - self.assertIsInstance(activity['object'], dict) - self.assertEqual( - activity['signature'].creator, - '%s#main-key' % self.local_user.remote_id - ) - def test_to_delete_activity(self): ''' wrapper for Delete activity ''' MockSelf = namedtuple('Self', ('remote_id', 'to_activity')) diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index f9b95f34..c6911b6d 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -284,3 +284,24 @@ class Status(TestCase): with self.assertRaises(IntegrityError): models.Notification.objects.create( user=self.user, notification_type='GLORB') + + + def test_create_broadcast(self, broadcast_mock): + ''' should send out two verions of a status on create ''' + models.Comment.objects.create( + content='hi', user=self.user, book=self.book) + self.assertEqual(broadcast_mock.call_count, 2) + pure_call = broadcast_mock.call_args_list[0] + bw_call = broadcast_mock.call_args_list[1] + + self.assertEqual(pure_call[1]['software'], 'other') + args = pure_call[0][0] + self.assertEqual(args['type'], 'Create') + self.assertEqual(args['object']['type'], 'Note') + self.assertTrue('content' in args['object']) + + + self.assertEqual(bw_call[1]['software'], 'bookwyrm') + args = bw_call[0][0] + self.assertEqual(args['type'], 'Create') + self.assertEqual(args['object']['type'], 'Comment')