2025-12-04 11:09:54 +03:00
|
|
|
|
from aiogram import Router, F
|
|
|
|
|
|
from aiogram.filters import CommandStart, Command
|
2025-12-04 17:15:49 +03:00
|
|
|
|
from aiogram.types import (
|
|
|
|
|
|
Message,
|
|
|
|
|
|
InlineKeyboardMarkup,
|
|
|
|
|
|
InlineKeyboardButton,
|
|
|
|
|
|
CallbackQuery,
|
|
|
|
|
|
ReplyKeyboardMarkup,
|
|
|
|
|
|
KeyboardButton,
|
|
|
|
|
|
)
|
2025-12-04 11:09:54 +03:00
|
|
|
|
from aiogram.fsm.context import FSMContext
|
|
|
|
|
|
|
|
|
|
|
|
from database.db import async_session_maker
|
|
|
|
|
|
from services.user_service import UserService
|
2025-12-04 19:40:01 +03:00
|
|
|
|
from utils.i18n import t
|
2025-12-05 14:30:24 +03:00
|
|
|
|
from utils.levels import get_user_level_for_language
|
2025-12-04 11:09:54 +03:00
|
|
|
|
|
|
|
|
|
|
router = Router()
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
def main_menu_keyboard(lang: str = 'ru') -> ReplyKeyboardMarkup:
|
2025-12-04 17:17:31 +03:00
|
|
|
|
"""Клавиатура с основными командами (кнопки отправляют команды)."""
|
|
|
|
|
|
return ReplyKeyboardMarkup(
|
|
|
|
|
|
resize_keyboard=True,
|
|
|
|
|
|
keyboard=[
|
|
|
|
|
|
[
|
2025-12-04 19:40:01 +03:00
|
|
|
|
KeyboardButton(text=t(lang, "menu.add")),
|
|
|
|
|
|
KeyboardButton(text=t(lang, "menu.vocab")),
|
2025-12-04 17:17:31 +03:00
|
|
|
|
],
|
|
|
|
|
|
[
|
2025-12-04 19:40:01 +03:00
|
|
|
|
KeyboardButton(text=t(lang, "menu.task")),
|
|
|
|
|
|
KeyboardButton(text=t(lang, "menu.practice")),
|
2025-12-04 17:17:31 +03:00
|
|
|
|
],
|
|
|
|
|
|
[
|
2025-12-04 19:40:01 +03:00
|
|
|
|
KeyboardButton(text=t(lang, "menu.stats")),
|
|
|
|
|
|
KeyboardButton(text=t(lang, "menu.settings")),
|
2025-12-04 17:17:31 +03:00
|
|
|
|
],
|
|
|
|
|
|
],
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 11:09:54 +03:00
|
|
|
|
@router.message(CommandStart())
|
|
|
|
|
|
async def cmd_start(message: Message, state: FSMContext):
|
|
|
|
|
|
"""Обработчик команды /start"""
|
|
|
|
|
|
async with async_session_maker() as session:
|
2025-12-04 16:00:38 +03:00
|
|
|
|
# Проверяем, существует ли пользователь
|
|
|
|
|
|
existing_user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
|
|
|
|
|
is_new_user = existing_user is None
|
|
|
|
|
|
|
|
|
|
|
|
# Создаём или получаем пользователя
|
2025-12-04 11:09:54 +03:00
|
|
|
|
user = await UserService.get_or_create_user(
|
|
|
|
|
|
session,
|
|
|
|
|
|
telegram_id=message.from_user.id,
|
|
|
|
|
|
username=message.from_user.username
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
lang = (user.language_interface or 'ru')
|
|
|
|
|
|
|
2025-12-04 16:00:38 +03:00
|
|
|
|
if is_new_user:
|
|
|
|
|
|
# Новый пользователь
|
2025-12-04 11:09:54 +03:00
|
|
|
|
await message.answer(
|
2025-12-04 19:40:01 +03:00
|
|
|
|
t(lang, "start.new_intro", first_name=message.from_user.first_name),
|
|
|
|
|
|
reply_markup=main_menu_keyboard(lang),
|
Добавлены основные функции MVP: тематические подборки, импорт слов, диалоговая практика, напоминания и тест уровня
Новые команды:
- /words [тема] - AI-генерация тематических подборок слов (10 слов по теме с учётом уровня)
- /import - извлечение до 15 ключевых слов из текста (книги, статьи, песни)
- /practice - диалоговая практика с AI в 6 сценариях (ресторан, магазин, путешествие, работа, врач, общение)
- /reminder - настройка ежедневных напоминаний по расписанию
- /level_test - тест из 7 вопросов для определения уровня английского (A1-C2)
Основные изменения:
- AI сервис: добавлены методы generate_thematic_words, extract_words_from_text, start_conversation, continue_conversation, generate_level_test
- Диалоговая практика: исправление ошибок в реальном времени, подсказки, перевод реплик
- Напоминания: APScheduler для ежедневной отправки напоминаний в выбранное время
- Тест уровня: автоматическое определение уровня при регистрации, можно пропустить
- База данных: добавлены поля reminders_enabled, last_reminder_sent
- Vocabulary service: метод get_word_by_original для проверки дубликатов
- Зависимости: apscheduler==3.10.4
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:46:02 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Предлагаем пройти тест уровня
|
|
|
|
|
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
2025-12-04 19:40:01 +03:00
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'start.offer_btn'), callback_data="offer_level_test")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'start.skip_btn'), callback_data="skip_level_test")]
|
Добавлены основные функции MVP: тематические подборки, импорт слов, диалоговая практика, напоминания и тест уровня
Новые команды:
- /words [тема] - AI-генерация тематических подборок слов (10 слов по теме с учётом уровня)
- /import - извлечение до 15 ключевых слов из текста (книги, статьи, песни)
- /practice - диалоговая практика с AI в 6 сценариях (ресторан, магазин, путешествие, работа, врач, общение)
- /reminder - настройка ежедневных напоминаний по расписанию
- /level_test - тест из 7 вопросов для определения уровня английского (A1-C2)
Основные изменения:
- AI сервис: добавлены методы generate_thematic_words, extract_words_from_text, start_conversation, continue_conversation, generate_level_test
- Диалоговая практика: исправление ошибок в реальном времени, подсказки, перевод реплик
- Напоминания: APScheduler для ежедневной отправки напоминаний в выбранное время
- Тест уровня: автоматическое определение уровня при регистрации, можно пропустить
- База данных: добавлены поля reminders_enabled, last_reminder_sent
- Vocabulary service: метод get_word_by_original для проверки дубликатов
- Зависимости: apscheduler==3.10.4
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:46:02 +03:00
|
|
|
|
])
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
await message.answer(t(lang, "start.offer_test"), reply_markup=keyboard)
|
2025-12-04 11:09:54 +03:00
|
|
|
|
else:
|
|
|
|
|
|
# Существующий пользователь
|
|
|
|
|
|
await message.answer(
|
2025-12-04 19:40:01 +03:00
|
|
|
|
t(lang, "start.return", first_name=message.from_user.first_name),
|
|
|
|
|
|
reply_markup=main_menu_keyboard(lang),
|
2025-12-04 11:09:54 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 17:15:49 +03:00
|
|
|
|
@router.message(Command("menu"))
|
|
|
|
|
|
async def cmd_menu(message: Message):
|
|
|
|
|
|
"""Показать клавиатуру с основными командами."""
|
2025-12-04 19:40:01 +03:00
|
|
|
|
# Определяем язык пользователя
|
|
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
await message.answer(t(lang, "menu.below"), reply_markup=main_menu_keyboard(lang))
|
2025-12-04 17:15:49 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 17:22:50 +03:00
|
|
|
|
# Обработчики кнопок главного меню (по тексту)
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
def _menu_match(key: str):
|
|
|
|
|
|
labels = {t('ru', key), t('en', key), t('ja', key)}
|
|
|
|
|
|
return lambda m: m.text in labels
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.message(_menu_match('menu.add'))
|
2025-12-04 17:22:50 +03:00
|
|
|
|
async def btn_add_pressed(message: Message, state: FSMContext):
|
2025-12-05 20:15:47 +03:00
|
|
|
|
"""Показать меню добавления слов"""
|
2025-12-04 19:40:01 +03:00
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
2025-12-05 20:15:47 +03:00
|
|
|
|
|
|
|
|
|
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'add_menu.manual'), callback_data="add_manual")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'add_menu.thematic'), callback_data="add_thematic")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'add_menu.import'), callback_data="add_import")]
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
await message.answer(t(lang, 'add_menu.title'), reply_markup=keyboard)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data == "add_manual")
|
|
|
|
|
|
async def add_manual_callback(callback: CallbackQuery, state: FSMContext):
|
|
|
|
|
|
"""Добавить слово вручную"""
|
|
|
|
|
|
await callback.answer()
|
|
|
|
|
|
from bot.handlers.vocabulary import AddWordStates
|
|
|
|
|
|
|
|
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
|
2025-12-04 17:22:50 +03:00
|
|
|
|
await state.set_state(AddWordStates.waiting_for_word)
|
2025-12-05 20:15:47 +03:00
|
|
|
|
await callback.message.edit_text(t(lang, 'add.prompt'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data == "add_thematic")
|
|
|
|
|
|
async def add_thematic_callback(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)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
|
|
|
|
|
|
# Показываем подсказку по использованию /words
|
|
|
|
|
|
text = (
|
|
|
|
|
|
t(lang, 'words.help_title') + "\n\n" +
|
|
|
|
|
|
t(lang, 'words.help_usage') + "\n\n" +
|
|
|
|
|
|
t(lang, 'words.help_examples') + "\n\n" +
|
|
|
|
|
|
t(lang, 'words.help_note')
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Популярные темы как кнопки
|
|
|
|
|
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
|
|
|
|
[
|
|
|
|
|
|
InlineKeyboardButton(text=t(lang, 'words.topic_travel'), callback_data="words_travel"),
|
|
|
|
|
|
InlineKeyboardButton(text=t(lang, 'words.topic_food'), callback_data="words_food")
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
InlineKeyboardButton(text=t(lang, 'words.topic_work'), callback_data="words_work"),
|
|
|
|
|
|
InlineKeyboardButton(text=t(lang, 'words.topic_technology'), callback_data="words_technology")
|
|
|
|
|
|
],
|
|
|
|
|
|
[InlineKeyboardButton(text="⬅️ " + t(lang, 'settings.back'), callback_data="back_to_add_menu")]
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
await callback.message.edit_text(text, reply_markup=keyboard)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data.startswith("words_"))
|
|
|
|
|
|
async def words_topic_callback(callback: CallbackQuery, state: FSMContext):
|
|
|
|
|
|
"""Генерация слов по теме"""
|
|
|
|
|
|
topic = callback.data.replace("words_", "")
|
|
|
|
|
|
await callback.answer()
|
|
|
|
|
|
await callback.message.delete()
|
|
|
|
|
|
|
|
|
|
|
|
from bot.handlers.words import generate_words_for_theme
|
|
|
|
|
|
await generate_words_for_theme(callback.message, state, topic, callback.from_user.id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data == "add_import")
|
|
|
|
|
|
async def add_import_callback(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)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
|
|
|
|
|
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'import_menu.from_text'), callback_data="import_from_text")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'import_menu.from_file'), callback_data="import_from_file")],
|
|
|
|
|
|
[InlineKeyboardButton(text="⬅️ " + t(lang, 'settings.back'), callback_data="back_to_add_menu")]
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
await callback.message.edit_text(t(lang, 'import_menu.title'), reply_markup=keyboard)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data == "back_to_add_menu")
|
|
|
|
|
|
async def back_to_add_menu_callback(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)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
|
|
|
|
|
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'add_menu.manual'), callback_data="add_manual")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'add_menu.thematic'), callback_data="add_thematic")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'add_menu.import'), callback_data="add_import")]
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
await callback.message.edit_text(t(lang, 'add_menu.title'), reply_markup=keyboard)
|
2025-12-04 17:22:50 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
@router.message(_menu_match('menu.vocab'))
|
2025-12-04 17:22:50 +03:00
|
|
|
|
async def btn_vocab_pressed(message: Message):
|
|
|
|
|
|
from bot.handlers.vocabulary import cmd_vocabulary
|
|
|
|
|
|
await cmd_vocabulary(message)
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
@router.message(_menu_match('menu.task'))
|
2025-12-04 17:22:50 +03:00
|
|
|
|
async def btn_task_pressed(message: Message, state: FSMContext):
|
|
|
|
|
|
from bot.handlers.tasks import cmd_task
|
|
|
|
|
|
await cmd_task(message, state)
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
@router.message(_menu_match('menu.practice'))
|
2025-12-04 17:22:50 +03:00
|
|
|
|
async def btn_practice_pressed(message: Message, state: FSMContext):
|
|
|
|
|
|
from bot.handlers.practice import cmd_practice
|
|
|
|
|
|
await cmd_practice(message, state)
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
@router.message(_menu_match('menu.import'))
|
2025-12-04 17:22:50 +03:00
|
|
|
|
async def btn_import_pressed(message: Message, state: FSMContext):
|
2025-12-05 20:15:47 +03:00
|
|
|
|
"""Показать меню импорта"""
|
|
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
|
|
|
|
|
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'import_menu.from_text'), callback_data="import_from_text")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'import_menu.from_file'), callback_data="import_from_file")]
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
await message.answer(t(lang, 'import_menu.title'), reply_markup=keyboard)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data == "import_from_text")
|
|
|
|
|
|
async def import_from_text_callback(callback: CallbackQuery, state: FSMContext):
|
|
|
|
|
|
"""Импорт из текста"""
|
|
|
|
|
|
await callback.answer()
|
|
|
|
|
|
from bot.handlers.import_text import ImportStates
|
|
|
|
|
|
|
|
|
|
|
|
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.message.edit_text(t('ru', 'common.start_first'))
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
lang = user.language_interface or 'ru'
|
|
|
|
|
|
await state.set_state(ImportStates.waiting_for_text)
|
|
|
|
|
|
await callback.message.edit_text(
|
|
|
|
|
|
t(lang, 'import.title') + "\n\n" +
|
|
|
|
|
|
t(lang, 'import.desc') + "\n\n" +
|
|
|
|
|
|
t(lang, 'import.can_send') + "\n\n" +
|
|
|
|
|
|
t(lang, 'import.cancel_hint')
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data == "import_from_file")
|
|
|
|
|
|
async def import_from_file_callback(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)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
|
|
|
|
|
|
await callback.message.edit_text(t(lang, 'import_menu.file_hint'))
|
2025-12-04 17:22:50 +03:00
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
@router.message(_menu_match('menu.stats'))
|
2025-12-04 17:22:50 +03:00
|
|
|
|
async def btn_stats_pressed(message: Message):
|
|
|
|
|
|
from bot.handlers.tasks import cmd_stats
|
|
|
|
|
|
await cmd_stats(message)
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
@router.message(_menu_match('menu.settings'))
|
2025-12-04 17:22:50 +03:00
|
|
|
|
async def btn_settings_pressed(message: Message):
|
|
|
|
|
|
from bot.handlers.settings import cmd_settings
|
|
|
|
|
|
await cmd_settings(message)
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
@router.message(_menu_match('menu.words'))
|
2025-12-04 17:22:50 +03:00
|
|
|
|
async def btn_words_pressed(message: Message, state: FSMContext):
|
|
|
|
|
|
"""Подсказать про тематические слова и показать быстрые темы."""
|
|
|
|
|
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
2025-12-04 17:22:50 +03:00
|
|
|
|
text = (
|
2025-12-04 19:40:01 +03:00
|
|
|
|
t(lang, 'words.help_title') + "\n\n" +
|
|
|
|
|
|
t(lang, 'words.help_usage') + "\n\n" +
|
|
|
|
|
|
t(lang, 'words.popular')
|
2025-12-04 17:22:50 +03:00
|
|
|
|
)
|
|
|
|
|
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
2025-12-04 19:40:01 +03:00
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'words.topic_travel'), callback_data="menu_theme_travel")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'words.topic_food'), callback_data="menu_theme_food")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'words.topic_work'), callback_data="menu_theme_work")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'words.topic_nature'), callback_data="menu_theme_nature")],
|
|
|
|
|
|
[InlineKeyboardButton(text=t(lang, 'words.topic_technology'), callback_data="menu_theme_technology")],
|
2025-12-04 17:22:50 +03:00
|
|
|
|
])
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
# Сразу отвечаем на callback, чтобы избежать таймаута
|
|
|
|
|
|
await callback.answer()
|
2025-12-04 17:22:50 +03:00
|
|
|
|
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:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
await callback.answer(t('ru', 'common.start_first'), show_alert=True)
|
2025-12-04 17:22:50 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
lang = (user.language_interface or 'ru')
|
2025-12-05 14:30:24 +03:00
|
|
|
|
current_level = get_user_level_for_language(user)
|
2025-12-04 19:40:01 +03:00
|
|
|
|
generating = await callback.message.answer(t(lang, 'words.generating', theme=theme))
|
|
|
|
|
|
words = await ai_service.generate_thematic_words(
|
|
|
|
|
|
theme=theme,
|
2025-12-05 14:30:24 +03:00
|
|
|
|
level=current_level,
|
2025-12-04 19:40:01 +03:00
|
|
|
|
count=10,
|
|
|
|
|
|
learning_lang=user.learning_language,
|
|
|
|
|
|
translation_lang=user.language_interface,
|
|
|
|
|
|
)
|
2025-12-04 17:22:50 +03:00
|
|
|
|
await generating.delete()
|
|
|
|
|
|
|
|
|
|
|
|
if not words:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
await callback.message.answer(t(lang, 'words.generate_failed'))
|
2025-12-04 17:22:50 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Сохраняем в состояние как в /words
|
2025-12-05 14:30:24 +03:00
|
|
|
|
await state.update_data(theme=theme, words=words, user_id=user.id, level=current_level)
|
2025-12-04 17:22:50 +03:00
|
|
|
|
await state.set_state(WordsStates.viewing_words)
|
|
|
|
|
|
|
|
|
|
|
|
await show_words_list(callback.message, words, theme)
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 11:09:54 +03:00
|
|
|
|
@router.message(Command("help"))
|
|
|
|
|
|
async def cmd_help(message: Message):
|
|
|
|
|
|
"""Обработчик команды /help"""
|
2025-12-04 19:40:01 +03:00
|
|
|
|
# Определяем язык пользователя
|
|
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
await message.answer(t(lang, "start.help"))
|
Добавлены основные функции MVP: тематические подборки, импорт слов, диалоговая практика, напоминания и тест уровня
Новые команды:
- /words [тема] - AI-генерация тематических подборок слов (10 слов по теме с учётом уровня)
- /import - извлечение до 15 ключевых слов из текста (книги, статьи, песни)
- /practice - диалоговая практика с AI в 6 сценариях (ресторан, магазин, путешествие, работа, врач, общение)
- /reminder - настройка ежедневных напоминаний по расписанию
- /level_test - тест из 7 вопросов для определения уровня английского (A1-C2)
Основные изменения:
- AI сервис: добавлены методы generate_thematic_words, extract_words_from_text, start_conversation, continue_conversation, generate_level_test
- Диалоговая практика: исправление ошибок в реальном времени, подсказки, перевод реплик
- Напоминания: APScheduler для ежедневной отправки напоминаний в выбранное время
- Тест уровня: автоматическое определение уровня при регистрации, можно пропустить
- База данных: добавлены поля reminders_enabled, last_reminder_sent
- Vocabulary service: метод get_word_by_original для проверки дубликатов
- Зависимости: apscheduler==3.10.4
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:46:02 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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):
|
|
|
|
|
|
"""Пропустить тест уровня"""
|
2025-12-04 19:40:01 +03:00
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
await callback.message.edit_text(t(lang, 'start.skip_msg'))
|
Добавлены основные функции MVP: тематические подборки, импорт слов, диалоговая практика, напоминания и тест уровня
Новые команды:
- /words [тема] - AI-генерация тематических подборок слов (10 слов по теме с учётом уровня)
- /import - извлечение до 15 ключевых слов из текста (книги, статьи, песни)
- /practice - диалоговая практика с AI в 6 сценариях (ресторан, магазин, путешествие, работа, врач, общение)
- /reminder - настройка ежедневных напоминаний по расписанию
- /level_test - тест из 7 вопросов для определения уровня английского (A1-C2)
Основные изменения:
- AI сервис: добавлены методы generate_thematic_words, extract_words_from_text, start_conversation, continue_conversation, generate_level_test
- Диалоговая практика: исправление ошибок в реальном времени, подсказки, перевод реплик
- Напоминания: APScheduler для ежедневной отправки напоминаний в выбранное время
- Тест уровня: автоматическое определение уровня при регистрации, можно пропустить
- База данных: добавлены поля reminders_enabled, last_reminder_sent
- Vocabulary service: метод get_word_by_original для проверки дубликатов
- Зависимости: apscheduler==3.10.4
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:46:02 +03:00
|
|
|
|
await callback.answer()
|