Source code for src.main

"""
主程序入口
"""

import asyncio
import logging
import sys
from typing import List, Tuple, Dict
import click

from src.core.config_manager import ConfigManager
from src.core.notification_factory import NotificationFactory
from src.core.checker import BirthdayChecker, Recipient

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=[
        logging.StreamHandler(sys.stdout),
        logging.FileHandler("birthday_reminder.log"),
    ],
)
logger = logging.getLogger(__name__)


[docs] class BirthdayReminder:
[docs] def __init__(self, config_path: str = None): # 初始化配置管理器 self.config_manager = ConfigManager(config_path) # 验证配置 if not self.config_manager.validate_config(): raise ValueError("Invalid configuration") # 获取配置 self.config = self.config_manager.config # 初始化组件 self._initialize_components()
def _initialize_components(self): """初始化组件 - 简单直接""" try: # 创建生日检查器 self.birthday_checker = BirthdayChecker() # 创建通知发送器 notification_factory = NotificationFactory(self.config_manager.get_templates_dir()) self.notification_senders = notification_factory.create_senders(self.config) logger.info("Components initialized successfully") except Exception as e: logger.error(f"Failed to initialize components: {e}") raise
[docs] async def send_birthday_reminder(self, recipient: Recipient, extra_info: Dict) -> None: """发送生日提醒""" try: logger.info(f"Sending birthday reminder to {recipient.name}") for sender in self.notification_senders: try: # 渲染内容 content = sender.render_content( name=recipient.name, template_file=recipient.template_file, extra_info=extra_info, ) # 发送通知 await sender.send( recipient=recipient, content=content, days_until=extra_info["days_until"], age=extra_info["age"], ) logger.info(f"Successfully sent {type(sender).__name__} notification to {recipient.name}") except Exception as e: logger.error(f"Failed to send {type(sender).__name__} notification to {recipient.name}: {e}") # 继续尝试其他发送器,不中断整个流程 except Exception as e: logger.error(f"Failed to send birthday reminder to {recipient.name}: {e}") raise
[docs] def check_birthdays(self) -> List[Tuple[Recipient, bool, Dict]]: """检查所有人的生日""" try: logger.info(f"Checking birthdays for {len(self.config.recipients)} recipients") results = self.birthday_checker.check_birthdays(self.config.recipients) # 统计结果 birthday_count = sum(1 for _, is_birthday, _ in results if is_birthday) logger.info(f"Found {birthday_count} birthdays today") return results except Exception as e: logger.error(f"Failed to check birthdays: {e}") raise
[docs] async def run(self) -> None: """运行生日提醒主流程""" try: logger.info("Starting birthday reminder application") # 检查生日 birthday_results = self.check_birthdays() # 收集需要发送提醒的任务 tasks = [] for recipient, is_birthday, extra_info in birthday_results: if is_birthday: logger.info(f"Processing birthday for {recipient.name}") tasks.append(self.send_birthday_reminder(recipient, extra_info)) # 并发发送所有提醒 if tasks: await asyncio.gather(*tasks, return_exceptions=True) logger.info(f"Successfully processed {len(tasks)} birthday reminders") else: logger.info("No birthdays to process today") except Exception as e: logger.error(f"Application error: {type(e).__name__}: {e}") raise
[docs] def reload_config(self) -> None: """重新加载配置""" try: # 重新加载配置 self.config_manager._config = None self.config = self.config_manager.config # 重新初始化组件 self._initialize_components() logger.info("Configuration reloaded successfully") except Exception as e: logger.error(f"Failed to reload configuration: {e}") raise
@click.group() def cli(): """生日提醒系统 - 简洁版本""" pass @cli.command() @click.option('--config', '-c', help='配置文件路径', default="config.yml") def run(config): """运行生日提醒主流程""" try: app = BirthdayReminder(config) asyncio.run(app.run()) except Exception as e: logger.error(f"Application failed: {e}") sys.exit(1) @cli.command() @click.option('--config', '-c', help='配置文件路径', default="config.yml") def preview(): """预览生日提醒邮件内容(默认模板)""" try: from src.notification.sender_email import EmailSender EmailSender.preview_email(web_open=True) print("已生成预览文件并尝试打开浏览器。") except Exception as e: logger.error(f"Preview failed: {e}") sys.exit(1) @cli.command() @click.option('--config', '-c', help='配置文件路径', default="config.yml") def validate(config): """验证配置文件""" try: config_manager = ConfigManager(config) if config_manager.validate_config(): print("✅ 配置文件验证通过") else: print("❌ 配置文件验证失败") sys.exit(1) except Exception as e: print(f"❌ 配置文件验证失败: {e}") sys.exit(1) @cli.command() @click.option('--config', '-c', help='配置文件路径', default="config.yml") def info(config): """显示应用信息""" try: config_manager = ConfigManager(config) config = config_manager.config print("📋 应用信息:") print(f" 检查生日人数: {len(config.recipients)}") print(f" 通知类型: {', '.join(config.notification_types)}") print(f" 模板目录: {config_manager.get_templates_dir()}") if config.smtp_config: print(f" SMTP服务器: {config.smtp_config.host}:{config.smtp_config.port}") if config.serverchan_config: print(" ServerChan: 已配置") except Exception as e: logger.error(f"Failed to get app info: {e}") sys.exit(1) if __name__ == "__main__": cli()