Files
tg_bot_language/services/reminder_service.py
mamonov.ep f38ff2f18e feat: мини-истории, слово дня, меню практики
- Добавлены мини-истории для чтения с выбором жанра и вопросами
- Кнопка показа/скрытия перевода истории
- Количество вопросов берётся из настроек пользователя
- Слово дня генерируется глобально в 00:00 UTC
- Кнопка "Практика" открывает меню выбора режима
- Убран автоматический create_all при запуске (только миграции)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-09 15:05:38 +03:00

189 lines
7.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import logging
from datetime import datetime, timedelta
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, JLPT_LANGUAGES
from database.db import async_session_maker
logger = logging.getLogger(__name__)
class ReminderService:
"""Сервис для управления напоминаниями"""
def __init__(self, bot):
self.bot = bot
self.scheduler = AsyncIOScheduler()
def start(self):
"""Запустить планировщик"""
# Проверяем напоминания каждые 5 минут
self.scheduler.add_job(
self.check_and_send_reminders,
trigger='interval',
minutes=5,
id='check_reminders',
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()
logger.info("Планировщик напоминаний остановлен")
async def check_and_send_reminders(self):
"""Проверить и отправить напоминания пользователям"""
try:
async with async_session_maker() as session:
# Получаем всех пользователей с включенными напоминаниями
result = await session.execute(
select(User).where(
User.reminders_enabled == True,
User.daily_task_time.isnot(None)
)
)
users = list(result.scalars().all())
current_time = datetime.utcnow()
for user in users:
if await self._should_send_reminder(user, current_time):
await self._send_reminder(user, session)
except Exception as e:
logger.error(f"Ошибка при проверке напоминаний: {e}")
async def _should_send_reminder(self, user: User, current_time: datetime) -> bool:
"""
Проверить, нужно ли отправлять напоминание пользователю
Args:
user: Пользователь
current_time: Текущее время (UTC)
Returns:
True если нужно отправить
"""
if not user.daily_task_time:
return False
# Парсим время напоминания (формат HH:MM)
try:
hour, minute = map(int, user.daily_task_time.split(':'))
except:
return False
# Создаем datetime для времени напоминания сегодня (UTC)
reminder_time = current_time.replace(hour=hour, minute=minute, second=0, microsecond=0)
# Проверяем, не отправляли ли мы уже напоминание сегодня
if user.last_reminder_sent:
last_sent_date = user.last_reminder_sent.date()
current_date = current_time.date()
# Если уже отправляли сегодня, не отправляем снова
if last_sent_date == current_date:
return False
# Проверяем, наступило ли время напоминания (с погрешностью 5 минут)
time_diff = abs((current_time - reminder_time).total_seconds())
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):
"""
Отправить напоминание пользователю
Args:
user: Пользователь
session: Сессия базы данных
"""
try:
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="\n".join(message_parts),
parse_mode="HTML"
)
# Обновляем время последнего напоминания
user.last_reminder_sent = datetime.utcnow()
await session.commit()
logger.info(f"Напоминание отправлено пользователю {user.telegram_id}")
except Exception as e:
logger.error(f"Ошибка при отправке напоминания пользователю {user.telegram_id}: {e}")
# Глобальный экземпляр сервиса (будет инициализирован в main.py)
reminder_service: ReminderService = None
def init_reminder_service(bot):
"""Инициализировать сервис напоминаний"""
global reminder_service
reminder_service = ReminderService(bot)
return reminder_service