kopia lustrzana https://github.com/snarfed/bridgy-fed
AP users: serve ActivityPub user page with address (handle) in URL
eg /activitypub/@me@instance.com for #512circle-datastore-transactions
rodzic
ca64793fff
commit
e05ddb0a45
|
@ -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()
|
||||
|
|
38
pages.py
38
pages.py
|
@ -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 @-@
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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)
|
||||
|
|
Ładowanie…
Reference in New Issue