add Object.replace_copies_with_originals

pull/691/head
Ryan Barrett 2023-10-17 14:37:36 -07:00
rodzic dcfdf35416
commit 96b84511eb
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
3 zmienionych plików z 174 dodań i 41 usunięć

Wyświetl plik

@ -1,4 +1,5 @@
"""Datastore model classes."""
import copy
from datetime import timedelta, timezone
import itertools
import json
@ -614,26 +615,12 @@ class Object(StringIdModel):
# populate actor/author if necessary and available
type = obj.get('objectType')
owner_field = ('actor' if type == 'activity'
field = ('actor' if type == 'activity'
else 'author' if type not in as1.ACTOR_TYPES
else None)
if owner_field and owner:
# logger.debug(f'Replacing {owner_field} {obj.get(owner_field)}...')
# load matching user, if any
user = User.get_for_copy(owner)
if user:
if user.obj and user.obj.as1:
obj[owner_field] = {
**user.obj.as1,
'id': user.key.id(),
}
else:
obj[owner_field] = user.key.id()
else:
obj[owner_field] = owner
# logger.debug(f' with {obj[owner_field]}')
if field and owner:
# logger.debug(f'Replacing {field} {obj.get(field)} with {owner}')
obj[field] = owner
return obj
@ -928,6 +915,67 @@ class Object(StringIdModel):
{util.ellipsize(name, chars=40)}
</a>"""
def replace_copies_with_originals(self):
"""Replaces ids copied from other protocols with their original ids.
Specifically, replaces these AS1 fields in place:
* ``actor``
* ``author``
* ``object``
* ``object.actor``
* ``object.author``
* ``object.id``
* ``object.inReplyTo``
* ``tags.[objectType=mention].url``
Looks up values in :attr:`User.copies` and :attr:`Object.copies` and
replaces with their key ids.
"""
if not self.as1 or not self.source_protocol:
return
outer_obj = copy.deepcopy(self.as1)
inner_obj = outer_obj['object'] = as1.get_object(outer_obj)
fields = 'actor', 'author', 'inReplyTo'
mention_tags = [t for t in (as1.get_objects(outer_obj, 'tags')
+ as1.get_objects(inner_obj, 'tags'))
if t.get('objectType') == 'mention']
# batch lookup matching users
ids = util.trim_nulls(
[outer_obj.get(f) for f in fields]
+ [inner_obj.get(f) for f in fields]
+ [inner_obj.get('id')]
+ [m.get('url') for m in mention_tags])
origs = (User.get_for_copies(ids)
+ Object.query(Object.copies.uri.IN(ids)).fetch())
replaced = False
def replace(obj, field):
val = obj.get(field)
if val:
for orig in origs:
target = Target(uri=val, protocol=self.source_protocol)
if target in orig.copies:
logger.debug(
f'Replacing copy {target} with original {orig.key.id()}')
obj[field] = orig.key.id()
nonlocal replaced
replaced = True
for obj in outer_obj, inner_obj:
for field in 'actor', 'author', 'inReplyTo':
replace(obj, field)
replace(inner_obj, 'id')
for tag in mention_tags:
replace(tag, 'url')
if replaced:
self.our_as1 = outer_obj
class Follower(ndb.Model):
"""A follower of a Bridgy Fed user."""

Wyświetl plik

@ -18,7 +18,7 @@ from oauth_dropins.webutil.testutil import NOW, requests_response
from oauth_dropins.webutil import util
# import first so that Fake is defined before URL routes are registered
from .testutil import Fake, TestCase
from .testutil import Fake, OtherFake, TestCase
from atproto import ATProto
from models import Follower, Object, OBJECT_EXPIRE_AGE, Target, User
@ -579,31 +579,9 @@ class ObjectTest(TestCase):
'object': 'http://example.com/original/post',
}
# no user
obj = Object(id='at://did:plc:foo/co.ll/123', bsky=like_bsky)
self.assert_equals(like_as1, obj.as1)
# matching user without Object
user = Fake(id='fake:user',
copies=[Target(uri='did:plc:foo', protocol='atproto')])
user.put()
self.assertEqual({
**like_as1,
'actor': 'fake:user',
}, obj.as1)
# matching user with Object
user.obj = self.store_object(id='at://did:plc:foo/profile/self',
our_as1={'foo': 'bar'})
user.put()
self.assertEqual({
**like_as1,
'actor': {
'id': 'fake:user',
'foo': 'bar',
},
}, obj.as1)
def test_as1_from_mf2_uses_url_as_id(self):
obj = Object(mf2={
'properties': {
@ -734,6 +712,83 @@ class ObjectTest(TestCase):
'object': {},
}, obj.key.get().as2)
def test_replace_copies_with_originals_empty(self):
obj = Object()
obj.replace_copies_with_originals()
self.assertIsNone(obj.as1)
def test_replace_copies_with_originals_follow(self):
follow = {
'id': 'fake:follow',
'objectType': 'activity',
'verb': 'follow',
'actor': 'fake:alice',
'object': 'fake:bob',
}
obj = Object(our_as1=follow, source_protocol='fake')
# no matching copy users
obj.replace_copies_with_originals()
self.assert_equals(follow, obj.our_as1)
# matching copy users
self.make_user('other:alice', cls=OtherFake,
copies=[Target(uri='fake:alice', protocol='fake')])
self.make_user('other:bob', cls=OtherFake,
copies=[Target(uri='fake:bob', protocol='fake')])
obj.replace_copies_with_originals()
self.assert_equals({
**follow,
'actor': 'other:alice',
'object': {'id': 'other:bob'},
}, obj.our_as1)
def test_replace_copies_with_originals_reply(self):
reply = {
'objectType': 'activity',
'verb': 'create',
'object': {
'id': 'fake:reply',
'objectType': 'note',
'inReplyTo': 'fake:post',
'author': 'fake:alice',
'tags': [{
'objectType': 'mention',
'url': 'fake:bob',
}],
},
}
obj = Object(our_as1=reply, source_protocol='fake')
# no matching copy users or objects
obj.replace_copies_with_originals()
self.assert_equals(reply, obj.our_as1)
# matching copies
self.make_user('other:alice', cls=OtherFake,
copies=[Target(uri='fake:alice', protocol='fake')])
self.make_user('other:bob', cls=OtherFake,
copies=[Target(uri='fake:bob', protocol='fake')])
self.store_object(id='other:post',
copies=[Target(uri='fake:post', protocol='fake')])
self.store_object(id='other:reply',
copies=[Target(uri='fake:reply', protocol='fake')])
obj.replace_copies_with_originals()
self.assert_equals({
'objectType': 'activity',
'verb': 'create',
'object': {
'id': 'other:reply',
'objectType': 'note',
'inReplyTo': 'other:post',
'author': 'other:alice',
'tags': [{
'objectType': 'mention',
'url': 'other:bob',
}],
},
}, obj.our_as1)
class FollowerTest(TestCase):

Wyświetl plik

@ -1386,6 +1386,36 @@ class ProtocolReceiveTest(TestCase):
)
self.assertEqual(2, Follower.query().count())
def test_replace_actor_copies_with_originals(self):
Fake.fetchable = {
'fake:alice': {},
'fake:bob': {},
}
follow = {
'id': 'fake:follow',
'objectType': 'activity',
'verb': 'follow',
'actor': 'fake:alice',
'object': 'fake:bob',
}
# no matching copy users
Fake.receive(Object(id='fake:follow', our_as1=follow, source_protocol='fake'))
self.assert_equals(follow, Object.get_by_id('fake:follow').our_as1)
# matching copy users
self.make_user('other:alice', cls=OtherFake,
copies=[Target(uri='fake:alice', protocol='fake')])
self.make_user('other:bob', cls=OtherFake,
copies=[Target(uri='fake:bob', protocol='fake')])
Fake.receive(Object(id='fake:follow', our_as1=follow, source_protocol='fake'))
self.assert_equals({
**follow,
'actor': 'other:alice',
'object': 'other:bob',
}, Object.get_by_id('fake:follow').our_as1)
def test_receive_task_handler(self):
note = {
'id': 'fake:post',