diff --git a/email_ui.py b/email_ui.py index 25dea63..9e8c491 100644 --- a/email_ui.py +++ b/email_ui.py @@ -1,20 +1,23 @@ # -*- coding: utf-8 -*- """ -email_ui.py — 一键群发工具 (独立可跑版) --------------------------------------------------- +email_ui.py — 一键群发工具 (强化校验版) +-------------------------------------- 运行: python email_ui.py """ -import os, logging, random +import os, logging, re, random from datetime import datetime import gradio as gr -from send_email_module import send_email # 同目录导入 +from send_email_module import send_email -EMAIL_CONFIG_KEY = "default" # 对应 config/default.json +EMAIL_CONFIG_KEY = "default" # 对应 config/default.json LOG_FILE = "email_send_log.txt" -logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") +logging.basicConfig(level=logging.INFO, + format="%(asctime)s %(levelname)s %(message)s") + +EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$") # 简易邮箱正则 -# -------------------- 内部函数 -------------------- +# -------------------- 工具 -------------------- def _load_template(file_obj): if file_obj is None: return "" @@ -22,30 +25,55 @@ def _load_template(file_obj): return f.read() +def _error(msg): + """生成统一的红字提示 + 让 state 为空 -> Send 仍锁定""" + return gr.update(value=f"
❌ {msg}
"), None + + +# -------------------- 预览 -------------------- def preview_email(to_addrs, subject, html_file, language, want_receipt, want_read_receipt): + # 0. 模板存在 tpl = _load_template(html_file) if not tpl: - return gr.HTML.update("❌ 请先上传 HTML 模板
"), None + return _error("请先上传 HTML 模板") + # 1. 收件人非空且格式合法 + raw_addrs = [a.strip() for a in to_addrs.split(",") if a.strip()] + if not raw_addrs: + return _error("收件人邮箱不能为空") + if invalid := [a for a in raw_addrs if not EMAIL_RE.match(a)]: + return _error(f"邮箱格式非法: {', '.join(invalid)}") + + # 2. 主题非空 + if not subject.strip(): + return _error("主题不能为空") + + # 3. 模板至少包含 {{recipient_name}} + if "{{recipient_name}}" not in tpl: + return _error("模板缺少 {{recipient_name}} 占位符") + + # ---- 全部通过,生成预览 ---- preview = (tpl.replace("{{recipient_name}}", "there") - .replace("{{recipient_email}}", "example@example.com") - .replace("{{encoded_recipient_email}}", "ENCODED_EXAMPLE") - .replace("{{timestamp}}", datetime.now().isoformat())) + .replace("{{recipient_email}}", "example@example.com") + .replace("{{encoded_recipient_email}}", "ENCODED_EXAMPLE") + .replace("{{timestamp}}", datetime.now().isoformat())) + state = { - "addresses": [a.strip() for a in to_addrs.split(",") if a.strip()], - "subject": subject, + "addresses": raw_addrs, + "subject": subject.strip(), "template": tpl, "language": language, "want_receipt": want_receipt, "want_read_receipt": want_read_receipt, } - return gr.update(value=preview), state # ✅ 任何版本通用 + return gr.update(value=preview), state +# -------------------- 发送 -------------------- def send_emails(state): if not state: - return "⚠️ 请先 Preview。" + return "⚠️ 请先 Preview 成功后再发送。" out_lines = [] for addr in state["addresses"]: res = send_email( @@ -59,32 +87,35 @@ def send_emails(state): out_lines.append(line) with open(LOG_FILE, "a", encoding="utf-8") as f: f.write(line + "\n") - return "\n".join(out_lines) + return "