274 lines
12 KiB
Python
274 lines
12 KiB
Python
from aiogram import Router, F
|
||
from aiogram.filters import CommandStart, Command
|
||
from aiogram.types import (
|
||
Message,
|
||
InlineKeyboardMarkup,
|
||
InlineKeyboardButton,
|
||
CallbackQuery,
|
||
ReplyKeyboardMarkup,
|
||
KeyboardButton,
|
||
)
|
||
from aiogram.fsm.context import FSMContext
|
||
|
||
from database.db import async_session_maker
|
||
from services.user_service import UserService
|
||
|
||
router = Router()
|
||
|
||
# Тексты кнопок главного меню
|
||
BTN_ADD = "➕ Добавить слово"
|
||
BTN_VOCAB = "📚 Словарь"
|
||
BTN_TASK = "🧠 Задание"
|
||
BTN_PRACTICE = "💬 Практика"
|
||
BTN_WORDS = "🎯 Тематические слова"
|
||
BTN_IMPORT = "📖 Импорт из текста"
|
||
BTN_STATS = "📊 Статистика"
|
||
BTN_SETTINGS = "⚙️ Настройки"
|
||
|
||
|
||
def main_menu_keyboard() -> ReplyKeyboardMarkup:
|
||
"""Клавиатура с основными командами (кнопки отправляют команды)."""
|
||
return ReplyKeyboardMarkup(
|
||
resize_keyboard=True,
|
||
keyboard=[
|
||
[
|
||
KeyboardButton(text=BTN_ADD),
|
||
KeyboardButton(text=BTN_VOCAB),
|
||
],
|
||
[
|
||
KeyboardButton(text=BTN_TASK),
|
||
KeyboardButton(text=BTN_PRACTICE),
|
||
],
|
||
[
|
||
KeyboardButton(text=BTN_WORDS),
|
||
KeyboardButton(text=BTN_IMPORT),
|
||
],
|
||
[
|
||
KeyboardButton(text=BTN_STATS),
|
||
KeyboardButton(text=BTN_SETTINGS),
|
||
],
|
||
],
|
||
)
|
||
|
||
|
||
@router.message(CommandStart())
|
||
async def cmd_start(message: Message, state: FSMContext):
|
||
"""Обработчик команды /start"""
|
||
async with async_session_maker() as session:
|
||
# Проверяем, существует ли пользователь
|
||
existing_user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||
is_new_user = existing_user is None
|
||
|
||
# Создаём или получаем пользователя
|
||
user = await UserService.get_or_create_user(
|
||
session,
|
||
telegram_id=message.from_user.id,
|
||
username=message.from_user.username
|
||
)
|
||
|
||
if is_new_user:
|
||
# Новый пользователь
|
||
await message.answer(
|
||
f"👋 Привет, {message.from_user.first_name}!\n\n"
|
||
f"Я бот для изучения английского языка. Помогу тебе:\n"
|
||
f"📚 Пополнять словарный запас (ручное/тематическое/из текста)\n"
|
||
f"✍️ Выполнять интерактивные задания\n"
|
||
f"💬 Практиковать язык в диалоге с AI\n"
|
||
f"📊 Отслеживать свой прогресс\n\n"
|
||
f"<b>Команды:</b>\n"
|
||
f"• /add [слово] - добавить слово\n"
|
||
f"• /words [тема] - тематическая подборка\n"
|
||
f"• /import - импорт из текста\n"
|
||
f"• /vocabulary - мой словарь\n"
|
||
f"• /task - задания\n"
|
||
f"• /practice - диалог с AI\n"
|
||
f"• /stats - статистика\n"
|
||
f"• /settings - настройки\n"
|
||
f"• /reminder - напоминания\n"
|
||
f"• /help - полная справка",
|
||
reply_markup=main_menu_keyboard(),
|
||
)
|
||
|
||
# Предлагаем пройти тест уровня
|
||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||
[InlineKeyboardButton(text="📊 Пройти тест уровня", callback_data="offer_level_test")],
|
||
[InlineKeyboardButton(text="➡️ Пропустить", callback_data="skip_level_test")]
|
||
])
|
||
|
||
await message.answer(
|
||
"🎯 <b>Определим твой уровень?</b>\n\n"
|
||
"Короткий тест (7 вопросов) поможет подобрать задания под твой уровень.\n"
|
||
"Это займёт 2-3 минуты.\n\n"
|
||
"Или можешь пропустить и установить уровень вручную позже в /settings",
|
||
reply_markup=keyboard
|
||
)
|
||
else:
|
||
# Существующий пользователь
|
||
await message.answer(
|
||
f"С возвращением, {message.from_user.first_name}! 👋\n\n"
|
||
f"Готов продолжить обучение?\n\n"
|
||
f"<b>Быстрый доступ:</b>\n"
|
||
f"• /vocabulary - посмотреть словарь\n"
|
||
f"• /task - получить задание\n"
|
||
f"• /practice - практика диалога\n"
|
||
f"• /words [тема] - тематическая подборка\n"
|
||
f"• /stats - статистика\n"
|
||
f"• /help - все команды",
|
||
reply_markup=main_menu_keyboard(),
|
||
)
|
||
|
||
|
||
@router.message(Command("menu"))
|
||
async def cmd_menu(message: Message):
|
||
"""Показать клавиатуру с основными командами."""
|
||
await message.answer("Главное меню доступно ниже ⤵️", reply_markup=main_menu_keyboard())
|
||
|
||
|
||
# Обработчики кнопок главного меню (по тексту)
|
||
|
||
@router.message(F.text == BTN_ADD)
|
||
async def btn_add_pressed(message: Message, state: FSMContext):
|
||
from bot.handlers.vocabulary import AddWordStates
|
||
await message.answer(
|
||
"Отправь слово, которое хочешь добавить:\n"
|
||
"Например: <code>/add elephant</code>\n\n"
|
||
"Или просто отправь слово без команды!"
|
||
)
|
||
await state.set_state(AddWordStates.waiting_for_word)
|
||
|
||
|
||
@router.message(F.text == BTN_VOCAB)
|
||
async def btn_vocab_pressed(message: Message):
|
||
from bot.handlers.vocabulary import cmd_vocabulary
|
||
await cmd_vocabulary(message)
|
||
|
||
|
||
@router.message(F.text == BTN_TASK)
|
||
async def btn_task_pressed(message: Message, state: FSMContext):
|
||
from bot.handlers.tasks import cmd_task
|
||
await cmd_task(message, state)
|
||
|
||
|
||
@router.message(F.text == BTN_PRACTICE)
|
||
async def btn_practice_pressed(message: Message, state: FSMContext):
|
||
from bot.handlers.practice import cmd_practice
|
||
await cmd_practice(message, state)
|
||
|
||
|
||
@router.message(F.text == BTN_IMPORT)
|
||
async def btn_import_pressed(message: Message, state: FSMContext):
|
||
from bot.handlers.import_text import cmd_import
|
||
await cmd_import(message, state)
|
||
|
||
|
||
@router.message(F.text == BTN_STATS)
|
||
async def btn_stats_pressed(message: Message):
|
||
from bot.handlers.tasks import cmd_stats
|
||
await cmd_stats(message)
|
||
|
||
|
||
@router.message(F.text == BTN_SETTINGS)
|
||
async def btn_settings_pressed(message: Message):
|
||
from bot.handlers.settings import cmd_settings
|
||
await cmd_settings(message)
|
||
|
||
|
||
@router.message(F.text == BTN_WORDS)
|
||
async def btn_words_pressed(message: Message, state: FSMContext):
|
||
"""Подсказать про тематические слова и показать быстрые темы."""
|
||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||
|
||
text = (
|
||
"📚 <b>Тематические подборки слов</b>\n\n"
|
||
"Используй: <code>/words [тема]</code>\n\n"
|
||
"Популярные темы:"
|
||
)
|
||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||
[InlineKeyboardButton(text="✈️ Путешествия", callback_data="menu_theme_travel")],
|
||
[InlineKeyboardButton(text="🍔 Еда", callback_data="menu_theme_food")],
|
||
[InlineKeyboardButton(text="💼 Работа", callback_data="menu_theme_work")],
|
||
[InlineKeyboardButton(text="🌿 Природа", callback_data="menu_theme_nature")],
|
||
[InlineKeyboardButton(text="💻 Технологии", callback_data="menu_theme_technology")],
|
||
])
|
||
await message.answer(text, reply_markup=keyboard)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("menu_theme_"))
|
||
async def pick_theme_from_menu(callback: CallbackQuery, state: FSMContext):
|
||
"""Сгенерировать слова по выбранной теме из меню и показать список."""
|
||
from database.db import async_session_maker
|
||
from services.user_service import UserService
|
||
from services.ai_service import ai_service
|
||
from bot.handlers.words import show_words_list, WordsStates
|
||
|
||
theme = callback.data.split("menu_theme_")[-1]
|
||
|
||
async with async_session_maker() as session:
|
||
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
||
if not user:
|
||
await callback.answer("Сначала /start", show_alert=True)
|
||
return
|
||
|
||
generating = await callback.message.answer(f"🔄 Генерирую подборку слов по теме '{theme}'...")
|
||
words = await ai_service.generate_thematic_words(theme=theme, level=user.level.value, count=10)
|
||
await generating.delete()
|
||
|
||
if not words:
|
||
await callback.message.answer("❌ Не удалось сгенерировать подборку. Попробуй позже.")
|
||
await callback.answer()
|
||
return
|
||
|
||
# Сохраняем в состояние как в /words
|
||
await state.update_data(theme=theme, words=words, user_id=user.id, level=user.level.name)
|
||
await state.set_state(WordsStates.viewing_words)
|
||
|
||
await show_words_list(callback.message, words, theme)
|
||
await callback.answer()
|
||
|
||
|
||
@router.message(Command("help"))
|
||
async def cmd_help(message: Message):
|
||
"""Обработчик команды /help"""
|
||
await message.answer(
|
||
"<b>📖 Справка по командам:</b>\n\n"
|
||
"<b>Управление словарём:</b>\n"
|
||
"• /add [слово] - добавить слово в словарь\n"
|
||
"• /vocabulary - просмотр словаря\n"
|
||
"• /words [тема] - тематическая подборка слов\n"
|
||
"• /import - импортировать слова из текста\n\n"
|
||
"<b>Обучение:</b>\n"
|
||
"• /task - задание (перевод, заполнение пропусков)\n"
|
||
"• /practice - диалог с ИИ (6 сценариев)\n"
|
||
"• /level_test - тест определения уровня\n\n"
|
||
"<b>Статистика:</b>\n"
|
||
"• /stats - твой прогресс\n\n"
|
||
"<b>Настройки:</b>\n"
|
||
"• /settings - уровень и язык\n"
|
||
"• /reminder - ежедневные напоминания\n\n"
|
||
"💡 Ты также можешь просто отправить мне слово, и я предложу добавить его в словарь!"
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data == "offer_level_test")
|
||
async def offer_level_test_callback(callback: CallbackQuery, state: FSMContext):
|
||
"""Начать тест уровня из приветствия"""
|
||
from bot.handlers.level_test import start_level_test
|
||
await callback.message.delete()
|
||
await start_level_test(callback.message, state)
|
||
await callback.answer()
|
||
|
||
|
||
@router.callback_query(F.data == "skip_level_test")
|
||
async def skip_level_test_callback(callback: CallbackQuery):
|
||
"""Пропустить тест уровня"""
|
||
await callback.message.edit_text(
|
||
"✅ Хорошо!\n\n"
|
||
"Ты можешь пройти тест позже командой /level_test\n"
|
||
"или установить уровень вручную в /settings\n\n"
|
||
"Давай начнём! Попробуй:\n"
|
||
"• /words travel - тематическая подборка\n"
|
||
"• /practice - диалог с AI\n"
|
||
"• /add hello - добавить слово"
|
||
)
|
||
await callback.answer()
|