Files
skill-email/scripts/send_email.py

293 lines
9.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
"""
邮件发送脚本
支持纯文字/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()