This commit is contained in:
hailin 2025-07-21 20:33:39 +08:00
parent 6317cd80bb
commit fe1b21fe06
2 changed files with 27 additions and 22 deletions

View File

@ -1,15 +1,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
email_ui.py 一键群发工具 (强化校验版) email_ui.py 一键群发工具 (强化校验版支持自定义默认称呼)
-------------------------------------- ------------------------------------------------------------
运行: python email_ui.py 运行: python email_ui.py
""" """
import os, logging, re, random import os, logging, re
from datetime import datetime from datetime import datetime
import gradio as gr 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"
LOG_FILE = "email_send_log.txt" LOG_FILE = "email_send_log.txt"
logging.basicConfig(level=logging.INFO, logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s") format="%(asctime)s %(levelname)s %(message)s")
@ -17,7 +17,7 @@ logging.basicConfig(level=logging.INFO,
EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$") # 简易邮箱正则 EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$") # 简易邮箱正则
# -------------------- 工具 -------------------- # ---------- 工具 ----------
def _load_template(file_obj): def _load_template(file_obj):
if file_obj is None: if file_obj is None:
return "" return ""
@ -25,36 +25,31 @@ def _load_template(file_obj):
return f.read() return f.read()
def _error(msg): def _error(msg): # 统一红字提示
"""生成统一的红字提示 + 让 state 为空 -> Send 仍锁定"""
return gr.update(value=f"<p style='color:red'>❌ {msg}</p>"), None return gr.update(value=f"<p style='color:red'>❌ {msg}</p>"), None
# -------------------- 预览 -------------------- # ---------- 预览 ----------
def preview_email(to_addrs, subject, html_file, language, def preview_email(to_addrs, subject, html_file, language,
want_receipt, want_read_receipt): want_receipt, want_read_receipt, default_name): # ✨ new
# 0. 模板存在
tpl = _load_template(html_file) tpl = _load_template(html_file)
if not tpl: if not tpl:
return _error("请先上传 HTML 模板") return _error("请先上传 HTML 模板")
# 1. 收件人非空且格式合法
raw_addrs = [a.strip() for a in to_addrs.split(",") if a.strip()] raw_addrs = [a.strip() for a in to_addrs.split(",") if a.strip()]
if not raw_addrs: if not raw_addrs:
return _error("收件人邮箱不能为空") return _error("收件人邮箱不能为空")
if invalid := [a for a in raw_addrs if not EMAIL_RE.match(a)]: if invalid := [a for a in raw_addrs if not EMAIL_RE.match(a)]:
return _error(f"邮箱格式非法: {', '.join(invalid)}") return _error(f"邮箱格式非法: {', '.join(invalid)}")
# 2. 主题非空
if not subject.strip(): if not subject.strip():
return _error("主题不能为空") return _error("主题不能为空")
# 3. 模板至少包含 {{recipient_name}}
if "{{recipient_name}}" not in tpl: if "{{recipient_name}}" not in tpl:
return _error("模板缺少 {{recipient_name}} 占位符") return _error("模板缺少 {{recipient_name}} 占位符")
# ---- 全部通过,生成预览 ---- # 生成预览,用用户填写的 default_name留空时仍展示 there
preview = (tpl.replace("{{recipient_name}}", "there") preview = (tpl.replace("{{recipient_name}}", default_name or "there")
.replace("{{recipient_email}}", "example@example.com") .replace("{{recipient_email}}", "example@example.com")
.replace("{{encoded_recipient_email}}", "ENCODED_EXAMPLE") .replace("{{encoded_recipient_email}}", "ENCODED_EXAMPLE")
.replace("{{timestamp}}", datetime.now().isoformat())) .replace("{{timestamp}}", datetime.now().isoformat()))
@ -66,18 +61,24 @@ def preview_email(to_addrs, subject, html_file, language,
"language": language, "language": language,
"want_receipt": want_receipt, "want_receipt": want_receipt,
"want_read_receipt": want_read_receipt, "want_read_receipt": want_read_receipt,
"default_name": default_name.strip(), # ✨ new
} }
return gr.update(value=preview), state return gr.update(value=preview), state
# -------------------- 发送 -------------------- # ---------- 发送 ----------
def send_emails(state): def send_emails(state):
if not state: if not state:
return "⚠️ 请先 Preview 成功后再发送。" return "⚠️ 请先 Preview 成功后再发送。"
out_lines = [] out_lines = []
for addr in state["addresses"]: for addr in state["addresses"]:
res = send_email( res = send_email(
EMAIL_CONFIG_KEY, addr, state["subject"], state["template"], "", EMAIL_CONFIG_KEY,
addr,
state["subject"],
state["template"],
state["default_name"], # ✨ new (可为空串)
request_receipt=state["want_receipt"], request_receipt=state["want_receipt"],
request_read_receipt=state["want_read_receipt"], request_read_receipt=state["want_read_receipt"],
language=state["language"], language=state["language"],
@ -90,7 +91,7 @@ def send_emails(state):
return "<br>".join(out_lines).replace("\n", "<br>") return "<br>".join(out_lines).replace("\n", "<br>")
# -------------------- Gradio UI -------------------- # ---------- Gradio UI ----------
with gr.Blocks(css=".gr-button {min-width:8rem}") as demo: with gr.Blocks(css=".gr-button {min-width:8rem}") as demo:
gr.Markdown("## ✉️ 简易 EDM 群发工具") gr.Markdown("## ✉️ 简易 EDM 群发工具")
@ -98,6 +99,10 @@ with gr.Blocks(css=".gr-button {min-width:8rem}") as demo:
subj = gr.Textbox(label="主题", placeholder="Subject here") subj = gr.Textbox(label="主题", placeholder="Subject here")
tpl = gr.File(label="上传 HTML 模板") tpl = gr.File(label="上传 HTML 模板")
lang = gr.Radio(["english", "chinese"], value="chinese", label="语言") lang = gr.Radio(["english", "chinese"], value="chinese", label="语言")
default_name = gr.Textbox( # ✨ new
label="默认称呼(留空=老板/there", value=""
)
with gr.Row(): with gr.Row():
rcpt = gr.Checkbox(label="投递回执") rcpt = gr.Checkbox(label="投递回执")
read_r = gr.Checkbox(label="已读回执") read_r = gr.Checkbox(label="已读回执")
@ -109,7 +114,7 @@ with gr.Blocks(css=".gr-button {min-width:8rem}") as demo:
preview_btn.click( preview_btn.click(
preview_email, preview_email,
[addrs, subj, tpl, lang, rcpt, read_r], [addrs, subj, tpl, lang, rcpt, read_r, default_name], # ✨ new
[out_html, state_box] [out_html, state_box]
).then( ).then(
lambda s: gr.update(interactive=s is not None), lambda s: gr.update(interactive=s is not None),

View File

@ -84,7 +84,7 @@ def send_email(key, to_address, subject, body_template, recipient_name,
msg['Subject'] = subject msg['Subject'] = subject
if not recipient_name: if not recipient_name:
recipient_name = "there" recipient_name = "老板" if language == "chinese" else "there"
encoded_email = xor_encode(to_address, 0xAE) encoded_email = xor_encode(to_address, 0xAE)
body = ( body = (