feat: мини-истории, слово дня, меню практики

- Добавлены мини-истории для чтения с выбором жанра и вопросами
- Кнопка показа/скрытия перевода истории
- Количество вопросов берётся из настроек пользователя
- Слово дня генерируется глобально в 00:00 UTC
- Кнопка "Практика" открывает меню выбора режима
- Убран автоматический create_all при запуске (только миграции)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-09 15:05:38 +03:00
parent 69c651c031
commit f38ff2f18e
22 changed files with 3131 additions and 77 deletions

View File

@@ -1,12 +1,12 @@
import logging
from datetime import datetime, timedelta
from typing import List
from typing import List, Optional
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import User
from database.models import User, JLPT_LANGUAGES
from database.db import async_session_maker
logger = logging.getLogger(__name__)
@@ -30,9 +30,26 @@ class ReminderService:
replace_existing=True
)
# Генерация слов дня в 00:00 UTC
self.scheduler.add_job(
self.generate_daily_words,
trigger=CronTrigger(hour=0, minute=0, timezone='UTC'),
id='generate_words_of_day',
replace_existing=True
)
self.scheduler.start()
logger.info("Планировщик напоминаний запущен")
async def generate_daily_words(self):
"""Генерация слов дня для всех уровней"""
try:
from services.wordofday_service import wordofday_service
results = await wordofday_service.generate_all_words_for_today()
logger.info(f"Слова дня сгенерированы: {results}")
except Exception as e:
logger.error(f"Ошибка генерации слов дня: {e}")
def shutdown(self):
"""Остановить планировщик"""
self.scheduler.shutdown()
@@ -97,6 +114,17 @@ class ReminderService:
return time_diff < 300 # 5 минут в секундах
async def _get_user_level(self, user: User) -> str:
"""Получить уровень пользователя для текущего языка изучения"""
# Сначала проверяем levels_by_language
if user.levels_by_language and user.learning_language in user.levels_by_language:
return user.levels_by_language[user.learning_language]
# Иначе используем общий уровень
if user.learning_language in JLPT_LANGUAGES:
return "N5" # Дефолтный JLPT уровень
return user.level.value if user.level else "A1"
async def _send_reminder(self, user: User, session: AsyncSession):
"""
Отправить напоминание пользователю
@@ -106,18 +134,37 @@ class ReminderService:
session: Сессия базы данных
"""
try:
message_text = (
"⏰ <b>Время для практики!</b>\n\n"
"Не забудь потренироваться сегодня:\n"
"• /task - выполни задания\n"
"• /practice - попрактикуй диалог\n"
"• /words - добавь новые слова\n\n"
"💪 Регулярная практика - ключ к успеху!"
from services.wordofday_service import wordofday_service
from utils.i18n import t
lang = user.language_interface or "ru"
# Получаем слово дня для пользователя
level = await self._get_user_level(user)
word_of_day = await wordofday_service.get_word_of_day(
learning_lang=user.learning_language,
level=level
)
# Формируем сообщение
message_parts = [t(lang, "reminder.daily_title") + "\n"]
# Добавляем слово дня если есть
if word_of_day:
word_text = await wordofday_service.format_word_for_user(
word_of_day,
translation_lang=user.translation_language or user.language_interface,
ui_lang=lang
)
message_parts.append(f"{t(lang, 'reminder.daily_wod')}\n{word_text}\n")
message_parts.append(t(lang, "reminder.daily_tips"))
message_parts.append(f"\n{t(lang, 'reminder.daily_motivation')}")
await self.bot.send_message(
chat_id=user.telegram_id,
text=message_text
text="\n".join(message_parts),
parse_mode="HTML"
)
# Обновляем время последнего напоминания