import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import time
import csv
import os
import configparser
from shared_config import CONFIG_PATH, CONFIG_FILE_LOCK, get_logger, CONFIG_INI_PATH
logger = get_logger(__name__)
# --- 邮件模板 ---
EXPIRATION_HTML_TEMPLATE = """
Token 到期通知
注意!
"""
SUCCESS_HTML_TEMPLATE = """
打卡成功通知
打卡成功!
{name},您好!
系统已于 {send_time} 成功为您完成自动打卡。
您无需进行任何操作,此邮件仅作通知。
"""
FAILURE_HTML_TEMPLATE = """
打卡失败通知
通知:自动打卡失败!
{name},您好!
系统于 {send_time} 尝试为您自动打卡时失败。
失败原因: 服务器返回 "需要登录",这通常意味着您的 Token 已失效。
请您立即前往 http://localhost:5000 刷新您的 Token,以确保后续打卡能够成功。
"""
def _send_email(to_email, subject, html_content, email_settings):
try:
msg = MIMEMultipart()
msg["From"] = email_settings['senderemail']
msg["To"] = to_email
msg["Subject"] = subject
msg.attach(MIMEText(html_content, 'html', 'utf-8'))
with smtplib.SMTP_SSL(email_settings['smtpserver'], int(email_settings['smtpport'])) as server:
server.login(email_settings['senderemail'], email_settings['senderpassword'])
server.sendmail(msg["From"], msg["To"], msg.as_string())
logger.info(f"已成功向 {to_email} 发送邮件,主题: {subject}")
except Exception as e:
logger.error(f"向 {to_email} 发送邮件时失败: {e}")
def send_notification_email(user_config, email_settings):
"""发送Token到期提醒邮件"""
html = EXPIRATION_HTML_TEMPLATE.format(
name=user_config["email"],
exp_time=time.strftime("%Y年%m月%d日 %H:%M:%S", time.localtime(float(user_config["jwt_exp"]))),
send_time=time.strftime("%Y年%m月%d日 %H:%M:%S", time.localtime())
)
_send_email(user_config["email"], "接龙管家Token到期通知", html, email_settings)
def send_success_notification(user_config, email_settings):
"""发送打卡成功通知邮件"""
html = SUCCESS_HTML_TEMPLATE.format(
name=user_config["email"],
send_time=time.strftime("%Y年%m月%d日 %H:%M:%S", time.localtime())
)
_send_email(user_config["email"], "自动打卡成功通知", html, email_settings)
def send_failure_notification(user_config, email_settings):
"""发送打卡失败通知邮件"""
html = FAILURE_HTML_TEMPLATE.format(
name=user_config["email"],
send_time=time.strftime("%Y年%m月%d日 %H:%M:%S", time.localtime())
)
_send_email(user_config["email"], "【紧急】自动打卡失败 - 需要刷新Token", html, email_settings)
def notification_worker_loop():
"""后台任务:检查Token是否即将过期,并发送邮件提醒。单次运行。"""
logger.info("Scheduler: 正在执行邮件过期通知检查...")
config_ini_path = os.path.join(os.path.dirname(__file__), 'config.ini')
try:
# 1. 读取邮件配置
if not os.path.exists(config_ini_path):
logger.warning("Scheduler: 找不到 config.ini,邮件通知功能将跳过。")
return # 直接退出
config_parser = configparser.ConfigParser()
config_parser.read(config_ini_path)
if 'Email' not in config_parser:
logger.warning("Scheduler: config.ini 中缺少 [Email] 部分,跳过。")
return
email_settings = config_parser['Email']
# 2. 线程安全地读取用户配置
with CONFIG_FILE_LOCK:
if not os.path.exists(CONFIG_PATH):
configs = []
else:
with open(CONFIG_PATH, mode='r', encoding='utf-8-sig') as file:
configs = list(csv.DictReader(file))
# 3. 检查每个用户的Token是否即将过期
now = time.time()
for user in configs:
if not user.get('jwt_exp') or not user.get('email'):
continue
try:
exp_time = float(user['jwt_exp'])
# 检查是否在 30 分钟内过期,并且尚未发送过提醒(可选,防止重复发送)
if 0 < (now - exp_time) < 1800:
logger.info(f"{user['Signature']} 的Token过期,准备发送邮件...")
send_notification_email(user, email_settings)
except (ValueError, TypeError):
logger.warning(f"Scheduler: 跳过用户 {user['Signature']},因为jwt_exp格式不正确: {user['jwt_exp']}")
continue
logger.info("Scheduler: 邮件到期检查完成。")
except Exception as e:
logger.error(f"Scheduler: 邮件通知任务发生严重错误: {e}")