mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-01-19 19:32:10 +00:00
Revert "Remove blockquote markup to prevent Telegram parse errors"
This commit is contained in:
@@ -14,7 +14,6 @@ from app.database.models import User
|
||||
from app.keyboards.admin import get_admin_main_keyboard
|
||||
from app.utils.validators import (
|
||||
get_html_help_text,
|
||||
format_telegram_quote,
|
||||
sanitize_html,
|
||||
validate_html_tags,
|
||||
)
|
||||
@@ -183,14 +182,14 @@ async def process_new_message_text(
|
||||
)
|
||||
|
||||
await state.clear()
|
||||
|
||||
|
||||
await message.answer(
|
||||
f"✅ <b>Сообщение добавлено!</b>\n\n"
|
||||
f"<b>ID:</b> {new_message.id}\n"
|
||||
f"<b>Статус:</b> {'🟢 Активно' if new_message.is_active else '🔴 Неактивно'}\n"
|
||||
f"<b>Создано:</b> {new_message.created_at.strftime('%d.%m.%Y %H:%M')}\n\n"
|
||||
f"<b>Предварительный просмотр:</b>\n"
|
||||
f"{format_telegram_quote(sanitize_html(message_text))}",
|
||||
f"<blockquote>{message_text}</blockquote>",
|
||||
reply_markup=get_user_messages_keyboard(db_user.language),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
@@ -319,7 +318,7 @@ async def view_user_message(
|
||||
await callback.answer("❌ Сообщение не найдено", show_alert=True)
|
||||
return
|
||||
|
||||
safe_content = format_telegram_quote(sanitize_html(message.message_text))
|
||||
safe_content = sanitize_html(message.message_text)
|
||||
|
||||
status_text = "🟢 Активно" if message.is_active else "🔴 Неактивно"
|
||||
|
||||
@@ -329,7 +328,7 @@ async def view_user_message(
|
||||
f"<b>Создано:</b> {message.created_at.strftime('%d.%m.%Y %H:%M')}\n"
|
||||
f"<b>Обновлено:</b> {message.updated_at.strftime('%d.%m.%Y %H:%M')}\n\n"
|
||||
f"<b>Содержимое:</b>\n"
|
||||
f"{safe_content}"
|
||||
f"<blockquote>{safe_content}</blockquote>"
|
||||
)
|
||||
|
||||
await callback.message.edit_text(
|
||||
@@ -459,7 +458,7 @@ async def edit_user_message_start(
|
||||
await callback.message.edit_text(
|
||||
f"✏️ <b>Редактирование сообщения ID {message.id}</b>\n\n"
|
||||
f"<b>Текущий текст:</b>\n"
|
||||
f"{format_telegram_quote(sanitize_html(message.message_text))}\n\n"
|
||||
f"<blockquote>{sanitize_html(message.message_text)}</blockquote>\n\n"
|
||||
f"Введите новый текст сообщения или отправьте /cancel для отмены:",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
@@ -524,7 +523,7 @@ async def process_edit_message_text(
|
||||
f"<b>ID:</b> {updated_message.id}\n"
|
||||
f"<b>Обновлено:</b> {updated_message.updated_at.strftime('%d.%m.%Y %H:%M')}\n\n"
|
||||
f"<b>Новый текст:</b>\n"
|
||||
f"{format_telegram_quote(sanitize_html(new_text))}",
|
||||
f"<blockquote>{sanitize_html(new_text)}</blockquote>",
|
||||
reply_markup=get_user_messages_keyboard(db_user.language),
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
@@ -194,7 +194,7 @@ def _format_server_lines(
|
||||
name = server.display_name or server.name
|
||||
flag_prefix = f"{server.flag} " if server.flag else ""
|
||||
server_line = f"{flag_prefix}{name} — {latency_text}"
|
||||
lines.append(f"• {server_line}")
|
||||
lines.append(f"<blockquote>{server_line}</blockquote>")
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ async def show_subscription_info(
|
||||
if show_devices and devices_list:
|
||||
message += "\n\n" + texts.t(
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_TITLE",
|
||||
"📱 <b>Подключенные устройства:</b>\n",
|
||||
"<blockquote>📱 <b>Подключенные устройства:</b>\n",
|
||||
)
|
||||
for device in devices_list[:5]:
|
||||
platform = device.get('platform', 'Unknown')
|
||||
@@ -365,7 +365,7 @@ async def show_subscription_info(
|
||||
if len(device_info) > 35:
|
||||
device_info = device_info[:32] + "..."
|
||||
message += f"• {device_info}\n"
|
||||
message += texts.t("SUBSCRIPTION_CONNECTED_DEVICES_FOOTER", "")
|
||||
message += texts.t("SUBSCRIPTION_CONNECTED_DEVICES_FOOTER", "</blockquote>")
|
||||
|
||||
subscription_link = get_display_subscription_link(subscription)
|
||||
hide_subscription_link = settings.should_hide_subscription_link()
|
||||
|
||||
@@ -1276,8 +1276,8 @@
|
||||
"SUBSCRIPTION_APPS_PROMPT": "Choose an app to connect:",
|
||||
"SUBSCRIPTION_APPS_TITLE": "📱 <b>Apps for {device_name}</b>",
|
||||
"SUBSCRIPTION_APP_NOT_FOUND": "❌ App not found",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_FOOTER": "",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_TITLE": "📱 <b>Connected devices:</b>\n",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_FOOTER": "</blockquote>",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_TITLE": "<blockquote>📱 <b>Connected devices:</b>\n",
|
||||
"SUBSCRIPTION_CONNECT_CUSTOM_MESSAGE": "🚀 <b>Connect subscription</b>\n\n📱 Tap the button below to open the app:",
|
||||
"SUBSCRIPTION_CONNECT_DEVICE_MESSAGE": "📱 <b>Connect subscription</b>\n\n🔗 <b>Subscription link:</b>\n<code>{subscription_url}</code>\n\n💡 <b>Choose your device</b> to get detailed setup instructions:",
|
||||
"SUBSCRIPTION_CONNECT_DEVICE_MESSAGE_HIDDEN": "📱 <b>Connect subscription</b>\n\nℹ️ The subscription link is available via the buttons below or in the “My subscription” section.\n\n💡 <b>Choose your device</b> to get detailed setup instructions:",
|
||||
|
||||
@@ -1288,8 +1288,8 @@
|
||||
"SUBSCRIPTION_APPS_PROMPT": "Выберите приложение для подключения:",
|
||||
"SUBSCRIPTION_APPS_TITLE": "📱 <b>Приложения для {device_name}</b>",
|
||||
"SUBSCRIPTION_APP_NOT_FOUND": "❌ Приложение не найдено",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_FOOTER": "",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_TITLE": "📱 <b>Подключенные устройства:</b>\n",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_FOOTER": "</blockquote>",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_TITLE": "<blockquote>📱 <b>Подключенные устройства:</b>\n",
|
||||
"SUBSCRIPTION_CONNECT_CUSTOM_MESSAGE": "🚀 <b>Подключить подписку</b>\n\n📱 Нажмите кнопку ниже, чтобы открыть приложение:",
|
||||
"SUBSCRIPTION_CONNECT_DEVICE_MESSAGE": "📱 <b>Подключить подписку</b>\n\n🔗 <b>Ссылка подписки:</b>\n<code>{subscription_url}</code>\n\n💡 <b>Выберите ваше устройство</b> для получения подробной инструкции по настройке:",
|
||||
"SUBSCRIPTION_CONNECT_DEVICE_MESSAGE_HIDDEN": "📱 <b>Подключить подписку</b>\n\nℹ️ Ссылка подписки доступна по кнопкам ниже или в разделе «Моя подписка».\n\n💡 <b>Выберите ваше устройство</b> для получения подробной инструкции по настройке:",
|
||||
|
||||
@@ -1280,8 +1280,8 @@
|
||||
"SUBSCRIPTION_APPS_PROMPT": "Оберіть додаток для підключення:",
|
||||
"SUBSCRIPTION_APPS_TITLE": "📱 <b>Додатки для {device_name}</b>",
|
||||
"SUBSCRIPTION_APP_NOT_FOUND": "❌ Додаток не знайдено",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_FOOTER": "",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_TITLE": "📱 <b>Підключені пристрої:</b>\n",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_FOOTER": "</blockquote>",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_TITLE": "<blockquote>📱 <b>Підключені пристрої:</b>\n",
|
||||
"SUBSCRIPTION_CONNECT_CUSTOM_MESSAGE": "🚀 <b>Підключити підписку</b>\n\n📱 Натисніть кнопку нижче, щоб відкрити додаток:",
|
||||
"SUBSCRIPTION_CONNECT_DEVICE_MESSAGE": "📱 <b>Підключити підписку</b>\n\n🔗 <b>Посилання підписки:</b>\n<code>{subscription_url}</code>\n\n💡 <b>Оберіть ваш пристрій</b> для отримання детальної інструкції з налаштування:",
|
||||
"SUBSCRIPTION_CONNECT_DEVICE_MESSAGE_HIDDEN": "📱 <b>Підключити підписку</b>\n\nℹ️ Посилання підписки доступне за кнопками нижче або в розділі «Моя підписка».\n\n💡 <b>Оберіть ваш пристрій</b> для отримання детальної інструкції з налаштування:",
|
||||
|
||||
@@ -1279,8 +1279,8 @@
|
||||
"SUBSCRIPTION_APPS_PROMPT":"请选择要连接的应用程序:",
|
||||
"SUBSCRIPTION_APPS_TITLE":"📱<b>适用于{device_name}的应用程序</b>",
|
||||
"SUBSCRIPTION_APP_NOT_FOUND":"❌未找到应用程序",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_FOOTER":"",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_TITLE":"📱<b>已连接设备:</b>\n",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_FOOTER":"</blockquote>",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_TITLE":"<blockquote>📱<b>已连接设备:</b>\n",
|
||||
"SUBSCRIPTION_CONNECT_CUSTOM_MESSAGE":"🚀<b>连接订阅</b>\n\n📱点击下方按钮打开应用程序:",
|
||||
"SUBSCRIPTION_CONNECT_DEVICE_MESSAGE":"📱<b>连接订阅</b>\n\n🔗<b>订阅链接:</b>\n<code>{subscription_url}</code>\n\n💡<b>请选择您的设备</b>以获取详细设置说明:",
|
||||
"SUBSCRIPTION_CONNECT_DEVICE_MESSAGE_HIDDEN":"📱<b>连接订阅</b>\n\nℹ️订阅链接在下方按钮中或“我的订阅”部分可用。\n\n💡<b>请选择您的设备</b>以获取详细设置说明:",
|
||||
@@ -1606,8 +1606,8 @@
|
||||
"SUBSCRIPTION_APPS_PROMPT":"请选择要连接的应用程序:",
|
||||
"SUBSCRIPTION_APPS_TITLE":"📱<b>适用于{device_name}的应用程序</b>",
|
||||
"SUBSCRIPTION_APP_NOT_FOUND":"❌未找到应用程序",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_FOOTER":"",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_TITLE":"📱<b>已连接设备:</b>\n",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_FOOTER":"</blockquote>",
|
||||
"SUBSCRIPTION_CONNECTED_DEVICES_TITLE":"<blockquote>📱<b>已连接设备:</b>\n",
|
||||
"SUBSCRIPTION_CONNECT_CUSTOM_MESSAGE":"🚀<b>连接订阅</b>\n\n📱点击下方按钮打开应用程序:",
|
||||
"SUBSCRIPTION_CONNECT_DEVICE_MESSAGE":"📱<b>连接订阅</b>\n\n🔗<b>订阅链接:</b>\n<code>{subscription_url}</code>\n\n💡<b>请选择您的设备</b>以获取详细设置说明:",
|
||||
"SUBSCRIPTION_CONNECT_DEVICE_MESSAGE_HIDDEN":"📱<b>连接订阅</b>\n\nℹ️订阅链接在下方按钮中或“我的订阅”部分可用。\n\n💡<b>请选择您的设备</b>以获取详细设置说明:",
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import html
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
@@ -65,14 +63,6 @@ def append_privacy_hint(text: str | None, language: str | None) -> str:
|
||||
return hint
|
||||
|
||||
|
||||
def _strip_html(text: str | None) -> str:
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
plain_text = html.unescape(re.sub(r"<[^>]+>", "", text))
|
||||
return plain_text.strip()
|
||||
|
||||
|
||||
def prepare_privacy_safe_kwargs(kwargs: Dict[str, Any] | None = None) -> Dict[str, Any]:
|
||||
safe_kwargs: Dict[str, Any] = dict(kwargs or {})
|
||||
safe_kwargs.pop("reply_markup", None)
|
||||
@@ -110,10 +100,7 @@ async def _answer_with_photo(self: Message, text: str = None, **kwargs):
|
||||
safe_kwargs = prepare_privacy_safe_kwargs(kwargs)
|
||||
return await _original_answer(self, fallback_text, **safe_kwargs)
|
||||
# Фоллбек, если Telegram ругается на caption или другое ограничение: отправим как текст
|
||||
fallback_text = _strip_html(text)
|
||||
safe_kwargs = dict(kwargs)
|
||||
safe_kwargs.pop("parse_mode", None)
|
||||
return await _original_answer(self, fallback_text, **safe_kwargs)
|
||||
return await _original_answer(self, text, **kwargs)
|
||||
except Exception:
|
||||
return await _original_answer(self, text, **kwargs)
|
||||
return await _original_answer(self, text, **kwargs)
|
||||
@@ -166,10 +153,7 @@ async def _edit_with_photo(self: Message, text: str, **kwargs):
|
||||
await self.delete()
|
||||
except Exception:
|
||||
pass
|
||||
fallback_text = _strip_html(text)
|
||||
safe_kwargs = dict(kwargs)
|
||||
safe_kwargs.pop("parse_mode", None)
|
||||
return await _original_answer(self, fallback_text, **safe_kwargs)
|
||||
return await _original_answer(self, text, **kwargs)
|
||||
return await _original_edit_text(self, text, **kwargs)
|
||||
|
||||
|
||||
@@ -178,3 +162,4 @@ def patch_message_methods():
|
||||
return
|
||||
Message.answer = _answer_with_photo
|
||||
Message.edit_text = _edit_with_photo
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import html
|
||||
import re
|
||||
|
||||
from aiogram import types
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
from aiogram.types import FSInputFile, InputMediaPhoto
|
||||
@@ -36,14 +33,6 @@ def _get_language(callback: types.CallbackQuery) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
def _strip_html(text: str | None) -> str:
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
plain_text = html.unescape(re.sub(r"<[^>]+>", "", text))
|
||||
return plain_text.strip()
|
||||
|
||||
|
||||
def _build_base_kwargs(keyboard: types.InlineKeyboardMarkup | None, parse_mode: str | None):
|
||||
kwargs: dict[str, object] = {}
|
||||
if parse_mode is not None:
|
||||
@@ -68,19 +57,6 @@ async def _answer_text(
|
||||
kwargs = prepare_privacy_safe_kwargs(kwargs)
|
||||
|
||||
kwargs.setdefault("parse_mode", parse_mode or "HTML")
|
||||
try:
|
||||
await callback.message.answer(
|
||||
caption,
|
||||
**kwargs,
|
||||
)
|
||||
return
|
||||
except TelegramBadRequest as send_error:
|
||||
if is_privacy_restricted_error(send_error):
|
||||
caption = append_privacy_hint(caption, language)
|
||||
kwargs = prepare_privacy_safe_kwargs(kwargs)
|
||||
else:
|
||||
caption = _strip_html(caption)
|
||||
kwargs.pop("parse_mode", None)
|
||||
|
||||
await callback.message.answer(
|
||||
caption,
|
||||
|
||||
@@ -4,13 +4,14 @@ from datetime import datetime
|
||||
import html
|
||||
|
||||
ALLOWED_HTML_TAGS = {
|
||||
'b', 'strong',
|
||||
'i', 'em',
|
||||
'u', 'ins',
|
||||
's', 'strike', 'del',
|
||||
'code',
|
||||
'pre',
|
||||
'a'
|
||||
'b', 'strong',
|
||||
'i', 'em',
|
||||
'u', 'ins',
|
||||
's', 'strike', 'del',
|
||||
'code',
|
||||
'pre',
|
||||
'a',
|
||||
'blockquote'
|
||||
}
|
||||
|
||||
SELF_CLOSING_TAGS = {
|
||||
@@ -145,24 +146,6 @@ def sanitize_html(text: str) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def strip_blockquote_tags(text: str) -> str:
|
||||
"""Remove Telegram-unsupported blockquote tags (both raw and escaped)."""
|
||||
if not text:
|
||||
return text
|
||||
|
||||
without_tags = re.sub(r"</?blockquote>\s*", "", text, flags=re.IGNORECASE)
|
||||
without_escaped = re.sub(r"</?blockquote[^&]*>\s*", "", without_tags, flags=re.IGNORECASE)
|
||||
return without_escaped
|
||||
|
||||
|
||||
def format_telegram_quote(text: str | None) -> str:
|
||||
"""Format text as a lightweight quote safe for Telegram HTML parse mode."""
|
||||
clean_text = strip_blockquote_tags(text or "").strip()
|
||||
if not clean_text:
|
||||
return "—"
|
||||
return f"<i>❝ {clean_text} ❞</i>"
|
||||
|
||||
|
||||
def sanitize_telegram_name(name: Optional[str]) -> Optional[str]:
|
||||
"""Санитизация Telegram-имени для безопасной вставки в HTML и хранения.
|
||||
Заменяет угловые скобки и амперсанд на безопасные визуальные аналоги.
|
||||
|
||||
Reference in New Issue
Block a user