Реализован MVP телеграм бота для изучения языков

Основные компоненты:
- База данных (PostgreSQL) с моделями User, Vocabulary, Task
- Интеграция с OpenAI API для перевода слов
- Команды: /start, /add, /vocabulary, /help
- Сервисы для работы с пользователями, словарем и AI

Реализовано:
 Регистрация и приветствие пользователя
 Добавление слов в словарь с автоматическим переводом
 Просмотр личного словаря
 Архитектура проекта с разделением на слои

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-04 11:09:54 +03:00
parent df9f9f3d4d
commit 1a02c979d0
18 changed files with 894 additions and 2 deletions

11
.env.example Normal file
View File

@@ -0,0 +1,11 @@
# Telegram Bot Token (получить у @BotFather)
BOT_TOKEN=your_telegram_bot_token_here
# OpenAI API Key
OPENAI_API_KEY=your_openai_api_key_here
# Database
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/language_bot
# Settings
DEBUG=True

142
README.md
View File

@@ -1,3 +1,141 @@
# tg_bot_language
# Telegram Бот для изучения языков
Бот для изучения иностранных языков
Интеллектуальный Telegram бот для изучения английского языка с использованием AI.
## Возможности
- 📚 Управление словарным запасом с автоматическим переводом через AI
- ✍️ Ежедневные задания для практики (в разработке)
- 💬 Диалоговая практика с ИИ (в разработке)
- 📊 Статистика прогресса (в разработке)
## Текущая версия (MVP)
**Реализовано:**
- ✅ Команда `/start` - приветствие и регистрация пользователя
- ✅ Команда `/add [слово]` - добавление слов в словарь с AI-переводом
- ✅ Команда `/vocabulary` - просмотр словаря
- ✅ Команда `/help` - справка
- ✅ База данных (PostgreSQL) для хранения пользователей и словарей
- ✅ Интеграция с OpenAI API для перевода слов
## Установка и запуск
### 1. Клонирование репозитория
```bash
git clone http://103.137.249.134:3000/NANDI/tg_bot_language.git
cd tg_bot_language
```
### 2. Установка зависимостей
```bash
pip install -r requirements.txt
```
### 3. Настройка окружения
Скопируйте `.env.example` в `.env`:
```bash
cp .env.example .env
```
Отредактируйте `.env` и заполните необходимые параметры:
```env
BOT_TOKEN=your_telegram_bot_token_here
OPENAI_API_KEY=your_openai_api_key_here
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/language_bot
DEBUG=True
```
**Получение токенов:**
- Telegram Bot Token: создайте бота через [@BotFather](https://t.me/BotFather)
- OpenAI API Key: получите на [platform.openai.com](https://platform.openai.com/api-keys)
### 4. Настройка базы данных
Создайте PostgreSQL базу данных:
```bash
createdb language_bot
```
Или используйте Docker:
```bash
docker run --name language-bot-db -e POSTGRES_PASSWORD=password -e POSTGRES_DB=language_bot -p 5432:5432 -d postgres:15
```
### 5. Запуск бота
```bash
python main.py
```
## Структура проекта
```
bot_tg_language/
├── bot/
│ ├── handlers/ # Обработчики команд
│ │ ├── start.py # /start, /help
│ │ └── vocabulary.py # /add, /vocabulary
│ └── keyboards/ # Клавиатуры (пока не используется)
├── database/
│ ├── models.py # Модели БД (User, Vocabulary, Task)
│ └── db.py # Подключение к БД
├── services/
│ ├── ai_service.py # Сервис для работы с OpenAI
│ ├── user_service.py # Сервис пользователей
│ └── vocabulary_service.py # Сервис словаря
├── config/
│ └── settings.py # Настройки приложения
├── main.py # Точка входа
├── requirements.txt # Зависимости
├── .env.example # Пример конфигурации
└── TZ.md # Техническое задание
```
## Использование
### Команды бота
- `/start` - Начать работу с ботом
- `/add [слово]` - Добавить слово в словарь
- `/vocabulary` - Посмотреть свой словарь
- `/help` - Показать справку
### Пример использования
1. Запустите бота: `/start`
2. Добавьте слово: `/add elephant`
3. Бот переведёт слово через AI и предложит добавить в словарь
4. Подтвердите добавление
5. Просмотрите словарь: `/vocabulary`
## Roadmap
См. [TZ.md](TZ.md) для полного технического задания.
**Следующие этапы:**
- [ ] Ежедневные задания с разными типами упражнений
- [ ] Тематические подборки слов
- [ ] Импорт слов из текста
- [ ] Диалоговая практика с AI
- [ ] Статистика и прогресс
- [ ] Spaced repetition алгоритм
## Технологии
- **Python 3.11+**
- **aiogram 3.x** - Telegram Bot framework
- **SQLAlchemy 2.x** - ORM для работы с БД
- **PostgreSQL** - База данных
- **OpenAI API** - AI для перевода и проверки
## Лицензия
MIT

0
bot/__init__.py Normal file
View File

0
bot/handlers/__init__.py Normal file
View File

66
bot/handlers/start.py Normal file
View File

@@ -0,0 +1,66 @@
from aiogram import Router, F
from aiogram.filters import CommandStart, Command
from aiogram.types import Message
from aiogram.fsm.context import FSMContext
from database.db import async_session_maker
from services.user_service import UserService
router = Router()
@router.message(CommandStart())
async def cmd_start(message: Message, state: FSMContext):
"""Обработчик команды /start"""
async with async_session_maker() as session:
user = await UserService.get_or_create_user(
session,
telegram_id=message.from_user.id,
username=message.from_user.username
)
if user.created_at.timestamp() > (message.date.timestamp() - 60):
# Новый пользователь (создан менее минуты назад)
await message.answer(
f"👋 Привет, {message.from_user.first_name}!\n\n"
f"Я бот для изучения английского языка. Помогу тебе:\n"
f"📚 Пополнять словарный запас\n"
f"✍️ Выполнять ежедневные задания\n"
f"💬 Практиковать язык в диалоге\n\n"
f"<b>Основные команды:</b>\n"
f"/add [слово] - добавить слово в словарь\n"
f"/vocabulary - мой словарь\n"
f"/task - получить задание\n"
f"/stats - статистика\n"
f"/help - справка\n\n"
f"Давай начнём! Отправь мне слово, которое хочешь выучить, или используй команду /add"
)
else:
# Существующий пользователь
await message.answer(
f"С возвращением, {message.from_user.first_name}! 👋\n\n"
f"Готов продолжить обучение?\n"
f"/vocabulary - посмотреть словарь\n"
f"/task - получить задание\n"
f"/stats - статистика"
)
@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"
"/import - импортировать слова из текста\n\n"
"<b>Обучение:</b>\n"
"/task - получить задание\n"
"/practice - практика с ИИ\n\n"
"<b>Статистика:</b>\n"
"/stats - твой прогресс\n\n"
"<b>Настройки:</b>\n"
"/settings - настройки бота\n\n"
"Ты также можешь просто отправить мне слово, и я предложу добавить его в словарь!"
)

191
bot/handlers/vocabulary.py Normal file
View File

@@ -0,0 +1,191 @@
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
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:
await message.answer(
"Отправь слово, которое хочешь добавить:\n"
"Например: <code>/add elephant</code>\n\n"
"Или просто отправь слово без команды!"
)
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:
await message.answer("Сначала запусти бота командой /start")
return
# Проверяем, есть ли уже такое слово
existing_word = await VocabularyService.find_word(session, user.id, word)
if existing_word:
await message.answer(
f"Слово '<b>{word}</b>' уже есть в твоём словаре!\n"
f"Перевод: {existing_word.word_translation}"
)
await state.clear()
return
# Показываем индикатор загрузки
processing_msg = await message.answer("⏳ Ищу перевод и примеры...")
# Получаем перевод через AI
word_data = await ai_service.translate_word(word)
# Удаляем сообщение о загрузке
await processing_msg.delete()
# Формируем примеры
examples_text = ""
if word_data.get("examples"):
examples_text = "\n\n<b>Примеры:</b>\n"
for idx, example in enumerate(word_data["examples"][:2], 1):
examples_text += f"{idx}. {example['en']}\n <i>{example['ru']}</i>\n"
# Отправляем карточку слова
card_text = (
f"📝 <b>{word_data['word']}</b>\n"
f"🔊 [{word_data.get('transcription', '')}]\n\n"
f"🇷🇺 {word_data['translation']}\n"
f"📂 Категория: {word_data.get('category', 'общая')}\n"
f"📊 Уровень: {word_data.get('difficulty', 'A1')}"
f"{examples_text}\n\n"
f"Добавить это слово в словарь?"
)
# Создаём inline-кнопки
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[
InlineKeyboardButton(text="✅ Добавить", callback_data=f"add_word_confirm"),
InlineKeyboardButton(text="❌ Отмена", callback_data="add_word_cancel")
]
])
# Сохраняем данные слова в состоянии
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):
"""Подтверждение добавления слова"""
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"],
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
)
# Получаем общее количество слов
words_count = await VocabularyService.get_words_count(session, user_id)
await callback.message.edit_text(
f"✅ Слово '<b>{word_data['word']}</b>' добавлено в твой словарь!\n\n"
f"Всего слов в словаре: {words_count}\n\n"
f"Продолжай добавлять новые слова или используй /task для практики!"
)
await state.clear()
await callback.answer()
@router.callback_query(F.data == "add_word_cancel")
async def cancel_add_word(callback: CallbackQuery, state: FSMContext):
"""Отмена добавления слова"""
await callback.message.edit_text("Отменено. Можешь добавить другое слово командой /add")
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:
await message.answer("Сначала запусти бота командой /start")
return
# Получаем слова пользователя
words = await VocabularyService.get_user_words(session, user.id, limit=10)
total_count = await VocabularyService.get_words_count(session, user.id)
if not words:
await message.answer(
"📚 Твой словарь пока пуст!\n\n"
"Добавь первое слово командой /add или просто отправь мне слово."
)
return
# Формируем список слов
words_list = "<b>📚 Твой словарь:</b>\n\n"
for idx, word in enumerate(words, 1):
progress = ""
if word.times_reviewed > 0:
accuracy = int((word.correct_answers / word.times_reviewed) * 100)
progress = f" ({accuracy}% точность)"
words_list += (
f"{idx}. <b>{word.word_original}</b> — {word.word_translation}\n"
f" 🔊 [{word.transcription or ''}]{progress}\n\n"
)
if total_count > 10:
words_list += f"\n<i>Показаны последние 10 из {total_count} слов</i>"
else:
words_list += f"\n<i>Всего слов: {total_count}</i>"
await message.answer(words_list)

