128 lines
4.4 KiB
Python
128 lines
4.4 KiB
Python
from datetime import datetime, timezone
|
|
from uuid import UUID
|
|
|
|
from apps.chat.events.event import MessageSentEvent
|
|
from apps.chat.events.publishers.longpoll import LongPollPublisher
|
|
from apps.chat.events.publishers.push import PushPublisher
|
|
from apps.chat.events.publishers.websocket import WebSocketPublisher
|
|
from apps.chat.exceptions import ConversationClosedError
|
|
from apps.chat.integrations.mattermost.client import MattermostClient
|
|
from apps.chat.models import Conversation, ConversationStatus, MattermostAccountMapping
|
|
from apps.chat.services.account import AccountService
|
|
from apps.chat.services.storage import StorageService
|
|
|
|
|
|
class MessageService:
|
|
def __init__(
|
|
self,
|
|
account_service: AccountService | None = None,
|
|
storage_service: StorageService | None = None,
|
|
mattermost_client: MattermostClient | None = None,
|
|
publishers: list | None = None,
|
|
):
|
|
self._account = account_service or AccountService()
|
|
self._storage_override = storage_service # lazy: only instantiate when needed
|
|
self._mm = mattermost_client or MattermostClient()
|
|
self._publishers = (
|
|
publishers
|
|
if publishers is not None
|
|
else [WebSocketPublisher(), PushPublisher(), LongPollPublisher()]
|
|
)
|
|
|
|
@property
|
|
def _storage(self) -> StorageService:
|
|
if self._storage_override is None:
|
|
self._storage_override = StorageService()
|
|
return self._storage_override
|
|
|
|
def send(
|
|
self,
|
|
conversation_uuid,
|
|
sender_uuid: UUID,
|
|
message_type: str,
|
|
text: str | None = None,
|
|
file=None,
|
|
) -> dict:
|
|
conversation = Conversation.objects.get(uuid=conversation_uuid)
|
|
if conversation.status == ConversationStatus.CLOSED:
|
|
raise ConversationClosedError(f"Conversation {conversation_uuid} is closed")
|
|
|
|
if not self._account.validate_user(sender_uuid):
|
|
raise ValueError(f"Sender {sender_uuid} is not valid")
|
|
|
|
file_url: str | None = None
|
|
if file is not None:
|
|
filename = getattr(file, "name", f"{sender_uuid}")
|
|
file_url = self._storage.upload_file(file, filename)
|
|
|
|
if message_type == "text":
|
|
mm_message = text or ""
|
|
else:
|
|
mm_message = file_url or text or ""
|
|
|
|
post_id = self._mm.post_message(conversation.mattermost_channel_id, mm_message)
|
|
|
|
event = MessageSentEvent(
|
|
chat_uuid=conversation_uuid,
|
|
post_id=post_id,
|
|
sender_uuid=sender_uuid,
|
|
message_type=message_type,
|
|
payload={"text": text, "file_url": file_url},
|
|
)
|
|
for publisher in self._publishers:
|
|
publisher.publish(event)
|
|
|
|
return {
|
|
"post_id": post_id,
|
|
"sender_uuid": sender_uuid,
|
|
"message_type": message_type,
|
|
"text": text,
|
|
"url": file_url,
|
|
"created_at": None,
|
|
}
|
|
|
|
def list_messages(
|
|
self,
|
|
conversation_uuid,
|
|
page: int,
|
|
per_page: int,
|
|
since: int | None = None,
|
|
) -> list[dict]:
|
|
conversation = Conversation.objects.get(uuid=conversation_uuid)
|
|
raw_posts = self._mm.get_posts(
|
|
conversation.mattermost_channel_id,
|
|
page=page,
|
|
per_page=per_page,
|
|
since=since,
|
|
)
|
|
|
|
mm_user_ids = {p["user_id"] for p in raw_posts if p.get("user_id")}
|
|
mappings = {
|
|
m.mattermost_user_id: m.user_uuid
|
|
for m in MattermostAccountMapping.objects.filter(
|
|
mattermost_user_id__in=mm_user_ids
|
|
)
|
|
} if mm_user_ids else {}
|
|
|
|
return [self._normalize_post(p, mappings) for p in raw_posts]
|
|
|
|
@staticmethod
|
|
def _normalize_post(post: dict, mappings: dict) -> dict:
|
|
mm_uid = post.get("user_id")
|
|
sender_uuid = mappings.get(mm_uid)
|
|
msg = post.get("message", "")
|
|
is_url = msg.startswith("http")
|
|
created_ms = post.get("create_at")
|
|
created_at = (
|
|
datetime.fromtimestamp(created_ms / 1000, tz=timezone.utc)
|
|
if created_ms
|
|
else None
|
|
)
|
|
return {
|
|
"post_id": post.get("id"),
|
|
"sender_uuid": sender_uuid,
|
|
"message_type": "file" if is_url else "text",
|
|
"text": None if is_url else msg,
|
|
"url": msg if is_url else None,
|
|
"created_at": created_at,
|
|
}
|