@@ -0,0 +1,161 @@
|
||||
import structlog
|
||||
|
||||
from app.models.domain import MaxIncomingMessage, TgIncomingMessage
|
||||
from app.models.tasks import Max2TgTask, MediaItem, NotifyFallbackTask, Tg2MaxTask
|
||||
from app.queue.protocols import QueuePort
|
||||
from app.storage.protocols import StoragePort
|
||||
from app.topic_locks import TopicLockRegistry
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class MessageRouter:
|
||||
"""Маршрутизация сообщений между MAX и Telegram через очереди и хранилище."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
storage: StoragePort,
|
||||
queue: QueuePort,
|
||||
forum_channel_id: int,
|
||||
topic_locks: TopicLockRegistry,
|
||||
) -> None:
|
||||
self._storage = storage
|
||||
self._queue = queue
|
||||
self._forum_channel_id = forum_channel_id
|
||||
self._topic_locks = topic_locks
|
||||
|
||||
async def handle_max_message(self, message: MaxIncomingMessage) -> bool:
|
||||
logger.debug(
|
||||
"router_max_message_received",
|
||||
max_chat_id=message.max_chat_id,
|
||||
message_id=message.max_message_id,
|
||||
text_len=len(message.text),
|
||||
media_count=len(message.media),
|
||||
)
|
||||
if message.max_chat_id is None or message.max_message_id is None:
|
||||
logger.warning(
|
||||
"router_max_message_rejected",
|
||||
reason="missing_ids",
|
||||
max_chat_id=message.max_chat_id,
|
||||
message_id=message.max_message_id,
|
||||
)
|
||||
return False
|
||||
|
||||
async with self._topic_locks.lock(message.max_chat_id):
|
||||
mapping = await self._storage.get_mapping_by_max_chat(message.max_chat_id)
|
||||
needs_new_topic = mapping is None
|
||||
if mapping is not None:
|
||||
logger.debug(
|
||||
"router_mapping_found",
|
||||
max_chat_id=message.max_chat_id,
|
||||
thread_id=mapping.tg_thread_id,
|
||||
)
|
||||
|
||||
reply_to_tg: int | None = None
|
||||
if message.reply_to_max_message_id is not None:
|
||||
link = await self._storage.get_tg_message_by_max(
|
||||
message.max_chat_id, message.reply_to_max_message_id
|
||||
)
|
||||
if link:
|
||||
reply_to_tg = link[2]
|
||||
logger.debug(
|
||||
"router_reply_mapped",
|
||||
max_reply_to=message.reply_to_max_message_id,
|
||||
tg_reply_to=reply_to_tg,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"router_reply_not_found",
|
||||
max_chat_id=message.max_chat_id,
|
||||
max_reply_to=message.reply_to_max_message_id,
|
||||
)
|
||||
|
||||
media = [MediaItem.model_validate(item) for item in message.media]
|
||||
|
||||
task = Max2TgTask(
|
||||
max_chat_id=message.max_chat_id,
|
||||
max_message_id=message.max_message_id,
|
||||
text=message.text,
|
||||
sender_name=message.sender_name,
|
||||
is_dm=message.is_dm,
|
||||
chat_title=message.chat_title,
|
||||
needs_new_topic=needs_new_topic,
|
||||
reply_to_tg_message_id=reply_to_tg,
|
||||
media=media,
|
||||
)
|
||||
await self._queue.enqueue_max2tg(task)
|
||||
logger.info(
|
||||
"max_message_enqueued",
|
||||
max_chat_id=message.max_chat_id,
|
||||
message_id=message.max_message_id,
|
||||
needs_new_topic=needs_new_topic,
|
||||
)
|
||||
return True
|
||||
|
||||
async def handle_tg_message(self, message: TgIncomingMessage) -> bool:
|
||||
logger.debug(
|
||||
"router_tg_message_received",
|
||||
tg_chat_id=message.tg_chat_id,
|
||||
tg_message_id=message.tg_message_id,
|
||||
thread_id=message.tg_thread_id,
|
||||
text_len=len(message.text),
|
||||
)
|
||||
if message.is_bot:
|
||||
logger.debug("router_tg_message_skipped", reason="is_bot")
|
||||
return False
|
||||
|
||||
if message.tg_chat_id != self._forum_channel_id:
|
||||
logger.debug(
|
||||
"router_tg_message_skipped",
|
||||
reason="wrong_chat",
|
||||
tg_chat_id=message.tg_chat_id,
|
||||
)
|
||||
return False
|
||||
|
||||
if message.tg_thread_id is None:
|
||||
logger.debug("router_tg_message_skipped", reason="no_thread_id")
|
||||
return False
|
||||
|
||||
mapping = await self._storage.get_mapping_by_tg_thread(
|
||||
message.tg_chat_id, message.tg_thread_id
|
||||
)
|
||||
if mapping is None:
|
||||
logger.debug(
|
||||
"tg_message_no_mapping",
|
||||
tg_chat_id=message.tg_chat_id,
|
||||
thread_id=message.tg_thread_id,
|
||||
)
|
||||
return False
|
||||
|
||||
reply_to_max: int | None = None
|
||||
if message.reply_to_tg_message_id is not None:
|
||||
link = await self._storage.get_max_message_by_tg(
|
||||
message.tg_chat_id, message.reply_to_tg_message_id
|
||||
)
|
||||
if link:
|
||||
reply_to_max = link[1]
|
||||
|
||||
media = [MediaItem.model_validate(item) for item in message.media]
|
||||
|
||||
task = Tg2MaxTask(
|
||||
max_chat_id=mapping.max_chat_id,
|
||||
text=message.text,
|
||||
tg_chat_id=message.tg_chat_id,
|
||||
tg_message_id=message.tg_message_id,
|
||||
tg_thread_id=message.tg_thread_id,
|
||||
reply_to_max_message_id=reply_to_max,
|
||||
media=media,
|
||||
)
|
||||
await self._queue.enqueue_tg2max(task)
|
||||
logger.info(
|
||||
"tg_message_enqueued",
|
||||
tg_message_id=message.tg_message_id,
|
||||
max_chat_id=mapping.max_chat_id,
|
||||
)
|
||||
return True
|
||||
|
||||
async def notify_error(self, title: str, details: str) -> None:
|
||||
logger.error("router_notify_error", title=title, details=details)
|
||||
await self._queue.enqueue_max2tg(
|
||||
NotifyFallbackTask(title=title, details=details)
|
||||
)
|
||||
Reference in New Issue
Block a user