90 lines
2.7 KiB
Python
90 lines
2.7 KiB
Python
"""결과 이메일 발송 — SMTP 실연동 (aiosmtplib).
|
|
|
|
SMTP 설정이 없으면 EmailUnavailable 을 던진다(조용한 실패 0).
|
|
경기 종료 후 워커가 구독자(notify=True + email)에게 개인화 결과 메일 발송.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from email.message import EmailMessage
|
|
|
|
from ..config import settings
|
|
|
|
log = logging.getLogger("triplepick.email")
|
|
|
|
|
|
class EmailUnavailable(RuntimeError):
|
|
pass
|
|
|
|
|
|
def _ensure_configured() -> None:
|
|
if not settings.smtp_host:
|
|
raise EmailUnavailable("SMTP_HOST 미설정")
|
|
|
|
|
|
async def send_email(to: str, subject: str, html: str, text: str) -> None:
|
|
_ensure_configured()
|
|
import aiosmtplib
|
|
|
|
msg = EmailMessage()
|
|
msg["From"] = settings.smtp_from
|
|
msg["To"] = to
|
|
msg["Subject"] = subject
|
|
msg.set_content(text)
|
|
msg.add_alternative(html, subtype="html")
|
|
|
|
await aiosmtplib.send(
|
|
msg,
|
|
hostname=settings.smtp_host,
|
|
port=settings.smtp_port,
|
|
username=settings.smtp_user or None,
|
|
password=settings.smtp_password or None,
|
|
start_tls=settings.smtp_starttls,
|
|
)
|
|
log.info("email sent → %s (%s)", to, subject)
|
|
|
|
|
|
def build_result_email(
|
|
*,
|
|
team_a: str,
|
|
team_b: str,
|
|
result_a: int,
|
|
result_b: int,
|
|
my_a: int,
|
|
my_b: int,
|
|
my_points: int,
|
|
ai_lines: list[tuple[str, bool]], # (모델명, 적중여부)
|
|
match_url: str,
|
|
) -> tuple[str, str, str]:
|
|
"""제목/HTML/텍스트 반환."""
|
|
hit = my_points > 0
|
|
headline = "적중! 🎯" if hit else "아쉽네요"
|
|
subject = f"[TriplePick] {team_a} {result_a}-{result_b} {team_b} 결과 — {headline}"
|
|
|
|
ai_html = "".join(
|
|
f"<li>{m}: {'적중 ✓' if ok else '빗나감'}</li>" for m, ok in ai_lines
|
|
)
|
|
ai_text = "\n".join(
|
|
f" - {m}: {'적중' if ok else '빗나감'}" for m, ok in ai_lines
|
|
)
|
|
|
|
html = f"""\
|
|
<div style="font-family:sans-serif;max-width:480px;margin:0 auto">
|
|
<h2>경기 결과</h2>
|
|
<p style="font-size:20px;font-weight:bold">{team_a} {result_a} - {result_b} {team_b}</p>
|
|
<p>당신의 예측: {team_a} {my_a} - {my_b} {team_b} →
|
|
<strong>{my_points}점 ({headline})</strong></p>
|
|
<h3>AI 3모델 적중 여부</h3>
|
|
<ul>{ai_html}</ul>
|
|
<p><a href="{match_url}">다음 경기 예측하러 가기 →</a></p>
|
|
<p style="color:#888;font-size:12px">100만원 챌린지 — 누적 포인트 1위에게 최종 상금.</p>
|
|
</div>"""
|
|
|
|
text = (
|
|
f"경기 결과: {team_a} {result_a}-{result_b} {team_b}\n"
|
|
f"당신의 예측: {team_a} {my_a}-{my_b} {team_b} → {my_points}점 ({headline})\n\n"
|
|
f"AI 3모델 적중 여부:\n{ai_text}\n\n"
|
|
f"다음 경기 예측: {match_url}\n"
|
|
)
|
|
return subject, html, text
|