2 Commits
Author SHA1 Message Date
kislovdm 64fe9c466e маршруты
Docker Hub / build-and-push (push) Failing after 15s
2026-04-23 19:56:20 +03:00
kislovdm 8bba0d9dd5 tech spec 2026-04-22 14:52:03 +03:00
3 changed files with 68 additions and 5 deletions
+50 -5
View File
@@ -204,11 +204,7 @@ class TelegramToMaxBridge:
chat_title = _telegram_chat_title(chat) chat_title = _telegram_chat_title(chat)
normalized = _normalize_title(chat_title) normalized = _normalize_title(chat_title)
if not normalized: max_chat_id = self._resolve_max_chat_id(message=message, normalized_title=normalized, chat=chat)
logger.error("Telegram chat without title/username, skip (chat=%s)", chat)
return
max_chat_id = self._resolve_max_chat_id_by_title(normalized)
if max_chat_id is None: if max_chat_id is None:
# требование: если в MAX нет канала/группы — ошибка и не пересылать # требование: если в MAX нет канала/группы — ошибка и не пересылать
logger.error("MAX чат с названием '%s' не найден — сообщение не пересылаю", chat_title) logger.error("MAX чат с названием '%s' не найден — сообщение не пересылаю", chat_title)
@@ -441,6 +437,55 @@ class TelegramToMaxBridge:
# reply_to в MAX — это id сообщения; если не нашли, просто отправляем без reply # reply_to в MAX — это id сообщения; если не нашли, просто отправляем без reply
return mapped return mapped
def _resolve_max_chat_id(
self,
*,
message: dict[str, Any],
normalized_title: str,
chat: dict[str, Any],
) -> int | None:
# При reply маршрутизируем по исходному сообщению (контекст диалога),
# чтобы не зависеть от username/title Telegram-чата.
from_reply = self._resolve_max_chat_id_from_reply(message)
if from_reply is not None:
return from_reply
if not normalized_title:
logger.error("Telegram chat without title/username, skip (chat=%s)", chat)
return None
return self._resolve_max_chat_id_by_title(normalized_title)
def _resolve_max_chat_id_from_reply(self, message: dict[str, Any]) -> int | None:
reply = message.get("reply_to_message")
if not isinstance(reply, dict):
return None
reply_mid = reply.get("message_id")
if reply_mid is None:
return None
chat = message.get("chat")
if not isinstance(chat, dict):
return None
telegram_chat_id = str(chat.get("id"))
max_chat_id = self._storage.get_max_chat_id_for_telegram(
telegram_chat_id=telegram_chat_id,
telegram_message_id=str(reply_mid),
)
if not max_chat_id:
return None
try:
return int(max_chat_id)
except ValueError:
logger.warning(
"Invalid max_chat_id '%s' in mapping for Telegram %s/%s",
max_chat_id,
telegram_chat_id,
reply_mid,
)
return None
async def _extract_attachments(self, message: dict[str, Any]) -> tuple[list[Any], list[str]]: async def _extract_attachments(self, message: dict[str, Any]) -> tuple[list[Any], list[str]]:
attachments: list[Any] = [] attachments: list[Any] = []
file_links: list[str] = [] file_links: list[str] = []
+16
View File
@@ -105,6 +105,22 @@ class BridgeStorage:
return None return None
return str(row[0]) return str(row[0])
def get_max_chat_id_for_telegram(
self, *, telegram_chat_id: str, telegram_message_id: str
) -> str | None:
with closing(self._connect()) as conn:
row = conn.execute(
"""
SELECT max_chat_id
FROM message_mapping
WHERE telegram_chat_id = ? AND telegram_message_id = ?
""",
(telegram_chat_id, telegram_message_id),
).fetchone()
if not row:
return None
return str(row[0])
def get_telegram_message_id_for_max( def get_telegram_message_id_for_max(
self, *, telegram_chat_id: str, max_chat_id: str, max_message_id: str self, *, telegram_chat_id: str, max_chat_id: str, max_message_id: str
) -> str | None: ) -> str | None:
+2
View File
@@ -66,6 +66,7 @@
- при неизвестных вложениях добавить уведомление в конец. - при неизвестных вложениях добавить уведомление в конец.
6. Обработать reply: 6. Обработать reply:
- попытаться найти Telegram `reply_to_message_id` через mapping; - попытаться найти Telegram `reply_to_message_id` через mapping;
- не дублировать вложения из исходного сообщения, на которое отвечают;
- если mapping не найден — добавить текстовый контекст ответа. - если mapping не найден — добавить текстовый контекст ответа.
7. Отправить контент: 7. Отправить контент:
- при `image+video > 1` — отправить единым альбомом (`sendMediaGroup`); - при `image+video > 1` — отправить единым альбомом (`sendMediaGroup`);
@@ -300,6 +301,7 @@
- если найден, отправлять `reply_to` в MAX. - если найден, отправлять `reply_to` в MAX.
- Для MAX->Telegram: - Для MAX->Telegram:
- если MAX message является reply, искать `telegram_message_id` в mapping; - если MAX message является reply, искать `telegram_message_id` в mapping;
- если MAX message является reply, не копировать медиа из `link.message` (исходного сообщения);
- если найден, отправлять `reply_to_message_id`; - если найден, отправлять `reply_to_message_id`;
- если не найден, добавлять текстовую пометку с превью исходного сообщения. - если не найден, добавлять текстовую пометку с превью исходного сообщения.