ATProto.send: handle DMs, translate and send as chat messages

for #1024, #966, etc
pull/1245/head
Ryan Barrett 2024-08-08 11:42:28 -07:00
rodzic 356903c3e9
commit 37c781a2df
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 6BE31FDF4776E9D4
2 zmienionych plików z 50 dodań i 36 usunięć

Wyświetl plik

@ -461,6 +461,10 @@ class ATProto(User, Protocol):
Doesn't deliver anywhere externally! Relays will receive this record Doesn't deliver anywhere externally! Relays will receive this record
through ``subscribeRepos`` and then deliver it to AppView(s), which will through ``subscribeRepos`` and then deliver it to AppView(s), which will
notify recipients as necessary. 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: if util.domain_from_link(url) not in DOMAINS:
logger.info(f'Target PDS {url} is not us') logger.info(f'Target PDS {url} is not us')
@ -527,6 +531,7 @@ class ATProto(User, Protocol):
# * delete actor => tombstone repo # * delete actor => tombstone repo
# * flag => send report to mod service # * flag => send report to mod service
# * stop-following => delete follow record (prepared above) # * stop-following => delete follow record (prepared above)
# * dm => chat message
verb = obj.as1.get('verb') verb = obj.as1.get('verb')
if verb == 'delete': if verb == 'delete':
atp_base_id = (base_id if ATProto.owns_id(base_id) atp_base_id = (base_id if ATProto.owns_id(base_id)
@ -537,7 +542,11 @@ class ATProto(User, Protocol):
arroba.server.storage.tombstone_repo(repo) arroba.server.storage.tombstone_repo(repo)
return True return True
elif verb == 'flag': if not record:
# _convert already logged
return False
if verb == 'flag':
logger.info(f'flag => createReport with {record}') logger.info(f'flag => createReport with {record}')
return to_cls.create_report(record, from_user=user) return to_cls.create_report(record, from_user=user)
@ -546,11 +555,13 @@ class ATProto(User, Protocol):
assert base_obj and base_obj.type == 'follow', base_obj assert base_obj and base_obj.type == 'follow', base_obj
verb = 'delete' verb = 'delete'
# write commit elif as1.is_dm(obj.as1):
if not record: # is_dm checked that `to` has one elem
# _convert already logged to_id = as1.get_ids(base_obj_as1, 'to')[0]
return False assert to_id.startswith('did:'), to_id
return ATProto.send_chat(record, from_repo=repo, to_did=to_id)
# write commit
type = record['$type'] type = record['$type']
lex_type = LEXICONS[type]['type'] lex_type = LEXICONS[type]['type']
assert lex_type == 'record', f"Can't store {type} object of type {lex_type}" assert lex_type == 'record', f"Can't store {type} object of type {lex_type}"
@ -835,12 +846,14 @@ class ATProto(User, Protocol):
logger.info(f'Created report on {mod_host}: {json_dumps(output)}') logger.info(f'Created report on {mod_host}: {json_dumps(output)}')
return True 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. """Sends a chat message to this user.
Args: Args:
msg (dict): ``chat.bsky.convo.defs#messageInput`` msg (dict): ``chat.bsky.convo.defs#messageInput``
from_user (models.User) from_repo (arroba.repo.Repo)
to_did (str)
Returns: Returns:
bool: True if the report was sent successfully, False if the flag's bool: True if the report was sent successfully, False if the flag's
@ -848,18 +861,11 @@ class ATProto(User, Protocol):
""" """
assert msg['$type'] == 'chat.bsky.convo.defs#messageInput' 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'] chat_host = os.environ['CHAT_HOST']
token = service_jwt(host=chat_host, token = service_jwt(host=chat_host,
aud=os.environ['CHAT_DID'], aud=os.environ['CHAT_DID'],
repo_did=from_did, repo_did=from_repo.did,
privkey=repo.signing_key) privkey=from_repo.signing_key)
client = Client(f'https://{chat_host}', truncate=True, headers={ client = Client(f'https://{chat_host}', truncate=True, headers={
'User-Agent': USER_AGENT, 'User-Agent': USER_AGENT,
'Authorization': f'Bearer {token}', 'Authorization': f'Bearer {token}',
@ -876,5 +882,5 @@ class ATProto(User, Protocol):
util.interpret_http_exception(e) util.interpret_http_exception(e)
return False 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 return True

Wyświetl plik

@ -1741,8 +1741,7 @@ Sed tortor neque, aliquet quis posuere aliquam […]
'Authorization': ANY, 'Authorization': ANY,
}) })
# sendMessage @patch('requests.post', return_value=requests_response({ # sendMessage
@patch('requests.post', return_value=requests_response({
'id': 'chat456', 'id': 'chat456',
'rev': '22222222tef2d', 'rev': '22222222tef2d',
'sender': {'did': 'did:plc:user'}, 'sender': {'did': 'did:plc:user'},
@ -1764,18 +1763,25 @@ Sed tortor neque, aliquet quis posuere aliquam […]
}), }),
requests_response(DID_DOC), 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() user = self.make_user_and_repo()
alice = ATProto(id='did:plc:alice')
self.assertTrue(alice.send_chat({ dm = Object(id='fake:dm', source_protocol='fake', our_as1={
'$type': 'chat.bsky.convo.defs#messageInput', 'objectType': 'note',
'text': 'hello world', 'actor': user.key.id(),
}, from_user=user)) '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( mock_get.assert_any_call(
'https://chat.service.local/xrpc/chat.bsky.convo.getConvoForMembers?members=did%3Aplc%3Aalice', '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( mock_post.assert_called_with(
'https://chat.service.local/xrpc/chat.bsky.convo.sendMessage', 'https://chat.service.local/xrpc/chat.bsky.convo.sendMessage',
json={ json={
@ -1783,12 +1789,12 @@ Sed tortor neque, aliquet quis posuere aliquam […]
'message': { 'message': {
'$type': 'chat.bsky.convo.defs#messageInput', '$type': 'chat.bsky.convo.defs#messageInput',
'text': 'hello world', 'text': 'hello world',
# unused
'createdAt': '2022-01-02T03:04:05.000Z',
'bridgyOriginalText': 'hello world',
'bridgyOriginalUrl': 'fake:dm',
}, },
}, data=None, headers={ }, data=None, headers=headers)
'Content-Type': 'application/json',
'User-Agent': common.USER_AGENT,
'Authorization': ANY,
})
# getConvoForMembers # getConvoForMembers
@patch('requests.get', return_value=requests_response({ @patch('requests.get', return_value=requests_response({
@ -1797,12 +1803,14 @@ Sed tortor neque, aliquet quis posuere aliquam […]
}, status=400)) }, status=400))
def test_send_chat_recipient_disabled(self, mock_get): def test_send_chat_recipient_disabled(self, mock_get):
user = self.make_user_and_repo() user = self.make_user_and_repo()
alice = ATProto(id='did:plc:alice')
self.assertFalse(alice.send_chat({ dm = Object(id='fake:dm', source_protocol='fake', our_as1={
'$type': 'chat.bsky.convo.defs#messageInput', 'objectType': 'note',
'text': 'hello world', 'actor': user.key.id(),
}, from_user=user)) 'content': 'hello world',
'to': ['did:plc:alice'],
})
self.assertFalse(ATProto.send(dm, 'https://bsky.brid.gy/'))
mock_get.assert_any_call( mock_get.assert_any_call(
'https://chat.service.local/xrpc/chat.bsky.convo.getConvoForMembers?members=did%3Aplc%3Aalice', 'https://chat.service.local/xrpc/chat.bsky.convo.getConvoForMembers?members=did%3Aplc%3Aalice',