diff --git a/activitypub.py b/activitypub.py
index dac7a51..81d43dc 100644
--- a/activitypub.py
+++ b/activitypub.py
@@ -6,6 +6,7 @@ import logging
from urllib.parse import quote_plus
from flask import abort, g, request
+from google.cloud import ndb
from granary import as1, as2
from httpsig import HeaderVerifier
from httpsig.requests_auth import HTTPSignatureAuth
@@ -51,9 +52,19 @@ class ActivityPub(User, Protocol):
"""ActivityPub protocol class.
Key id is AP/AS2 actor id URL. (*Not* fediverse/WebFinger @-@ handle!)
+
+ Includes extra `address` computed property for querying entities by handle.
"""
LABEL = 'activitypub'
+ @ndb.ComputedProperty
+ def address(self):
+ return self.ap_address()
+
+ def label_id(self):
+ """Returns this user's human-readable unique id, eg '@me@snarfed.org'."""
+ return self.address or self.key.id()
+
def web_url(self):
"""Returns this user's web URL aka web_url, eg 'https://foo.com/'."""
return util.get_url(self.actor_as2) or self.ap_actor()
diff --git a/models.py b/models.py
index 97ef23e..a5c6ce7 100644
--- a/models.py
+++ b/models.py
@@ -174,11 +174,26 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
if self.actor_as2:
return as2.to_as1(self.actor_as2)
+ def name(self):
+ """Returns this user's human-readable name, eg 'Ryan Barrett'."""
+ if self.actor_as2:
+ name = self.actor_as2.get('name')
+ if name:
+ return name
+
+ return self.label_id()
+
+ def label_id(self):
+ """Returns this user's human-readable unique id, eg '@me@snarfed.org'."""
+ return self.key.id()
+
def username(self):
"""Returns the user's preferred username.
Uses stored representative h-card if available, falls back to id.
+ TODO(#512): move to Web
+
Returns: str
"""
id = self.key.id()
@@ -255,7 +270,7 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
def user_page_path(self, rest=None):
"""Returns the user's Bridgy Fed user page path."""
- path = f'/{self.LABEL}/{self.key.id()}'
+ path = f'/{self.LABEL}/{self.label_id()}'
if rest:
path += f'/{rest}'
return path
@@ -263,12 +278,8 @@ class User(StringIdModel, metaclass=ProtocolUserMeta):
def user_page_link(self):
"""Returns a pretty user page link with the user's name and profile picture."""
actor = self.actor_as2 or {}
- name = (actor.get('name') or
- # prettify if domain, noop if username
- util.domain_from_link(self.username()))
img = util.get_url(actor, 'icon') or ''
-
- return f' {name}'
+ return f' {self.name()}'
class Target(ndb.Model):
diff --git a/tests/test_activitypub.py b/tests/test_activitypub.py
index 9855024..2e8c3f7 100644
--- a/tests/test_activitypub.py
+++ b/tests/test_activitypub.py
@@ -1543,17 +1543,20 @@ class ActivityPubUtilsTest(TestCase):
activitypub.postprocess_as2(activitypub.postprocess_as2(obj)),
ignore=['to'])
- def test_ap_actor(self):
+ def test_ap_address(self):
user = ActivityPub(actor_as2={**ACTOR, 'preferredUsername': 'me'})
self.assertEqual('@me@mas.to', user.ap_address())
+ self.assertEqual('@me@mas.to', user.address)
user = ActivityPub(actor_as2=ACTOR)
self.assertEqual('@swentel@mas.to', user.ap_address())
+ self.assertEqual('@swentel@mas.to', user.address)
user = ActivityPub(id='https://mas.to/users/alice')
self.assertEqual('@alice@mas.to', user.ap_address())
+ self.assertEqual('@alice@mas.to', user.address)
- def test_ap_address(self):
+ def test_ap_actor(self):
user = self.make_user('http://foo/actor', cls=ActivityPub)
self.assertEqual('http://foo/actor', user.ap_actor())
@@ -1566,3 +1569,10 @@ class ActivityPubUtilsTest(TestCase):
user.actor_as2['url'] = ['http://my/url']
self.assertEqual('http://my/url', user.web_url())
+
+ def test_label_id(self):
+ user = self.make_user('http://foo', cls=ActivityPub)
+ self.assertEqual('http://foo', user.label_id())
+
+ user.actor_as2 = ACTOR
+ self.assertEqual('@swentel@mas.to', user.label_id())
diff --git a/tests/test_follow.py b/tests/test_follow.py
index f69b663..9c500cb 100644
--- a/tests/test_follow.py
+++ b/tests/test_follow.py
@@ -230,7 +230,7 @@ class FollowTest(testutil.TestCase):
def test_callback_user_use_instead(self, mock_get, mock_post):
user = self.make_user('www.alice.com')
self.user.use_instead = user.key
- user.put()
+ self.user.put()
mock_get.side_effect = (
requests_response(''),
diff --git a/tests/test_models.py b/tests/test_models.py
index 42e1c79..cbc6822 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -94,6 +94,18 @@ class UserTest(TestCase):
'https://user', '://y.z'):
self.assertFalse(g.user.is_web_url(url), url)
+ def test_name(self):
+ self.assertEqual('y.z', g.user.name())
+
+ g.user.actor_as2 = {'id': 'abc'}
+ self.assertEqual('y.z', g.user.name())
+
+ g.user.actor_as2 = {'name': 'alice'}
+ self.assertEqual('alice', g.user.name())
+
+ def test_label_id(self):
+ self.assertEqual('y.z', g.user.label_id())
+
class ObjectTest(TestCase):
def setUp(self):
diff --git a/tests/test_web.py b/tests/test_web.py
index 88bd5c6..a5e66d5 100644
--- a/tests/test_web.py
+++ b/tests/test_web.py
@@ -1501,6 +1501,9 @@ http://this/404s
'preferredUsername': 'user.com',
})
+ def test_label_id(self, _, __):
+ self.assertEqual('user.com', self.user.label_id())
+
def test_web_url(self, _, __):
self.assertEqual('https://user.com/', self.user.web_url())
diff --git a/web.py b/web.py
index 31b3f1e..f473dc3 100644
--- a/web.py
+++ b/web.py
@@ -49,6 +49,10 @@ class Web(User, Protocol):
def _get_kind(cls):
return 'MagicKey'
+ def label_id(self):
+ # prettify if domain, noop if username
+ return util.domain_from_link(self.username(), minimize=False)
+
def web_url(self):
"""Returns this user's web URL aka web_url, eg 'https://foo.com/'."""
return f'https://{self.key.id()}/'