"""邮件模板渲染。""" from __future__ import annotations import html import re from .system_settings import ( APP_NAME, DEFAULT_EMAIL_TEST_BODY, DEFAULT_EMAIL_TEST_HTML, DEFAULT_EMAIL_TEST_SUBJECT, DEFAULT_EMAIL_VERIFY_BODY, DEFAULT_EMAIL_VERIFY_HTML, DEFAULT_EMAIL_VERIFY_SUBJECT, DEFAULT_PASSWORD_RESET_BODY, DEFAULT_PASSWORD_RESET_HTML, DEFAULT_PASSWORD_RESET_SUBJECT, SystemSettingsData, ) _TEMPLATE_VAR_PATTERN = re.compile(r"\{(\w+)\}") def render_email_template(template: str, **context: str) -> str: """安全渲染邮件模板,未知占位符保留原样。""" value = template or "" def replacer(match: re.Match) -> str: key = match.group(1) return context.get(key, match.group(0)) return _TEMPLATE_VAR_PATTERN.sub(replacer, value) def _text_to_html(text: str) -> str: escaped = html.escape(text or "") return f'
{escaped}'
def build_verify_email_content(
settings: SystemSettingsData,
username: str,
to_email: str,
link: str,
) -> tuple[str, str, str]:
context = {
"username": username,
"email": to_email,
"link": link,
"app_name": APP_NAME,
}
subject_tpl = settings.email_verify_subject or DEFAULT_EMAIL_VERIFY_SUBJECT
body_tpl = settings.email_verify_body or DEFAULT_EMAIL_VERIFY_BODY
html_tpl = settings.email_verify_html or ""
subject = render_email_template(subject_tpl, **context)
text_body = render_email_template(body_tpl, **context)
if html_tpl.strip():
html_body = render_email_template(html_tpl, **context)
else:
html_body = _text_to_html(text_body)
return subject, text_body, html_body
def build_password_reset_content(
settings: SystemSettingsData,
username: str,
to_email: str,
link: str,
expire_hours: int,
) -> tuple[str, str, str]:
context = {
"username": username,
"email": to_email,
"link": link,
"app_name": APP_NAME,
"expire_hours": str(expire_hours),
}
subject = render_email_template(DEFAULT_PASSWORD_RESET_SUBJECT, **context)
text_body = render_email_template(DEFAULT_PASSWORD_RESET_BODY, **context)
html_body = render_email_template(DEFAULT_PASSWORD_RESET_HTML, **context)
return subject, text_body, html_body
def build_test_email_content(settings: SystemSettingsData, to_email: str) -> tuple[str, str, str]:
context = {
"username": "测试用户",
"email": to_email,
"link": settings.app_url_normalized(),
"app_name": APP_NAME,
}
subject_tpl = settings.email_test_subject or DEFAULT_EMAIL_TEST_SUBJECT
body_tpl = settings.email_test_body or DEFAULT_EMAIL_TEST_BODY
html_tpl = settings.email_test_html or ""
subject = render_email_template(subject_tpl, **context)
text_body = render_email_template(body_tpl, **context)
if html_tpl.strip():
html_body = render_email_template(html_tpl, **context)
else:
html_body = _text_to_html(text_body)
return subject, text_body, html_body