kopia lustrzana https://github.com/snarfed/bridgy-fed
rodzic
ea1e2777e8
commit
374354a21f
|
@ -100,6 +100,25 @@ class ActivityPub(User, Protocol):
|
||||||
"""
|
"""
|
||||||
return None if util.is_web(id) else False
|
return None if util.is_web(id) else False
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
|
if obj.type not in as1.ACTOR_TYPES:
|
||||||
|
logger.info(f'{obj.key} type {type} is not an actor')
|
||||||
|
|
||||||
|
actor = obj.as2 or as2.from_as1(obj.as1)
|
||||||
|
if not actor:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if shared:
|
||||||
|
shared_inbox = actor.get('endpoints', {}).get('sharedInbox')
|
||||||
|
if shared_inbox:
|
||||||
|
return shared_inbox
|
||||||
|
|
||||||
|
return actor.get('inbox') or actor.get('publicInbox')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def send(cls, obj, url, log_data=True):
|
def send(cls, obj, url, log_data=True):
|
||||||
"""Delivers an activity to an inbox URL."""
|
"""Delivers an activity to an inbox URL."""
|
||||||
|
|
24
protocol.py
24
protocol.py
|
@ -279,6 +279,30 @@ class Protocol:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def target_for(cls, obj, shared=False):
|
||||||
|
"""Returns a recipient :class:`Object`'s delivery target (endpoint).
|
||||||
|
|
||||||
|
To be implemented by subclasses.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
* 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`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj: :class:`Object`
|
||||||
|
shared: boolean, optional. If `True`, returns a common/shared
|
||||||
|
endpoint, eg ActivityPub's `sharedInbox`, that can be reused for
|
||||||
|
multiple recipients for efficiency
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str target endpoint or `None`
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def receive(from_cls, id, **props):
|
def receive(from_cls, id, **props):
|
||||||
"""Handles an incoming activity.
|
"""Handles an incoming activity.
|
||||||
|
|
|
@ -24,7 +24,7 @@ from werkzeug.exceptions import BadGateway
|
||||||
from .testutil import Fake, TestCase
|
from .testutil import Fake, TestCase
|
||||||
|
|
||||||
import activitypub
|
import activitypub
|
||||||
from activitypub import ActivityPub
|
from activitypub import ActivityPub, postprocess_as2
|
||||||
import common
|
import common
|
||||||
import models
|
import models
|
||||||
from models import Follower, Object
|
from models import Follower, Object
|
||||||
|
@ -1434,7 +1434,7 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
'id': 'http://localhost/r/xyz',
|
'id': 'http://localhost/r/xyz',
|
||||||
'inReplyTo': 'foo',
|
'inReplyTo': 'foo',
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
}, activitypub.postprocess_as2({
|
}, postprocess_as2({
|
||||||
'id': 'xyz',
|
'id': 'xyz',
|
||||||
'inReplyTo': ['foo', 'bar'],
|
'inReplyTo': ['foo', 'bar'],
|
||||||
}))
|
}))
|
||||||
|
@ -1444,7 +1444,7 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
'id': 'http://localhost/r/xyz',
|
'id': 'http://localhost/r/xyz',
|
||||||
'url': ['http://localhost/r/foo', 'http://localhost/r/bar'],
|
'url': ['http://localhost/r/foo', 'http://localhost/r/bar'],
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
}, activitypub.postprocess_as2({
|
}, postprocess_as2({
|
||||||
'id': 'xyz',
|
'id': 'xyz',
|
||||||
'url': ['foo', 'bar'],
|
'url': ['foo', 'bar'],
|
||||||
}))
|
}))
|
||||||
|
@ -1455,7 +1455,7 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
'attachment': [{'url': 'http://r/foo'}, {'url': 'http://r/bar'}],
|
'attachment': [{'url': 'http://r/foo'}, {'url': 'http://r/bar'}],
|
||||||
'image': [{'url': 'http://r/foo'}, {'url': 'http://r/bar'}],
|
'image': [{'url': 'http://r/foo'}, {'url': 'http://r/bar'}],
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
}, activitypub.postprocess_as2({
|
}, postprocess_as2({
|
||||||
'id': 'xyz',
|
'id': 'xyz',
|
||||||
'image': [{'url': 'http://r/foo'}, {'url': 'http://r/bar'}],
|
'image': [{'url': 'http://r/foo'}, {'url': 'http://r/bar'}],
|
||||||
}))
|
}))
|
||||||
|
@ -1478,7 +1478,7 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
'url': 'http://localhost/r/site',
|
'url': 'http://localhost/r/site',
|
||||||
}],
|
}],
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
}, activitypub.postprocess_as2({
|
}, postprocess_as2({
|
||||||
'attributedTo': [{'id': 'bar'}, {'id': 'baz'}],
|
'attributedTo': [{'id': 'bar'}, {'id': 'baz'}],
|
||||||
'actor': {'id': 'baj'},
|
'actor': {'id': 'baj'},
|
||||||
}))
|
}))
|
||||||
|
@ -1488,7 +1488,7 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
'id': 'http://localhost/r/xyz',
|
'id': 'http://localhost/r/xyz',
|
||||||
'type': 'Note',
|
'type': 'Note',
|
||||||
'to': [as2.PUBLIC_AUDIENCE],
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
}, activitypub.postprocess_as2({
|
}, postprocess_as2({
|
||||||
'id': 'xyz',
|
'id': 'xyz',
|
||||||
'type': 'Note',
|
'type': 'Note',
|
||||||
}))
|
}))
|
||||||
|
@ -1502,7 +1502,7 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
{'type': 'Mention', 'href': 'foo'},
|
{'type': 'Mention', 'href': 'foo'},
|
||||||
],
|
],
|
||||||
'to': ['https://www.w3.org/ns/activitystreams#Public'],
|
'to': ['https://www.w3.org/ns/activitystreams#Public'],
|
||||||
}, activitypub.postprocess_as2({
|
}, postprocess_as2({
|
||||||
'tag': [
|
'tag': [
|
||||||
{'name': 'bar', 'href': 'bar'},
|
{'name': 'bar', 'href': 'bar'},
|
||||||
{'type': 'Tag','name': '#baz'},
|
{'type': 'Tag','name': '#baz'},
|
||||||
|
@ -1512,7 +1512,7 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
}))
|
}))
|
||||||
|
|
||||||
def test_postprocess_as2_url_attachments(self):
|
def test_postprocess_as2_url_attachments(self):
|
||||||
got = activitypub.postprocess_as2(as2.from_as1({
|
got = postprocess_as2(as2.from_as1({
|
||||||
'objectType': 'person',
|
'objectType': 'person',
|
||||||
'urls': [
|
'urls': [
|
||||||
{
|
{
|
||||||
|
@ -1553,7 +1553,7 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
# preferredUsername stays y.z despite user's username. since Mastodon
|
# preferredUsername stays y.z despite user's username. since Mastodon
|
||||||
# queries Webfinger for preferredUsername@fed.brid.gy
|
# queries Webfinger for preferredUsername@fed.brid.gy
|
||||||
# https://github.com/snarfed/bridgy-fed/issues/77#issuecomment-949955109
|
# https://github.com/snarfed/bridgy-fed/issues/77#issuecomment-949955109
|
||||||
self.assertEqual('user.com', activitypub.postprocess_as2({
|
self.assertEqual('user.com', postprocess_as2({
|
||||||
'type': 'Person',
|
'type': 'Person',
|
||||||
'url': 'https://user.com/about-me',
|
'url': 'https://user.com/about-me',
|
||||||
'preferredUsername': 'nick',
|
'preferredUsername': 'nick',
|
||||||
|
@ -1755,10 +1755,9 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
):
|
):
|
||||||
with self.subTest(obj=obj):
|
with self.subTest(obj=obj):
|
||||||
obj = copy.deepcopy(obj)
|
obj = copy.deepcopy(obj)
|
||||||
self.assert_equals(
|
self.assert_equals(postprocess_as2(obj),
|
||||||
activitypub.postprocess_as2(obj),
|
postprocess_as2(postprocess_as2(obj)),
|
||||||
activitypub.postprocess_as2(activitypub.postprocess_as2(obj)),
|
ignore=['to'])
|
||||||
ignore=['to'])
|
|
||||||
|
|
||||||
def test_ap_address(self):
|
def test_ap_address(self):
|
||||||
user = ActivityPub(obj=Object(id='a', as2={**ACTOR, 'preferredUsername': 'me'}))
|
user = ActivityPub(obj=Object(id='a', as2={**ACTOR, 'preferredUsername': 'me'}))
|
||||||
|
@ -1795,3 +1794,30 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
user.obj = Object(id='a', as2=ACTOR)
|
user.obj = Object(id='a', as2=ACTOR)
|
||||||
self.assertEqual('@swentel@mas.to', user.readable_id)
|
self.assertEqual('@swentel@mas.to', user.readable_id)
|
||||||
self.assertEqual('@swentel@mas.to', user.readable_or_key_id())
|
self.assertEqual('@swentel@mas.to', user.readable_or_key_id())
|
||||||
|
|
||||||
|
def test_target_for(self):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
ActivityPub.target_for(Object(source_protocol='web'))
|
||||||
|
|
||||||
|
self.assertEqual(ACTOR['inbox'], ActivityPub.target_for(
|
||||||
|
Object(source_protocol='ap', as2=ACTOR)))
|
||||||
|
|
||||||
|
actor = copy.deepcopy(ACTOR)
|
||||||
|
del actor['inbox']
|
||||||
|
self.assertIsNone(ActivityPub.target_for(
|
||||||
|
Object(source_protocol='ap', as2=actor)))
|
||||||
|
|
||||||
|
actor['publicInbox'] = 'so-public'
|
||||||
|
self.assertEqual('so-public', ActivityPub.target_for(
|
||||||
|
Object(source_protocol='ap', as2=actor)))
|
||||||
|
|
||||||
|
# sharedInbox
|
||||||
|
self.assertEqual('so-public', ActivityPub.target_for(
|
||||||
|
Object(source_protocol='ap', as2=actor), shared=True))
|
||||||
|
actor['endpoints'] = {
|
||||||
|
'sharedInbox': 'so-shared',
|
||||||
|
}
|
||||||
|
self.assertEqual('so-public', ActivityPub.target_for(
|
||||||
|
Object(source_protocol='ap', as2=actor)))
|
||||||
|
self.assertEqual('so-shared', ActivityPub.target_for(
|
||||||
|
Object(source_protocol='ap', as2=actor), shared=True))
|
||||||
|
|
|
@ -1852,3 +1852,14 @@ class WebProtocolTest(TestCase):
|
||||||
</html>
|
</html>
|
||||||
""", html, ignore_blanks=True)
|
""", html, ignore_blanks=True)
|
||||||
self.assertEqual({'Content-Type': 'text/html; charset=utf-8'}, headers)
|
self.assertEqual({'Content-Type': 'text/html; charset=utf-8'}, headers)
|
||||||
|
|
||||||
|
def test_target_for(self, _, __):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
Web.target_for(Object(id='x', source_protocol='ap'))
|
||||||
|
|
||||||
|
self.assertIsNone(Web.target_for(Object(id='x', source_protocol='web')))
|
||||||
|
|
||||||
|
self.assertEqual('http://foo', Web.target_for(
|
||||||
|
Object(id='http://foo', source_protocol='web')))
|
||||||
|
self.assertEqual('http://foo', Web.target_for(
|
||||||
|
Object(id='http://foo', source_protocol='web'), shared=True))
|
||||||
|
|
11
web.py
11
web.py
|
@ -252,6 +252,17 @@ class Web(User, Protocol):
|
||||||
|
|
||||||
return None if util.is_web(id) else False
|
return None if util.is_web(id) else False
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
|
if not util.is_web(obj.key.id()):
|
||||||
|
logger.warning(f"{obj.key} is source_protocol web but id isn't a URL!")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return obj.key.id()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def send(cls, obj, url):
|
def send(cls, obj, url):
|
||||||
"""Sends a webmention to a given target URL.
|
"""Sends a webmention to a given target URL.
|
||||||
|
|
Ładowanie…
Reference in New Issue