AP users: serve ActivityPub user page with address (handle) in URL

eg /activitypub/@me@instance.com

for #512
circle-datastore-transactions
Ryan Barrett 2023-06-01 22:00:47 -07:00
rodzic ca64793fff
commit e05ddb0a45
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
4 zmienionych plików z 53 dodań i 23 usunięć

Wyświetl plik

@ -61,6 +61,12 @@ class ActivityPub(User, Protocol):
def address(self):
return self.ap_address()
@classmethod
def get_by_id(cls, id):
"""Override User.get_by_id to fall back to querying by address."""
return (super(User, ActivityPub).get_by_id(id)
or ActivityPub.query(ActivityPub.address == id).get())
def label_id(self):
"""Returns this user's human-readable unique id, eg '@me@snarfed.org'."""
return self.address or self.key.id()

Wyświetl plik

@ -53,27 +53,27 @@ def web_user_redirects(**kwargs):
return redirect(f'/web/{path}', code=301)
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<regex("{DOMAIN_RE}"):domain>')
def user(protocol, domain):
g.user = PROTOCOLS[protocol].get_by_id(domain)
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<id>')
def user(protocol, id):
g.user = PROTOCOLS[protocol].get_by_id(id)
if not g.user or not g.user.direct:
return USER_NOT_FOUND_HTML, 404
elif g.user.key.id() != domain:
return redirect(f'/{protocol}/{g.user.key.id()}', code=301)
elif id != g.user.label_id():
return redirect(g.user.user_page_path(), code=301)
assert not g.user.use_instead
query = Object.query(
Object.domains == domain,
Object.domains == id,
Object.labels.IN(('notification', 'user')),
)
objects, before, after = fetch_objects(query)
followers = Follower.query(Follower.dest == domain, Follower.status == 'active')\
followers = Follower.query(Follower.dest == id, Follower.status == 'active')\
.count(limit=FOLLOWERS_UI_LIMIT)
followers = f'{followers}{"+" if followers == FOLLOWERS_UI_LIMIT else ""}'
following = Follower.query(Follower.src == domain, Follower.status == 'active')\
following = Follower.query(Follower.src == id, Follower.status == 'active')\
.count(limit=FOLLOWERS_UI_LIMIT)
following = f'{following}{"+" if following == FOLLOWERS_UI_LIMIT else ""}'
@ -88,13 +88,13 @@ def user(protocol, domain):
)
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<regex("{DOMAIN_RE}"):domain>/<any(followers,following):collection>')
def followers_or_following(protocol, domain, collection):
g.user = PROTOCOLS[protocol].get_by_id(domain) # g.user is used in template
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<id>/<any(followers,following):collection>')
def followers_or_following(protocol, id, collection):
g.user = PROTOCOLS[protocol].get_by_id(id) # g.user is used in template
if not g.user:
return USER_NOT_FOUND_HTML, 404
followers, before, after = Follower.fetch_page(domain, collection)
followers, before, after = Follower.fetch_page(id, collection)
for f in followers:
f.url = f.src if collection == 'followers' else f.dest
@ -113,27 +113,27 @@ def followers_or_following(protocol, domain, collection):
)
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<regex("{DOMAIN_RE}"):domain>/feed')
def feed(protocol, domain):
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<id>/feed')
def feed(protocol, id):
format = request.args.get('format', 'html')
if format not in ('html', 'atom', 'rss'):
error(f'format {format} not supported; expected html, atom, or rss')
g.user = PROTOCOLS[protocol].get_by_id(domain)
g.user = PROTOCOLS[protocol].get_by_id(id)
if not g.user:
return render_template('user_not_found.html', domain=domain), 404
return render_template('user_not_found.html', domain=id), 404
objects, _, _ = Object.query(
Object.domains == domain, Object.labels == 'feed') \
Object.domains == id, Object.labels == 'feed') \
.order(-Object.created) \
.fetch_page(PAGE_SIZE)
activities = [obj.as1 for obj in objects if not obj.deleted]
actor = {
'displayName': domain,
'displayName': id,
'url': g.user.web_url(),
}
title = f'Bridgy Fed feed for {domain}'
title = f'Bridgy Fed feed for {id}'
# TODO: inject/merge common.pretty_link into microformats2.render_content
# (specifically into hcard_to_html) somehow to convert Mastodon URLs to @-@

Wyświetl plik

@ -1576,3 +1576,13 @@ class ActivityPubUtilsTest(TestCase):
user.actor_as2 = ACTOR
self.assertEqual('@swentel@mas.to', user.label_id())
def test_get_by_id(self):
user = self.make_user('http://foo', cls=ActivityPub)
self.assert_entities_equal(user, ActivityPub.get_by_id('http://foo'))
self.assertIsNone(ActivityPub.get_by_id('@swentel@mas.to'))
user.actor_as2 = ACTOR
user.put()
self.assert_entities_equal(user, ActivityPub.get_by_id('http://foo'))
self.assert_entities_equal(user, ActivityPub.get_by_id('@swentel@mas.to'))

Wyświetl plik

@ -15,12 +15,17 @@ from oauth_dropins.webutil.testutil import requests_response
# import first so that Fake is defined before URL routes are registered
from .testutil import Fake, TestCase
from activitypub import ActivityPub
import common
from models import Object, Follower, User
from web import Web
from .test_web import ACTOR_AS2, ACTOR_HTML, ACTOR_MF2, REPOST_AS2
ACTOR_WITH_PREFERRED_USERNAME = {
**ACTOR,
'preferredUsername': 'me',
}
def contents(activities):
return [(a.get('object') or a)['content'] for a in activities]
@ -42,6 +47,18 @@ class PagesTest(TestCase):
got = self.client.get('/fake/foo.com')
self.assert_equals(200, got.status_code)
def test_user_activitypub_address(self):
user = self.make_user('foo', cls=ActivityPub,
actor_as2=ACTOR_WITH_PREFERRED_USERNAME)
self.assertEqual('@me@plus.google.com', user.address)
got = self.client.get('/activitypub/@me@plus.google.com')
self.assert_equals(200, got.status_code)
got = self.client.get('/activitypub/foo')
self.assert_equals(301, got.status_code)
self.assert_equals('/activitypub/@me@plus.google.com', got.headers['Location'])
def test_user_objects(self):
self.add_objects()
got = self.client.get('/web/user.com')
@ -117,10 +134,7 @@ class PagesTest(TestCase):
Follower.get_or_create('bar.com', 'https://no.stored/users/follow')
Follower.get_or_create('bar.com', 'https://masto/user', last_follow={
**FOLLOW_WITH_ACTOR,
'actor': {
**ACTOR,
'preferredUsername': 'me',
},
'actor': ACTOR_WITH_PREFERRED_USERNAME,
})
got = self.client.get('/web/bar.com/followers')
self.assert_equals(200, got.status_code)