kopia lustrzana https://github.com/snarfed/bridgy-fed
rodzic
d72be97d78
commit
eafba6d7cd
|
@ -7,7 +7,7 @@ import re
|
|||
from flask import request
|
||||
from google.cloud import ndb
|
||||
from google.cloud.ndb import OR
|
||||
from granary import as2
|
||||
from granary import as1, as2
|
||||
from oauth_dropins.webutil import flask_util, util
|
||||
from oauth_dropins.webutil.flask_util import error
|
||||
from oauth_dropins.webutil.util import json_dumps, json_loads
|
||||
|
@ -119,11 +119,14 @@ def inbox(domain=None):
|
|||
return accept_follow(activity, activity_unwrapped, user)
|
||||
|
||||
# send webmentions to each target
|
||||
activity_as2 = json_dumps(activity_unwrapped)
|
||||
activity_as1 = json_dumps(as2.to_as1(activity_unwrapped))
|
||||
activity_as2_str = json_dumps(activity_unwrapped)
|
||||
activity_as1 = as2.to_as1(activity_unwrapped)
|
||||
as1_type = as1.object_type(activity_as1)
|
||||
activity_as1_str = json_dumps(activity_as1)
|
||||
sent = common.send_webmentions(as2.to_as1(activity), proxy=True,
|
||||
source_protocol='activitypub',
|
||||
as2=activity_as2, as1=activity_as1)
|
||||
as2=activity_as2_str, as1=activity_as1_str,
|
||||
type=as1_type)
|
||||
|
||||
if not sent and type in ('Create', 'Announce'):
|
||||
# check that this activity is public. only do this check for Creates,
|
||||
|
@ -145,7 +148,8 @@ def inbox(domain=None):
|
|||
projection=[Follower.src]).fetch()]
|
||||
|
||||
key = Object(id=source, source_protocol='activitypub', domains=domains,
|
||||
status='complete', as2=activity_as2, as1=activity_as1).put()
|
||||
status='complete', as2=activity_as2_str, as1=activity_as1_str,
|
||||
type=as1_type).put()
|
||||
logging.info(f'Wrote Object {key} with {len(domains)} follower domains')
|
||||
|
||||
return ''
|
||||
|
@ -210,7 +214,8 @@ def accept_follow(follow, follow_unwrapped, user):
|
|||
# send webmention
|
||||
common.send_webmentions(as2.to_as1(follow), proxy=True, source_protocol='activitypub',
|
||||
as2=json_dumps(follow_unwrapped),
|
||||
as1=json_dumps(as2.to_as1(follow_unwrapped)))
|
||||
as1=json_dumps(as2.to_as1(follow_unwrapped)),
|
||||
type='follow')
|
||||
|
||||
return resp.text, resp.status_code
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import re
|
|||
import urllib.parse
|
||||
|
||||
from flask import request
|
||||
from granary import as2, microformats2
|
||||
from granary import as1, as2, microformats2
|
||||
from httpsig.requests_auth import HTTPSignatureAuth
|
||||
import mf2util
|
||||
from oauth_dropins.webutil import util, webmention
|
||||
|
|
|
@ -247,7 +247,7 @@ class Object(StringIdModel):
|
|||
# uses a blob.)
|
||||
as1 = ndb.TextProperty(required=True) # converted from source data
|
||||
as2 = ndb.TextProperty() # only one of the rest will be populated...
|
||||
bsky = ndb.TextProperty() # Bluesky / AT Protocol
|
||||
bsky = ndb.TextProperty() # Bluesky / AT Protocol
|
||||
mf2 = ndb.TextProperty() # HTML microformats2
|
||||
|
||||
type = ndb.StringProperty() # AS1 objectType, or verb if it's an activity
|
||||
|
|
|
@ -290,15 +290,16 @@ class ActivityPubTest(testutil.TestCase):
|
|||
self.assertEqual(400, got.status_code)
|
||||
|
||||
def test_inbox_reply_object(self, *mocks):
|
||||
self._test_inbox_reply(REPLY_OBJECT, REPLY_OBJECT, *mocks)
|
||||
self._test_inbox_reply(REPLY_OBJECT, REPLY_OBJECT, 'comment', *mocks)
|
||||
|
||||
def test_inbox_reply_object_wrapped(self, *mocks):
|
||||
self._test_inbox_reply(REPLY_OBJECT_WRAPPED, REPLY_OBJECT, *mocks)
|
||||
self._test_inbox_reply(REPLY_OBJECT_WRAPPED, REPLY_OBJECT, 'comment', *mocks)
|
||||
|
||||
def test_inbox_reply_create_activity(self, *mocks):
|
||||
self._test_inbox_reply(REPLY, REPLY, *mocks)
|
||||
self._test_inbox_reply(REPLY, REPLY, 'post', *mocks)
|
||||
|
||||
def _test_inbox_reply(self, reply, expected_as2, mock_head, mock_get, mock_post):
|
||||
def _test_inbox_reply(self, reply, expected_as2, expected_type,
|
||||
mock_head, mock_get, mock_post):
|
||||
mock_head.return_value = requests_response(url='http://or.ig/post')
|
||||
mock_get.return_value = requests_response(
|
||||
'<html><head><link rel="webmention" href="/webmention"></html>')
|
||||
|
@ -318,12 +319,13 @@ class ActivityPubTest(testutil.TestCase):
|
|||
},
|
||||
)
|
||||
|
||||
obj = Object.get_by_id('http://th.is/reply')
|
||||
self.assertEqual(['or.ig'], obj.domains)
|
||||
self.assertEqual('activitypub', obj.source_protocol)
|
||||
self.assertEqual('complete', obj.status)
|
||||
self.assertEqual(expected_as2, json_loads(obj.as2))
|
||||
self.assertEqual(as2.to_as1(expected_as2), json_loads(obj.as1))
|
||||
self.assert_object('http://th.is/reply',
|
||||
domains=['or.ig'],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
as2=expected_as2,
|
||||
as1=as2.to_as1(expected_as2),
|
||||
type=expected_type)
|
||||
|
||||
def test_inbox_reply_to_self_domain(self, mock_head, mock_get, mock_post):
|
||||
self._test_inbox_ignore_reply_to('http://localhost/th.is',
|
||||
|
@ -358,17 +360,18 @@ class ActivityPubTest(testutil.TestCase):
|
|||
with self.client:
|
||||
got = self.client.post('/foo.com/inbox', json=NOTE)
|
||||
self.assertEqual(200, got.status_code, got.get_data(as_text=True))
|
||||
|
||||
obj = Object.get_by_id('http://th.is/note/as2')
|
||||
self.assertEqual('activitypub', obj.source_protocol)
|
||||
self.assertEqual('complete', obj.status)
|
||||
expected_as2 = common.redirect_unwrap({
|
||||
**NOTE,
|
||||
'actor': ACTOR,
|
||||
})
|
||||
self.assertEqual(expected_as2, json_loads(obj.as2))
|
||||
self.assertEqual(as2.to_as1(expected_as2), json_loads(obj.as1))
|
||||
self.assert_equals(['foo.com', 'baz.com'], obj.domains)
|
||||
|
||||
self.assert_object('http://th.is/note/as2',
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
as2=expected_as2,
|
||||
as1=as2.to_as1(expected_as2),
|
||||
domains=['foo.com', 'baz.com'],
|
||||
type='post')
|
||||
|
||||
def test_inbox_not_public(self, mock_head, mock_get, mock_post):
|
||||
Follower.get_or_create(ACTOR['id'], 'foo.com')
|
||||
|
@ -385,12 +388,12 @@ class ActivityPubTest(testutil.TestCase):
|
|||
self.assertEqual(0, Object.query().count())
|
||||
|
||||
def test_inbox_mention_object(self, *mocks):
|
||||
self._test_inbox_mention(MENTION_OBJECT, *mocks)
|
||||
self._test_inbox_mention(MENTION_OBJECT, 'note', *mocks)
|
||||
|
||||
def test_inbox_mention_create_activity(self, *mocks):
|
||||
self._test_inbox_mention(MENTION, *mocks)
|
||||
self._test_inbox_mention(MENTION, 'post', *mocks)
|
||||
|
||||
def _test_inbox_mention(self, mention, mock_head, mock_get, mock_post):
|
||||
def _test_inbox_mention(self, mention, expected_type, mock_head, mock_get, mock_post):
|
||||
mock_head.return_value = requests_response(url='http://tar.get')
|
||||
mock_get.return_value = requests_response(
|
||||
'<html><head><link rel="webmention" href="/webmention"></html>')
|
||||
|
@ -411,14 +414,14 @@ class ActivityPubTest(testutil.TestCase):
|
|||
},
|
||||
)
|
||||
|
||||
obj = Object.get_by_id('http://th.is/mention')
|
||||
self.assertEqual(['tar.get'], obj.domains)
|
||||
self.assertEqual('activitypub', obj.source_protocol)
|
||||
self.assertEqual('complete', obj.status)
|
||||
|
||||
expected_as2 = common.redirect_unwrap(mention)
|
||||
self.assertEqual(expected_as2, json_loads(obj.as2))
|
||||
self.assertEqual(as2.to_as1(expected_as2), json_loads(obj.as1))
|
||||
self.assert_object('http://th.is/mention',
|
||||
domains=['tar.get'],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
as2=expected_as2,
|
||||
as1=as2.to_as1(expected_as2),
|
||||
type=expected_type) # not mention (?)
|
||||
|
||||
def test_inbox_like(self, mock_head, mock_get, mock_post):
|
||||
mock_head.return_value = requests_response(url='http://or.ig/post')
|
||||
|
@ -447,23 +450,28 @@ class ActivityPubTest(testutil.TestCase):
|
|||
'target': 'http://or.ig/post',
|
||||
}, kwargs['data'])
|
||||
|
||||
obj = Object.get_by_id('http://th.is/like#ok')
|
||||
self.assertEqual(['or.ig'], obj.domains)
|
||||
self.assertEqual('activitypub', obj.source_protocol)
|
||||
self.assertEqual('complete', obj.status)
|
||||
self.assertEqual(LIKE_WITH_ACTOR, json_loads(obj.as2))
|
||||
self.assertEqual(as2.to_as1(LIKE_WITH_ACTOR), json_loads(obj.as1))
|
||||
self.assert_object('http://th.is/like#ok',
|
||||
domains=['or.ig'],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
as2=LIKE_WITH_ACTOR,
|
||||
as1=as2.to_as1(LIKE_WITH_ACTOR),
|
||||
type='like')
|
||||
|
||||
def test_inbox_follow_accept_with_id(self, mock_head, mock_get, mock_post):
|
||||
self._test_inbox_follow_accept(FOLLOW_WRAPPED, ACCEPT,
|
||||
mock_head, mock_get, mock_post)
|
||||
|
||||
obj = Object.query().get()
|
||||
follow = copy.deepcopy(FOLLOW_WITH_ACTOR)
|
||||
follow['url'] = 'https://mastodon.social/users/swentel#followed-https://www.realize.be/'
|
||||
|
||||
self.assertEqual(follow, json_loads(obj.as2))
|
||||
self.assertEqual(as2.to_as1(follow), json_loads(obj.as1))
|
||||
self.assert_object('https://mastodon.social/6d1a',
|
||||
domains=['www.realize.be'],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
as2=follow,
|
||||
as1=as2.to_as1(follow),
|
||||
type='follow')
|
||||
|
||||
follower = Follower.query().get()
|
||||
self.assertEqual(FOLLOW_WRAPPED_WITH_ACTOR, json_loads(follower.last_follow))
|
||||
|
@ -493,14 +501,18 @@ class ActivityPubTest(testutil.TestCase):
|
|||
})
|
||||
self.assertEqual(follow, json_loads(follower.last_follow))
|
||||
|
||||
obj = Object.query().get()
|
||||
follow.update({
|
||||
'actor': FOLLOW_WITH_ACTOR['actor'],
|
||||
'object': unwrapped_user,
|
||||
'url': 'https://mastodon.social/users/swentel#followed-https://www.realize.be/',
|
||||
})
|
||||
self.assertEqual(follow, json_loads(obj.as2))
|
||||
self.assertEqual(as2.to_as1(follow), json_loads(obj.as1))
|
||||
self.assert_object('https://mastodon.social/6d1a',
|
||||
domains=['www.realize.be'],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
as2=follow,
|
||||
as1=as2.to_as1(follow),
|
||||
type='follow')
|
||||
|
||||
def _test_inbox_follow_accept(self, follow_as2, accept_as2,
|
||||
mock_head, mock_get, mock_post):
|
||||
|
@ -535,11 +547,6 @@ class ActivityPubTest(testutil.TestCase):
|
|||
'target': 'https://www.realize.be/',
|
||||
}, kwargs['data'])
|
||||
|
||||
obj = Object.get_by_id('https://mastodon.social/6d1a')
|
||||
self.assertEqual(['www.realize.be'], obj.domains)
|
||||
self.assertEqual('activitypub', obj.source_protocol)
|
||||
self.assertEqual('complete', obj.status)
|
||||
|
||||
# check that we stored a Follower object
|
||||
follower = Follower.get_by_id(f'www.realize.be {FOLLOW["actor"]}')
|
||||
self.assertEqual('active', follower.status)
|
||||
|
@ -709,10 +716,13 @@ class ActivityPubTest(testutil.TestCase):
|
|||
got = self.client.post('/foo.com/inbox', json=LIKE)
|
||||
self.assertEqual(200, got.status_code)
|
||||
|
||||
obj = Object.get_by_id('http://th.is/like#ok')
|
||||
self.assertEqual(['or.ig'], obj.domains)
|
||||
self.assertEqual('activitypub', obj.source_protocol)
|
||||
self.assertEqual('ignored', obj.status)
|
||||
self.assert_object('http://th.is/like#ok',
|
||||
domains=['or.ig'],
|
||||
source_protocol='activitypub',
|
||||
status='ignored',
|
||||
as2=LIKE_WITH_ACTOR,
|
||||
as1=as2.to_as1(LIKE_WITH_ACTOR),
|
||||
type='like')
|
||||
|
||||
def test_followers_collection_unknown_user(self, *args):
|
||||
resp = self.client.get('/foo.com/followers')
|
||||
|
|
|
@ -340,21 +340,6 @@ class WebmentionTest(testutil.TestCase):
|
|||
self.author = requests_response(ACTOR_HTML, url='https://orig/',
|
||||
content_type=CONTENT_TYPE_HTML)
|
||||
|
||||
def assert_object(self, id, **props):
|
||||
got = Object.get_by_id(id)
|
||||
assert got, id
|
||||
|
||||
# sort keys in JSON properties
|
||||
for prop in 'as1', 'as2', 'bsky', 'mf2':
|
||||
if prop in props:
|
||||
props[prop] = json_dumps(json_loads(props[prop]), sort_keys=True)
|
||||
got_val = getattr(got, prop, None)
|
||||
if got_val:
|
||||
setattr(got, prop, json_dumps(json_loads(got_val), sort_keys=True))
|
||||
|
||||
self.assert_entities_equal(Object(id=id, **props), got,
|
||||
ignore=['created', 'updated'])
|
||||
|
||||
def assert_deliveries(self, mock_post, inboxes, data):
|
||||
self.assertEqual(len(inboxes), len(mock_post.call_args_list))
|
||||
calls = {call[0][0]: call for call in mock_post.call_args_list}
|
||||
|
@ -509,10 +494,9 @@ class WebmentionTest(testutil.TestCase):
|
|||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
ap_delivered=['https://foo.com/inbox'],
|
||||
ap_undelivered=[],
|
||||
ap_failed=[],
|
||||
mf2=json_dumps(self.reply_mf2),
|
||||
as1=json_dumps(self.reply_as1),
|
||||
mf2=self.reply_mf2,
|
||||
as1=self.reply_as1,
|
||||
type='comment',
|
||||
)
|
||||
|
||||
def test_update_reply(self, mock_get, mock_post):
|
||||
|
@ -631,9 +615,10 @@ class WebmentionTest(testutil.TestCase):
|
|||
domains=['a'],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
mf2=json_dumps(self.repost_mf2),
|
||||
as1=json_dumps(self.repost_as1),
|
||||
mf2=self.repost_mf2,
|
||||
as1=self.repost_as1,
|
||||
ap_delivered=['https://foo.com/inbox'],
|
||||
type='share',
|
||||
)
|
||||
|
||||
def test_link_rel_alternate_as2(self, mock_get, mock_post):
|
||||
|
@ -807,11 +792,13 @@ class WebmentionTest(testutil.TestCase):
|
|||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
#(different_create_mf2 if inbox == 'https://updated/inbox' else
|
||||
mf2=json_dumps(self.create_mf2),
|
||||
as1=json_dumps(self.create_as1),
|
||||
mf2=self.create_mf2,
|
||||
as1=self.create_as1,
|
||||
ap_delivered=inboxes,
|
||||
type='note',
|
||||
)
|
||||
#(different_create_as1 if inbox == 'https://updated/inbox' else
|
||||
|
||||
def test_create_with_image(self, mock_get, mock_post):
|
||||
create_html = self.create_html.replace(
|
||||
'</body>', '<img class="u-photo" src="http://im/age" />\n</body>')
|
||||
|
@ -868,9 +855,10 @@ class WebmentionTest(testutil.TestCase):
|
|||
domains=['a'],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
mf2=json_dumps(self.follow_mf2),
|
||||
as1=json_dumps(self.follow_as1),
|
||||
mf2=self.follow_mf2,
|
||||
as1=self.follow_as1,
|
||||
ap_delivered=['https://foo.com/inbox'],
|
||||
type='follow',
|
||||
)
|
||||
|
||||
followers = Follower.query().fetch()
|
||||
|
@ -932,9 +920,10 @@ class WebmentionTest(testutil.TestCase):
|
|||
domains=['a'],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
mf2=json_dumps(self.follow_fragment_mf2),
|
||||
as1=json_dumps(self.follow_fragment_as1),
|
||||
mf2=self.follow_fragment_mf2,
|
||||
as1=self.follow_fragment_as1,
|
||||
ap_delivered=['https://foo.com/inbox'],
|
||||
type='follow',
|
||||
)
|
||||
|
||||
followers = Follower.query().fetch()
|
||||
|
@ -986,9 +975,10 @@ class WebmentionTest(testutil.TestCase):
|
|||
domains=['a'],
|
||||
source_protocol='activitypub',
|
||||
status='failed',
|
||||
mf2=json_dumps(self.follow_mf2),
|
||||
as1=json_dumps(self.follow_as1),
|
||||
mf2=self.follow_mf2,
|
||||
as1=self.follow_as1,
|
||||
ap_failed=['https://foo.com/inbox'],
|
||||
type='follow',
|
||||
)
|
||||
|
||||
def test_repost_blocklisted_error(self, mock_get, mock_post):
|
||||
|
@ -1069,7 +1059,8 @@ class WebmentionTest(testutil.TestCase):
|
|||
domains=['orig'],
|
||||
source_protocol='activitypub',
|
||||
status='complete',
|
||||
mf2=json_dumps(ACTOR_MF2),
|
||||
as1=json_dumps(expected_as1),
|
||||
mf2=ACTOR_MF2,
|
||||
as1=expected_as1,
|
||||
ap_delivered=['https://inbox', 'https://shared/inbox'],
|
||||
type='update',
|
||||
)
|
||||
|
|
|
@ -9,10 +9,12 @@ from granary import as2
|
|||
from oauth_dropins.webutil import testutil, util
|
||||
from oauth_dropins.webutil.appengine_config import ndb_client
|
||||
from oauth_dropins.webutil.testutil import requests_response
|
||||
from oauth_dropins.webutil.util import json_dumps, json_loads
|
||||
import requests
|
||||
|
||||
from app import app, cache
|
||||
import common
|
||||
from models import Object
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase, testutil.Asserts):
|
||||
|
@ -58,7 +60,6 @@ class TestCase(unittest.TestCase, testutil.Asserts):
|
|||
def as2_resp(self, obj):
|
||||
return requests_response(obj, content_type=as2.CONTENT_TYPE)
|
||||
|
||||
|
||||
def assert_req(self, mock, url, **kwargs):
|
||||
"""Checks a mock requests call."""
|
||||
kwargs.setdefault('headers', {}).setdefault(
|
||||
|
@ -66,3 +67,19 @@ class TestCase(unittest.TestCase, testutil.Asserts):
|
|||
kwargs.setdefault('stream', True)
|
||||
kwargs.setdefault('timeout', util.HTTP_TIMEOUT)
|
||||
mock.assert_any_call(url, **kwargs)
|
||||
|
||||
def assert_object(self, id, **props):
|
||||
got = Object.get_by_id(id)
|
||||
assert got, id
|
||||
|
||||
# sort keys in JSON properties
|
||||
for prop in 'as1', 'as2', 'bsky', 'mf2':
|
||||
if prop in props:
|
||||
props[prop] = json_dumps(props[prop], sort_keys=True)
|
||||
got_val = getattr(got, prop, None)
|
||||
if got_val:
|
||||
setattr(got, prop, json_dumps(json_loads(got_val), sort_keys=True))
|
||||
|
||||
self.assert_entities_equal(Object(id=id, **props), got,
|
||||
ignore=['created', 'updated'])
|
||||
|
||||
|
|
|
@ -149,13 +149,16 @@ class Webmention(View):
|
|||
as1=json_dumps(self.source_as1),
|
||||
ap_undelivered=list(inboxes_to_targets.keys()),
|
||||
ap_delivered=[],
|
||||
ap_failed=[])
|
||||
ap_failed=[],
|
||||
type=as1.object_type(self.source_as1))
|
||||
|
||||
if (obj.status == 'complete' and
|
||||
not as1.activity_changed(json_loads(obj.as1), self.source_as1)):
|
||||
logger.info(f'Skipping; new content is same as content published before at {obj.updated}')
|
||||
return 'OK'
|
||||
|
||||
obj.put()
|
||||
|
||||
# TODO: collect by inbox, add 'to' fields, de-dupe inboxes and recipients
|
||||
#
|
||||
# make copy of ap_undelivered because we modify it below
|
||||
|
|
Ładowanie…
Reference in New Issue