diff --git a/protocol.py b/protocol.py index 23893b0..dbddb25 100644 --- a/protocol.py +++ b/protocol.py @@ -879,6 +879,7 @@ class Protocol: return 'OK', 200 from_user.disable_protocol(proto) + proto.maybe_delete_copy(from_user) return 'OK', 200 elif obj.type == 'post': @@ -898,6 +899,7 @@ class Protocol: proto.bot_follow(from_user) elif content == 'no': from_user.disable_protocol(proto) + proto.maybe_delete_copy(from_user) return 'OK', 200 # fetch actor if necessary @@ -1075,6 +1077,44 @@ class Protocol: url=target, protocol=user.LABEL, user=bot.key.urlsafe()) + @classmethod + def maybe_delete_copy(copy_cls, user): + """Deletes a user's copy actor in a given protocol. + + ...if ``copy_cls`` 's :attr:`Protocol.HAS_COPIES` is True. Otherwise, + does nothing. + + TODO: this should eventually go through receive for protocols that need + to deliver to all followers' targets, eg AP. + + Args: + user (User) + """ + if not copy_cls.HAS_COPIES: + return + + copy_user_id = user.get_copy(copy_cls) + if not copy_user_id: + logger.warning(f"Tried to delete {user.key} copy for {copy_cls.LABEL}, which doesn't exist!") + return + + now = util.now().isoformat() + delete_id = f'{copy_user_id}#delete-copy-{copy_cls.LABEL}-{now}' + delete = Object(id=delete_id, our_as1={ + 'id': delete_id, + 'objectType': 'activity', + 'verb': 'delete', + 'actor': 'fake:user', + 'object': copy_user_id, + }) + target = Target(protocol=copy_cls.LABEL, uri=copy_cls.target_for(delete)) + delete.undelivered = [target] + delete.put() + + common.create_task(queue='send', obj=delete.key.urlsafe(), + url=target.uri, protocol=target.protocol, + user=user.key.urlsafe()) + @classmethod def handle_bare_object(cls, obj): """If obj is a bare object, wraps it in a create or update activity. diff --git a/tests/test_protocol.py b/tests/test_protocol.py index d426fba..d7a66e1 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -1993,6 +1993,12 @@ class ProtocolReceiveTest(TestCase): self.assertEqual([], Fake.created_for) self.assertFalse(user.is_enabled(Fake)) + # ...and delete copy actor + self.assertEqual( + [('fake:u:eefake:user#delete-copy-fake-2022-01-02T03:04:05+00:00', + 'fake:u:eefake:user#delete-copy-fake-2022-01-02T03:04:05+00:00:target')], + Fake.sent) + def test_follow_bot_user_refreshes_profile(self): # bot user self.make_user('fa.brid.gy', cls=Web) @@ -2132,6 +2138,12 @@ class ProtocolReceiveTest(TestCase): self.assertEqual([], Fake.created_for) self.assertFalse(user.is_enabled(Fake)) + # ...and delete copy actor + self.assertEqual( + [('fake:u:eefake:user#delete-copy-fake-2022-01-02T03:04:05+00:00', + 'fake:u:eefake:user#delete-copy-fake-2022-01-02T03:04:05+00:00:target')], + Fake.sent) + @patch('protocol.LIMITED_DOMAINS', ['lim.it']) @patch('requests.get') def test_limited_domain_update_profile_without_follow(self, mock_get):