Refactor message handling in broadcast and user management to utilize new utility functions
- Introduced `get_message_content` and `send_message_by_type` utility functions to streamline content type handling and message sending for various media types. - Updated `process_broadcast_message_handler` and `process_direct_message_handler` to leverage these new functions, reducing code duplication and improving maintainability. - Enhanced error handling for empty messages and improved message formatting with admin signatures.
This commit is contained in:
+31
-147
@@ -19,6 +19,7 @@ from bot.keyboards.inline.admin_keyboards import (
|
||||
)
|
||||
from bot.middlewares.i18n import JsonI18n
|
||||
from bot.utils.message_queue import get_queue_manager
|
||||
from bot.utils import get_message_content, send_message_by_type, send_message_via_queue, MessageContent
|
||||
|
||||
router = Router(name="admin_broadcast_router")
|
||||
|
||||
@@ -77,122 +78,33 @@ async def process_broadcast_message_handler(
|
||||
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||
|
||||
# Определяем тип содержимого и сохраняем данные в state
|
||||
text = (message.text or message.caption or "").strip()
|
||||
entities = message.entities or message.caption_entities or []
|
||||
|
||||
content_type = "text"
|
||||
file_id = None
|
||||
|
||||
if message.photo:
|
||||
content_type = "photo"
|
||||
# Берем самое большое фото
|
||||
file_id = message.photo[-1].file_id
|
||||
elif message.video:
|
||||
content_type = "video"
|
||||
file_id = message.video.file_id
|
||||
elif message.animation:
|
||||
content_type = "animation"
|
||||
file_id = message.animation.file_id
|
||||
elif message.document:
|
||||
content_type = "document"
|
||||
file_id = message.document.file_id
|
||||
elif message.audio:
|
||||
content_type = "audio"
|
||||
file_id = message.audio.file_id
|
||||
elif message.voice:
|
||||
content_type = "voice"
|
||||
file_id = message.voice.file_id
|
||||
elif message.sticker:
|
||||
content_type = "sticker"
|
||||
file_id = message.sticker.file_id
|
||||
elif message.video_note:
|
||||
content_type = "video_note"
|
||||
file_id = message.video_note.file_id
|
||||
content = get_message_content(message)
|
||||
|
||||
# Если нет ни текста, ни медиа — ошибка
|
||||
if not text and not file_id:
|
||||
if not content.text and not content.file_id:
|
||||
await message.answer(_("admin_broadcast_error_no_message"))
|
||||
return
|
||||
|
||||
# Сохраняем данные для рассылки
|
||||
await state.update_data(
|
||||
broadcast_text=text,
|
||||
broadcast_text=content.text,
|
||||
broadcast_entities=entities,
|
||||
broadcast_content_type=content_type,
|
||||
broadcast_file_id=file_id,
|
||||
broadcast_content_type=content.content_type,
|
||||
broadcast_file_id=content.file_id,
|
||||
broadcast_target="all",
|
||||
)
|
||||
|
||||
# Отправляем превью-копию того, что будет разослано
|
||||
try:
|
||||
if content_type == "text":
|
||||
await bot.send_message(
|
||||
chat_id=message.chat.id,
|
||||
text=text,
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
disable_notification=True,
|
||||
)
|
||||
elif content_type == "photo":
|
||||
await bot.send_photo(
|
||||
chat_id=message.chat.id,
|
||||
photo=file_id,
|
||||
caption=text or None,
|
||||
parse_mode="HTML",
|
||||
disable_notification=True,
|
||||
)
|
||||
elif content_type == "video":
|
||||
await bot.send_video(
|
||||
chat_id=message.chat.id,
|
||||
video=file_id,
|
||||
caption=text or None,
|
||||
parse_mode="HTML",
|
||||
disable_notification=True,
|
||||
)
|
||||
elif content_type == "animation":
|
||||
await bot.send_animation(
|
||||
chat_id=message.chat.id,
|
||||
animation=file_id,
|
||||
caption=text or None,
|
||||
parse_mode="HTML",
|
||||
disable_notification=True,
|
||||
)
|
||||
elif content_type == "document":
|
||||
await bot.send_document(
|
||||
chat_id=message.chat.id,
|
||||
document=file_id,
|
||||
caption=text or None,
|
||||
parse_mode="HTML",
|
||||
disable_notification=True,
|
||||
)
|
||||
elif content_type == "audio":
|
||||
await bot.send_audio(
|
||||
chat_id=message.chat.id,
|
||||
audio=file_id,
|
||||
caption=text or None,
|
||||
parse_mode="HTML",
|
||||
disable_notification=True,
|
||||
)
|
||||
elif content_type == "voice":
|
||||
await bot.send_voice(
|
||||
chat_id=message.chat.id,
|
||||
voice=file_id,
|
||||
caption=text or None,
|
||||
parse_mode="HTML",
|
||||
disable_notification=True,
|
||||
)
|
||||
elif content_type == "sticker":
|
||||
await bot.send_sticker(
|
||||
chat_id=message.chat.id,
|
||||
sticker=file_id,
|
||||
disable_notification=True,
|
||||
)
|
||||
elif content_type == "video_note":
|
||||
await bot.send_video_note(
|
||||
chat_id=message.chat.id,
|
||||
video_note=file_id,
|
||||
disable_notification=True,
|
||||
)
|
||||
await send_message_by_type(
|
||||
bot,
|
||||
chat_id=message.chat.id,
|
||||
content=content,
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
disable_notification=True,
|
||||
)
|
||||
except TelegramBadRequest as e:
|
||||
await message.answer(
|
||||
_(
|
||||
@@ -308,11 +220,15 @@ async def confirm_broadcast_callback_handler(
|
||||
user_fsm_data = await state.get_data()
|
||||
|
||||
if action == "send":
|
||||
text = user_fsm_data.get("broadcast_text")
|
||||
# Создаем объект контента из сохраненных данных
|
||||
content = MessageContent(
|
||||
content_type=user_fsm_data.get("broadcast_content_type", "text"),
|
||||
file_id=user_fsm_data.get("broadcast_file_id"),
|
||||
text=user_fsm_data.get("broadcast_text")
|
||||
)
|
||||
entities = user_fsm_data.get("broadcast_entities", [])
|
||||
content_type = user_fsm_data.get("broadcast_content_type", "text")
|
||||
file_id = user_fsm_data.get("broadcast_file_id")
|
||||
if not text and content_type == "text":
|
||||
|
||||
if not content.text and content.content_type == "text":
|
||||
await callback.message.edit_text(_("admin_broadcast_error_no_message"))
|
||||
await state.clear()
|
||||
await callback.answer(
|
||||
@@ -335,7 +251,7 @@ async def confirm_broadcast_callback_handler(
|
||||
failed_count = 0
|
||||
admin_user = callback.from_user
|
||||
logging.info(
|
||||
f"Admin {admin_user.id} broadcasting '{text[:50]}...' to {len(user_ids)} users."
|
||||
f"Admin {admin_user.id} broadcasting '{(content.text or '')[:50]}...' to {len(user_ids)} users."
|
||||
)
|
||||
|
||||
# Get message queue manager
|
||||
@@ -347,45 +263,13 @@ async def confirm_broadcast_callback_handler(
|
||||
# Queue all messages for sending
|
||||
for uid in user_ids:
|
||||
try:
|
||||
if content_type == "text":
|
||||
await queue_manager.send_message(
|
||||
chat_id=uid,
|
||||
text=text,
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
elif content_type == "photo":
|
||||
await queue_manager.send_photo(
|
||||
chat_id=uid, photo=file_id, caption=text or None, parse_mode="HTML"
|
||||
)
|
||||
elif content_type == "video":
|
||||
await queue_manager.send_video(
|
||||
chat_id=uid, video=file_id, caption=text or None, parse_mode="HTML"
|
||||
)
|
||||
elif content_type == "animation":
|
||||
await queue_manager.send_animation(
|
||||
chat_id=uid, animation=file_id, caption=text or None, parse_mode="HTML"
|
||||
)
|
||||
elif content_type == "document":
|
||||
await queue_manager.send_document(
|
||||
chat_id=uid, document=file_id, caption=text or None, parse_mode="HTML"
|
||||
)
|
||||
elif content_type == "audio":
|
||||
await queue_manager.send_audio(
|
||||
chat_id=uid, audio=file_id, caption=text or None, parse_mode="HTML"
|
||||
)
|
||||
elif content_type == "voice":
|
||||
await queue_manager.send_voice(
|
||||
chat_id=uid, voice=file_id, caption=text or None, parse_mode="HTML"
|
||||
)
|
||||
elif content_type == "sticker":
|
||||
await queue_manager.send_sticker(
|
||||
chat_id=uid, sticker=file_id
|
||||
)
|
||||
elif content_type == "video_note":
|
||||
await queue_manager.send_video_note(
|
||||
chat_id=uid, video_note=file_id
|
||||
)
|
||||
await send_message_via_queue(
|
||||
queue_manager,
|
||||
uid,
|
||||
content,
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
sent_count += 1
|
||||
|
||||
# Log successful queuing
|
||||
@@ -396,7 +280,7 @@ async def confirm_broadcast_callback_handler(
|
||||
"telegram_username": admin_user.username,
|
||||
"telegram_first_name": admin_user.first_name,
|
||||
"event_type": "admin_broadcast_queued",
|
||||
"content": f"To user {uid}: [{content_type}] {(text or '')[:70]}...",
|
||||
"content": f"To user {uid}: [{content.content_type}] {(content.text or '')[:70]}...",
|
||||
"is_admin_event": True,
|
||||
"target_user_id": uid,
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ from bot.keyboards.inline.admin_keyboards import get_back_to_admin_panel_keyboar
|
||||
from bot.services.subscription_service import SubscriptionService
|
||||
from bot.services.panel_api_service import PanelApiService
|
||||
from bot.middlewares.i18n import JsonI18n
|
||||
from bot.utils import get_message_content, send_direct_message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder, InlineKeyboardButton
|
||||
|
||||
router = Router(name="admin_user_management_router")
|
||||
@@ -615,120 +616,33 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
# Prepare admin signature and content type
|
||||
# Prepare admin signature and get content
|
||||
admin_signature = _(
|
||||
"admin_direct_message_signature",
|
||||
default="\n\n---\n💬 Сообщение от администратора"
|
||||
)
|
||||
|
||||
content = get_message_content(message)
|
||||
|
||||
content_type = "text"
|
||||
file_id = None
|
||||
|
||||
if message.photo:
|
||||
content_type = "photo"
|
||||
file_id = message.photo[-1].file_id
|
||||
elif message.video:
|
||||
content_type = "video"
|
||||
file_id = message.video.file_id
|
||||
elif message.animation:
|
||||
content_type = "animation"
|
||||
file_id = message.animation.file_id
|
||||
elif message.document:
|
||||
content_type = "document"
|
||||
file_id = message.document.file_id
|
||||
elif message.audio:
|
||||
content_type = "audio"
|
||||
file_id = message.audio.file_id
|
||||
elif message.voice:
|
||||
content_type = "voice"
|
||||
file_id = message.voice.file_id
|
||||
elif message.sticker:
|
||||
content_type = "sticker"
|
||||
file_id = message.sticker.file_id
|
||||
elif message.video_note:
|
||||
content_type = "video_note"
|
||||
file_id = message.video_note.file_id
|
||||
|
||||
if not text and not file_id:
|
||||
if not content.text and not content.file_id:
|
||||
await message.answer(_(
|
||||
"admin_direct_empty_message",
|
||||
default="❌ Пустое сообщение. Отправьте текст или медиа."
|
||||
))
|
||||
return
|
||||
|
||||
caption_with_signature = (text + admin_signature) if text else None
|
||||
caption_with_signature = (content.text + admin_signature) if content.text else None
|
||||
|
||||
# Send to target user similar to broadcast
|
||||
# Send to target user using our fancy match/case function
|
||||
try:
|
||||
if content_type == "text":
|
||||
await bot.send_message(
|
||||
target_user_id,
|
||||
(caption_with_signature or admin_signature),
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
elif content_type == "photo":
|
||||
await bot.send_photo(
|
||||
chat_id=target_user_id,
|
||||
photo=file_id,
|
||||
caption=caption_with_signature,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
elif content_type == "video":
|
||||
await bot.send_video(
|
||||
chat_id=target_user_id,
|
||||
video=file_id,
|
||||
caption=caption_with_signature,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
elif content_type == "animation":
|
||||
await bot.send_animation(
|
||||
chat_id=target_user_id,
|
||||
animation=file_id,
|
||||
caption=caption_with_signature,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
elif content_type == "document":
|
||||
await bot.send_document(
|
||||
chat_id=target_user_id,
|
||||
document=file_id,
|
||||
caption=caption_with_signature,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
elif content_type == "audio":
|
||||
await bot.send_audio(
|
||||
chat_id=target_user_id,
|
||||
audio=file_id,
|
||||
caption=caption_with_signature,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
elif content_type == "voice":
|
||||
await bot.send_voice(
|
||||
chat_id=target_user_id,
|
||||
voice=file_id,
|
||||
caption=caption_with_signature,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
elif content_type == "sticker":
|
||||
# Stickers do not support captions; send sticker and then optional signature/text
|
||||
await bot.send_sticker(chat_id=target_user_id, sticker=file_id)
|
||||
if caption_with_signature:
|
||||
await bot.send_message(
|
||||
target_user_id,
|
||||
caption_with_signature,
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
elif content_type == "video_note":
|
||||
# Video notes do not support captions; send media then optional signature/text
|
||||
await bot.send_video_note(chat_id=target_user_id, video_note=file_id)
|
||||
if caption_with_signature:
|
||||
await bot.send_message(
|
||||
target_user_id,
|
||||
caption_with_signature,
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
await send_direct_message(
|
||||
bot,
|
||||
target_user_id,
|
||||
content,
|
||||
extra_text=admin_signature,
|
||||
parse_mode="HTML",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
except TelegramBadRequest as e:
|
||||
await message.answer(_(
|
||||
"admin_broadcast_invalid_html",
|
||||
|
||||
+229
-1
@@ -1 +1,229 @@
|
||||
# Bot utilities package
|
||||
# Bot utilities package
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from aiogram import types
|
||||
|
||||
|
||||
@dataclass
|
||||
class MessageContent:
|
||||
"""Класс для хранения информации о контенте сообщения"""
|
||||
content_type: str
|
||||
file_id: Optional[str] = None
|
||||
text: Optional[str] = None
|
||||
|
||||
|
||||
def get_message_content(message: types.Message) -> MessageContent:
|
||||
"""
|
||||
Определяет тип контента сообщения и возвращает его данные.
|
||||
Использует match/case вместо длинных if-elif цепочек.
|
||||
"""
|
||||
text = (message.text or message.caption or "").strip()
|
||||
|
||||
# Проверяем наличие медиа-контента
|
||||
media_content = None
|
||||
if message.photo:
|
||||
media_content = ("photo", message.photo[-1].file_id)
|
||||
elif message.video:
|
||||
media_content = ("video", message.video.file_id)
|
||||
elif message.animation:
|
||||
media_content = ("animation", message.animation.file_id)
|
||||
elif message.document:
|
||||
media_content = ("document", message.document.file_id)
|
||||
elif message.audio:
|
||||
media_content = ("audio", message.audio.file_id)
|
||||
elif message.voice:
|
||||
media_content = ("voice", message.voice.file_id)
|
||||
elif message.sticker:
|
||||
media_content = ("sticker", message.sticker.file_id)
|
||||
elif message.video_note:
|
||||
media_content = ("video_note", message.video_note.file_id)
|
||||
|
||||
# Используем match/case для определения типа контента
|
||||
match media_content:
|
||||
case (content_type, file_id):
|
||||
return MessageContent(content_type=content_type, file_id=file_id, text=text)
|
||||
case None:
|
||||
return MessageContent(content_type="text", text=text)
|
||||
case _:
|
||||
return MessageContent(content_type="text", text=text)
|
||||
|
||||
|
||||
async def send_message_by_type(bot, chat_id: int, content: MessageContent, **kwargs) -> None:
|
||||
"""
|
||||
Отправляет сообщение указанного типа.
|
||||
Использует match/case вместо длинных if-elif цепочек.
|
||||
"""
|
||||
match content.content_type:
|
||||
case "text":
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=content.text,
|
||||
**kwargs
|
||||
)
|
||||
case "photo":
|
||||
await bot.send_photo(
|
||||
chat_id=chat_id,
|
||||
photo=content.file_id,
|
||||
caption=content.text or None,
|
||||
**kwargs
|
||||
)
|
||||
case "video":
|
||||
await bot.send_video(
|
||||
chat_id=chat_id,
|
||||
video=content.file_id,
|
||||
caption=content.text or None,
|
||||
**kwargs
|
||||
)
|
||||
case "animation":
|
||||
await bot.send_animation(
|
||||
chat_id=chat_id,
|
||||
animation=content.file_id,
|
||||
caption=content.text or None,
|
||||
**kwargs
|
||||
)
|
||||
case "document":
|
||||
await bot.send_document(
|
||||
chat_id=chat_id,
|
||||
document=content.file_id,
|
||||
caption=content.text or None,
|
||||
**kwargs
|
||||
)
|
||||
case "audio":
|
||||
await bot.send_audio(
|
||||
chat_id=chat_id,
|
||||
audio=content.file_id,
|
||||
caption=content.text or None,
|
||||
**kwargs
|
||||
)
|
||||
case "voice":
|
||||
await bot.send_voice(
|
||||
chat_id=chat_id,
|
||||
voice=content.file_id,
|
||||
caption=content.text or None,
|
||||
**kwargs
|
||||
)
|
||||
case "sticker":
|
||||
await bot.send_sticker(
|
||||
chat_id=chat_id,
|
||||
sticker=content.file_id,
|
||||
# stickers не поддерживают caption - удаляем его из kwargs
|
||||
**{k: v for k, v in kwargs.items() if k != 'caption'}
|
||||
)
|
||||
case "video_note":
|
||||
await bot.send_video_note(
|
||||
chat_id=chat_id,
|
||||
video_note=content.file_id,
|
||||
# video_note не поддерживает caption - удаляем его из kwargs
|
||||
**{k: v for k, v in kwargs.items() if k != 'caption'}
|
||||
)
|
||||
case _:
|
||||
# Fallback для неизвестных типов
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=content.text or "Unknown content type",
|
||||
**{k: v for k, v in kwargs.items() if k != 'caption'}
|
||||
)
|
||||
|
||||
|
||||
async def send_message_via_queue(queue_manager, uid: int, content: MessageContent, **kwargs) -> None:
|
||||
"""
|
||||
Отправляет сообщение через очередь в зависимости от типа контента.
|
||||
Использует match/case вместо длинных if-elif цепочек.
|
||||
"""
|
||||
match content.content_type:
|
||||
case "text":
|
||||
await queue_manager.send_message(
|
||||
chat_id=uid, text=content.text, **kwargs
|
||||
)
|
||||
case "photo":
|
||||
await queue_manager.send_photo(
|
||||
chat_id=uid, photo=content.file_id, caption=content.text or None, **kwargs
|
||||
)
|
||||
case "video":
|
||||
await queue_manager.send_video(
|
||||
chat_id=uid, video=content.file_id, caption=content.text or None, **kwargs
|
||||
)
|
||||
case "animation":
|
||||
await queue_manager.send_animation(
|
||||
chat_id=uid, animation=content.file_id, caption=content.text or None, **kwargs
|
||||
)
|
||||
case "document":
|
||||
await queue_manager.send_document(
|
||||
chat_id=uid, document=content.file_id, caption=content.text or None, **kwargs
|
||||
)
|
||||
case "audio":
|
||||
await queue_manager.send_audio(
|
||||
chat_id=uid, audio=content.file_id, caption=content.text or None, **kwargs
|
||||
)
|
||||
case "voice":
|
||||
await queue_manager.send_voice(
|
||||
chat_id=uid, voice=content.file_id, caption=content.text or None, **kwargs
|
||||
)
|
||||
case "sticker":
|
||||
await queue_manager.send_sticker(
|
||||
chat_id=uid, sticker=content.file_id
|
||||
)
|
||||
case "video_note":
|
||||
await queue_manager.send_video_note(
|
||||
chat_id=uid, video_note=content.file_id
|
||||
)
|
||||
case _:
|
||||
# Fallback для неизвестных типов
|
||||
await queue_manager.send_message(
|
||||
chat_id=uid, text=content.text or "Unknown content type", **kwargs
|
||||
)
|
||||
|
||||
|
||||
async def send_direct_message(bot, chat_id: int, content: MessageContent, extra_text: str = "", **kwargs) -> None:
|
||||
"""
|
||||
Отправляет прямое сообщение с дополнительной обработкой для sticker и video_note.
|
||||
Для этих типов медиа отправляется отдельное текстовое сообщение, т.к. они не поддерживают caption.
|
||||
"""
|
||||
match content.content_type:
|
||||
case "sticker":
|
||||
# Отправляем стикер
|
||||
await bot.send_sticker(
|
||||
chat_id=chat_id,
|
||||
sticker=content.file_id,
|
||||
**{k: v for k, v in kwargs.items() if k != 'caption'}
|
||||
)
|
||||
# Если есть текст с подписью, отправляем отдельно
|
||||
if content.text or extra_text:
|
||||
text_to_send = (content.text + extra_text) if content.text else extra_text
|
||||
await bot.send_message(
|
||||
chat_id,
|
||||
text_to_send,
|
||||
**{k: v for k, v in kwargs.items() if k not in ['caption']}
|
||||
)
|
||||
case "video_note":
|
||||
# Отправляем видео-заметку
|
||||
await bot.send_video_note(
|
||||
chat_id=chat_id,
|
||||
video_note=content.file_id,
|
||||
**{k: v for k, v in kwargs.items() if k != 'caption'}
|
||||
)
|
||||
# Если есть текст с подписью, отправляем отдельно
|
||||
if content.text or extra_text:
|
||||
text_to_send = (content.text + extra_text) if content.text else extra_text
|
||||
await bot.send_message(
|
||||
chat_id,
|
||||
text_to_send,
|
||||
**{k: v for k, v in kwargs.items() if k not in ['caption']}
|
||||
)
|
||||
case "text":
|
||||
# Для текста объединяем с extra_text
|
||||
final_text = (content.text + extra_text) if content.text else extra_text
|
||||
await bot.send_message(
|
||||
chat_id=chat_id,
|
||||
text=final_text,
|
||||
**kwargs
|
||||
)
|
||||
case _:
|
||||
# Для остальных типов медиа используем caption
|
||||
final_caption = (content.text + extra_text) if content.text else None
|
||||
await send_message_by_type(
|
||||
bot, chat_id,
|
||||
MessageContent(content.content_type, content.file_id, final_caption),
|
||||
**kwargs
|
||||
)
|
||||
Reference in New Issue
Block a user