From 98098aeabbeffdd2c367cfe6787f89dc1bb48f58 Mon Sep 17 00:00:00 2001 From: Ryan Barrett Date: Fri, 16 Aug 2024 14:03:44 -0700 Subject: [PATCH] when an unbridged user replies to a bridged user, DM them a prompt one time only, per user per protocol! for #1205 --- protocol.py | 38 ++++++++++++++++++++++++-------------- tests/test_protocol.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/protocol.py b/protocol.py index f0f54357..86f8270b 100644 --- a/protocol.py +++ b/protocol.py @@ -1373,22 +1373,33 @@ class Protocol: # process direct targets for id in sorted(target_uris): - protocol = Protocol.for_id(id) - if not protocol: + target_proto = Protocol.for_id(id) + if not target_proto: logger.info(f"Can't determine protocol for {id}") continue - elif protocol.is_blocklisted(id): + elif target_proto.is_blocklisted(id): logger.info(f'{id} is blocklisted') continue - elif (protocol not in to_protocols - and obj.source_protocol != protocol.LABEL): - continue - orig_obj = protocol.load(id) + orig_obj = target_proto.load(id) if not orig_obj or not orig_obj.as1: logger.info(f"Couldn't load {id}") continue + target_author_key = target_proto.actor_key(orig_obj) + if (target_proto not in to_protocols + and obj.source_protocol != target_proto.LABEL): + # if author isn't bridged and inReplyTo author is, DM a prompt + if id in in_reply_tos: + if target_author := target_author_key.get(): + if target_author.is_enabled(from_cls): + target_proto.maybe_bot_dm(to_user=from_user, + type='replied_to_bridged_user', + text=f"""\ + Hi! You recently replied to {obj.actor_link(image=False)}, who's bridged here from {target_proto.PHRASE}. If you want them to see your replies in the future, you can bridge your account into {target_proto.PHRASE} by following this account. See the docs for more information.""") + + continue + # deliver self-replies to followers # https://github.com/snarfed/bridgy-fed/issues/639 if id in in_reply_tos and owner == as1.get_owner(orig_obj.as1): @@ -1404,11 +1415,11 @@ 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 == from_cls and from_cls.LABEL != 'fake': + if target_proto == from_cls and from_cls.LABEL != 'fake': logger.info(f'Skipping same-protocol target {id}') continue - target = protocol.target_for(orig_obj) + target = target_proto.target_for(orig_obj) if not target: # TODO: surface errors like this somehow? logger.error(f"Can't find delivery target for {id}") @@ -1417,14 +1428,13 @@ class Protocol: logger.info(f'Target for {id} is {target}') # only use orig_obj for inReplyTos and repost objects # https://github.com/snarfed/bridgy-fed/issues/1237 - targets[Target(protocol=protocol.LABEL, uri=target)] = ( + targets[Target(protocol=target_proto.LABEL, uri=target)] = ( orig_obj if id in in_reply_tos or id in as1.get_ids(obj.as1, 'object') else None) - orig_user = protocol.actor_key(orig_obj) - if orig_user: - logger.info(f'Recipient is {orig_user}') - obj.add('notify', orig_user) + if target_author_key: + logger.info(f'Recipient is {target_author_key}') + obj.add('notify', target_author_key) if obj.type == 'undo': logger.info('Object is an undo; adding targets for inner object') diff --git a/tests/test_protocol.py b/tests/test_protocol.py index c5060ef7..05282f47 100644 --- a/tests/test_protocol.py +++ b/tests/test_protocol.py @@ -1055,6 +1055,8 @@ class ProtocolReceiveTest(TestCase): self.assertEqual(0, mock_send.call_count) def test_reply_to_non_bridged_post_skips_enabled_protocol_with_followers(self): + self.make_user(id='fa.brid.gy', cls=Web) + # should skip even if it's enabled and we have followers there self.user.enabled_protocols = ['eefake'] self.user.put() @@ -1076,6 +1078,40 @@ class ProtocolReceiveTest(TestCase): self.assertEqual(202, code) self.assertEqual([], ExplicitEnableFake.sent) + def test_reply_from_non_bridged_post_isnt_bridged_but_gets_dm_prompt(self): + self.make_user(id='fa.brid.gy', cls=Web) + self.user.enabled_protocols = ['eefake'] + self.user.put() + + eve = self.make_user('eefake:eve', cls=ExplicitEnableFake, obj_as1={ + 'id': 'eefake:eve', + }) + + self.store_object(id='fake:post', source_protocol='fake', our_as1={ + 'id': 'fake:post', + 'objectType': 'note', + 'author': 'fake:alice', + }) + + # with self.assertRaises(NoContent): + _, code = ExplicitEnableFake.receive_as1({ + 'id': 'eefake:reply', + 'objectType': 'note', + 'actor': 'eefake:eve', + 'inReplyTo': 'fake:post', + }) + self.assertEqual(204, code) + + self.assertEqual([], Fake.sent) + self.assertEqual([ + ('https://fa.brid.gy/#replied_to_bridged_user-dm-eefake:eve-2022-01-02T03:04:05+00:00', + 'eefake:eve:target'), + ], ExplicitEnableFake.sent) + + eve = eve.key.get() + self.assertEqual([DM(protocol='fake', type='replied_to_bridged_user')], + eve.sent_dms) + @patch.object(ATProto, 'send', return_value=True) def test_repost_of_non_bridged_account_skips_atproto(self, mock_send): user = self.make_user('eefake:user', cls=ExplicitEnableFake,