fix: use parsed HTML length for Telegram caption limit checks

Replace hardcoded raw HTML length checks (len(text) <= 900/1000/1024)
with centralized caption_exceeds_telegram_limit() that strips HTML tags
and unescapes entities before measuring against the real 1024-char limit.
Fixes logo disappearing when promo discounts add HTML markup to captions.
This commit is contained in:
Fringg
2026-03-09 02:43:43 +03:00
parent 4a5cacda38
commit 2649e12f64
5 changed files with 29 additions and 10 deletions
+4 -4
View File
@@ -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(),
+3 -2
View File
@@ -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,
+2 -1
View File
@@ -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
+18 -2
View File
@@ -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:
+2 -1
View File
@@ -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()