"""Обработчики пользовательских сообщений в чате""" from aiogram import Router, F from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from aiogram.filters import StateFilter, Command from src.filters.case_insensitive import CaseInsensitiveCommand from sqlalchemy.ext.asyncio import AsyncSession import asyncio from typing import List, Dict, Optional, Set, Any from collections import deque import time from src.core.chat_services import ( ChatSettingsService, ChatPermissionService, ChatMessageService, BanService ) from src.core.services import UserService from src.core.database import async_session_maker from src.core.config import ADMIN_IDS from src.utils.account_utils import parse_accounts_from_message class ChatStates(StatesGroup): """Состояния для работы в чате""" in_chat = State() # Пользователь находится в режиме чата def is_admin(user_id: int) -> bool: """Проверка является ли пользователь админом""" return user_id in ADMIN_IDS def _contains_account_numbers(text: str) -> bool: """Проверка содержит ли текст номера счетов""" if not text: return False accounts = parse_accounts_from_message(text) return len(accounts) > 0 router = Router(name='chat_router') @router.message(CaseInsensitiveCommand("chat")) async def enter_chat_command(message: Message, state: FSMContext): """Войти в режим чата через команду /chat (регистронезависимо)""" await enter_chat(message, state) @router.callback_query(F.data == "enter_chat") async def enter_chat_callback(callback: CallbackQuery, state: FSMContext): """Войти в режим чата через кнопку""" await callback.answer() await enter_chat(callback.message, state) async def enter_chat(message: Message, state: FSMContext): """Общая функция входа в чат""" from src.utils.keyboards import get_chat_reply_keyboard await state.set_state(ChatStates.in_chat) keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🚪 Выйти из чата", callback_data="exit_chat")], [InlineKeyboardButton(text="🏠 Главная", callback_data="back_to_main")] ]) # Обычная клавиатура для чата reply_keyboard = get_chat_reply_keyboard() await message.answer( "💬 Вы вошли в режим чата\n\n" "Теперь все ваши сообщения будут рассылаться участникам.\n" "Вы можете отправлять текст, фото, видео, документы и стикеры.\n\n" "Для выхода нажмите кнопку ниже или отправьте /exit", reply_markup=reply_keyboard, # Обычная клавиатура parse_mode="HTML" ) # Inline клавиатура отдельным сообщением await message.answer( "Выберите действие:", reply_markup=keyboard ) @router.message(CaseInsensitiveCommand("exit"), StateFilter(ChatStates.in_chat)) async def exit_chat_command(message: Message, state: FSMContext): """Выйти из режима чата через команду /exit (регистронезависимо)""" await exit_chat(message, state) @router.callback_query(F.data == "exit_chat", StateFilter(ChatStates.in_chat)) async def exit_chat_callback(callback: CallbackQuery, state: FSMContext): """Выйти из режима чата через кнопку""" await callback.answer() await exit_chat(callback.message, state) async def exit_chat(message: Message, state: FSMContext): """Общая функция выхода из чата""" from src.utils.keyboards import get_main_reply_keyboard from src.core.config import ADMIN_IDS from src.core.services import UserService from src.core.database import async_session_maker await state.clear() # Получаем информацию о пользователе async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, message.from_user.id) is_registered = user.is_registered if user else False is_admin_user = message.from_user.id in ADMIN_IDS keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="💬 Войти в чат", callback_data="enter_chat")], [InlineKeyboardButton(text="🏠 Главная", callback_data="back_to_main")] ]) # Обычная клавиатура reply_keyboard = get_main_reply_keyboard(is_admin=is_admin_user, is_registered=is_registered) await message.answer( "✅ Вы вышли из режима чата\n\n" "Ваши сообщения больше не будут рассылаться.", reply_markup=reply_keyboard, # Обычная клавиатура parse_mode="HTML" ) # Inline клавиатура отдельным сообщением await message.answer( "Выберите действие:", reply_markup=keyboard ) @router.message(StateFilter(ChatStates.in_chat), F.text) async def check_exit_keywords(message: Message, state: FSMContext): """Проверка на ключевые слова для выхода из чата + обработка сообщений""" import logging logger = logging.getLogger(__name__) text = message.text.strip().lower() # Проверяем ключевые слова для выхода exit_keywords = ['/start', 'start', 'старт', '/exit'] if text in exit_keywords: if text in ['/start', 'start', 'старт']: # Выходим из чата и показываем главное меню await state.clear() from src.components.ui import UserUI keyboard = UserUI.get_main_menu_keyboard(message.from_user.id) await message.answer( "🏠 Главное меню\n\n" "Вы вышли из режима чата.", reply_markup=keyboard, parse_mode="HTML" ) return # Не обрабатываем дальше else: # Для /exit просто выходим await exit_chat(message, state) return # ===== ОБРАБОТКА ОБЫЧНОГО СООБЩЕНИЯ ЧАТА ===== # Защита от дубликатов - если сообщение уже обработано, пропускаем if _is_message_processed(message.message_id): logger.warning(f"[CHAT] Дубликат сообщения {message.message_id}, пропускаем") return logger.info(f"[CHAT] check_exit_keywords вызван для обработки: user={message.from_user.id}, text={message.text[:50] if message.text else 'None'}") # ПРОВЕРКА СЧЕТОВ: Если админ отправил сообщение с номерами счетов - НЕ рассылаем # Пропускаем для account_router (который идет после chat_router) if is_admin(message.from_user.id) and message.text and not message.text.startswith('/'): if _contains_account_numbers(message.text): logger.info(f"[CHAT] Обнаружены счета от админа, пропускаем - account_router обработает") # Не делаем return, выбрасываем исключение для пропуска в следующий обработчик from aiogram.handlers import SkipHandler raise SkipHandler() # БЫСТРОЕ УДАЛЕНИЕ: Если админ отвечает на сообщение словом "удалить"/"del"/"-" if message.reply_to_message and is_admin(message.from_user.id): if message.text and message.text.lower().strip() in ['удалить', 'del', '-']: async with async_session_maker() as session: # Ищем сообщение в БД по telegram_message_id msg_to_delete = await ChatMessageService.get_message_by_telegram_id( session, telegram_message_id=message.reply_to_message.message_id ) if msg_to_delete: # Получаем админа admin = await UserService.get_or_create_user( session, message.from_user.id, username=message.from_user.username, first_name=message.from_user.first_name, last_name=message.from_user.last_name ) # Помечаем как удаленное success = await ChatMessageService.mark_as_deleted( session, msg_to_delete.id, admin.id if admin else None ) if success: # Удаляем у всех получателей deleted_count = 0 if msg_to_delete.forwarded_message_ids: for user_tg_id, tg_msg_id in msg_to_delete.forwarded_message_ids.items(): try: await message.bot.delete_message( chat_id=int(user_tg_id), message_id=tg_msg_id ) deleted_count += 1 except Exception as e: logger.warning(f"Не удалось удалить {tg_msg_id} у {user_tg_id}: {e}") # Удаляем оригинал у отправителя try: await message.bot.delete_message( chat_id=msg_to_delete.sender.telegram_id, message_id=msg_to_delete.telegram_message_id ) deleted_count += 1 except Exception as e: logger.warning(f"Не удалось удалить оригинал: {e}") # Удаляем команду админа try: await message.delete() except: pass # Отправляем уведомление (самоудаляющееся) notification = await message.answer(f"✅ Сообщение удалено у {deleted_count} получателей") await asyncio.sleep(3) try: await notification.delete() except: pass return else: await message.answer("❌ Сообщение не найдено в БД") return # Проверяем является ли это командой if message.text and message.text.startswith('/'): # Список команд, которые НЕ нужно пересылать # (Базовые команды /start, /help уже обработаны раньше в main.py) user_commands = ['/my_code', '/my_accounts'] admin_commands = [ '/add_account', '/remove_account', '/verify_winner', '/winner_status', '/user_info', '/check_unclaimed', '/redraw', '/chat_mode', '/set_forward', '/global_ban', '/ban', '/unban', '/banlist', '/delete_msg', '/chat_stats' ] # Извлекаем команду (первое слово) command = message.text.split()[0] if message.text else '' # ИЗМЕНЕНИЕ: Если это команда от АДМИНА - не пересылаем (админ сам её видит) if is_admin(message.from_user.id): # Если это админская команда - пропускаем, она будет обработана другими обработчиками if command in admin_commands: return # Если это пользовательская команда от админа - тоже пропускаем if command in user_commands: return # Любая другая команда от админа - тоже не пересылаем return # ИЗМЕНЕНИЕ: Если команда от обычного пользователя - ПЕРЕСЫЛАЕМ админу # Чтобы админ видел, что пользователь отправил /start или другую команду # НЕ делаем return, продолжаем выполнение для пересылки async with async_session_maker() as session: # Проверяем права на отправку can_send, reason = await ChatPermissionService.can_send_message( session, message.from_user.id, is_admin=is_admin(message.from_user.id) ) if not can_send: await message.answer(f"❌ {reason}") return # Получаем настройки чата settings = await ChatSettingsService.get_or_create_settings(session) # Получаем или создаем пользователя user = await UserService.get_or_create_user( session, message.from_user.id, username=message.from_user.username, first_name=message.from_user.first_name, last_name=message.from_user.last_name ) # Обрабатываем в зависимости от режима if settings.mode == 'broadcast': # Режим рассылки с планировщиком # Передаем объект user для динамического формирования подписей # ВСЕГДА исключаем отправителя - он не должен получать своё же сообщение forwarded_ids, success, fail = await broadcast_message_with_scheduler( message, sender_user=user, exclude_user_id=message.from_user.id ) # Сохраняем сообщение в историю await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='text', text=message.text, forwarded_ids=forwarded_ids ) # Показываем статистику доставки только админам if is_admin(message.from_user.id): await message.answer( f"✅ Сообщение разослано!\n" f"📤 Доставлено: {success}\n" f"❌ Не доставлено: {fail}" ) elif settings.mode == 'forward': # Режим пересылки в канал if not settings.forward_chat_id: await message.answer("❌ Канал для пересылки не настроен") return success, channel_msg_id = await forward_to_channel(message, settings.forward_chat_id) if success: # Сохраняем сообщение в историю await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='text', text=message.text, forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None ) await message.answer("✅ Сообщение переслано в канал") else: await message.answer("❌ Не удалось переслать сообщение") # Настройки для планировщика рассылки BATCH_SIZE = 20 # Количество сообщений в пакете BATCH_DELAY = 1.0 # Задержка между пакетами в секундах # Защита от дубликатов сообщений (храним последние 100 message_id) _processed_messages: deque = deque(maxlen=100) def _is_message_processed(message_id: int) -> bool: """Проверка, было ли сообщение уже обработано""" if message_id in _processed_messages: return True _processed_messages.append(message_id) return False async def get_all_active_users(session: AsyncSession) -> List: """Получить всех пользователей для рассылки (всем, кто когда-либо общался с ботом)""" users = await UserService.get_all_users(session) # Рассылаем всем пользователям - и зарегистрированным, и незарегистрированным # Они все имеют право общаться в чате (главное - что они вошли в чат) return users async def broadcast_message_with_scheduler( message: Message, sender_user: Any, # User model object exclude_user_id: Optional[int] = None, admin_only: bool = False ) -> tuple[Dict[str, int], int, int]: """ Разослать сообщение всем пользователям с планировщиком (пакетная отправка). Подписи формируются динамически в зависимости от получателя: - Админы видят: nickname (карта: XXXX) - Обычные пользователи видят: nickname (от пользователя) или "Админ" (от админа) Args: message: Сообщение для рассылки sender_user: Объект User отправителя exclude_user_id: ID пользователя для исключения admin_only: Рассылать только админам Возвращает: (forwarded_ids, success_count, fail_count) """ import logging logger = logging.getLogger(__name__) async with async_session_maker() as session: users = await get_all_active_users(session) logger.info(f"[CHAT] broadcast_message_with_scheduler: всего пользователей для рассылки: {len(users)}") if exclude_user_id: users = [u for u in users if u.telegram_id != exclude_user_id] logger.info(f"[CHAT] После исключения отправителя: {len(users)} пользователей") # Если только для админов - фильтруем if admin_only: users = [u for u in users if u.telegram_id in ADMIN_IDS] logger.info(f"[CHAT] Фильтр админов: {len(users)} пользователей") forwarded_ids = {} success_count = 0 fail_count = 0 # Разбиваем на пакеты for i in range(0, len(users), BATCH_SIZE): batch = users[i:i + BATCH_SIZE] # Отправляем пакет tasks = [] for recipient_user in batch: # Формируем подпись в зависимости от получателя if recipient_user.telegram_id in ADMIN_IDS: # Админы видят полную информацию: nickname (карта: XXXX) sender_name = sender_user.nickname if sender_user.nickname else ( f"@{sender_user.username}" if sender_user.username else sender_user.first_name ) if sender_user.club_card_number: sender_name += f" (карта: {sender_user.club_card_number})" sender_info = sender_name tasks.append(_send_message_to_admin_with_sender(message, recipient_user.telegram_id, sender_info)) else: # Обычные пользователи видят: # - "Админ" если отправитель - админ # - nickname если отправитель - обычный пользователь if sender_user.telegram_id in ADMIN_IDS: sender_info = "Админ" tasks.append(_send_message_to_user_with_sender(message, recipient_user.telegram_id, sender_info)) else: sender_info = sender_user.nickname if sender_user.nickname else ( f"@{sender_user.username}" if sender_user.username else sender_user.first_name ) tasks.append(_send_message_to_user_with_sender(message, recipient_user.telegram_id, sender_info)) # Ждем завершения пакета results = await asyncio.gather(*tasks, return_exceptions=True) # Обрабатываем результаты for user, result in zip(batch, results): if isinstance(result, Exception): fail_count += 1 elif result is not None: forwarded_ids[str(user.telegram_id)] = result success_count += 1 else: fail_count += 1 # Задержка между пакетами (если есть еще пакеты) if i + BATCH_SIZE < len(users): await asyncio.sleep(BATCH_DELAY) logger.info(f"[CHAT] broadcast_message_with_scheduler завершена: успешно={success_count}, ошибок={fail_count}") return forwarded_ids, success_count, fail_count async def _send_message_to_user(message: Message, user_telegram_id: int) -> Optional[int]: """ Отправить сообщение конкретному пользователю. Возвращает message_id при успехе или None при ошибке. """ try: sent_msg = await message.copy_to(user_telegram_id) return sent_msg.message_id except Exception as e: print(f"Failed to send message to {user_telegram_id}: {e}") return None async def _send_message_to_user_with_sender(message: Message, user_telegram_id: int, sender_info: str) -> Optional[int]: """ Отправить сообщение обычному пользователю с информацией об отправителе. Возвращает message_id при успехе или None при ошибке. """ try: # Формируем текст с информацией об отправителе header = f"📨 {sender_info}:\n\n" if message.text: # Текстовое сообщение sent_msg = await message.bot.send_message( user_telegram_id, header + message.text, parse_mode="HTML" ) elif message.photo: # Фото caption = header + (message.caption or "") sent_msg = await message.bot.send_photo( user_telegram_id, photo=message.photo[-1].file_id, caption=caption, parse_mode="HTML" ) elif message.video: # Видео caption = header + (message.caption or "") sent_msg = await message.bot.send_video( user_telegram_id, video=message.video.file_id, caption=caption, parse_mode="HTML" ) elif message.document: # Документ caption = header + (message.caption or "") sent_msg = await message.bot.send_document( user_telegram_id, document=message.document.file_id, caption=caption, parse_mode="HTML" ) elif message.animation: # GIF caption = header + (message.caption or "") sent_msg = await message.bot.send_animation( user_telegram_id, animation=message.animation.file_id, caption=caption, parse_mode="HTML" ) elif message.sticker: # Стикер - сначала отправляем заголовок, потом стикер await message.bot.send_message(user_telegram_id, header, parse_mode="HTML") sent_msg = await message.bot.send_sticker(user_telegram_id, sticker=message.sticker.file_id) elif message.voice: # Голосовое сообщение sent_msg = await message.bot.send_voice( user_telegram_id, voice=message.voice.file_id, caption=header, parse_mode="HTML" ) elif message.video_note: # Видео-кружок await message.bot.send_message(user_telegram_id, header, parse_mode="HTML") sent_msg = await message.bot.send_video_note(user_telegram_id, video_note=message.video_note.file_id) else: # Неизвестный тип - просто копируем await message.bot.send_message(user_telegram_id, header, parse_mode="HTML") sent_msg = await message.copy_to(user_telegram_id) return sent_msg.message_id except Exception as e: print(f"Failed to send message to {user_telegram_id}: {e}") return None async def _send_message_to_admin_with_sender(message: Message, admin_telegram_id: int, sender_info: str) -> Optional[int]: """ Отправить сообщение админу с информацией об отправителе. Возвращает message_id при успехе или None при ошибке. """ try: # Формируем текст с информацией об отправителе header = f"📨 Сообщение от {sender_info}:\n\n" if message.text: # Текстовое сообщение sent_msg = await message.bot.send_message( admin_telegram_id, header + message.text, parse_mode="HTML" ) elif message.photo: # Фото caption = header + (message.caption or "") sent_msg = await message.bot.send_photo( admin_telegram_id, photo=message.photo[-1].file_id, caption=caption, parse_mode="HTML" ) elif message.video: # Видео caption = header + (message.caption or "") sent_msg = await message.bot.send_video( admin_telegram_id, video=message.video.file_id, caption=caption, parse_mode="HTML" ) elif message.document: # Документ caption = header + (message.caption or "") sent_msg = await message.bot.send_document( admin_telegram_id, document=message.document.file_id, caption=caption, parse_mode="HTML" ) elif message.animation: # GIF caption = header + (message.caption or "") sent_msg = await message.bot.send_animation( admin_telegram_id, animation=message.animation.file_id, caption=caption, parse_mode="HTML" ) elif message.sticker: # Стикер - сначала отправляем заголовок, потом стикер await message.bot.send_message(admin_telegram_id, header, parse_mode="HTML") sent_msg = await message.bot.send_sticker(admin_telegram_id, sticker=message.sticker.file_id) elif message.voice: # Голосовое сообщение sent_msg = await message.bot.send_voice( admin_telegram_id, voice=message.voice.file_id, caption=header, parse_mode="HTML" ) elif message.video_note: # Видео-кружок await message.bot.send_message(admin_telegram_id, header, parse_mode="HTML") sent_msg = await message.bot.send_video_note(admin_telegram_id, video_note=message.video_note.file_id) else: # Неизвестный тип - просто копируем await message.bot.send_message(admin_telegram_id, header, parse_mode="HTML") sent_msg = await message.copy_to(admin_telegram_id) return sent_msg.message_id except Exception as e: print(f"Failed to send message with sender info to admin {admin_telegram_id}: {e}") return None async def forward_to_channel(message: Message, channel_id: str) -> tuple[bool, Optional[int]]: """Переслать сообщение в канал/группу""" try: # Пересылаем сообщение в канал sent_msg = await message.forward(channel_id) return True, sent_msg.message_id except Exception as e: print(f"Failed to forward message to channel {channel_id}: {e}") return False, None @router.message(F.photo, StateFilter(ChatStates.in_chat)) async def handle_photo_message(message: Message, state: FSMContext): """Обработчик фото""" # Защита от дубликатов if _is_message_processed(message.message_id): return async with async_session_maker() as session: can_send, reason = await ChatPermissionService.can_send_message( session, message.from_user.id, is_admin=is_admin(message.from_user.id) ) if not can_send: await message.answer(f"❌ {reason}") return settings = await ChatSettingsService.get_or_create_settings(session) user = await UserService.get_or_create_user( session, message.from_user.id, username=message.from_user.username, first_name=message.from_user.first_name, last_name=message.from_user.last_name ) # Получаем file_id самого большого фото photo = message.photo[-1] if settings.mode == 'broadcast': # Рассылаем фото - ВСЕГДА исключаем отправителя forwarded_ids, success, fail = await broadcast_message_with_scheduler( message, sender_user=user, exclude_user_id=message.from_user.id ) await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='photo', text=message.caption, file_id=photo.file_id, forwarded_ids=forwarded_ids ) # Показываем статистику только админам if is_admin(message.from_user.id): await message.answer(f"✅ Фото разослано: {success} получателей") elif settings.mode == 'forward': if settings.forward_chat_id: success, channel_msg_id = await forward_to_channel(message, settings.forward_chat_id) if success: await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='photo', text=message.caption, file_id=photo.file_id, forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None ) await message.answer("✅ Фото переслано в канал") @router.message(F.video, StateFilter(ChatStates.in_chat)) async def handle_video_message(message: Message, state: FSMContext): """Обработчик видео""" # Защита от дубликатов if _is_message_processed(message.message_id): return async with async_session_maker() as session: can_send, reason = await ChatPermissionService.can_send_message( session, message.from_user.id, is_admin=is_admin(message.from_user.id) ) if not can_send: await message.answer(f"❌ {reason}") return settings = await ChatSettingsService.get_or_create_settings(session) user = await UserService.get_or_create_user( session, message.from_user.id, username=message.from_user.username, first_name=message.from_user.first_name, last_name=message.from_user.last_name ) if settings.mode == 'broadcast': # Рассылаем видео forwarded_ids, success, fail = await broadcast_message_with_scheduler( message, sender_user=user, exclude_user_id=message.from_user.id ) await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='video', text=message.caption, file_id=message.video.file_id, forwarded_ids=forwarded_ids ) # Показываем статистику только админам if is_admin(message.from_user.id): await message.answer(f"✅ Видео разослано: {success} получателей") elif settings.mode == 'forward': if settings.forward_chat_id: success, channel_msg_id = await forward_to_channel(message, settings.forward_chat_id) if success: await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='video', text=message.caption, file_id=message.video.file_id, forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None ) await message.answer("✅ Видео переслано в канал") @router.message(F.document, StateFilter(ChatStates.in_chat)) async def handle_document_message(message: Message, state: FSMContext): """Обработчик документов""" # Защита от дубликатов if _is_message_processed(message.message_id): return async with async_session_maker() as session: can_send, reason = await ChatPermissionService.can_send_message( session, message.from_user.id, is_admin=is_admin(message.from_user.id) ) if not can_send: await message.answer(f"❌ {reason}") return settings = await ChatSettingsService.get_or_create_settings(session) user = await UserService.get_or_create_user( session, message.from_user.id, username=message.from_user.username, first_name=message.from_user.first_name, last_name=message.from_user.last_name ) if settings.mode == 'broadcast': # Рассылаем документ forwarded_ids, success, fail = await broadcast_message_with_scheduler( message, sender_user=user, exclude_user_id=message.from_user.id ) await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='document', text=message.caption, file_id=message.document.file_id, forwarded_ids=forwarded_ids ) # Показываем статистику только админам if is_admin(message.from_user.id): await message.answer(f"✅ Документ разослан: {success} получателей") elif settings.mode == 'forward': if settings.forward_chat_id: success, channel_msg_id = await forward_to_channel(message, settings.forward_chat_id) if success: await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='document', text=message.caption, file_id=message.document.file_id, forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None ) await message.answer("✅ Документ переслан в канал") @router.message(F.animation, StateFilter(ChatStates.in_chat)) async def handle_animation_message(message: Message, state: FSMContext): """Обработчик GIF анимаций""" # Защита от дубликатов if _is_message_processed(message.message_id): return async with async_session_maker() as session: can_send, reason = await ChatPermissionService.can_send_message( session, message.from_user.id, is_admin=is_admin(message.from_user.id) ) if not can_send: await message.answer(f"❌ {reason}") return settings = await ChatSettingsService.get_or_create_settings(session) user = await UserService.get_or_create_user( session, message.from_user.id, username=message.from_user.username, first_name=message.from_user.first_name, last_name=message.from_user.last_name ) if settings.mode == 'broadcast': # Рассылаем анимацию forwarded_ids, success, fail = await broadcast_message_with_scheduler( message, sender_user=user, exclude_user_id=message.from_user.id ) await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='animation', text=message.caption, file_id=message.animation.file_id, forwarded_ids=forwarded_ids ) # Показываем статистику только админам if is_admin(message.from_user.id): await message.answer(f"✅ Анимация разослана: {success} получателей") elif settings.mode == 'forward': if settings.forward_chat_id: success, channel_msg_id = await forward_to_channel(message, settings.forward_chat_id) if success: await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='animation', text=message.caption, file_id=message.animation.file_id, forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None ) await message.answer("✅ Анимация переслана в канал") @router.message(F.sticker, StateFilter(ChatStates.in_chat)) async def handle_sticker_message(message: Message, state: FSMContext): """Обработчик стикеров""" # Защита от дубликатов if _is_message_processed(message.message_id): return async with async_session_maker() as session: can_send, reason = await ChatPermissionService.can_send_message( session, message.from_user.id, is_admin=is_admin(message.from_user.id) ) if not can_send: await message.answer(f"❌ {reason}") return settings = await ChatSettingsService.get_or_create_settings(session) user = await UserService.get_or_create_user( session, message.from_user.id, username=message.from_user.username, first_name=message.from_user.first_name, last_name=message.from_user.last_name ) if settings.mode == 'broadcast': # Рассылаем стикер forwarded_ids, success, fail = await broadcast_message_with_scheduler( message, sender_user=user, exclude_user_id=message.from_user.id ) await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='sticker', file_id=message.sticker.file_id, forwarded_ids=forwarded_ids ) # Показываем статистику только админам if is_admin(message.from_user.id): await message.answer(f"✅ Стикер разослан: {success} получателей") elif settings.mode == 'forward': if settings.forward_chat_id: success, channel_msg_id = await forward_to_channel(message, settings.forward_chat_id) if success: await ChatMessageService.save_message( session, user_id=user.id, telegram_message_id=message.message_id, message_type='sticker', file_id=message.sticker.file_id, forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None ) await message.answer("✅ Стикер переслан в канал") @router.message(F.voice) async def handle_voice_message(message: Message): """Обработчик голосовых сообщений - ЗАБЛОКИРОВАНО""" await message.answer( "🚫 Голосовые сообщения запрещены.\n\n" "Пожалуйста, используйте текстовые сообщения или изображения." ) return @router.message(F.audio) async def handle_audio_message(message: Message): """Обработчик аудиофайлов (музыка, аудиозаписи) - ЗАБЛОКИРОВАНО""" await message.answer( "🚫 Аудиофайлы запрещены.\n\n" "Пожалуйста, используйте текстовые сообщения или изображения." ) return