Skip to content

Commit

Permalink
ATProto.set_username: fetch new DID doc; User.handle_as: use handle i…
Browse files Browse the repository at this point in the history
…n DID doc

for #826
  • Loading branch information
snarfed committed Oct 29, 2024
1 parent 572b42d commit c0f8945
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 10 deletions.
23 changes: 15 additions & 8 deletions atproto.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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 <a href="https://bsky.social/about/blog/4-28-2023-domain-handle-tutorial">with DNS</a> <a href="https://atproto.com/specs/handle#handle-resolution">or HTTP</a>. Once you're done, <a href="https://bsky-debug.app/handle?handle={username}">check your work here</a>, then DM me <code>username {username}</code> again.""")
raise RuntimeError(f"""You'll need to connect that domain to your bridged Bluesky account, either <a href="https://bsky.social/about/blog/4-28-2023-domain-handle-tutorial">with DNS</a> <a href="https://atproto.com/specs/handle#handle-resolution">or HTTP</a>. Once you're done, <a href="https://bsky-debug.app/handle?handle={username}">check your work here</a>, then DM me <code>username {username}</code> again.""")

try:
repo = arroba.server.storage.load_repo(copy_did)
Expand All @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -798,8 +805,7 @@ def user_link(self, name=True, handle=True, pictures=False, proto=None,
img = f'<img src="{pic}" class="profile"> '

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()
Expand Down
7 changes: 7 additions & 0 deletions tests/test_atproto.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, _):
Expand Down Expand Up @@ -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({
Expand Down
9 changes: 9 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,15 @@ def test_handle_as_web_custom_username(self, *_):
self.assertEqual('alice', self.user.username())
self.assertEqual('@[email protected]', 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'
Expand Down

0 comments on commit c0f8945

Please sign in to comment.