formalize Protocol.target_for() to take any object, not just actors

important because webmention endpoints are per page (object), not per site (actor).
pull/561/head
Ryan Barrett 2023-06-20 17:06:32 -07:00
rodzic ab1c28ee4d
commit 2a7425a8c6
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
4 zmienionych plików z 54 dodań i 12 usunięć

Wyświetl plik

@ -102,11 +102,28 @@ class ActivityPub(User, Protocol):
@classmethod
def target_for(cls, obj, shared=False):
"""Returns `obj`'s inbox if it has one, otherwise `None`."""
assert obj.source_protocol in (cls.LABEL, cls.ABBREV)
"""Returns `obj`'s or its author's/actor's inbox, if available."""
assert obj.source_protocol in (cls.LABEL, cls.ABBREV, 'ui', None), str(obj)
if not obj.as1:
return None
if obj.type not in as1.ACTOR_TYPES:
logger.info(f'{obj.key} type {type} is not an actor')
for field in 'actor', 'author':
inner_obj = as1.get_object(obj.as1, field)
inner_id = inner_obj.get('id') or as1.get_url(inner_obj)
if not inner_id:
continue
# TODO: need a "soft" kwarg for load to suppress errors?
actor = cls.load(inner_id)
if actor and actor.as1:
target = cls.target_for(actor)
if target:
logger.info(f'Target for {obj.key} via {inner_id} is {target}')
return target
logger.info(f'{obj.key} type {obj.type} is not an actor and has no author or actor with inbox')
actor = obj.as_as2()

Wyświetl plik

@ -236,10 +236,6 @@ class Protocol:
True if the activity is sent successfully, False if it is ignored due
to protocol logic. (Failures are raised as exceptions.)
Returns:
True if the activity was sent successfully, False if it was discarded
or ignored due to protocol logic, ie not network or other failures
Raises:
:class:`werkzeug.HTTPException` if the request fails
"""
@ -282,7 +278,7 @@ class Protocol:
@classmethod
def target_for(cls, obj, shared=False):
"""Returns a recipient :class:`Object`'s delivery target (endpoint).
"""Returns an :class:`Object`'s delivery target (endpoint).
To be implemented by subclasses.
@ -291,7 +287,8 @@ class Protocol:
* If obj has `source_protocol` `'web'`, returns its URL, as a
webmention target.
* If obj is an `'activitypub'` actor, returns its inbox.
* If obj is another `'activitypub'` object, returns `None`.
* If obj is an `'activitypub'` object, returns it's author's or actor's
inbox.
Args:
obj: :class:`Object`
@ -300,7 +297,7 @@ class Protocol:
multiple recipients for efficiency
Returns:
str target endpoint or `None`
str target endpoint, or `None` if not available.
"""
raise NotImplementedError()

Wyświetl plik

@ -1784,10 +1784,11 @@ class ActivityPubUtilsTest(TestCase):
self.assertEqual('@swentel@mas.to', user.readable_id)
self.assertEqual('@swentel@mas.to', user.readable_or_key_id())
def test_target_for(self):
def test_target_for_not_activitypub(self):
with self.assertRaises(AssertionError):
ActivityPub.target_for(Object(source_protocol='web'))
def test_target_for_actor(self):
self.assertEqual(ACTOR['inbox'], ActivityPub.target_for(
Object(source_protocol='ap', as2=ACTOR)))
@ -1810,3 +1811,30 @@ class ActivityPubUtilsTest(TestCase):
Object(source_protocol='ap', as2=actor)))
self.assertEqual('so-shared', ActivityPub.target_for(
Object(source_protocol='ap', as2=actor), shared=True))
def test_target_for_object(self):
obj = Object(as2=NOTE_OBJECT, source_protocol='ap')
self.assertIsNone(ActivityPub.target_for(obj))
Object(id=ACTOR['id'], as2=ACTOR).put()
obj.as2 = {
**NOTE_OBJECT,
'author': ACTOR['id'],
}
self.assertEqual('http://mas.to/inbox', ActivityPub.target_for(obj))
del obj.as2['author']
obj.as2['actor'] = copy.deepcopy(ACTOR)
obj.as2['actor']['url'] = [obj.as2['actor'].pop('id')]
self.assertEqual('http://mas.to/inbox', ActivityPub.target_for(obj))
@patch('requests.get')
def test_target_for_object_fetch(self, mock_get):
mock_get.return_value = self.as2_resp(ACTOR)
obj = Object(as2={
**NOTE_OBJECT,
'author': 'http://the/author',
}, source_protocol='ap')
self.assertEqual('http://mas.to/inbox', ActivityPub.target_for(obj))
mock_get.assert_has_calls([self.as2_req('http://the/author')])

2
web.py
Wyświetl plik

@ -253,7 +253,7 @@ class Web(User, Protocol):
@classmethod
def target_for(cls, obj, shared=False):
"""Returns `obj`'s id, as a URL webmention target."""
assert obj.source_protocol in (cls.LABEL, cls.ABBREV)
assert obj.source_protocol in (cls.LABEL, cls.ABBREV, 'ui', None)
if not util.is_web(obj.key.id()):
logger.warning(f"{obj.key} is source_protocol web but id isn't a URL!")