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):
|
def address(self):
|
||||||
return self.ap_address()
|
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):
|
def label_id(self):
|
||||||
"""Returns this user's human-readable unique id, eg '@me@snarfed.org'."""
|
"""Returns this user's human-readable unique id, eg '@me@snarfed.org'."""
|
||||||
return self.address or self.key.id()
|
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)
|
return redirect(f'/web/{path}', code=301)
|
||||||
|
|
||||||
|
|
||||||
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<regex("{DOMAIN_RE}"):domain>')
|
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<id>')
|
||||||
def user(protocol, domain):
|
def user(protocol, id):
|
||||||
g.user = PROTOCOLS[protocol].get_by_id(domain)
|
g.user = PROTOCOLS[protocol].get_by_id(id)
|
||||||
if not g.user or not g.user.direct:
|
if not g.user or not g.user.direct:
|
||||||
return USER_NOT_FOUND_HTML, 404
|
return USER_NOT_FOUND_HTML, 404
|
||||||
elif g.user.key.id() != domain:
|
elif id != g.user.label_id():
|
||||||
return redirect(f'/{protocol}/{g.user.key.id()}', code=301)
|
return redirect(g.user.user_page_path(), code=301)
|
||||||
|
|
||||||
assert not g.user.use_instead
|
assert not g.user.use_instead
|
||||||
|
|
||||||
query = Object.query(
|
query = Object.query(
|
||||||
Object.domains == domain,
|
Object.domains == id,
|
||||||
Object.labels.IN(('notification', 'user')),
|
Object.labels.IN(('notification', 'user')),
|
||||||
)
|
)
|
||||||
objects, before, after = fetch_objects(query)
|
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)
|
.count(limit=FOLLOWERS_UI_LIMIT)
|
||||||
followers = f'{followers}{"+" if followers == FOLLOWERS_UI_LIMIT else ""}'
|
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)
|
.count(limit=FOLLOWERS_UI_LIMIT)
|
||||||
following = f'{following}{"+" if following == FOLLOWERS_UI_LIMIT else ""}'
|
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>')
|
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<id>/<any(followers,following):collection>')
|
||||||
def followers_or_following(protocol, domain, collection):
|
def followers_or_following(protocol, id, collection):
|
||||||
g.user = PROTOCOLS[protocol].get_by_id(domain) # g.user is used in template
|
g.user = PROTOCOLS[protocol].get_by_id(id) # g.user is used in template
|
||||||
if not g.user:
|
if not g.user:
|
||||||
return USER_NOT_FOUND_HTML, 404
|
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:
|
for f in followers:
|
||||||
f.url = f.src if collection == 'followers' else f.dest
|
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')
|
@app.get(f'/<any({",".join(PROTOCOLS)}):protocol>/<id>/feed')
|
||||||
def feed(protocol, domain):
|
def feed(protocol, id):
|
||||||
format = request.args.get('format', 'html')
|
format = request.args.get('format', 'html')
|
||||||
if format not in ('html', 'atom', 'rss'):
|
if format not in ('html', 'atom', 'rss'):
|
||||||
error(f'format {format} not supported; expected html, atom, or 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:
|
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(
|
objects, _, _ = Object.query(
|
||||||
Object.domains == domain, Object.labels == 'feed') \
|
Object.domains == id, Object.labels == 'feed') \
|
||||||
.order(-Object.created) \
|
.order(-Object.created) \
|
||||||
.fetch_page(PAGE_SIZE)
|
.fetch_page(PAGE_SIZE)
|
||||||
activities = [obj.as1 for obj in objects if not obj.deleted]
|
activities = [obj.as1 for obj in objects if not obj.deleted]
|
||||||
|
|
||||||
actor = {
|
actor = {
|
||||||
'displayName': domain,
|
'displayName': id,
|
||||||
'url': g.user.web_url(),
|
'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
|
# TODO: inject/merge common.pretty_link into microformats2.render_content
|
||||||
# (specifically into hcard_to_html) somehow to convert Mastodon URLs to @-@
|
# (specifically into hcard_to_html) somehow to convert Mastodon URLs to @-@
|
||||||
|
|
|
@ -1576,3 +1576,13 @@ class ActivityPubUtilsTest(TestCase):
|
||||||
|
|
||||||
user.actor_as2 = ACTOR
|
user.actor_as2 = ACTOR
|
||||||
self.assertEqual('@swentel@mas.to', user.label_id())
|
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
|
# import first so that Fake is defined before URL routes are registered
|
||||||
from .testutil import Fake, TestCase
|
from .testutil import Fake, TestCase
|
||||||
|
|
||||||
|
from activitypub import ActivityPub
|
||||||
import common
|
import common
|
||||||
from models import Object, Follower, User
|
from models import Object, Follower, User
|
||||||
from web import Web
|
from web import Web
|
||||||
|
|
||||||
from .test_web import ACTOR_AS2, ACTOR_HTML, ACTOR_MF2, REPOST_AS2
|
from .test_web import ACTOR_AS2, ACTOR_HTML, ACTOR_MF2, REPOST_AS2
|
||||||
|
|
||||||
|
ACTOR_WITH_PREFERRED_USERNAME = {
|
||||||
|
**ACTOR,
|
||||||
|
'preferredUsername': 'me',
|
||||||
|
}
|
||||||
|
|
||||||
def contents(activities):
|
def contents(activities):
|
||||||
return [(a.get('object') or a)['content'] for a in 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')
|
got = self.client.get('/fake/foo.com')
|
||||||
self.assert_equals(200, got.status_code)
|
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):
|
def test_user_objects(self):
|
||||||
self.add_objects()
|
self.add_objects()
|
||||||
got = self.client.get('/web/user.com')
|
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://no.stored/users/follow')
|
||||||
Follower.get_or_create('bar.com', 'https://masto/user', last_follow={
|
Follower.get_or_create('bar.com', 'https://masto/user', last_follow={
|
||||||
**FOLLOW_WITH_ACTOR,
|
**FOLLOW_WITH_ACTOR,
|
||||||
'actor': {
|
'actor': ACTOR_WITH_PREFERRED_USERNAME,
|
||||||
**ACTOR,
|
|
||||||
'preferredUsername': 'me',
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
got = self.client.get('/web/bar.com/followers')
|
got = self.client.get('/web/bar.com/followers')
|
||||||
self.assert_equals(200, got.status_code)
|
self.assert_equals(200, got.status_code)
|
||||||
|
|
Ładowanie…
Reference in New Issue