feat: 项目管理机制 + 邮件发送技能 + 心跳任务

新增:
- PROJECTS.md: 项目开发记录文件(简介、启动命令、检查方式)
- email-sender技能: SMTP邮件发送工具
- 心跳任务: 每日对话总结

更新:
- MEMORY.md: 添加自动推送规范
- HEARTBEAT.md: 添加每日总结任务配置
This commit is contained in:
2026-04-09 12:11:59 +08:00
parent bd0847ce9f
commit 213f898a7c
7 changed files with 803 additions and 6 deletions

View File

@@ -0,0 +1,2 @@
# SMTP配置文件包含密码
smtp_config.json

View File

@@ -0,0 +1,293 @@
#!/usr/bin/env python3
"""
邮件发送脚本
支持纯文字/HTML 邮件、抄送/密送、附件和批量发送
"""
import smtplib
import os
import sys
import json
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.utils import formatdate, make_msgid
from typing import List, Optional
# 配置文件路径
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
CONFIG_FILE = os.path.join(SCRIPT_DIR, "smtp_config.json")
def load_config() -> dict:
"""加载 SMTP 配置"""
if not os.path.exists(CONFIG_FILE):
return {"accounts": []}
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
def save_config(config: dict):
"""保存 SMTP 配置"""
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)
def setup_account(
name: str,
smtp_server: str,
smtp_port: int,
email: str,
password: str,
use_tls: bool = True
):
"""配置 SMTP 账号"""
config = load_config()
# 检查是否已存在同名配置
for i, acc in enumerate(config["accounts"]):
if acc["name"] == name:
print(f"配置 '{name}' 已存在,正在更新...")
config["accounts"][i] = {
"name": name,
"smtp_server": smtp_server,
"smtp_port": smtp_port,
"email": email,
"password": password, # 明文存储,仅用于本地
"use_tls": use_tls
}
save_config(config)
return
# 添加新配置
config["accounts"].append({
"name": name,
"smtp_server": smtp_server,
"smtp_port": smtp_port,
"email": email,
"password": password,
"use_tls": use_tls
})
save_config(config)
print(f"✅ 配置 '{name}' 已保存")
def list_accounts():
"""列出所有 SMTP 配置"""
config = load_config()
if not config["accounts"]:
print("暂无 SMTP 配置")
return
print("\n=== SMTP 配置列表 ===")
for i, acc in enumerate(config["accounts"], 1):
print(f"\n{i}. {acc['name']}")
print(f" 服务器: {acc['smtp_server']}:{acc['smtp_port']}")
print(f" 邮箱: {acc['email']}")
print(f" TLS: {'' if acc['use_tls'] else ''}")
def get_account(name: Optional[str] = None) -> Optional[dict]:
"""获取 SMTP 配置"""
config = load_config()
if not config["accounts"]:
return None
if name is None:
return config["accounts"][0]
for acc in config["accounts"]:
if acc["name"] == name:
return acc
return None
def send_email(
to: str,
subject: str,
body: str,
account_name: Optional[str] = None,
html: bool = False,
cc: Optional[str] = None,
bcc: Optional[str] = None,
from_name: Optional[str] = None,
attachments: Optional[List[str]] = None,
verbose: bool = False
) -> bool:
"""
发送邮件
参数:
to: 收件人邮箱地址
subject: 邮件主题
body: 邮件内容
account_name: SMTP 配置名称(可选,默认使用第一个)
html: 是否为 HTML 邮件(默认 False
cc: 抄送邮箱地址,多个用逗号分隔(可选)
bcc: 密送邮箱地址,多个用逗号分隔(可选)
from_name: 发件人显示名称(可选)
attachments: 附件文件路径列表(可选)
verbose: 是否显示详细日志(默认 False
返回:
bool: 发送成功返回 True失败返回 False
"""
try:
# 获取 SMTP 配置
account = get_account(account_name)
if account is None:
print("❌ 错误:未找到 SMTP 配置,请先运行配置")
return False
# 创建邮件
if attachments:
msg = MIMEMultipart()
msg.attach(MIMEText(body, "html" if html else "plain", "utf-8"))
else:
msg = MIMEText(body, "html" if html else "plain", "utf-8")
# 设置邮件头
msg["Subject"] = subject
msg["From"] = f"{from_name} <{account['email']}>" if from_name else account["email"]
msg["To"] = to
msg["Date"] = formatdate(localtime=True)
msg["Message-Id"] = make_msgid(domain=account["email"].split("@")[1])
if cc:
msg["Cc"] = cc
# 添加附件
if attachments:
for file_path in attachments:
if not os.path.exists(file_path):
print(f"⚠️ 附件不存在,已跳过: {file_path}")
continue
with open(file_path, "rb") as f:
part = MIMEApplication(f.read())
filename = os.path.basename(file_path)
part.add_header(
"Content-Disposition",
"attachment",
filename=filename
)
msg.attach(part)
if verbose:
print(f"📎 已添加附件: {filename}")
# 连接 SMTP 服务器
if verbose:
print(f"📡 正在连接 {account['smtp_server']}:{account['smtp_port']}...")
if account["use_tls"]:
server = smtplib.SMTP(account["smtp_server"], account["smtp_port"])
server.starttls()
else:
server = smtplib.SMTP(account["smtp_server"], account["smtp_port"])
# 登录
if verbose:
print(f"🔑 正在登录 {account['email']}...")
server.login(account["email"], account["password"])
# 准备收件人列表
recipients = [to]
if cc:
recipients.extend([addr.strip() for addr in cc.split(",")])
if bcc:
recipients.extend([addr.strip() for addr in bcc.split(",")])
# 发送邮件
if verbose:
print(f"📤 正在发送邮件...")
server.sendmail(account["email"], recipients, msg.as_string())
server.quit()
print(f"✅ 邮件发送成功")
print(f" 收件人: {to}")
if cc:
print(f" 抄送: {cc}")
if attachments:
valid_attachments = [a for a in attachments if os.path.exists(a)]
print(f" 附件: {len(valid_attachments)}")
return True
except Exception as e:
print(f"❌ 邮件发送失败: {e}")
return False
def main():
"""命令行入口"""
import argparse
parser = argparse.ArgumentParser(description="邮件发送工具")
subparsers = parser.add_subparsers(dest="command", help="可用命令")
# 配置命令
config_parser = subparsers.add_parser("config", help="配置 SMTP")
config_parser.add_argument("name", help="配置名称")
config_parser.add_argument("--server", required=True, help="SMTP 服务器地址")
config_parser.add_argument("--port", type=int, required=True, help="SMTP 端口")
config_parser.add_argument("--email", required=True, help="邮箱地址")
config_parser.add_argument("--password", required=True, help="邮箱密码")
config_parser.add_argument("--no-tls", action="store_true", help="不使用 TLS默认使用")
# 列出配置命令
subparsers.add_parser("list", help="列出所有配置")
# 发送命令
send_parser = subparsers.add_parser("send", help="发送邮件")
send_parser.add_argument("--to", "-t", required=True, help="收件人邮箱")
send_parser.add_argument("--subject", "-s", required=True, help="邮件主题")
send_parser.add_argument("--body", "-b", required=True, help="邮件内容")
send_parser.add_argument("--account", "-a", help="使用指定配置")
send_parser.add_argument("--html", action="store_true", help="HTML 格式")
send_parser.add_argument("--cc", help="抄送邮箱(多个用逗号分隔)")
send_parser.add_argument("--bcc", help="密送邮箱(多个用逗号分隔)")
send_parser.add_argument("--from-name", help="发件人显示名称")
send_parser.add_argument("--attach", "-f", action="append", help="附件文件路径(可多次使用)")
send_parser.add_argument("-v", "--verbose", action="store_true", help="显示详细日志")
args = parser.parse_args()
if args.command == "config":
setup_account(
name=args.name,
smtp_server=args.server,
smtp_port=args.port,
email=args.email,
password=args.password,
use_tls=not args.no_tls
)
elif args.command == "list":
list_accounts()
elif args.command == "send":
success = send_email(
to=args.to,
subject=args.subject,
body=args.body,
account_name=args.account,
html=args.html,
cc=args.cc,
bcc=args.bcc,
from_name=args.from_name,
attachments=args.attach,
verbose=args.verbose
)
sys.exit(0 if success else 1)
else:
parser.print_help()
if __name__ == "__main__":
main()