admin d139ff3398 添加 weekly-report 技能:生成项目周报并发送邮件
- SKILL.md 定义完整执行流程(拉取代码、统计commits/PR、生成报告、发邮件)
- send_email.py 从环境变量读取 SMTP 凭证,收件人从项目 report-config.json 读取
- 更新 README 技能列表

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 14:02:05 +08:00

122 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
周报邮件发送脚本
SMTP 凭证从环境变量读取,不接受命令行参数传入凭证。
必须设置的环境变量:
SMTP_HOST - SMTP 服务器地址
SMTP_PORT - SMTP 端口SSL用465STARTTLS用587
SMTP_USER - 发件人邮箱
SMTP_PASSWORD - SMTP 密码或授权码
"""
import argparse
import json
import os
import smtplib
import sys
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
def load_config(config_path: str) -> dict:
try:
with open(config_path, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
print(f"错误:找不到配置文件 {config_path}", file=sys.stderr)
print("请在项目根目录创建 report-config.json格式", file=sys.stderr)
print('{"recipients": ["email@example.com"], "project_name": "项目名"}', file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"错误report-config.json 格式错误 - {e}", file=sys.stderr)
sys.exit(1)
def get_smtp_config() -> tuple:
host = os.environ.get("SMTP_HOST")
port = os.environ.get("SMTP_PORT", "465")
user = os.environ.get("SMTP_USER")
password = os.environ.get("SMTP_PASSWORD")
missing = [k for k, v in {
"SMTP_HOST": host,
"SMTP_USER": user,
"SMTP_PASSWORD": password,
}.items() if not v]
if missing:
print(f"错误:缺少以下环境变量:{', '.join(missing)}", file=sys.stderr)
print("请在 shell 中设置(不要写入任何文件):", file=sys.stderr)
for k in missing:
print(f" export {k}=your_value", file=sys.stderr)
sys.exit(1)
try:
port = int(port)
except ValueError:
print(f"错误SMTP_PORT 必须是数字,当前值为 '{port}'", file=sys.stderr)
sys.exit(1)
return host, port, user, password
def send_email(recipients: list, subject: str, body_html: str, sender: str):
host, port, user, password = get_smtp_config()
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = sender or user
msg["To"] = ", ".join(recipients)
msg.attach(MIMEText(body_html, "html", "utf-8"))
try:
if port == 465:
with smtplib.SMTP_SSL(host, port, timeout=30) as smtp:
smtp.login(user, password)
smtp.sendmail(user, recipients, msg.as_string())
else:
with smtplib.SMTP(host, port, timeout=30) as smtp:
smtp.ehlo()
smtp.starttls()
smtp.login(user, password)
smtp.sendmail(user, recipients, msg.as_string())
except smtplib.SMTPAuthenticationError:
print("错误SMTP 认证失败,请检查 SMTP_USER 和 SMTP_PASSWORD", file=sys.stderr)
sys.exit(1)
except smtplib.SMTPConnectError:
print(f"错误:无法连接到 {host}:{port},请检查 SMTP_HOST 和 SMTP_PORT", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"错误:发送失败 - {e}", file=sys.stderr)
sys.exit(1)
print(f"邮件已发送至:{', '.join(recipients)}")
def main():
parser = argparse.ArgumentParser(description="发送周报邮件")
parser.add_argument("--config", required=True, help="report-config.json 路径")
parser.add_argument("--subject", required=True, help="邮件主题")
parser.add_argument("--body-file", required=True, help="HTML 正文文件路径")
args = parser.parse_args()
config = load_config(args.config)
recipients = config.get("recipients", [])
if not recipients:
print("错误report-config.json 中 recipients 为空", file=sys.stderr)
sys.exit(1)
try:
with open(args.body_file, "r", encoding="utf-8") as f:
body_html = f.read()
except FileNotFoundError:
print(f"错误:找不到邮件正文文件 {args.body_file}", file=sys.stderr)
sys.exit(1)
send_email(recipients, args.subject, body_html, sender=config.get("sender", ""))
if __name__ == "__main__":
main()