Files
new_lottery_bot/src/handlers/chat_handlers.py
Andrew K. Choi b123e9f714
Some checks reported errors
continuous-integration/drone/push Build encountered an error
feat: блокировка голосовых сообщений и аудиофайлов
- Голосовые сообщения (F.voice) теперь отклоняются с предупреждением
- Аудиофайлы/музыка (F.audio) теперь отклоняются с предупреждением
- Пользователям предлагается использовать текст или изображения
- Упрощена логика обработчиков - теперь просто блокировка без проверок
2025-11-19 05:30:07 +09:00

505 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Обработчики пользовательских сообщений в чате"""
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