feat: sending and receiving messages with files, editing text messages

This commit is contained in:
2025-04-02 15:28:22 +04:00
parent 2cdccb33ca
commit 00522da68f
13 changed files with 361 additions and 70 deletions

View File

@@ -1,5 +1,6 @@
import aiohttp
import jwt
from fastapi import UploadFile
from backend.config import CHATS_SYNC_URL, CHAT_CONNECTOR_API_KEY
from external.chat.schemas import *
@@ -22,7 +23,6 @@ class ChatClient:
async def _method(self, http_method, method, **kwargs):
async with aiohttp.ClientSession(headers=self.headers) as session:
async with session.request(http_method, self.base_url + method, **kwargs) as response:
print(response)
return await response.json()
async def create_group(self, request: ExternalCreateGroupRequest) -> ExternalCreateGroupResponse:
@@ -34,3 +34,22 @@ class ChatClient:
json_data = request.model_dump()
response = await self._method('POST', self.groups_endpoint + '/topic/create', json=json_data)
return ExternalCreateTopicResponse.model_validate(response)
async def send_messages_with_files(
self,
tg_group_id: str,
tg_topic_id: int,
caption: str,
files: list[UploadFile],
) -> ExternalSendMessagesWithFilesResponse:
query_params = f'?tg_group_id={tg_group_id}&tg_topic_id={tg_topic_id}&caption={caption}'
data = aiohttp.FormData(default_to_multipart=True)
for file in files:
content = await file.read()
data.add_field('files', content, filename=file.filename, content_type=file.content_type)
response = await self._method('POST', self.chats_sync_endpoint + '/send' + query_params, data=data)
return ExternalSendMessagesWithFilesResponse.model_validate(response)

View File

@@ -1,9 +1,25 @@
from typing import Optional
from uuid import UUID
from schemas.base import BaseSchema
from schemas.base import BaseSchema, OkMessageSchema
# region Entities
class ExternalSendFileSchema(BaseSchema):
buffer: bytes
file_name: str
file_size: int
class ExternalMessageFileSchema(BaseSchema):
file_path: str
type: str
file_name: str
file_size: int
# endregion
# region Requests
class ExternalCreateGroupRequest(BaseSchema):
@@ -29,4 +45,8 @@ class ExternalCreateGroupResponse(BaseSchema):
class ExternalCreateTopicResponse(BaseSchema):
tg_topic_id: int
class ExternalSendMessagesWithFilesResponse(OkMessageSchema):
files: list[ExternalMessageFileSchema]
# endregion

View File

@@ -24,6 +24,7 @@ async def consume_messages():
try:
async for message in consumer:
print("consume")
await consumer_service.consume_message(message)
finally:
await consumer.stop()

View File

@@ -1,3 +1,5 @@
from typing import Optional
from schemas.base import OkMessageSchema, BaseSchema
@@ -10,11 +12,19 @@ class TelegramUserSchema(BaseSchema):
username: str
class MessageFileSchema(BaseSchema):
file_path: str
type: str
file_name: str
file_size: int
class MessageFromTelegramSchema(BaseSchema):
group_id: str
tg_topic_id: int
text: str
text: Optional[str]
sender: TelegramUserSchema
file: Optional[MessageFileSchema]
# endregion
@@ -41,4 +51,9 @@ class SendMessageToConnectorResponse(BaseConnectorResponse):
class DeleteMessageResponse(BaseConnectorResponse):
message_id: int
class EditMessageResponse(BaseConnectorResponse):
message_id: int
text: str
# endregion

View File

@@ -11,8 +11,11 @@ class BaseMessageSchema(BaseSchema):
group_id: str
class MessageSchema(BaseMessageSchema):
class EditMessageSchema(BaseMessageSchema):
text: str
class MessageSchema(EditMessageSchema):
topic_id: int
@@ -33,4 +36,8 @@ class SendMessageToConnectorRequest(BaseConnectorRequest):
class SendMessageDeletingToConnectorRequest(BaseConnectorRequest):
pass
class SendMessageEditingToConnectorRequest(BaseConnectorRequest):
message: EditMessageSchema
# endregion

View File

