Protocol.targets: if it's a reply to native post that's bridged, bridge the reply too

in-reply-to-bridged
Ryan Barrett 2024-05-12 21:27:05 -07:00
rodzic 79e5d2be07
commit 099a636ed5
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
4 zmienionych plików z 101 dodań i 54 usunięć

2
ids.py
Wyświetl plik

@ -252,8 +252,10 @@ def translate_object_id(*, id, from_, to):
if from_.LABEL in COPIES_PROTOCOLS or to.LABEL in COPIES_PROTOCOLS:
if obj := from_.load(id, remote=False):
if copy := obj.get_copy(to):
util.d(f'copy {to.LABEL}:o:{from_.ABBREV}:{id}')
return copy
if orig := models.get_original(id):
util.d(f'orig {to.LABEL}:o:{from_.ABBREV}:{id}')
return orig.key.id()
match from_.LABEL, to.LABEL:

Wyświetl plik

@ -451,6 +451,9 @@ class Protocol:
To be implemented by subclasses.
NOTE: if this protocol's ``HAS_COPIES`` is True, and this method creates
a copy and sends it, it *must* add that copy to :attr:`copies`!
Args:
obj (models.Object): with activity to send
url (str): destination URL to send to
@ -1141,13 +1144,13 @@ class Protocol:
for in_reply_to in in_reply_tos:
if proto := Protocol.for_id(in_reply_to):
if in_reply_to_obj := proto.load(in_reply_to):
if proto.LABEL != obj.source_protocol:
in_reply_to_protocols.add(proto._get_kind())
if obj.source_protocol in (proto.LABEL, proto.ABBREV):
for copy in in_reply_to_obj.copies:
add(target_uris, copy.uri)
in_reply_to_protocols.add(
PROTOCOLS[copy.protocol]._get_kind())
else:
proto_labels = proto.DEFAULT_ENABLED_PROTOCOLS + tuple(
c.protocol for c in in_reply_to_obj.copies)
in_reply_to_protocols.update(PROTOCOLS[c.protocol]._get_kind()
for c in in_reply_to_obj.copies)
in_reply_to_protocols.add(proto._get_kind())
if reply_owner := as1.get_owner(in_reply_to_obj.as1):
in_reply_to_owners.append(reply_owner)
@ -1211,54 +1214,7 @@ class Protocol:
return targets
if (obj.type in ('post', 'update', 'delete', 'share')
and (not is_reply or (is_self_reply and in_reply_to_protocols))):
logger.info(f'Delivering to followers of {user_key}')
followers = Follower.query(Follower.to == user_key,
Follower.status == 'active'
).fetch()
user_keys = [f.from_ for f in followers]
if is_reply:
user_keys = [k for k in user_keys if k.kind() in in_reply_to_protocols]
users = [u for u in ndb.get_multi(user_keys) if u]
User.load_multi(users)
# which object should we add to followers' feeds, if any
feed_obj = None
if obj.type == 'share':
feed_obj = obj
else:
inner = as1.get_object(obj.as1)
# don't add profile updates to feeds
if not (obj.type == 'update'
and inner.get('objectType') in as1.ACTOR_TYPES):
inner_id = inner.get('id')
if inner_id:
feed_obj = cls.load(inner_id)
for user in users:
if feed_obj:
feed_obj.add('feed', user.key)
# TODO: should we pass remote=False through here to Protocol.load?
target = user.target_for(user.obj, shared=True) if user.obj else None
if not target:
# TODO: surface errors like this somehow?
logger.error(f'Follower {user.key} has no delivery target')
continue
# normalize URL (lower case hostname, etc)
# ...but preserve our PDS URL without trailing slash in path
# https://atproto.com/specs/did#did-documents
target = util.dedupe_urls([target], trailing_slash=False)[0]
# HACK: use last target object from above for reposts, which
# has its resolved id
targets[Target(protocol=user.LABEL, uri=target)] = \
orig_obj if obj.as1.get('verb') == 'share' else None
if feed_obj:
feed_obj.put()
and (not is_reply or in_reply_to_protocols)):
# include ATProto if this user is enabled there.
# TODO: abstract across protocols. maybe with this, below
# targets.update({
@ -1281,6 +1237,56 @@ class Protocol:
Target(protocol=ATProto.LABEL, uri=ATProto.PDS_URL), None)
logger.info(f'user has ATProto enabled, adding {ATProto.PDS_URL}')
if not is_reply or (is_self_reply and in_reply_to_protocols):
logger.info(f'Delivering to followers of {user_key}')
followers = Follower.query(Follower.to == user_key,
Follower.status == 'active'
).fetch()
user_keys = [f.from_ for f in followers]
if is_reply:
user_keys = [k for k in user_keys
if k.kind() in in_reply_to_protocols]
users = [u for u in ndb.get_multi(user_keys) if u]
User.load_multi(users)
# which object should we add to followers' feeds, if any
feed_obj = None
if obj.type == 'share':
feed_obj = obj
else:
inner = as1.get_object(obj.as1)
# don't add profile updates to feeds
if not (obj.type == 'update'
and inner.get('objectType') in as1.ACTOR_TYPES):
inner_id = inner.get('id')
if inner_id:
feed_obj = cls.load(inner_id)
for user in users:
if feed_obj:
feed_obj.add('feed', user.key)
# TODO: should we pass remote=False through here to Protocol.load?
target = (user.target_for(user.obj, shared=True)
if user.obj else None)
if not target:
# TODO: surface errors like this somehow?
logger.error(f'Follower {user.key} has no delivery target')
continue
# normalize URL (lower case hostname, etc)
# ...but preserve our PDS URL without trailing slash in path
# https://atproto.com/specs/did#did-documents
target = util.dedupe_urls([target], trailing_slash=False)[0]
# HACK: use last target object from above for reposts, which
# has its resolved id
targets[Target(protocol=user.LABEL, uri=target)] = \
orig_obj if obj.as1.get('verb') == 'share' else None
if feed_obj:
feed_obj.put()
# de-dupe targets, discard same-domain
# maps string target URL to (Target, Object) tuple
candidates = {t.uri: (t, obj) for t, obj in targets.items()}

