This commit is contained in:
parent
6317cd80bb
commit
fe1b21fe06
43
email_ui.py
43
email_ui.py
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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 = (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue