Skip to content

Commit

Permalink
ATProto.send: handle DMs, translate and send as chat messages
Browse files Browse the repository at this point in the history
for #1024, #966, etc
  • Loading branch information
snarfed committed Aug 9, 2024
1 parent 356903c commit 37c781a
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 36 deletions.
40 changes: 23 additions & 17 deletions atproto.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,10 @@ def send(to_cls, obj, url, from_user=None, orig_obj=None):
Doesn't deliver anywhere externally! Relays will receive this record
through ``subscribeRepos`` and then deliver it to AppView(s), which will
notify recipients as necessary.
Exceptions:
* ``flag``s are translated to ``createReport`` to the mod service
* DMs are translated to ``sendMessage`` to the chat service
"""
if util.domain_from_link(url) not in DOMAINS:
logger.info(f'Target PDS {url} is not us')
Expand Down Expand Up @@ -527,6 +531,7 @@ def send(to_cls, obj, url, from_user=None, orig_obj=None):
# * delete actor => tombstone repo
# * flag => send report to mod service
# * stop-following => delete follow record (prepared above)
# * dm => chat message
verb = obj.as1.get('verb')
if verb == 'delete':
atp_base_id = (base_id if ATProto.owns_id(base_id)
Expand All @@ -537,7 +542,11 @@ def send(to_cls, obj, url, from_user=None, orig_obj=None):
arroba.server.storage.tombstone_repo(repo)
return True

elif verb == 'flag':
if not record:
# _convert already logged
return False

if verb == 'flag':
logger.info(f'flag => createReport with {record}')
return to_cls.create_report(record, from_user=user)

Expand All @@ -546,11 +555,13 @@ def send(to_cls, obj, url, from_user=None, orig_obj=None):
assert base_obj and base_obj.type == 'follow', base_obj
verb = 'delete'

# write commit
if not record:
# _convert already logged
return False
elif as1.is_dm(obj.as1):
# is_dm checked that `to` has one elem
to_id = as1.get_ids(base_obj_as1, 'to')[0]
assert to_id.startswith('did:'), to_id
return ATProto.send_chat(record, from_repo=repo, to_did=to_id)

# write commit
type = record['$type']
lex_type = LEXICONS[type]['type']
assert lex_type == 'record', f"Can't store {type} object of type {lex_type}"
Expand Down Expand Up @@ -835,31 +846,26 @@ def create_report(cls, input, from_user):
logger.info(f'Created report on {mod_host}: {json_dumps(output)}')
return True

def send_chat(self, msg, from_user):
@classmethod
def send_chat(cls, msg, from_repo, to_did):
"""Sends a chat message to this user.
Args:
msg (dict): ``chat.bsky.convo.defs#messageInput``
from_user (models.User)
from_repo (arroba.repo.Repo)
to_did (str)
Returns:
bool: True if the report was sent successfully, False if the flag's
actor is not bridged into ATProto
"""
assert msg['$type'] == 'chat.bsky.convo.defs#messageInput'

to_did = self.key.id()
from_did = from_user.get_copy(ATProto)
if not from_did or not from_user.is_enabled(ATProto):
return False

repo = arroba.server.storage.load_repo(from_did)

chat_host = os.environ['CHAT_HOST']
token = service_jwt(host=chat_host,
aud=os.environ['CHAT_DID'],
repo_did=from_did,
privkey=repo.signing_key)
repo_did=from_repo.did,
privkey=from_repo.signing_key)
client = Client(f'https://{chat_host}', truncate=True, headers={
'User-Agent': USER_AGENT,
'Authorization': f'Bearer {token}',
Expand All @@ -876,5 +882,5 @@ def send_chat(self, msg, from_user):
util.interpret_http_exception(e)
return False

logger.info(f'Sent chat message from {from_user.handle} to {self.handle} {to_did}: {json_dumps(sent)}')
logger.info(f'Sent chat message from {from_repo.handle} to {to_did}: {json_dumps(sent)}')
return True
46 changes: 27 additions & 19 deletions tests/test_atproto.py
Original file line number Diff line number Diff line change
Expand Up @@ -1741,8 +1741,7 @@ def test_send_flag_createReport(self, _, mock_post):
'Authorization': ANY,
})

# sendMessage
@patch('requests.post', return_value=requests_response({
@patch('requests.post', return_value=requests_response({ # sendMessage
'id': 'chat456',
'rev': '22222222tef2d',
'sender': {'did': 'did:plc:user'},
Expand All @@ -1764,31 +1763,38 @@ def test_send_flag_createReport(self, _, mock_post):
}),
requests_response(DID_DOC),
])
def test_send_chat(self, mock_get, mock_post):
def test_send_dm_chat(self, mock_get, mock_post):
user = self.make_user_and_repo()
alice = ATProto(id='did:plc:alice')

self.assertTrue(alice.send_chat({
'$type': 'chat.bsky.convo.defs#messageInput',
'text': 'hello world',
}, from_user=user))
dm = Object(id='fake:dm', source_protocol='fake', our_as1={
'objectType': 'note',
'actor': user.key.id(),
'content': 'hello world',
'to': ['did:plc:alice'],
})
self.assertTrue(ATProto.send(dm, 'https://bsky.brid.gy/'))

headers = {
'Content-Type': 'application/json',
'User-Agent': common.USER_AGENT,
'Authorization': ANY,
}
mock_get.assert_any_call(
'https://chat.service.local/xrpc/chat.bsky.convo.getConvoForMembers?members=did%3Aplc%3Aalice',
json=None, data=None, headers=ANY)
json=None, data=None, headers=headers)
mock_post.assert_called_with(
'https://chat.service.local/xrpc/chat.bsky.convo.sendMessage',
json={
'convoId': 'convo123',
'message': {
'$type': 'chat.bsky.convo.defs#messageInput',
'text': 'hello world',
# unused
'createdAt': '2022-01-02T03:04:05.000Z',
'bridgyOriginalText': 'hello world',
'bridgyOriginalUrl': 'fake:dm',
},
}, data=None, headers={
'Content-Type': 'application/json',
'User-Agent': common.USER_AGENT,
'Authorization': ANY,
})
}, data=None, headers=headers)

# getConvoForMembers
@patch('requests.get', return_value=requests_response({
Expand All @@ -1797,12 +1803,14 @@ def test_send_chat(self, mock_get, mock_post):
}, status=400))
def test_send_chat_recipient_disabled(self, mock_get):
user = self.make_user_and_repo()
alice = ATProto(id='did:plc:alice')

self.assertFalse(alice.send_chat({
'$type': 'chat.bsky.convo.defs#messageInput',
'text': 'hello world',
}, from_user=user))
dm = Object(id='fake:dm', source_protocol='fake', our_as1={
'objectType': 'note',
'actor': user.key.id(),
'content': 'hello world',
'to': ['did:plc:alice'],
})
self.assertFalse(ATProto.send(dm, 'https://bsky.brid.gy/'))

mock_get.assert_any_call(
'https://chat.service.local/xrpc/chat.bsky.convo.getConvoForMembers?members=did%3Aplc%3Aalice',
Expand Down

0 comments on commit 37c781a

Please sign in to comment.