139 lines
5.6 KiB
Python
139 lines
5.6 KiB
Python
import asyncio
|
|
import random
|
|
import logging
|
|
from datetime import datetime, time, timedelta
|
|
from typing import Optional
|
|
from config import Config
|
|
from ollama_client import OllamaClient
|
|
from template_manager import TemplateManager
|
|
from notification_client import NotificationClient
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class NotificationScheduler:
|
|
"""Handles scheduling of notifications with random intervals and silent time."""
|
|
|
|
def __init__(self, config: Config):
|
|
self.config = config
|
|
timezone_name = getattr(config, 'timezone', 'UTC')
|
|
self.template_manager = TemplateManager(config.templates_dir, timezone_name)
|
|
self.notification_client = NotificationClient(config)
|
|
self.running = False
|
|
self._timezone = None
|
|
self._timezone_name = None
|
|
self._init_timezone()
|
|
|
|
def _init_timezone(self):
|
|
"""Initialize timezone from config."""
|
|
import pytz
|
|
|
|
timezone_name = getattr(self.config, 'timezone', 'UTC')
|
|
try:
|
|
self._timezone = pytz.timezone(timezone_name)
|
|
self._timezone_name = timezone_name
|
|
except pytz.exceptions.UnknownTimeZoneError:
|
|
logger.warning(f"Unknown timezone '{timezone_name}', falling back to UTC")
|
|
self._timezone = pytz.UTC
|
|
self._timezone_name = "UTC (fallback)"
|
|
|
|
def _get_current_time(self):
|
|
"""Get current time in configured timezone."""
|
|
return datetime.now(self._timezone)
|
|
|
|
def _is_silent_time(self) -> bool:
|
|
"""Check if current time is within silent hours."""
|
|
current_datetime = self._get_current_time()
|
|
current_time = current_datetime.time()
|
|
|
|
# Parse silent time configuration
|
|
silent_start = datetime.strptime(self.config.silent_start, "%H:%M").time()
|
|
silent_end = datetime.strptime(self.config.silent_end, "%H:%M").time()
|
|
|
|
# Log current time for debugging
|
|
logger.info(f"Current time: {current_datetime.strftime('%Y-%m-%d %H:%M:%S')} (timezone {self._timezone_name})")
|
|
logger.info(f"Silent period: {self.config.silent_start} to {self.config.silent_end}")
|
|
|
|
# Handle overnight silent period (e.g., 20:00 to 12:00)
|
|
if silent_start >= silent_end:
|
|
is_silent = current_time >= silent_start or current_time <= silent_end
|
|
else:
|
|
is_silent = silent_start <= current_time <= silent_end
|
|
|
|
logger.info(f"Silent time check: {is_silent}")
|
|
return is_silent
|
|
|
|
def _get_next_interval(self) -> int:
|
|
"""Get random interval in seconds between min and max."""
|
|
minutes = random.randint(self.config.min_interval, self.config.max_interval)
|
|
return minutes * 60
|
|
|
|
async def _send_notification(self) -> bool:
|
|
"""Send a notification with Ollama response."""
|
|
try:
|
|
async with OllamaClient(self.config) as client:
|
|
# Check if Ollama is available
|
|
if not await client.check_health():
|
|
logger.error("Ollama service is not available")
|
|
return False
|
|
|
|
# Get random prompt
|
|
prompt = self.template_manager.get_random_prompt()
|
|
logger.info(f"Using prompt: {prompt}")
|
|
|
|
# Get response from Ollama
|
|
strip_think_tags = getattr(self.config, 'strip_think_tags', True)
|
|
response = await client.generate_response(prompt, strip_think_tags=strip_think_tags)
|
|
if not response:
|
|
logger.error("Failed to get response from Ollama")
|
|
return False
|
|
|
|
# Send notification
|
|
success = await self.notification_client.send_notification(response)
|
|
if success:
|
|
logger.info("Notification sent successfully")
|
|
else:
|
|
logger.error("Failed to send notification")
|
|
|
|
return success
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error sending notification: {e}")
|
|
return False
|
|
|
|
async def start(self):
|
|
"""Start the notification scheduler."""
|
|
self.running = True
|
|
logger.info("Starting notification scheduler...")
|
|
|
|
while self.running:
|
|
try:
|
|
# Check if it's silent time
|
|
if self._is_silent_time():
|
|
logger.info("Silent time - skipping notification")
|
|
# Wait 30 minutes before checking again
|
|
await asyncio.sleep(1800)
|
|
continue
|
|
|
|
# Send notification
|
|
await self._send_notification()
|
|
|
|
# Calculate next interval
|
|
interval = self._get_next_interval()
|
|
current_time = self._get_current_time()
|
|
next_time = current_time + timedelta(seconds=interval)
|
|
logger.info(f"Next notification scheduled for: {next_time.strftime('%Y-%m-%d %H:%M:%S')} (timezone {self._timezone_name})")
|
|
|
|
# Wait for next interval
|
|
await asyncio.sleep(interval)
|
|
|
|
except asyncio.CancelledError:
|
|
logger.info("Scheduler cancelled")
|
|
break
|
|
except Exception as e:
|
|
logger.error(f"Error in scheduler: {e}")
|
|
# Wait 5 minutes on error
|
|
await asyncio.sleep(300)
|
|
|
|
def stop(self):
|
|
"""Stop the notification scheduler."""
|
|
self.running = False |