Activity => Object: populate Object.type

#286
activity-redesign
Ryan Barrett 2023-01-29 14:13:58 -08:00
rodzic d72be97d78
commit eafba6d7cd
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
7 zmienionych plików z 115 dodań i 89 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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')

Wyświetl plik

@ -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',
)

Wyświetl plik

@ -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'])

Wyświetl plik

@ -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