"""Обработчики пользовательских сообщений в чате""" from aiogram import Router, F from aiogram.types import Message from sqlalchemy.ext.asyncio import AsyncSession import asyncio from typing import List, Dict, Optional 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 def is_admin(user_id: int) -> bool: """Проверка является ли пользователь админом""" return user_id in ADMIN_IDS router = Router(name='chat_router') # Настройки для планировщика рассылки BATCH_SIZE = 20 # Количество сообщений в пакете BATCH_DELAY = 1.0 # Задержка между пакетами в секундах async def get_all_active_users(session: AsyncSession) -> List: """Получить всех зарегистрированных пользователей для рассылки""" users = await UserService.get_all_users(session) return [u for u in users if u.is_registered] # Используем is_registered вместо is_active async def broadcast_message_with_scheduler(message: Message, exclude_user_id: Optional[int] = None) -> tuple[Dict[str, int], int, int]: """ Разослать сообщение всем пользователям с планировщиком (пакетная отправка). Возвращает: (forwarded_ids, success_count, fail_count) """ async with async_session_maker() as session: users = await get_all_active_users(session) if exclude_user_id: users = [u for u in users if u.telegram_id != exclude_user_id] 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 user in batch: tasks.append(_send_message_to_user(message, user.telegram_id)) # Ждем завершения пакета 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) 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 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.text) async def handle_text_message(message: Message): """Обработчик текстовых сообщений""" import logging logger = logging.getLogger(__name__) logger.info(f"[CHAT] handle_text_message вызван: user={message.from_user.id}, text={message.text[:50] if message.text else 'None'}") # Проверяем является ли это командой 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 command in user_commands: return # Если это админская команда if command in admin_commands: # Проверяем права админа if not is_admin(message.from_user.id): await message.answer("❌ У вас нет прав для выполнения этой команды") return # Если админ - команда будет обработана другими обработчиками, пропускаем пересылку return # Если неизвестная команда - тоже не пересылаем 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_user_by_telegram_id(session, message.from_user.id) if not user: await message.answer("❌ Пользователь не найден") return # Обрабатываем в зависимости от режима if settings.mode == 'broadcast': # Режим рассылки с планировщиком # НЕ исключаем отправителя - админ должен видеть все сообщения forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=None) # Сохраняем сообщение в историю 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("❌ Не удалось переслать сообщение") @router.message(F.photo) async def handle_photo_message(message: Message): """Обработчик фото""" 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_user_by_telegram_id(session, message.from_user.id) if not user: return # Получаем file_id самого большого фото photo = message.photo[-1] if settings.mode == 'broadcast': # НЕ исключаем отправителя - админ должен видеть все сообщения forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=None) 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) async def handle_video_message(message: Message): """Обработчик видео""" 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_user_by_telegram_id(session, message.from_user.id) if not user: return if settings.mode == 'broadcast': # НЕ исключаем отправителя - админ должен видеть все сообщения forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=None) 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) async def handle_document_message(message: Message): """Обработчик документов""" 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_user_by_telegram_id(session, message.from_user.id) if not user: return if settings.mode == 'broadcast': # НЕ исключаем отправителя - админ должен видеть все сообщения forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=None) 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) async def handle_animation_message(message: Message): """Обработчик GIF анимаций""" 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_user_by_telegram_id(session, message.from_user.id) if not user: return if settings.mode == 'broadcast': # НЕ исключаем отправителя - админ должен видеть все сообщения forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=None) 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) async def handle_sticker_message(message: Message): """Обработчик стикеров""" 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_user_by_telegram_id(session, message.from_user.id) if not user: return if settings.mode == 'broadcast': # НЕ исключаем отправителя - админ должен видеть все сообщения forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=None) 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