Files
2026-07-17 09:24:47 +08:00

104 lines
3.1 KiB
Python

"""邮件模板渲染。"""
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'<pre style="font-family:sans-serif;white-space:pre-wrap;margin:0;">{escaped}</pre>'
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