@@ -1,6 +1,5 @@
import pickle
from datetime import datetime
from typing import Optional
from uuid import UUID
from aiokafka import ConsumerRecord
@@ -8,13 +7,14 @@ from sqlalchemy import select
from external.kafka.enums import KafkaMessageType
from external.kafka.schemas.consumer import *
from models import Message, MessageStatus, TgUser, Chat, TgGroup
from models import Message, MessageStatus, TgUser, Chat, TgGroup, MessageFile
from services.base import BaseService
class ConsumerService(BaseService):
async def consume_message(self, message: ConsumerRecord):
value = pickle.loads(message.value)
print("Consumer: received message: ", value)
try:
if 'ok' in value:
@@ -36,7 +36,8 @@ class ConsumerService(BaseService):
response = SendMessageToConnectorResponse.model_validate(value)
await self._process_connector_send_response(response)
case KafkaMessageType.EDIT:
pass
response = EditMessageResponse.model_validate(value)
await self._process_connector_edit_response(response)
case KafkaMessageType.DELETE:
response = DeleteMessageResponse.model_validate(value)
await self._process_connector_delete_response(response)
@@ -59,6 +60,16 @@ class ConsumerService(BaseService):
message = await self.session.get(Message, response.message_id)
message.is_deleted = True
await self.session.commit()
async def _process_connector_edit_response(self, response: EditMessageResponse):
if not response.ok:
return
message = await self.session.get(Message, response.message_id)
message.text = response.text
message.is_edited = True
await self.session.commit()
async def _get_chat(self, group_id: str, tg_topic_id: int) -> Optional[Chat]:
stmt = (
@@ -81,12 +92,17 @@ class ConsumerService(BaseService):
if not chat:
return
file = None
if request.message.file:
file = MessageFile(**request.message.file.model_dump())
message = Message(
text=request.message.text,
text=request.message.text if request.message.text else "",
created_at=datetime.now(),
tg_sender_id=tg_sender.id,
chat_id=chat.id,
status=MessageStatus.success,
file=file,
)
self.session.add(message)
await self.session.commit()

View File

@@ -6,12 +6,21 @@ from aiohttp import ClientConnectorError
from backend.config import KAFKA_PRODUCER_TOPIC, CHAT_CONNECTOR_API_KEY
from external.kafka import producer
from external.kafka.enums import KafkaMessageType
from external.kafka.schemas.producer import MessageSchema, SendMessageToConnectorRequest, \
SendMessageDeletingToConnectorRequest, BaseMessageSchema
from external.kafka.schemas.producer import *
from services.base import BaseService
class ProducerService(BaseService):
@staticmethod
async def _send_message(request: BaseConnectorRequest):
try:
await producer.send(KAFKA_PRODUCER_TOPIC, value=pickle.dumps(request.model_dump()))
except ClientConnectorError:
return False, 'Ошибка подключения к коннектору'
except Exception as e:
return False, str(e)
return True, 'Сообщение отправлено'
@staticmethod
async def send_message_to_connector(
message_text: str,
@@ -19,42 +28,50 @@ class ProducerService(BaseService):
topic_id: int,
message_id: int,
) -> tuple[bool, str]:
try:
request = SendMessageToConnectorRequest(
message=MessageSchema(
message_id=message_id,
text=message_text,
group_id=str(group_id),
topic_id=topic_id,
),
message_type=KafkaMessageType.SEND,
app_auth_key=CHAT_CONNECTOR_API_KEY,
)
await producer.send(KAFKA_PRODUCER_TOPIC, value=pickle.dumps(request.model_dump()))
except ClientConnectorError:
return False, 'Ошибка подключения к коннектору'
except Exception as e:
return False, str(e)
return True, 'Сообщение отправлено'
request = SendMessageToConnectorRequest(
message_type=KafkaMessageType.SEND,
app_auth_key=CHAT_CONNECTOR_API_KEY,
message=MessageSchema(
message_id=message_id,
text=message_text,
group_id=str(group_id),
topic_id=topic_id,
),
)
return await ProducerService._send_message(request)
@staticmethod
async def send_message_deleting_to_connector(message_id: int, tg_message_id: int, group_id: UUID) -> tuple[bool, str]:
try:
request = SendMessageDeletingToConnectorRequest(
message_type=KafkaMessageType.DELETE,
app_auth_key=CHAT_CONNECTOR_API_KEY,
message=BaseMessageSchema(
message_id=message_id,
tg_message_id=tg_message_id,
group_id=str(group_id),
),
async def send_message_deleting_to_connector(
message_id: int,
tg_message_id: int,
group_id: UUID,
) -> tuple[bool, str]:
request = SendMessageDeletingToConnectorRequest(
message_type=KafkaMessageType.DELETE,
app_auth_key=CHAT_CONNECTOR_API_KEY,
message=BaseMessageSchema(
message_id=message_id,
tg_message_id=tg_message_id,
group_id=str(group_id),
),
)
return await ProducerService._send_message(request)
@staticmethod
async def send_message_editing_to_connector(
message_id: int,
tg_message_id: int,
group_id: UUID,
text: str,
) -> tuple[bool, str]:
request = SendMessageEditingToConnectorRequest(
message_type=KafkaMessageType.EDIT,
app_auth_key=CHAT_CONNECTOR_API_KEY,
message=EditMessageSchema(
message_id=message_id,
tg_message_id=tg_message_id,
group_id=str(group_id),
text=text,
)
await producer.send(KAFKA_PRODUCER_TOPIC, value=pickle.dumps(request.model_dump()))
except ClientConnectorError:
return False, 'Ошибка подключения к коннектору'
except Exception as e:
return False, str(e)
return True, 'Сообщение отправлено'
)
return await ProducerService._send_message(request)