This commit is contained in:
@@ -11,7 +11,8 @@ class KeyboardBuilderImpl(IKeyboardBuilder):
|
||||
def get_main_keyboard(self, is_admin: bool = False, is_registered: bool = False):
|
||||
"""Получить главную клавиатуру"""
|
||||
buttons = [
|
||||
[InlineKeyboardButton(text="🎲 Активные розыгрыши", callback_data="active_lotteries")]
|
||||
[InlineKeyboardButton(text="🎲 Активные розыгрыши", callback_data="active_lotteries")],
|
||||
[InlineKeyboardButton(text="💬 Войти в чат", callback_data="enter_chat")]
|
||||
]
|
||||
|
||||
# Показываем кнопку регистрации только незарегистрированным пользователям (не админам)
|
||||
|
||||
@@ -163,7 +163,13 @@ async def cmd_ban(message: Message):
|
||||
return
|
||||
|
||||
# Получаем админа
|
||||
admin = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
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
|
||||
)
|
||||
|
||||
# Баним
|
||||
ban = await BanService.ban_user(
|
||||
@@ -271,7 +277,13 @@ async def cmd_delete_message(message: Message):
|
||||
|
||||
async with async_session_maker() as session:
|
||||
# Получаем админа
|
||||
admin = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
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
|
||||
)
|
||||
|
||||
# Находим сообщение в базе по telegram_message_id
|
||||
from sqlalchemy import select
|
||||
|
||||
@@ -414,7 +414,13 @@ async def confirm_create_lottery(callback: CallbackQuery, state: FSMContext):
|
||||
data = await state.get_data()
|
||||
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||||
user = await UserService.get_or_create_user(
|
||||
session,
|
||||
callback.from_user.id,
|
||||
username=callback.from_user.username,
|
||||
first_name=callback.from_user.first_name,
|
||||
last_name=callback.from_user.last_name
|
||||
)
|
||||
|
||||
lottery = await LotteryService.create_lottery(
|
||||
session,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"""Обработчики пользовательских сообщений в чате"""
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message
|
||||
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 sqlalchemy.ext.asyncio import AsyncSession
|
||||
import asyncio
|
||||
from typing import List, Dict, Optional, Set
|
||||
@@ -19,6 +22,11 @@ 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
|
||||
@@ -34,6 +42,69 @@ def _contains_account_numbers(text: str) -> bool:
|
||||
|
||||
router = Router(name='chat_router')
|
||||
|
||||
|
||||
@router.message(Command("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):
|
||||
"""Общая функция входа в чат"""
|
||||
await state.set_state(ChatStates.in_chat)
|
||||
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="🚪 Выйти из чата", callback_data="exit_chat")],
|
||||
[InlineKeyboardButton(text="🏠 В главное меню", callback_data="back_to_main")]
|
||||
])
|
||||
|
||||
await message.answer(
|
||||
"💬 <b>Вы вошли в режим чата</b>\n\n"
|
||||
"Теперь все ваши сообщения будут рассылаться участникам.\n"
|
||||
"Вы можете отправлять текст, фото, видео, документы и стикеры.\n\n"
|
||||
"Для выхода нажмите кнопку ниже или отправьте /exit",
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
|
||||
@router.message(Command("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):
|
||||
"""Общая функция выхода из чата"""
|
||||
await state.clear()
|
||||
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="💬 Войти в чат", callback_data="enter_chat")],
|
||||
[InlineKeyboardButton(text="🏠 В главное меню", callback_data="back_to_main")]
|
||||
])
|
||||
|
||||
await message.answer(
|
||||
"✅ <b>Вы вышли из режима чата</b>\n\n"
|
||||
"Ваши сообщения больше не будут рассылаться.",
|
||||
reply_markup=keyboard,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
|
||||
# Настройки для планировщика рассылки
|
||||
BATCH_SIZE = 20 # Количество сообщений в пакете
|
||||
BATCH_DELAY = 1.0 # Задержка между пакетами в секундах
|
||||
@@ -224,8 +295,8 @@ async def forward_to_channel(message: Message, channel_id: str) -> tuple[bool, O
|
||||
return False, None
|
||||
|
||||
|
||||
@router.message(F.text)
|
||||
async def handle_text_message(message: Message):
|
||||
@router.message(F.text, StateFilter(ChatStates.in_chat))
|
||||
async def handle_text_message(message: Message, state: FSMContext):
|
||||
"""Обработчик текстовых сообщений"""
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -238,11 +309,13 @@ async def handle_text_message(message: Message):
|
||||
logger.info(f"[CHAT] handle_text_message вызван: user={message.from_user.id}, text={message.text[:50] if message.text else 'None'}")
|
||||
|
||||
# ПРОВЕРКА СЧЕТОВ: Если админ отправил сообщение с номерами счетов - НЕ рассылаем
|
||||
# Это сообщение будет обработано account_router для добавления в розыгрыш
|
||||
# Пропускаем для 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 # Пропускаем - обработает account_router
|
||||
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):
|
||||
@@ -256,9 +329,12 @@ async def handle_text_message(message: Message):
|
||||
|
||||
if msg_to_delete:
|
||||
# Получаем админа
|
||||
admin = await UserService.get_user_by_telegram_id(
|
||||
admin = await UserService.get_or_create_user(
|
||||
session,
|
||||
message.from_user.id
|
||||
message.from_user.id,
|
||||
username=message.from_user.username,
|
||||
first_name=message.from_user.first_name,
|
||||
last_name=message.from_user.last_name
|
||||
)
|
||||
|
||||
# Помечаем как удаленное
|
||||
@@ -355,11 +431,14 @@ async def handle_text_message(message: Message):
|
||||
# Получаем настройки чата
|
||||
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
|
||||
# Получаем или создаем пользователя
|
||||
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':
|
||||
@@ -421,8 +500,8 @@ async def handle_text_message(message: Message):
|
||||
await message.answer("❌ Не удалось переслать сообщение")
|
||||
|
||||
|
||||
@router.message(F.photo)
|
||||
async def handle_photo_message(message: Message):
|
||||
@router.message(F.photo, StateFilter(ChatStates.in_chat))
|
||||
async def handle_photo_message(message: Message, state: FSMContext):
|
||||
"""Обработчик фото"""
|
||||
# Защита от дубликатов
|
||||
if _is_message_processed(message.message_id):
|
||||
@@ -440,10 +519,13 @@ async def handle_photo_message(message: Message):
|
||||
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
|
||||
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]
|
||||
@@ -495,8 +577,8 @@ async def handle_photo_message(message: Message):
|
||||
await message.answer("✅ Фото переслано в канал")
|
||||
|
||||
|
||||
@router.message(F.video)
|
||||
async def handle_video_message(message: Message):
|
||||
@router.message(F.video, StateFilter(ChatStates.in_chat))
|
||||
async def handle_video_message(message: Message, state: FSMContext):
|
||||
"""Обработчик видео"""
|
||||
# Защита от дубликатов
|
||||
if _is_message_processed(message.message_id):
|
||||
@@ -514,10 +596,13 @@ async def handle_video_message(message: Message):
|
||||
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
|
||||
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':
|
||||
# Формируем информацию об отправителе для админов (если это не админ)
|
||||
@@ -565,8 +650,8 @@ async def handle_video_message(message: Message):
|
||||
await message.answer("✅ Видео переслано в канал")
|
||||
|
||||
|
||||
@router.message(F.document)
|
||||
async def handle_document_message(message: Message):
|
||||
@router.message(F.document, StateFilter(ChatStates.in_chat))
|
||||
async def handle_document_message(message: Message, state: FSMContext):
|
||||
"""Обработчик документов"""
|
||||
# Защита от дубликатов
|
||||
if _is_message_processed(message.message_id):
|
||||
@@ -584,10 +669,13 @@ async def handle_document_message(message: Message):
|
||||
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
|
||||
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':
|
||||
# Формируем информацию об отправителе для админов (если это не админ)
|
||||
@@ -635,8 +723,8 @@ async def handle_document_message(message: Message):
|
||||
await message.answer("✅ Документ переслан в канал")
|
||||
|
||||
|
||||
@router.message(F.animation)
|
||||
async def handle_animation_message(message: Message):
|
||||
@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):
|
||||
@@ -654,10 +742,13 @@ async def handle_animation_message(message: Message):
|
||||
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
|
||||
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':
|
||||
# Формируем информацию об отправителе для админов (если это не админ)
|
||||
@@ -705,8 +796,8 @@ async def handle_animation_message(message: Message):
|
||||
await message.answer("✅ Анимация переслана в канал")
|
||||
|
||||
|
||||
@router.message(F.sticker)
|
||||
async def handle_sticker_message(message: Message):
|
||||
@router.message(F.sticker, StateFilter(ChatStates.in_chat))
|
||||
async def handle_sticker_message(message: Message, state: FSMContext):
|
||||
"""Обработчик стикеров"""
|
||||
# Защита от дубликатов
|
||||
if _is_message_processed(message.message_id):
|
||||
@@ -724,10 +815,13 @@ async def handle_sticker_message(message: Message):
|
||||
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
|
||||
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':
|
||||
# Формируем информацию об отправителе для админов (если это не админ)
|
||||
|
||||
@@ -38,7 +38,13 @@ async def show_chat_menu(message: Message, state: FSMContext):
|
||||
await state.clear()
|
||||
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
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 not user:
|
||||
await message.answer("❌ Вы не зарегистрированы. Используйте /start")
|
||||
@@ -134,7 +140,13 @@ async def start_conversation(callback: CallbackQuery, state: FSMContext):
|
||||
await callback.message.edit_text("❌ Пользователь не найден")
|
||||
return
|
||||
|
||||
sender = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||||
sender = await UserService.get_or_create_user(
|
||||
session,
|
||||
callback.from_user.id,
|
||||
username=callback.from_user.username,
|
||||
first_name=callback.from_user.first_name,
|
||||
last_name=callback.from_user.last_name
|
||||
)
|
||||
|
||||
# Получаем последние 10 сообщений из диалога
|
||||
messages = await P2PMessageService.get_conversation(
|
||||
@@ -182,7 +194,13 @@ async def show_conversations(callback: CallbackQuery):
|
||||
await callback.answer()
|
||||
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||||
sender = await UserService.get_or_create_user(
|
||||
session,
|
||||
callback.from_user.id,
|
||||
username=callback.from_user.username,
|
||||
first_name=callback.from_user.first_name,
|
||||
last_name=callback.from_user.last_name
|
||||
)
|
||||
|
||||
conversations = await P2PMessageService.get_recent_conversations(session, user.id, limit=10)
|
||||
|
||||
@@ -274,7 +292,13 @@ async def handle_p2p_message(message: Message, state: FSMContext):
|
||||
return
|
||||
|
||||
async with async_session_maker() as session:
|
||||
sender = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
sender = 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
|
||||
)
|
||||
sender_name = f"@{sender.username}" if sender.username else sender.first_name
|
||||
|
||||
# Определяем тип сообщения
|
||||
|
||||
Reference in New Issue
Block a user