104 lines
3.1 KiB
Python
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
|