Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

351 lines
13 KiB
Python
Executable File

#!/usr/bin/env python3
"""
邮件发送器
用于发送每日资讯摘要
"""
import os
import json
import smtplib
import logging
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from datetime import datetime
from typing import List, Dict, Optional
logger = logging.getLogger(__name__)
class EmailSender:
"""邮件发送器"""
def __init__(self, config_path=None):
"""初始化邮件发送器"""
if config_path is None:
config_path = "/home/ubuntu/nanocode/it_automation_enhanced/config/config.json"
self.config_path = config_path
self.load_config()
# 检查配置
email_config = self.config.get('email', {})
if not email_config.get('smtp_host') or email_config.get('smtp_host', '').startswith('{{'):
logger.warning("邮件配置不完整,邮件功能将禁用")
def load_config(self):
"""加载配置"""
try:
with open(self.config_path, 'r', encoding='utf-8') as f:
self.config = json.load(f)
logger.info(f"邮件配置加载成功")
except Exception as e:
logger.error(f"配置加载失败: {e}")
self.config = {}
def send_email(self, subject: str, content: str, to_emails: List[str] = None,
attachments: List[str] = None) -> bool:
"""发送邮件"""
email_config = self.config.get('email', {})
# 检查是否启用
if not email_config.get('enable_daily_digest', True):
logger.info("邮件功能已禁用")
return False
# 获取配置
smtp_host = email_config.get('smtp_host')
smtp_port = email_config.get('smtp_port', 587)
username = email_config.get('username')
password = email_config.get('password')
from_email = email_config.get('from_email')
if not all([smtp_host, username, password, from_email]):
logger.error("邮件配置不完整")
return False
if to_emails is None:
to_emails = [email_config.get('to_email')]
if not to_emails[0]:
logger.error("收件人邮箱未配置")
return False
try:
# 创建邮件
msg = MIMEMultipart()
msg['From'] = from_email
msg['To'] = ', '.join(to_emails)
msg['Subject'] = subject
msg['Date'] = datetime.now().strftime('%a, %d %b %Y %H:%M:%S %z')
# 添加正文
msg.attach(MIMEText(content, 'html' if '<html>' in content.lower() else 'plain', 'utf-8'))
# 添加附件
if attachments:
for attachment_path in attachments:
if os.path.exists(attachment_path):
with open(attachment_path, 'rb') as f:
part = MIMEApplication(f.read(), Name=os.path.basename(attachment_path))
part['Content-Disposition'] = f'attachment; filename="{os.path.basename(attachment_path)}"'
msg.attach(part)
logger.info(f"添加附件: {attachment_path}")
# 连接SMTP服务器并发送
logger.info(f"连接SMTP服务器: {smtp_host}:{smtp_port}")
if smtp_port == 465:
# SSL连接
server = smtplib.SMTP_SSL(smtp_host, smtp_port)
else:
# TLS连接
server = smtplib.SMTP(smtp_host, smtp_port)
server.starttls()
server.login(username, password)
server.send_message(msg)
server.quit()
logger.info(f"邮件发送成功: {subject} -> {', '.join(to_emails)}")
return True
except Exception as e:
logger.error(f"邮件发送失败: {e}")
return False
def create_daily_digest(self, articles: List[Dict], date: str = None) -> Dict:
"""创建每日摘要"""
if date is None:
date = datetime.now().strftime('%Y-%m-%d')
# 统计信息
total_articles = len(articles)
sources = {}
categories = {}
for article in articles:
source = article.get('source', '未知')
category = article.get('category', '未分类')
sources[source] = sources.get(source, 0) + 1
categories[category] = categories.get(category, 0) + 1
# 创建HTML内容
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>IT资讯每日摘要 - {date}</title>
<style>
body {{ font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 0 auto; padding: 20px; }}
.header {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px; margin-bottom: 30px; }}
.header h1 {{ margin: 0; font-size: 28px; }}
.header .date {{ margin-top: 10px; opacity: 0.9; }}
.stats {{ background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 30px; }}
.stats-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; }}
.stat-card {{ background: white; padding: 15px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
.stat-card h3 {{ margin: 0 0 10px 0; color: #667eea; }}
.stat-card .number {{ font-size: 32px; font-weight: bold; color: #764ba2; }}
.articles {{ margin-top: 30px; }}
.article {{ background: white; padding: 20px; margin-bottom: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); border-left: 4px solid #667eea; }}
.article-title {{ font-size: 18px; font-weight: bold; margin: 0 0 10px 0; color: #333; }}
.article-meta {{ color: #666; font-size: 14px; margin-bottom: 10px; }}
.article-meta span {{ margin-right: 15px; }}
.article-summary {{ color: #444; line-height: 1.6; }}
.footer {{ margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; color: #666; font-size: 14px; text-align: center; }}
.tag {{ display: inline-block; background: #e9ecef; color: #495057; padding: 4px 8px; border-radius: 4px; font-size: 12px; margin-right: 5px; margin-bottom: 5px; }}
</style>
</head>
<body>
<div class="header">
<h1>📰 IT资讯每日摘要</h1>
<div class="date">{date} | 共收录 {total_articles} 篇文章</div>
</div>
<div class="stats">
<h2>📊 今日统计</h2>
<div class="stats-grid">
<div class="stat-card">
<h3>文章总数</h3>
<div class="number">{total_articles}</div>
</div>
<div class="stat-card">
<h3>来源分布</h3>
<div>
"""
# 添加来源统计
for source, count in sources.items():
html_content += f'<div>{source}: {count}篇</div>'
html_content += """
</div>
</div>
<div class="stat-card">
<h3>分类分布</h3>
<div>
"""
# 添加分类统计
for category, count in categories.items():
html_content += f'<div>{category}: {count}篇</div>'
html_content += f"""
</div>
</div>
</div>
</div>
<div class="articles">
<h2>📋 今日精选</h2>
"""
# 添加文章列表(最多10篇)
for i, article in enumerate(articles[:10]):
title = article.get('title', '无标题')
source = article.get('source', '未知')
category = article.get('category', '未分类')
url = article.get('url', '#')
summary = article.get('summary', article.get('description', '暂无摘要'))
html_content += f"""
<div class="article">
<div class="article-title">
<a href="{url}" style="color: #667eea; text-decoration: none;">{i+1}. {title}</a>
</div>
<div class="article-meta">
<span>来源: {source}</span>
<span>分类: <span class="tag">{category}</span></span>
</div>
<div class="article-summary">
{summary[:200]}...
</div>
</div>
"""
html_content += """
</div>
<div class="footer">
<p>本邮件由 IT资讯自动化系统 自动生成</p>
<p>系统版本: 1.0.0 | 生成时间: """ + datetime.now().strftime('%Y-%m-%d %H:%M:%S') + """</p>
<p>如需退订或调整接收频率,请回复本邮件</p>
</div>
</body>
</html>
"""
# 创建纯文本版本
text_content = f"IT资讯每日摘要 - {date}\n"
text_content += "=" * 50 + "\n\n"
text_content += f"共收录 {total_articles} 篇文章\n\n"
text_content += "来源统计:\n"
for source, count in sources.items():
text_content += f" {source}: {count}篇\n"
text_content += "\n分类统计:\n"
for category, count in categories.items():
text_content += f" {category}: {count}篇\n"
text_content += "\n精选文章:\n"
for i, article in enumerate(articles[:5]):
title = article.get('title', '无标题')
source = article.get('source', '未知')
text_content += f"{i+1}. {title} [{source}]\n"
text_content += f"\n\n--\nIT资讯自动化系统 v1.0.0\n{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
return {
'date': date,
'total_articles': total_articles,
'sources': sources,
'categories': categories,
'html_content': html_content,
'text_content': text_content,
'subject': f"IT资讯每日摘要 - {date} ({total_articles}篇文章)"
}
def send_daily_digest(self, articles: List[Dict], date: str = None) -> bool:
"""发送每日摘要"""
logger.info(f"准备发送每日摘要,文章数量: {len(articles)}")
if not articles:
logger.warning("没有文章可发送")
return False
# 创建摘要
digest = self.create_daily_digest(articles, date)
# 发送邮件
success = self.send_email(
subject=digest['subject'],
content=digest['html_content'],
to_emails=None # 使用配置中的收件人
)
if success:
logger.info(f"每日摘要发送成功: {digest['subject']}")
else:
logger.error("每日摘要发送失败")
return success
def send_test_email(self):
"""发送测试邮件"""
test_articles = [
{
'title': 'AI芯片技术的最新突破',
'source': 'TechNews',
'category': 'AI',
'url': 'https://example.com/ai-chip',
'summary': '研究人员在AI芯片能效比方面取得重大进展'
},
{
'title': 'Python 3.12性能优化',
'source': 'PythonWeekly',
'category': '编程',
'url': 'https://example.com/python-optimize',
'summary': 'Python 3.12在性能方面有显著提升'
},
{
'title': '云计算安全指南',
'source': 'CloudTech',
'category': '云计算',
'url': 'https://example.com/cloud-security',
'summary': '最新的云计算安全最佳实践'
}
]
print("发送测试邮件...")
success = self.send_daily_digest(test_articles)
if success:
print("✅ 测试邮件发送成功")
else:
print("❌ 测试邮件发送失败(可能是配置问题)")
return success
def test_email_sender():
"""测试邮件发送器"""
import sys
logging.basicConfig(level=logging.INFO)
sender = EmailSender()
# 检查配置
email_config = sender.config.get('email', {})
print("邮件配置检查:")
for key, value in email_config.items():
if 'password' in key:
print(f" {key}: [已隐藏]")
else:
print(f" {key}: {value}")
print("\n发送测试邮件...")
sender.send_test_email()
if __name__ == "__main__":
test_email_sender()