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