This commit is contained in:
kislovdm
2026-04-20 19:19:45 +03:00
parent d019e884b6
commit 2a471910e4
2 changed files with 148 additions and 72 deletions
+148 -24
View File
@@ -5,7 +5,7 @@ from typing import Any
from max_parser import parse_message from max_parser import parse_message
from models import ParsedMessage from models import ParsedMessage
from pymax import MaxClient from pymax import MaxClient
from pymax.types import PhotoAttach, VideoAttach from pymax.types import AudioAttach, FileAttach, Message, PhotoAttach, StickerAttach, VideoAttach
from storage import BridgeStorage from storage import BridgeStorage
from telegram_api import TelegramApiError, TelegramClient from telegram_api import TelegramApiError, TelegramClient
@@ -340,29 +340,24 @@ class MaxToTelegramBridge:
except Exception: except Exception:
logger.debug("Cannot resolve chat title", exc_info=True) logger.debug("Cannot resolve chat title", exc_info=True)
attaches = getattr(max_message, "attaches", None) or [] await self._collect_message_attachments(
for attach in attaches: message=max_message,
if isinstance(attach, PhotoAttach): parsed=parsed,
parsed.image_urls.extend(self._extract_photo_urls(attach)) source_tag="root",
elif isinstance(attach, VideoAttach): )
try:
video = await self._max_client.get_video_by_id( link = getattr(max_message, "link", None)
chat_id=max_message.chat_id, linked_message = getattr(link, "message", None)
message_id=max_message.id, if linked_message is not None:
video_id=attach.video_id, if not (parsed.text or "").strip():
) linked_text = str(getattr(linked_message, "text", "") or "").strip()
video_url = getattr(video, "url", None) if linked_text:
if video_url: parsed.text = linked_text
parsed.video_urls.append(str(video_url)) await self._collect_message_attachments(
except Exception: message=linked_message,
logger.exception("Cannot resolve video URL from Max") parsed=parsed,
else: source_tag="forward",
urls = self._extract_any_urls(attach) )
if urls:
parsed.file_urls.extend(urls)
else:
if not self._is_forward_attach_like(attach):
parsed.unknown_attachments.append(type(attach).__name__)
# Убираем дубли URL, если парсер и enrich нашли одинаковые вложения. # Убираем дубли URL, если парсер и enrich нашли одинаковые вложения.
parsed.image_urls = list(dict.fromkeys(parsed.image_urls)) parsed.image_urls = list(dict.fromkeys(parsed.image_urls))
@@ -371,6 +366,127 @@ class MaxToTelegramBridge:
parsed.unknown_attachments = list(dict.fromkeys(parsed.unknown_attachments)) parsed.unknown_attachments = list(dict.fromkeys(parsed.unknown_attachments))
return parsed return parsed
async def _collect_message_attachments(
self,
*,
message: Any,
parsed: ParsedMessage,
source_tag: str,
) -> None:
attaches = getattr(message, "attaches", None) or []
message_chat_id = getattr(message, "chat_id", None)
message_id = getattr(message, "id", None)
if message_chat_id is None:
message_chat_id = parsed.chat_id
for attach in attaches:
if isinstance(attach, PhotoAttach):
parsed.image_urls.extend(self._extract_photo_urls(attach))
continue
if isinstance(attach, VideoAttach):
try:
video = await self._max_client.get_video_by_id(
chat_id=message_chat_id,
message_id=message_id,
video_id=attach.video_id,
)
video_url = getattr(video, "url", None)
if video_url:
parsed.video_urls.append(str(video_url))
except Exception:
logger.exception("Cannot resolve video URL from Max (%s)", source_tag)
continue
if isinstance(attach, FileAttach):
resolved = await self._resolve_file_attach_url(
message_chat_id=message_chat_id,
message_id=message_id,
attach=attach,
)
if resolved:
parsed.file_urls.append(resolved)
else:
fallback = str(getattr(attach, "name", "") or "").strip()
if fallback:
parsed.text = self._append_missing_file_note(parsed.text, fallback)
else:
parsed.unknown_attachments.append(type(attach).__name__)
continue
if isinstance(attach, AudioAttach):
audio_url = str(getattr(attach, "url", "") or "").strip()
if audio_url:
parsed.file_urls.append(audio_url)
else:
parsed.unknown_attachments.append(type(attach).__name__)
continue
if isinstance(attach, StickerAttach):
sticker_url = str(getattr(attach, "url", "") or "").strip()
if sticker_url:
parsed.image_urls.append(sticker_url)
else:
parsed.unknown_attachments.append(type(attach).__name__)
continue
# Fallback на случай сырого Attach/нестандартного типа:
if await self._resolve_generic_file_attach(
message_chat_id=message_chat_id,
message_id=message_id,
attach=attach,
parsed=parsed,
):
continue
urls = self._extract_any_urls(attach)
if urls:
parsed.file_urls.extend(urls)
continue
parsed.unknown_attachments.append(type(attach).__name__)
async def _resolve_file_attach_url(self, *, message_chat_id: Any, message_id: Any, attach: FileAttach) -> str | None:
file_id = getattr(attach, "file_id", None)
if file_id is None or message_id is None:
return None
try:
file_info = await self._max_client.get_file_by_id(
chat_id=message_chat_id,
message_id=message_id,
file_id=file_id,
)
url = getattr(file_info, "url", None)
return str(url) if url else None
except Exception:
logger.exception("Cannot resolve file URL from Max (file_id=%s)", file_id)
return None
async def _resolve_generic_file_attach(
self,
*,
message_chat_id: Any,
message_id: Any,
attach: Any,
parsed: ParsedMessage,
) -> bool:
file_id = getattr(attach, "file_id", None)
if file_id is None or message_id is None:
return False
try:
file_info = await self._max_client.get_file_by_id(
chat_id=message_chat_id,
message_id=message_id,
file_id=file_id,
)
url = getattr(file_info, "url", None)
if url:
parsed.file_urls.append(str(url))
return True
except Exception:
logger.debug("Cannot resolve generic file attach from Max", exc_info=True)
return False
def _is_self_message(self, max_message: Any) -> bool: def _is_self_message(self, max_message: Any) -> bool:
sender = getattr(max_message, "sender", None) sender = getattr(max_message, "sender", None)
me = getattr(self._max_client, "me", None) me = getattr(self._max_client, "me", None)
@@ -459,6 +575,14 @@ class MaxToTelegramBridge:
return True return True
return False return False
@staticmethod
def _append_missing_file_note(current_text: str, file_name: str) -> str:
text = (current_text or "").strip()
note = f"[MAX forwarded file without direct URL] {file_name}"
if not text:
return note
return f"{text}\n{note}"
@staticmethod @staticmethod
def _append_unknown_attachment_notice(*, parsed: ParsedMessage, text: str) -> str: def _append_unknown_attachment_notice(*, parsed: ParsedMessage, text: str) -> str:
if not parsed.unknown_attachments: if not parsed.unknown_attachments:
-48
View File
@@ -94,49 +94,6 @@ def _collect_urls(node: Any) -> list[str]:
return list(dict.fromkeys(urls)) return list(dict.fromkeys(urls))
def _extract_forwarded_text(message: Any) -> str:
candidates: list[str] = []
seen_ids: set[int] = set()
forward_keys = {"forward", "forwarded", "forwards", "link", "message", "messages", "payload", "quote", "origin"}
text_keys = {"text", "message", "body", "caption"}
def walk(value: Any, inside_forward: bool) -> None:
if value is None:
return
obj_id = id(value)
if obj_id in seen_ids:
return
seen_ids.add(obj_id)
if isinstance(value, str):
if inside_forward:
text = value.strip()
if text and not (text.startswith("http://") or text.startswith("https://")):
candidates.append(text)
return
if isinstance(value, (list, tuple, set)):
for item in value:
walk(item, inside_forward)
return
if isinstance(value, dict):
current_is_forward = inside_forward or any(key in value for key in forward_keys)
for key, nested in value.items():
if key in text_keys and current_is_forward and isinstance(nested, str):
text = nested.strip()
if text and not (text.startswith("http://") or text.startswith("https://")):
candidates.append(text)
walk(nested, current_is_forward)
return
if hasattr(value, "__dict__"):
walk(vars(value), inside_forward)
walk(message, False)
uniq = list(dict.fromkeys(candidates))
if not uniq:
return ""
return "\n\n".join(uniq[:5])
def _extract_media_urls(message: Any) -> tuple[list[str], list[str], list[str], list[str]]: def _extract_media_urls(message: Any) -> tuple[list[str], list[str], list[str], list[str]]:
image_urls: list[str] = [] image_urls: list[str] = []
video_urls: list[str] = [] video_urls: list[str] = []
@@ -239,11 +196,6 @@ def parse_message(message: Any) -> ParsedMessage:
message_id = _stringify(_get_attr(message, ["id", "message_id", "mid"])) or "unknown-id" message_id = _stringify(_get_attr(message, ["id", "message_id", "mid"])) or "unknown-id"
chat_id = _stringify(_get_attr(message, ["chat_id", "dialog_id", "peer_id"])) or "unknown-chat" chat_id = _stringify(_get_attr(message, ["chat_id", "dialog_id", "peer_id"])) or "unknown-chat"
text = _stringify(_get_attr(message, ["text", "message", "body"])) text = _stringify(_get_attr(message, ["text", "message", "body"]))
forwarded_text = _extract_forwarded_text(message)
if text and forwarded_text and forwarded_text != text:
text = f"{text}\n\n[forwarded]\n{forwarded_text}"
elif not text and forwarded_text:
text = forwarded_text
image_urls, video_urls, file_urls, unknown_attachments = _extract_media_urls(message) image_urls, video_urls, file_urls, unknown_attachments = _extract_media_urls(message)
reply_mid, reply_preview = _extract_max_reply(message) reply_mid, reply_preview = _extract_max_reply(message)