2025-12-04 11:09:54 +03:00
|
|
|
|
from aiogram import Router, F
|
|
|
|
|
|
from aiogram.filters import Command
|
|
|
|
|
|
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
|
|
|
|
|
from aiogram.fsm.context import FSMContext
|
|
|
|
|
|
from aiogram.fsm.state import State, StatesGroup
|
|
|
|
|
|
|
|
|
|
|
|
from database.db import async_session_maker
|
|
|
|
|
|
from database.models import WordSource
|
|
|
|
|
|
from services.user_service import UserService
|
|
|
|
|
|
from services.vocabulary_service import VocabularyService
|
|
|
|
|
|
from services.ai_service import ai_service
|
2025-12-05 14:30:24 +03:00
|
|
|
|
from utils.i18n import t, get_user_lang
|
2025-12-04 11:09:54 +03:00
|
|
|
|
|
|
|
|
|
|
router = Router()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AddWordStates(StatesGroup):
|
|
|
|
|
|
"""Состояния для добавления слова"""
|
|
|
|
|
|
waiting_for_confirmation = State()
|
|
|
|
|
|
waiting_for_word = State()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.message(Command("add"))
|
|
|
|
|
|
async def cmd_add(message: Message, state: FSMContext):
|
|
|
|
|
|
"""Обработчик команды /add [слово]"""
|
|
|
|
|
|
# Получаем слово из команды
|
|
|
|
|
|
parts = message.text.split(maxsplit=1)
|
|
|
|
|
|
|
|
|
|
|
|
if len(parts) < 2:
|
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, 'add.prompt'))
|
2025-12-04 11:09:54 +03:00
|
|
|
|
await state.set_state(AddWordStates.waiting_for_word)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
word = parts[1].strip()
|
|
|
|
|
|
await process_word_addition(message, state, word)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.message(AddWordStates.waiting_for_word)
|
|
|
|
|
|
async def process_word_input(message: Message, state: FSMContext):
|
|
|
|
|
|
"""Обработка ввода слова"""
|
|
|
|
|
|
word = message.text.strip()
|
|
|
|
|
|
await process_word_addition(message, state, word)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def process_word_addition(message: Message, state: FSMContext, word: str):
|
|
|
|
|
|
"""Обработка добавления слова"""
|
|
|
|
|
|
# Получаем пользователя
|
|
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
|
|
|
|
|
|
|
|
|
|
|
if not user:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
await message.answer(t('ru', 'common.start_first'))
|
2025-12-04 11:09:54 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Проверяем, есть ли уже такое слово
|
|
|
|
|
|
existing_word = await VocabularyService.find_word(session, user.id, word)
|
|
|
|
|
|
if existing_word:
|
2025-12-05 14:30:24 +03:00
|
|
|
|
lang = get_user_lang(user)
|
|
|
|
|
|
await message.answer(t(lang, 'add.exists', word=word, translation=existing_word.word_translation))
|
2025-12-04 11:09:54 +03:00
|
|
|
|
await state.clear()
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Показываем индикатор загрузки
|
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'
|
|
|
|
|
|
processing_msg = await message.answer(t(lang, 'add.searching'))
|
2025-12-04 11:09:54 +03:00
|
|
|
|
|
|
|
|
|
|
# Получаем перевод через AI
|
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)
|
|
|
|
|
|
source_lang = user.learning_language if user else 'en'
|
|
|
|
|
|
ui_lang = user.language_interface if user else 'ru'
|
|
|
|
|
|
word_data = await ai_service.translate_word(word, source_lang=source_lang, translation_lang=ui_lang)
|
2025-12-04 11:09:54 +03:00
|
|
|
|
|
|
|
|
|
|
# Удаляем сообщение о загрузке
|
|
|
|
|
|
await processing_msg.delete()
|
|
|
|
|
|
|
|
|
|
|
|
# Формируем примеры
|
|
|
|
|
|
examples_text = ""
|
|
|
|
|
|
if word_data.get("examples"):
|
2025-12-04 19:40:01 +03:00
|
|
|
|
examples_text = "\n\n" + t(lang, 'add.examples_header') + "\n"
|
2025-12-04 11:09:54 +03:00
|
|
|
|
for idx, example in enumerate(word_data["examples"][:2], 1):
|
2025-12-04 19:40:01 +03:00
|
|
|
|
src = example.get(source_lang) or example.get('en') or example.get('ru') or ''
|
|
|
|
|
|
tr = example.get(ui_lang) or example.get('ru') or example.get('en') or ''
|
|
|
|
|
|
examples_text += f"{idx}. {src}\n <i>{tr}</i>\n"
|
2025-12-04 11:09:54 +03:00
|
|
|
|
|
|
|
|
|
|
# Отправляем карточку слова
|
|
|
|
|
|
card_text = (
|
|
|
|
|
|
f"📝 <b>{word_data['word']}</b>\n"
|
|
|
|
|
|
f"🔊 [{word_data.get('transcription', '')}]\n\n"
|
2025-12-04 19:40:01 +03:00
|
|
|
|
f"{t(lang, 'add.translation_label')}: {word_data['translation']}\n"
|
|
|
|
|
|
f"{t(lang, 'add.category_label')}: {word_data.get('category', '')}\n"
|
|
|
|
|
|
f"{t(lang, 'add.level_label')}: {word_data.get('difficulty', 'A1')}"
|
2025-12-04 11:09:54 +03:00
|
|
|
|
f"{examples_text}\n\n"
|
2025-12-04 19:40:01 +03:00
|
|
|
|
f"{t(lang, 'add.confirm_question')}"
|
2025-12-04 11:09:54 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Создаём inline-кнопки
|
|
|
|
|
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
|
|
|
|
|
[
|
2025-12-04 19:40:01 +03:00
|
|
|
|
InlineKeyboardButton(text=t(lang, 'add.btn_add'), callback_data=f"add_word_confirm"),
|
|
|
|
|
|
InlineKeyboardButton(text=t(lang, 'add.btn_cancel'), callback_data="add_word_cancel")
|
2025-12-04 11:09:54 +03:00
|
|
|
|
]
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
# Сохраняем данные слова в состоянии
|
|
|
|
|
|
await state.update_data(word_data=word_data, user_id=user.id)
|
|
|
|
|
|
await state.set_state(AddWordStates.waiting_for_confirmation)
|
|
|
|
|
|
|
|
|
|
|
|
await message.answer(card_text, reply_markup=keyboard)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data == "add_word_confirm", AddWordStates.waiting_for_confirmation)
|
|
|
|
|
|
async def confirm_add_word(callback: CallbackQuery, state: FSMContext):
|
|
|
|
|
|
"""Подтверждение добавления слова"""
|
2025-12-04 19:40:01 +03:00
|
|
|
|
# Отвечаем сразу, запись в БД и подсчёт могут занять время
|
|
|
|
|
|
await callback.answer()
|
2025-12-04 11:09:54 +03:00
|
|
|
|
data = await state.get_data()
|
|
|
|
|
|
word_data = data.get("word_data")
|
|
|
|
|
|
user_id = data.get("user_id")
|
|
|
|
|
|
|
|
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
# Добавляем слово в базу
|
|
|
|
|
|
await VocabularyService.add_word(
|
|
|
|
|
|
session,
|
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
|
word_original=word_data["word"],
|
|
|
|
|
|
word_translation=word_data["translation"],
|
2025-12-04 19:40:01 +03:00
|
|
|
|
source_lang=source_lang,
|
|
|
|
|
|
translation_lang=ui_lang,
|
2025-12-04 11:09:54 +03:00
|
|
|
|
transcription=word_data.get("transcription"),
|
|
|
|
|
|
examples={"examples": word_data.get("examples", [])},
|
|
|
|
|
|
category=word_data.get("category"),
|
|
|
|
|
|
difficulty_level=word_data.get("difficulty"),
|
|
|
|
|
|
source=WordSource.MANUAL
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Получаем общее количество слов
|
2025-12-04 19:40:01 +03:00
|
|
|
|
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
|
|
|
|
|
|
words_count = await VocabularyService.get_words_count(session, user_id, learning_lang=user.learning_language)
|
2025-12-04 11:09:54 +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, callback.from_user.id)
|
|
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
2025-12-04 11:09:54 +03:00
|
|
|
|
await callback.message.edit_text(
|
2025-12-04 19:40:01 +03:00
|
|
|
|
t(lang, 'add.added_success', word=word_data['word'], count=words_count)
|
2025-12-04 11:09:54 +03:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
await state.clear()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.callback_query(F.data == "add_word_cancel")
|
|
|
|
|
|
async def cancel_add_word(callback: CallbackQuery, state: FSMContext):
|
|
|
|
|
|
"""Отмена добавления слова"""
|
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, 'add.cancelled'))
|
2025-12-04 11:09:54 +03:00
|
|
|
|
await state.clear()
|
|
|
|
|
|
await callback.answer()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.message(Command("vocabulary"))
|
|
|
|
|
|
async def cmd_vocabulary(message: Message):
|
|
|
|
|
|
"""Обработчик команды /vocabulary"""
|
|
|
|
|
|
async with async_session_maker() as session:
|
|
|
|
|
|
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
|
|
|
|
|
|
|
|
|
|
|
if not user:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
await message.answer(t('ru', 'common.start_first'))
|
2025-12-04 11:09:54 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Получаем слова пользователя
|
2025-12-04 19:40:01 +03:00
|
|
|
|
words = await VocabularyService.get_user_words(session, user.id, limit=10, learning_lang=user.learning_language)
|
|
|
|
|
|
total_count = await VocabularyService.get_words_count(session, user.id, learning_lang=user.learning_language)
|
2025-12-04 11:09:54 +03:00
|
|
|
|
|
|
|
|
|
|
if not words:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
await message.answer(t(lang, 'vocab.empty'))
|
2025-12-04 11:09:54 +03:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# Формируем список слов
|
2025-12-04 19:40:01 +03:00
|
|
|
|
lang = (user.language_interface if user else 'ru') or 'ru'
|
|
|
|
|
|
words_list = t(lang, 'vocab.header') + "\n\n"
|
2025-12-04 11:09:54 +03:00
|
|
|
|
for idx, word in enumerate(words, 1):
|
|
|
|
|
|
progress = ""
|
|
|
|
|
|
if word.times_reviewed > 0:
|
|
|
|
|
|
accuracy = int((word.correct_answers / word.times_reviewed) * 100)
|
2025-12-04 19:40:01 +03:00
|
|
|
|
progress = " " + t(lang, 'vocab.accuracy_inline', n=accuracy)
|
2025-12-04 11:09:54 +03:00
|
|
|
|
|
|
|
|
|
|
words_list += (
|
|
|
|
|
|
f"{idx}. <b>{word.word_original}</b> — {word.word_translation}\n"
|
|
|
|
|
|
f" 🔊 [{word.transcription or ''}]{progress}\n\n"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if total_count > 10:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
words_list += "\n" + t(lang, 'vocab.shown_last', n=total_count)
|
2025-12-04 11:09:54 +03:00
|
|
|
|
else:
|
2025-12-04 19:40:01 +03:00
|
|
|
|
words_list += "\n" + t(lang, 'vocab.total', n=total_count)
|
2025-12-04 11:09:54 +03:00
|
|
|
|
|
|
|
|
|
|
await message.answer(words_list)
|