"""邮件模板渲染。""" 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