import logging import asyncio from aiogram import Bot from apscheduler.schedulers.asyncio import AsyncIOScheduler from datetime import datetime from config.settings import Settings from .subscription_service import SubscriptionService from db.database import get_db_connection_manager from bot.middlewares.i18n import JsonI18n from .panel_api_service import PanelApiService async def send_expiration_warnings(bot: Bot, settings: Settings, i18n: JsonI18n, panel_service: PanelApiService): """ Sends expiration warnings to users whose subscriptions are ending soon. This function is called by the scheduler. """ logging.info( f"Scheduler job: Checking for expiring subscriptions at {datetime.now()}..." ) sub_service = SubscriptionService(get_db_connection_manager, settings, panel_service) expiring_subs = await sub_service.get_subscriptions_ending_soon( settings.SUBSCRIPTION_EXPIRATION_NOTIFICATION_DAYS) if not expiring_subs: logging.info("No subscriptions found ending soon for notification.") return logging.info( f"Found {len(expiring_subs)} subscriptions ending soon for notification." ) for sub_info in expiring_subs: user_id = sub_info['user_id'] if not user_id: logging.warning( f"Skipping notification for subscription without user_id: {sub_info}" ) continue user_lang = sub_info.get('language_code') if sub_info.get( 'language_code') else getattr(settings, 'DEFAULT_LANGUAGE', 'en') first_name = sub_info.get('first_name', 'User') end_date_str = sub_info['end_date_str'] days_left_float = sub_info.get('days_left') days_left_display = 'N/A' if days_left_float is not None: days_left_display = max(0, int(round(days_left_float))) _ = lambda key, **kwargs: i18n.gettext(user_lang, key, **kwargs) message_text = _("subscription_ending_soon_notification", user_name=first_name, end_date=end_date_str, days_left=days_left_display) try: await bot.send_message(user_id, message_text) await sub_service.update_last_notification_sent( user_id, end_date_str) logging.info( f"Sent expiration warning to user {user_id} for subscription ending {end_date_str}" ) except Exception as e: logging.error( f"Failed to send expiration warning to user {user_id}: {e}") await asyncio.sleep(0.1) async def schedule_subscription_notifications(bot: Bot, settings: Settings, i18n: JsonI18n, scheduler: AsyncIOScheduler): """Schedules the daily job to send expiration warnings.""" async def job_wrapper(): panel_service = PanelApiService(settings) try: await send_expiration_warnings(bot, settings, i18n, panel_service) except Exception as e: logging.error( f"Error in scheduled job 'send_expiration_warnings': {e}", exc_info=True) finally: await panel_service.close_session() try: notification_hour = int(settings.SUBSCRIPTION_NOTIFICATION_HOUR_UTC) notification_minute = int( settings.SUBSCRIPTION_NOTIFICATION_MINUTE_UTC) except (ValueError, TypeError): logging.warning( "SUBSCRIPTION_NOTIFICATION_HOUR_UTC or MINUTE_UTC is invalid. Defaulting to 9:00 UTC." ) notification_hour = 9 notification_minute = 0 scheduler.add_job(job_wrapper, 'cron', hour=notification_hour, minute=notification_minute, name="daily_subscription_expiration_warnings", misfire_grace_time=60 * 15) logging.info( f"Subscription expiration warning job scheduled daily at {notification_hour:02d}:{notification_minute:02d} UTC." )