Files
AnonBot/handlers/start.py

344 lines
15 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
"""
Обработчики команд /start и /help
"""
from datetime import datetime
from aiogram import Router, F
from aiogram.types import Message
from aiogram.filters import Command, CommandStart
from aiogram.fsm.context import FSMContext
from config import config
from models.user import User
from services.infrastructure.database import DatabaseService
from services.auth.auth_new import AuthService
from services.utils import UtilsService
from services.business.user_service import UserService
from services.business.message_service import MessageService
from services.infrastructure.logger import get_logger
from services.infrastructure.metrics import get_metrics_service, track_message_processing
from keyboards.reply import get_main_keyboard_for_user, get_admin_reply_keyboard
from keyboards.inline import get_admin_keyboard
from dependencies import inject_start_services, inject_link_services, inject_main_menu_services
logger = get_logger(__name__)
router = Router()
async def _create_welcome_message(user: User, referral_link: str) -> str:
"""Создание приветственного сообщения"""
welcome_text = f"👋 <b>Добро пожаловать, {user.display_name}!</b>\n\n"
welcome_text += "🤖 Я бот для анонимных вопросов.\n\n"
welcome_text += "📝 <b>Как это работает:</b>\n"
welcome_text += "• Поделитесь своей ссылкой с друзьями\n"
welcome_text += "• Они смогут задать вам анонимные вопросы\n"
welcome_text += "• Вы получите уведомления и сможете ответить\n\n"
welcome_text += f"🔗 <b>Ваша персональная ссылка:</b>\n"
welcome_text += f"<code>{referral_link}</code>\n\n"
welcome_text += "💡 <b>Совет:</b> Скопируйте ссылку и поделитесь ею в социальных сетях!"
return welcome_text
async def _process_start_command(
message: Message,
user_service: UserService,
auth: AuthService,
utils: UtilsService,
message_service: MessageService,
validator
) -> User:
"""Обработка команды /start без аргументов"""
# Валидируем Telegram ID пользователя
user_id_validation = validator.validate_telegram_id(message.from_user.id)
if not user_id_validation:
logger.error(f"❌ Невалидный Telegram ID: {message.from_user.id}")
await message_service.send_message(
message,
"❌ Ошибка: недопустимый ID пользователя.",
get_main_keyboard_for_user(message.from_user.id)
)
raise ValueError(f"Invalid Telegram ID: {message.from_user.id}")
# Создаем или обновляем пользователя
user = await user_service.create_or_update_user(message.from_user, message.chat.id)
# Проверяем, является ли пользователь админом
is_admin = auth.is_admin(user.telegram_id)
# Генерируем персональную ссылку
bot_info = await message.bot.get_me()
referral_link = user_service.generate_referral_link(bot_info.username, user)
# Создаем приветственное сообщение
welcome_text = await _create_welcome_message(user, referral_link)
# Выбираем клавиатуру в зависимости от роли
if is_admin:
keyboard = get_admin_reply_keyboard()
else:
keyboard = get_main_keyboard_for_user(user.telegram_id)
logger.info(f"⌨️ Создана клавиатура для пользователя {user.telegram_id}: {type(keyboard).__name__}")
# Отправляем сообщение
await message_service.send_message(message, welcome_text, keyboard)
logger.info(f"✅ Приветственное сообщение отправлено пользователю {user.telegram_id}")
return user
@router.message(CommandStart())
@track_message_processing("start_command")
@inject_start_services
async def cmd_start(
message: Message,
state: FSMContext,
user_service: UserService,
auth: AuthService,
utils: UtilsService,
message_service: MessageService,
validator,
**kwargs
):
"""Обработчик команды /start"""
try:
logger.info(f"🚀 Команда /start от пользователя {message.from_user.id} ({message.from_user.first_name})")
# Сбрасываем состояние FSM при команде /start
await state.clear()
logger.info(f"🔄 Состояние FSM сброшено для пользователя {message.from_user.id}")
# Получаем аргументы команды
args = message.text.split()[1:] if len(message.text.split()) > 1 else []
# Обрабатываем deep linking если есть аргументы
if args:
logger.info(f"🔗 Обработка deep link: {args[0]}")
await handle_deep_link(message, args[0], user_service, state, message_service, validator)
else:
# Обрабатываем обычную команду /start
await _process_start_command(message, user_service, auth, utils, message_service, validator)
except Exception as e:
logger.error(f"💥 Ошибка в обработчике /start: {e}")
await message_service.send_error_message(
message,
"❌ Произошла ошибка при запуске бота. Попробуйте позже."
)
async def handle_deep_link(
message: Message,
ref_code: str,
user_service: UserService,
state: FSMContext,
message_service: MessageService,
validator
):
"""Обработка deep linking для анонимных вопросов"""
try:
# Валидируем Telegram ID пользователя
user_id_validation = validator.validate_telegram_id(message.from_user.id)
if not user_id_validation:
logger.error(f"❌ Невалидный Telegram ID: {message.from_user.id}")
await message_service.send_message(
message,
"❌ Ошибка: недопустимый ID пользователя.",
get_main_keyboard_for_user(message.from_user.id)
)
return
# Создаем или обновляем пользователя (важно для любого /start)
current_user = await user_service.create_or_update_user(message.from_user, message.chat.id)
logger.info(f"✅ Пользователь создан/обновлен при deep link: {current_user.telegram_id}")
# Валидируем deep link
validation_result = validator.validate_deep_link(ref_code)
if not validation_result:
logger.warning(f"⚠️ Невалидный deep link от пользователя {message.from_user.id}: {ref_code}")
await message_service.send_message(
message,
f"{validation_result.error_message}",
get_main_keyboard_for_user(message.from_user.id)
)
return
# Используем санитизированное значение
ref_code = validation_result.sanitized_value
if not ref_code.startswith('ref_'):
await message_service.send_message(
message,
"❌ Неверная ссылка.",
get_main_keyboard_for_user(message.from_user.id)
)
return
# Извлекаем анонимный ID из реферального кода
anonymous_id = ref_code[4:] # Убираем 'ref_'
# Ищем пользователя по profile_link
target_user = await user_service.get_user_by_profile_link(anonymous_id)
if not target_user:
await message_service.send_message(
message,
"❌ Пользователь, на которого вы перешли, не найден.",
get_main_keyboard_for_user(message.from_user.id)
)
return
# Отправляем сообщение о переходе по ссылке
deep_link_text = (
f"👋 Вы перешли по ссылке пользователя {target_user.display_name}!\n\n"
f"📝 Теперь вы можете задать анонимный вопрос.\n"
f"Просто отправьте ваше сообщение, и оно будет передано получателю."
)
await message_service.send_message(
message,
deep_link_text,
get_main_keyboard_for_user(message.from_user.id)
)
# Устанавливаем состояние ожидания вопроса
from aiogram.fsm.state import State, StatesGroup
class QuestionStates(StatesGroup):
waiting_for_question = State()
await state.set_state(QuestionStates.waiting_for_question)
await state.update_data(target_user_id=target_user.telegram_id)
except Exception as e:
logger.error(f"Ошибка при обработке deep link: {e}")
await message_service.send_error_message(
message,
"❌ Произошла ошибка при обработке ссылки."
)
@router.message(Command("help"))
async def cmd_help(message: Message):
"""Обработчик команды /help"""
help_text = "📖 <b>Справка по боту</b>\n\n"
help_text += "🤖 <b>Основные команды:</b>\n"
help_text += "/start - Запуск бота и получение персональной ссылки\n"
help_text += "/help - Показать эту справку\n"
help_text += "/stats - Показать статистику (только для админов)\n\n"
help_text += "📝 <b>Как задать анонимный вопрос:</b>\n"
help_text += "1. Перейдите по персональной ссылке пользователя\n"
help_text += "2. Отправьте ваш вопрос боту\n"
help_text += "3. Вопрос будет передан получателю анонимно\n\n"
help_text += "💬 <b>Как ответить на вопрос:</b>\n"
help_text += "1. Получите уведомление о новом вопросе\n"
help_text += "2. Нажмите кнопку 'Ответить'\n"
help_text += "3. Введите ваш ответ\n"
help_text += "4. Ответ будет отправлен анонимно\n\n"
help_text += "🔗 <b>Ваша персональная ссылка:</b>\n"
help_text += "Используйте кнопку 'Моя ссылка' для получения ссылки\n\n"
help_text += "❓ <b>Нужна помощь?</b>\n"
help_text += "Обратитесь к администратору бота: @Kerrad1"
await message.answer(help_text, parse_mode="HTML")
@router.message(F.text == " Помощь")
async def help_button(message: Message):
"""Обработчик кнопки 'Помощь'"""
await cmd_help(message)
@router.message(F.text == "🔗 Моя ссылка")
@inject_link_services
async def my_link_button(
message: Message,
user_service: UserService,
message_service: MessageService,
**kwargs
):
"""Обработчик кнопки 'Моя ссылка'"""
try:
# Получаем пользователя из БД
user = await user_service.get_user_by_telegram_id(message.from_user.id)
if not user:
await message_service.send_message(
message,
"❌ Пользователь не найден. Используйте /start для регистрации."
)
return
# Получаем информацию о боте
bot_info = await message.bot.get_me()
referral_link = user_service.generate_referral_link(bot_info.username, user)
link_text = "🔗 <b>Ваша персональная ссылка:</b>\n\n"
link_text += f"<code>{referral_link}</code>\n\n"
link_text += "📝 <b>Как использовать:</b>\n"
link_text += "• Скопируйте ссылку\n"
link_text += "• Поделитесь ею в социальных сетях\n"
link_text += "• Друзья смогут задать вам анонимные вопросы\n\n"
link_text += "💡 <b>Совет:</b> Добавьте ссылку в описание профиля или поделитесь в Stories!"
await message_service.send_message(message, link_text)
except Exception as e:
logger.error(f"Ошибка при получении ссылки: {e}")
await message_service.send_error_message(
message,
"❌ Произошла ошибка при получении ссылки. Попробуйте позже."
)
@router.message(F.text == "⬅️ Главное меню")
@inject_main_menu_services
async def back_to_main(
message: Message,
state: FSMContext,
auth: AuthService,
message_service: MessageService,
**kwargs
):
"""Обработчик кнопки 'Главное меню'"""
# Сбрасываем состояние FSM при возврате в главное меню
await state.clear()
logger.info(f"🔄 Состояние FSM сброшено для пользователя {message.from_user.id} (кнопка 'Главное меню')")
is_admin = auth.is_admin(message.from_user.id)
if is_admin:
keyboard = get_admin_reply_keyboard()
else:
keyboard = get_main_keyboard_for_user(message.from_user.id)
await message_service.send_message(
message,
"🏠 <b>Главное меню</b>\n\nВыберите действие:",
keyboard
)
@router.message(F.text == "⚙️ Админ панель")
@inject_main_menu_services
async def admin_panel_button(
message: Message,
auth: AuthService,
message_service: MessageService,
**kwargs
):
"""Обработчик кнопки 'Админ панель'"""
# Проверяем, является ли пользователь админом
if not auth.is_admin(message.from_user.id):
await message_service.send_message(
message,
"У вас нет прав для доступа к админ панели."
)
return
# Получаем inline клавиатуру для админов
admin_keyboard = get_admin_keyboard()
await message_service.send_message(
message,
"👑 <b>Админ панель</b>\n\nВыберите действие:",
admin_keyboard
)