Wyświetl plik

@ -956,6 +956,39 @@ class ProtocolReceiveTest(TestCase):
self.assertEqual([(obj.key.id(), 'fake:post:target')], Fake.sent)
self.assertEqual([(obj.key.id(), 'other:eve:target')], OtherFake.sent)
def test_create_reply_is_bridged_if_original_post_is_bridged(self):
eve = self.make_user('fake:eve', cls=Fake, obj_id='fake:eve')
self.store_object(id='fake:post', source_protocol='fake',
copies=[Target(protocol='other', uri='other:post')],
our_as1={
'objectType': 'note',
'id': 'fake:post',
'author': 'fake:eve',
})
self.store_object(id='other:post', source_protocol='other',
our_as1={
'objectType': 'note',
'id': 'other:post',
'author': 'fake:eve',
})
reply_as1 = {
'id': 'fake:reply',
'objectType': 'note',
'inReplyTo': 'fake:post',
'author': 'fake:user',
}
self.assertEqual(('OK', 202), Fake.receive_as1(reply_as1))
reply = self.assert_object('fake:reply', our_as1=reply_as1, type='note')
self.assertEqual([('fake:reply#bridgy-fed-create', 'other:post:target')],
OtherFake.sent)
create = Object.get_by_id('fake:reply#bridgy-fed-create')
STATE https://github.com/snarfed/bridgy-fed/issues/1038\\\
big problem is we're setting copies on the #create, not on the original object
self.assertEqual([Target(protocol='other', uri='other:post')], create.copies)
def test_create_reply_isnt_bridged_if_original_isnt_bridged(self):
eve = self.make_user('other:eve', cls=OtherFake, obj_id='other:eve')
Follower.get_or_create(to=self.user, from_=eve)

Wyświetl plik

@ -126,6 +126,12 @@ class Fake(User, protocol.Protocol):
def send(cls, obj, url, from_user=None, orig_obj=None):
logger.info(f'{cls.__name__}.send {url} {obj.as1}')
cls.sent.append((obj.key.id(), url))
copy_id = ids.translate_object_id(id=obj.key.id(),
from_=PROTOCOLS[obj.source_protocol],
to=cls)
add(obj.copies, Target(uri=copy_id, protocol=cls.LABEL))
obj.put()
return True
@classmethod