View File

0
config/__init__.py Normal file
View File

26
config/settings.py Normal file
View File

@@ -0,0 +1,26 @@
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Настройки приложения"""
# Telegram
bot_token: str
# OpenAI
openai_api_key: str
# Database
database_url: str
# App settings
debug: bool = False
model_config = SettingsConfigDict(
env_file='.env',
env_file_encoding='utf-8',
case_sensitive=False
)
settings = Settings()

0
database/__init__.py Normal file
View File

29
database/db.py Normal file
View File

@@ -0,0 +1,29 @@
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from config.settings import settings
from database.models import Base
# Создание движка базы данных
engine = create_async_engine(
settings.database_url,
echo=settings.debug,
future=True
)
# Фабрика сессий
async_session_maker = async_sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False
)
async def init_db():
"""Инициализация базы данных (создание таблиц)"""
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def get_session() -> AsyncSession:
"""Получение сессии базы данных"""
async with async_session_maker() as session:
yield session

83
database/models.py Normal file
View File

@@ -0,0 +1,83 @@
from datetime import datetime
from typing import Optional
from sqlalchemy import String, BigInteger, DateTime, Integer, Boolean, JSON, Enum as SQLEnum
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
import enum
class Base(DeclarativeBase):
"""Базовая модель"""
pass
class LanguageLevel(str, enum.Enum):
"""Уровни владения языком"""
A1 = "A1"
A2 = "A2"
B1 = "B1"
B2 = "B2"
C1 = "C1"
C2 = "C2"
class WordSource(str, enum.Enum):
"""Источник добавления слова"""
MANUAL = "manual" # Ручное добавление
SUGGESTED = "suggested" # Предложено ботом
CONTEXT = "context" # Из контекста диалога
IMPORT = "import" # Импорт из текста
ERROR = "error" # Из ошибок в заданиях
class User(Base):
"""Модель пользователя"""
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
telegram_id: Mapped[int] = mapped_column(BigInteger, unique=True, nullable=False)
username: Mapped[Optional[str]] = mapped_column(String(255))
language_interface: Mapped[str] = mapped_column(String(2), default="ru") # ru/en
learning_language: Mapped[str] = mapped_column(String(2), default="en") # en
level: Mapped[LanguageLevel] = mapped_column(SQLEnum(LanguageLevel), default=LanguageLevel.A1)
timezone: Mapped[str] = mapped_column(String(50), default="UTC")
daily_task_time: Mapped[Optional[str]] = mapped_column(String(5)) # HH:MM
streak_days: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
last_active: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class Vocabulary(Base):
"""Модель словарного запаса"""
__tablename__ = "vocabulary"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
word_original: Mapped[str] = mapped_column(String(255), nullable=False)
word_translation: Mapped[str] = mapped_column(String(255), nullable=False)
transcription: Mapped[Optional[str]] = mapped_column(String(255))
examples: Mapped[Optional[dict]] = mapped_column(JSON) # JSON массив примеров
category: Mapped[Optional[str]] = mapped_column(String(100))
difficulty_level: Mapped[Optional[LanguageLevel]] = mapped_column(SQLEnum(LanguageLevel))
source: Mapped[WordSource] = mapped_column(SQLEnum(WordSource), default=WordSource.MANUAL)
times_reviewed: Mapped[int] = mapped_column(Integer, default=0)
correct_answers: Mapped[int] = mapped_column(Integer, default=0)
last_reviewed: Mapped[Optional[datetime]] = mapped_column(DateTime)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
notes: Mapped[Optional[str]] = mapped_column(String(500)) # Заметки пользователя
class Task(Base):
"""Модель задания"""
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True)
task_type: Mapped[str] = mapped_column(String(50), nullable=False) # translate, sentence, fill, etc.
content: Mapped[dict] = mapped_column(JSON, nullable=False) # Содержание задания
correct_answer: Mapped[Optional[str]] = mapped_column(String(500))
user_answer: Mapped[Optional[str]] = mapped_column(String(500))
is_correct: Mapped[Optional[bool]] = mapped_column(Boolean)
ai_feedback: Mapped[Optional[str]] = mapped_column(String(1000))
completed_at: Mapped[Optional[datetime]] = mapped_column(DateTime)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)

41
main.py Normal file
View File

@@ -0,0 +1,41 @@
import asyncio
import logging
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from config.settings import settings
from bot.handlers import start, vocabulary
from database.db import init_db
async def main():
"""Главная функция запуска бота"""
# Настройка логирования
logging.basicConfig(
level=logging.INFO if settings.debug else logging.WARNING,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Инициализация бота и диспетчера
bot = Bot(
token=settings.bot_token,
default=DefaultBotProperties(parse_mode=ParseMode.HTML)
)
dp = Dispatcher()
# Регистрация роутеров
dp.include_router(start.router)
dp.include_router(vocabulary.router)
# Инициализация базы данных
await init_db()
# Запуск бота
logging.info("Бот запущен")
await dp.start_polling(bot)
if __name__ == '__main__':
asyncio.run(main())

8
requirements.txt Normal file
View File

@@ -0,0 +1,8 @@
aiogram==3.13.1
sqlalchemy==2.0.36
asyncpg==0.30.0
alembic==1.14.0
python-dotenv==1.0.1
openai==1.57.3
pydantic==2.10.3
pydantic-settings==2.6.1

0
services/__init__.py Normal file
View File

117
services/ai_service.py Normal file
View File

@@ -0,0 +1,117 @@
from openai import AsyncOpenAI
from config.settings import settings
from typing import Dict, List
class AIService:
"""Сервис для работы с OpenAI API"""
def __init__(self):
self.client = AsyncOpenAI(api_key=settings.openai_api_key)
async def translate_word(self, word: str, target_lang: str = "ru") -> Dict:
"""
Перевести слово и получить дополнительную информацию
Args:
word: Слово для перевода
target_lang: Язык перевода (по умолчанию русский)
Returns:
Dict с переводом, транскрипцией и примерами
"""
prompt = f"""Переведи английское слово/фразу "{word}" на русский язык.
Верни ответ строго в формате JSON:
{{
"word": "{word}",
"translation": "перевод",
"transcription": "транскрипция в IPA",
"examples": [
{{"en": "пример на английском", "ru": "перевод примера"}},
{{"en": "ещё один пример", "ru": "перевод примера"}}
],
"category": "категория слова (работа, еда, путешествия и т.д.)",
"difficulty": "уровень сложности (A1/A2/B1/B2/C1/C2)"
}}
Важно: верни только JSON, без дополнительного текста."""
try:
response = await self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Ты - помощник для изучения английского языка. Отвечай только в формате JSON."},
{"role": "user", "content": prompt}
],
temperature=0.3,
response_format={"type": "json_object"}
)
import json
result = json.loads(response.choices[0].message.content)
return result
except Exception as e:
# Fallback в случае ошибки
return {
"word": word,
"translation": "Ошибка перевода",
"transcription": "",
"examples": [],
"category": "unknown",
"difficulty": "A1"
}
async def check_answer(self, question: str, correct_answer: str, user_answer: str) -> Dict:
"""
Проверить ответ пользователя с помощью ИИ
Args:
question: Вопрос задания
correct_answer: Правильный ответ
user_answer: Ответ пользователя
Returns:
Dict с результатом проверки и обратной связью
"""
prompt = f"""Проверь ответ пользователя на задание по английскому языку.
Задание: {question}
Правильный ответ: {correct_answer}
Ответ пользователя: {user_answer}
Верни ответ в формате JSON:
{{
"is_correct": true/false,
"feedback": "краткое объяснение (если ответ неверный, объясни ошибку и дай правильный вариант)",
"score": 0-100
}}
Учитывай возможные вариации ответа. Если смысл передан правильно, даже с небольшими грамматическими неточностями, засчитывай ответ."""
try:
response = await self.client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Ты - преподаватель английского языка. Проверяй ответы справедливо, учитывая контекст."},
{"role": "user", "content": prompt}
],
temperature=0.3,
response_format={"type": "json_object"}
)
import json
result = json.loads(response.choices[0].message.content)
return result
except Exception as e:
return {
"is_correct": False,
"feedback": "Ошибка проверки ответа",
"score": 0
}
# Глобальный экземпляр сервиса
ai_service = AIService()

59
services/user_service.py Normal file
View File

@@ -0,0 +1,59 @@
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import User, LanguageLevel
from typing import Optional
class UserService:
"""Сервис для работы с пользователями"""
@staticmethod
async def get_or_create_user(session: AsyncSession, telegram_id: int, username: Optional[str] = None) -> User:
"""
Получить пользователя или создать нового
Args:
session: Сессия базы данных
telegram_id: Telegram ID пользователя
username: Username пользователя
Returns:
Объект пользователя
"""
# Попытка найти существующего пользователя
result = await session.execute(
select(User).where(User.telegram_id == telegram_id)
)
user = result.scalar_one_or_none()
if user:
return user
# Создание нового пользователя
new_user = User(
telegram_id=telegram_id,
username=username,
level=LanguageLevel.A1
)
session.add(new_user)
await session.commit()
await session.refresh(new_user)
return new_user
@staticmethod
async def get_user_by_telegram_id(session: AsyncSession, telegram_id: int) -> Optional[User]:
"""
Получить пользователя по Telegram ID
Args:
session: Сессия базы данных
telegram_id: Telegram ID пользователя
Returns:
Объект пользователя или None
"""
result = await session.execute(
select(User).where(User.telegram_id == telegram_id)
)
return result.scalar_one_or_none()

View File

@@ -0,0 +1,123 @@
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import Vocabulary, WordSource, LanguageLevel
from typing import List, Optional
class VocabularyService:
"""Сервис для работы со словарным запасом"""
@staticmethod
async def add_word(
session: AsyncSession,
user_id: int,
word_original: str,
word_translation: str,
transcription: Optional[str] = None,
examples: Optional[dict] = None,
category: Optional[str] = None,
difficulty_level: Optional[str] = None,
source: WordSource = WordSource.MANUAL,
notes: Optional[str] = None
) -> Vocabulary:
"""
Добавить слово в словарь пользователя
Args:
session: Сессия базы данных
user_id: ID пользователя
word_original: Оригинальное слово
word_translation: Перевод
transcription: Транскрипция
examples: Примеры использования
category: Категория слова
difficulty_level: Уровень сложности
source: Источник добавления
notes: Заметки пользователя
Returns:
Созданный объект слова
"""
# Преобразование difficulty_level в enum
difficulty_enum = None
if difficulty_level:
try:
difficulty_enum = LanguageLevel[difficulty_level]
except KeyError:
difficulty_enum = None
new_word = Vocabulary(
user_id=user_id,
word_original=word_original,
word_translation=word_translation,
transcription=transcription,
examples=examples,
category=category,
difficulty_level=difficulty_enum,
source=source,
notes=notes
)
session.add(new_word)
await session.commit()
await session.refresh(new_word)
return new_word
@staticmethod
async def get_user_words(session: AsyncSession, user_id: int, limit: int = 50) -> List[Vocabulary]:
"""
Получить все слова пользователя
Args:
session: Сессия базы данных
user_id: ID пользователя
limit: Максимальное количество слов
Returns:
Список слов пользователя
"""
result = await session.execute(
select(Vocabulary)
.where(Vocabulary.user_id == user_id)
.order_by(Vocabulary.created_at.desc())
.limit(limit)
)
return list(result.scalars().all())
@staticmethod
async def get_words_count(session: AsyncSession, user_id: int) -> int:
"""
Получить количество слов в словаре пользователя
Args:
session: Сессия базы данных
user_id: ID пользователя
Returns:
Количество слов
"""
result = await session.execute(
select(Vocabulary).where(Vocabulary.user_id == user_id)
)
return len(list(result.scalars().all()))
@staticmethod
async def find_word(session: AsyncSession, user_id: int, word: str) -> Optional[Vocabulary]:
"""
Найти слово в словаре пользователя
Args:
session: Сессия базы данных
user_id: ID пользователя
word: Слово для поиска
Returns:
Объект слова или None
"""
result = await session.execute(
select(Vocabulary)
.where(Vocabulary.user_id == user_id)
.where(Vocabulary.word_original.ilike(f"%{word}%"))
)
return result.scalar_one_or_none()