diff --git a/protocol.py b/protocol.py index 6f9019f..cac7606 100644 --- a/protocol.py +++ b/protocol.py @@ -871,6 +871,7 @@ class Protocol: # fall through to deliver to followers + # TODO: add undo here, test for it elif obj.type == 'delete': if not inner_obj_id: error("Couldn't find id of object to delete") @@ -1225,7 +1226,7 @@ class Protocol: return 'OK', 202 @classmethod - def targets(cls, obj, from_user): + def targets(from_cls, obj, from_user): """Collects the targets to send a :class:`models.Object` to. Targets are both objects - original posts, events, etc - and actors. @@ -1281,7 +1282,7 @@ class Protocol: logger.info(f'Adding target {target} for copy {copy.uri} of original {id}') targets[Target(protocol=copy.protocol, uri=target)] = orig_obj - if protocol == cls and cls.LABEL != 'fake': + if protocol == from_cls and from_cls.LABEL != 'fake': logger.info(f'Skipping same-protocol target {id}') continue @@ -1298,10 +1299,20 @@ class Protocol: logger.info(f'Recipient is {orig_user}') obj.add('notify', orig_user) + if obj.type == 'undo': + logger.info('Object is an undo; adding targets for inner object') + inner_obj_as1 = as1.get_object(obj.as1) + if set(inner_obj_as1.keys()) == {'id'}: + inner_obj = from_cls.load(inner_obj_as1['id']) + else: + inner_obj = Object(our_as1=inner_obj_as1) + if inner_obj: + targets.update(from_cls.targets(inner_obj, from_user=from_user)) + logger.info(f'Direct (and copy) targets: {targets.keys()}') # deliver to followers, if appropriate - user_key = cls.actor_key(obj) + user_key = from_cls.actor_key(obj) if not user_key: logger.info("Can't tell who this is from! Skipping followers.") return targets @@ -1332,7 +1343,7 @@ class Protocol: and inner.get('objectType') in as1.ACTOR_TYPES): inner_id = inner.get('id') if inner_id: - feed_obj = cls.load(inner_id) + feed_obj = from_cls.load(inner_id) # include ATProto if this user is enabled there. # TODO: abstract across protocols. maybe with this, below diff --git a/tests/test_protocol.py b/tests/test_protocol.py index 454aa80..21ec65b 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -933,6 +933,66 @@ class ProtocolReceiveTest(TestCase): ignore=['created', 'updated']) self.assertEqual(0, mock_send.call_count) + def test_targets_block(self): + self.bob.obj.our_as1 = {'foo': 'bar'} + self.bob.obj.put() + + block = { + 'objectType': 'activity', + 'verb': 'block', + 'id': 'fake:block', + 'actor': 'fake:alice', + 'object': 'fake:bob', + } + self.assertEqual( + [Target(uri='fake:bob:target', protocol='fake')], + list(Fake.targets(Object(our_as1=block), from_user=self.user).keys())) + + def test_targets_undo_composite_object(self): + self.bob.obj.our_as1 = {'foo': 'bar'} + self.bob.obj.put() + + undo = { + 'objectType': 'activity', + 'verb': 'undo', + 'id': 'fake:undo', + 'actor': 'fake:alice', + 'object': { + 'objectType': 'activity', + 'verb': 'block', + 'id': 'fake:block', + 'actor': 'fake:alice', + 'object': 'fake:bob', + }, + } + self.assertEqual( + [Target(uri='fake:bob:target', protocol='fake')], + list(Fake.targets(Object(our_as1=undo), from_user=self.user).keys())) + + def test_targets_undo_object_id(self): + self.bob.obj.our_as1 = {'foo': 'bar'} + self.bob.obj.put() + + self.store_object(id='fake:block', our_as1={ + 'objectType': 'activity', + 'verb': 'block', + 'id': 'fake:block', + 'actor': 'fake:alice', + 'object': 'fake:bob', + }) + + undo = { + 'objectType': 'activity', + 'verb': 'undo', + 'id': 'fake:undo', + 'actor': 'fake:alice', + 'object': 'fake:block', + } + self.assertEqual( + [Target(uri='fake:block:target', protocol='fake'), + Target(uri='fake:bob:target', protocol='fake')], + list(Fake.targets(Object(our_as1=undo), from_user=self.user).keys())) + @patch.object(ATProto, 'send', return_value=True) def test_atproto_targets_normalize_pds_url(self, mock_send): # we were over-normalizing our PDS URL https://atproto.brid.gy , adding @@ -1929,6 +1989,28 @@ class ProtocolReceiveTest(TestCase): self.assertEqual([('fake:block', 'fake:bob:target')], Fake.sent) + def test_undo_block(self): + self.make_user(id='other:eve', cls=OtherFake, obj_as1={}) + self.make_followers() + + block = { + 'objectType': 'activity', + 'verb': 'block', + 'id': 'fake:block', + 'actor': 'fake:user', + 'object': 'other:eve', + } + self.store_object(id='fake:block', our_as1=block) + + self.assertEqual(('OK', 202), Fake.receive_as1({ + 'objectType': 'activity', + 'verb': 'undo', + 'id': 'fake:undo', + 'actor': 'fake:user', + 'object': block, + })) + self.assertEqual([('fake:undo', 'fake:block:target')], Fake.sent) + @skip def test_from_bridgy_fed_domain_fails(self): with self.assertRaises(BadRequest):