diff --git a/app/handlers/start.py b/app/handlers/start.py index 6576e7f1..b8a49355 100644 --- a/app/handlers/start.py +++ b/app/handlers/start.py @@ -1958,7 +1958,7 @@ async def get_main_menu_text_simple(user_name, texts, db: AsyncSession): async def required_sub_channel_check( query: types.CallbackQuery, bot: Bot, state: FSMContext, db: AsyncSession, db_user=None ): - from app.utils.message_patch import _cache_logo_file_id, get_logo_media + from app.utils.message_patch import _cache_logo_file_id, caption_exceeds_telegram_limit, get_logo_media language = DEFAULT_LANGUAGE texts = get_texts(language) @@ -2129,7 +2129,7 @@ async def required_sub_channel_check( if pinned_message and pinned_message.send_before_menu: await _send_pinned_message(bot, db, user, pinned_message) - if settings.ENABLE_LOGO_MODE and len(menu_text) <= 900: + if settings.ENABLE_LOGO_MODE and not caption_exceeds_telegram_limit(menu_text): _result = await bot.send_photo( chat_id=query.from_user.id, photo=get_logo_media(), @@ -2255,7 +2255,7 @@ async def required_sub_channel_check( if pinned_message and pinned_message.send_before_menu: await _send_pinned_message(bot, db, user, pinned_message) - if settings.ENABLE_LOGO_MODE and len(menu_text) <= 900: + if settings.ENABLE_LOGO_MODE and not caption_exceeds_telegram_limit(menu_text): _result = await bot.send_photo( chat_id=query.from_user.id, photo=get_logo_media(), @@ -2286,7 +2286,7 @@ async def required_sub_channel_check( else: rules_text = await get_rules(language) - if settings.ENABLE_LOGO_MODE and len(rules_text) <= 900: + if settings.ENABLE_LOGO_MODE and not caption_exceeds_telegram_limit(rules_text): _result = await bot.send_photo( chat_id=query.from_user.id, photo=get_logo_media(), diff --git a/app/services/admin_notification_service.py b/app/services/admin_notification_service.py index c92be503..dc40195d 100644 --- a/app/services/admin_notification_service.py +++ b/app/services/admin_notification_service.py @@ -22,6 +22,7 @@ from app.database.models import ( Transaction, User, ) +from app.utils.message_patch import caption_exceeds_telegram_limit from app.utils.timezone import format_local_datetime @@ -1915,7 +1916,7 @@ class AdminNotificationService: keyboard: types.InlineKeyboardMarkup | None = None, ) -> bool: """Отправить фото с текстом в тикет-топик. - Если текст <= 1024 символов — отправляем фото с caption. + Если текст помещается в caption (≤1024 символов после парсинга HTML) — фото с caption. Иначе — сначала текст, потом фото в тот же топик. """ if not self.chat_id: @@ -1924,7 +1925,7 @@ class AdminNotificationService: thread_id = self.ticket_topic_id or self.topic_id try: - if len(text) <= 1024: + if not caption_exceeds_telegram_limit(text): # Фото с caption — всё в одном сообщении photo_kwargs: dict = { 'chat_id': self.chat_id, diff --git a/app/services/monitoring_service.py b/app/services/monitoring_service.py index e4fe7232..ffe528f8 100644 --- a/app/services/monitoring_service.py +++ b/app/services/monitoring_service.py @@ -58,6 +58,7 @@ from app.services.notification_settings_service import NotificationSettingsServi from app.services.promo_offer_service import promo_offer_service from app.services.subscription_service import SubscriptionService from app.utils.cache import cache +from app.utils.message_patch import caption_exceeds_telegram_limit from app.utils.miniapp_buttons import build_miniapp_or_callback_button from app.utils.promo_offer import get_user_active_promo_discount_percent from app.utils.subscription_utils import ( @@ -110,7 +111,7 @@ class MonitoringService: logger.debug('Пропуск уведомления: пользователь недоступен', user_id=user.id, status=user.status) return None - if settings.ENABLE_LOGO_MODE and LOGO_PATH.exists() and (text is None or len(text) <= 1000): + if settings.ENABLE_LOGO_MODE and LOGO_PATH.exists() and not caption_exceeds_telegram_limit(text): try: from app.utils.message_patch import _cache_logo_file_id, get_logo_media diff --git a/app/utils/message_patch.py b/app/utils/message_patch.py index 8fd6049e..e6ca9cd6 100644 --- a/app/utils/message_patch.py +++ b/app/utils/message_patch.py @@ -1,3 +1,5 @@ +import html as html_module +import re from pathlib import Path from typing import Any @@ -9,6 +11,20 @@ from app.localization.texts import get_texts LOGO_PATH = Path(settings.LOGO_FILE) + +# Telegram API: caption limit is 1024 characters AFTER HTML entity parsing (tags stripped) +TELEGRAM_CAPTION_LIMIT = 1024 +_HTML_TAG_RE = re.compile(r'<[^>]+>') + + +def caption_exceeds_telegram_limit(text: str | None) -> bool: + """Check if text exceeds Telegram's caption limit (1024 parsed chars).""" + if not text: + return False + stripped = html_module.unescape(_HTML_TAG_RE.sub('', text)) + return len(stripped) > TELEGRAM_CAPTION_LIMIT + + _PRIVACY_RESTRICTED_CODE = 'BUTTON_USER_PRIVACY_RESTRICTED' # Кеш file_id логотипа: после первой загрузки Telegram возвращает file_id, @@ -139,7 +155,7 @@ async def _answer_with_photo(self: Message, text: str = None, **kwargs): return await _original_answer(self, text, **kwargs) # Если caption слишком длинный для фото — отправим как текст try: - if text is not None and len(text) > 900: + if caption_exceeds_telegram_limit(text): return await _text_answer(self, text, **kwargs) except Exception: pass @@ -207,7 +223,7 @@ async def _edit_with_photo(self: Message, text: str, **kwargs): language = _get_language(self) # Если caption потенциально слишком длинный — отправим как текст вместо caption try: - if text is not None and len(text) > 900: + if caption_exceeds_telegram_limit(text): try: await self.delete() except Exception: diff --git a/app/utils/photo_message.py b/app/utils/photo_message.py index e9273d02..b04268b9 100644 --- a/app/utils/photo_message.py +++ b/app/utils/photo_message.py @@ -11,6 +11,7 @@ from .message_patch import ( LOGO_PATH, _cache_logo_file_id, append_privacy_hint, + caption_exceeds_telegram_limit, get_logo_media, is_privacy_restricted_error, is_qr_message, @@ -137,7 +138,7 @@ async def edit_or_answer_photo( return # Если текст слишком длинный для caption — отправим как текст - if caption and len(caption) > 1000: + if caption_exceeds_telegram_limit(caption): try: if callback.message.photo: await callback.message.delete()