diff --git a/atproto.py b/atproto.py
index fe2a12fc..4ed80d70 100644
--- a/atproto.py
+++ b/atproto.py
@@ -163,20 +163,24 @@ def resolve_handle(self, handle=None):
return {'did': got.key.id()}
-def did_to_handle(did):
- """Resolves a DID to a handle _if_ we have the DID doc stored locally.
+def did_to_handle(did, remote=None):
+ """Resolves a DID to a handle.
Args:
did (str)
Returns:
str: handle, or None
+ remote (bool): whether to fetch the object over the network. See
+ :meth:`Protocol.load`
"""
- if did_obj := ATProto.load(did, did_doc=True):
- if aka := util.get_first(did_obj.raw, 'alsoKnownAs', ''):
- handle, _, _ = parse_at_uri(aka)
- if handle:
- return handle
+ if did_obj := ATProto.load(did, did_doc=True, remote=remote):
+ # use first at:// URI in alsoKnownAs
+ for aka in util.get_list(did_obj.raw, 'alsoKnownAs'):
+ if aka.startswith('at://'):
+ handle, _, _ = parse_at_uri(aka)
+ if handle:
+ return handle
class Cursor(StringIdModel):
@@ -495,7 +499,7 @@ def set_username(to_cls, user, username):
# resolve_handle checks that username is a valid domain
resolved = did.resolve_handle(username, get_fn=util.requests_get)
if resolved != copy_did:
- raise RuntimeError(f"""Sure! You'll need to connect that domain to your bridged Bluesky account, either with DNS or HTTP. Once you're done, check your work here, then DM me username {username}
again.""")
+ raise RuntimeError(f"""You'll need to connect that domain to your bridged Bluesky account, either with DNS or HTTP. Once you're done, check your work here, then DM me username {username}
again.""")
try:
repo = arroba.server.storage.load_repo(copy_did)
@@ -509,6 +513,9 @@ def set_username(to_cls, user, username):
get_fn=util.requests_get, post_fn=util.requests_post)
arroba.server.storage.write_event(repo=repo, type='identity', handle=username)
+ # refresh our stored DID doc
+ user.obj = to_cls.load(copy_did, did_doc=True, remote=True)
+
@classmethod
def send(to_cls, obj, url, from_user=None, orig_obj_id=None):
"""Creates a record if we own its repo.
diff --git a/models.py b/models.py
index a2acd1b2..c9241be8 100644
--- a/models.py
+++ b/models.py
@@ -600,6 +600,13 @@ def handle_as(self, to_proto):
if isinstance(to_proto, str):
to_proto = PROTOCOLS[to_proto]
+ # override to-ATProto to use custom domain handle in DID doc
+ from atproto import ATProto, did_to_handle
+ if to_proto == ATProto:
+ if did := self.get_copy(ATProto):
+ if handle := did_to_handle(did, remote=False):
+ return handle
+
# override web users to always use domain instead of custom username
# TODO: fall back to id if handle is unset?
handle = self.key.id() if self.LABEL == 'web' else self.handle
@@ -798,8 +805,7 @@ def user_link(self, name=True, handle=True, pictures=False, proto=None,
img = f' '
if handle:
- handle_str = ids.translate_handle(handle=self.handle, from_=self,
- to=proto, enhanced=False)
+ handle_str = self.handle_as(proto)
if name and self.name() != handle_str:
name_str = self.name()
diff --git a/tests/test_atproto.py b/tests/test_atproto.py
index be6e5742..3b6eec2e 100644
--- a/tests/test_atproto.py
+++ b/tests/test_atproto.py
@@ -1216,6 +1216,11 @@ def test_set_dns_existing(self, mock_rrsets, mock_changes):
'cid': 'orig',
'operation': {'alsoKnownAs': ['at://ol.d', 'http://ol.d']},
}]),
+ # fetch updated DID doc
+ requests_response({
+ **DID_DOC,
+ 'alsoKnownAs': ['at://ne.w'],
+ }),
])
@patch('requests.post', return_value=requests_response('OK')) # update DID on PLC
def test_set_username(self, mock_post, mock_get, mock_create_task, _):
@@ -1246,6 +1251,8 @@ def test_set_username(self, mock_post, mock_get, mock_create_task, _):
'did': 'did:plc:user',
}, timeout=15, stream=True, headers=ANY)
+ self.assertEqual('ne.w', user.handle_as(ATProto))
+
# check #identity event
seq = self.storage.last_seq(SUBSCRIBE_REPOS_NSID)
self.assertEqual({
diff --git a/tests/test_models.py b/tests/test_models.py
index 2fa108a5..238ecae5 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -282,6 +282,15 @@ def test_handle_as_web_custom_username(self, *_):
self.assertEqual('alice', self.user.username())
self.assertEqual('@y.za@web.brid.gy', self.user.handle_as('ap'))
+ def test_handle_as_atproto_custom_handle(self, *_):
+ self.assertEqual('y.za.web.brid.gy', self.user.handle_as(ATProto))
+
+ self.user.copies = [Target(uri='did:plc:user', protocol='atproto')]
+ self.assertEqual('y.za.web.brid.gy', self.user.handle_as(ATProto))
+
+ self.store_object(id='did:plc:user', raw=DID_DOC)
+ self.assertEqual('ha.nl', self.user.handle_as(ATProto))
+
def test_handle_as_None(self):
class NoHandle(Fake):
ABBREV = 'nohandle'