from __future__ import annotations import asyncio import smtplib import time from datetime import datetime from email.header import Header from email.mime.text import MIMEText from typing import Any from .config import ( EMAIL_NOTIFY_ENABLED, SMTP_HOST, SMTP_PASSWORD, SMTP_PORT, SMTP_TO, SMTP_USER, ) _last_push: dict[tuple[str, str], tuple[str, str, float]] = {} def _truncate(text: str, max_len: int = 220) -> str: t = str(text or "").strip() if len(t) <= max_len: return t return f"{t[:max_len]}..." def _send_mail(subject: str, body: str) -> tuple[bool, str]: if not EMAIL_NOTIFY_ENABLED: return False, "email_disabled" if not SMTP_HOST or not SMTP_USER or not SMTP_PASSWORD or not SMTP_TO: return False, "email_config_incomplete" try: msg = MIMEText(body, "plain", "utf-8") msg["Subject"] = Header(subject, "utf-8") msg["From"] = f"{Header('千牛AI通知', 'utf-8').encode()} <{SMTP_USER}>" msg["To"] = SMTP_TO if int(SMTP_PORT) == 465: with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, timeout=10) as server: server.login(SMTP_USER, SMTP_PASSWORD) server.sendmail(SMTP_USER, [SMTP_TO], msg.as_string()) else: with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=10) as server: server.starttls() server.login(SMTP_USER, SMTP_PASSWORD) server.sendmail(SMTP_USER, [SMTP_TO], msg.as_string()) return True, "ok" except Exception as e: return False, str(e) async def push_chat_to_email( *, customer_name: str, customer_id: str, acc_id: str, customer_msg: str, reply_msg: str, goods_name: str = "", recent_dialogue: list[dict[str, Any]] | None = None, ) -> tuple[bool, str]: if not EMAIL_NOTIFY_ENABLED: return False, "email_disabled" key = (str(customer_id or ""), str(acc_id or "")) now = time.time() last = _last_push.get(key) cm = str(customer_msg or "") rm = str(reply_msg or "") if last: lcm, lrm, lts = last if lcm == cm and lrm == rm and (now - lts) < 30: return False, "dedup_30s" _last_push[key] = (cm, rm, now) ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") subject = f"[千牛AI]{acc_id or '店铺'}-{customer_name or customer_id or '客户'}" lines: list[str] = [f"时间: {ts}"] lines.append(f"店铺: {acc_id or '未知店铺'}") lines.append(f"客户: {customer_name or '-'} ({customer_id or '-'})") if goods_name: lines.append(f"商品: {_truncate(goods_name, 120)}") lines.append("") for item in (recent_dialogue or [])[-8:]: role = "客户" if str(item.get("role", "")) == "user" else "客服" txt = _truncate(str(item.get("text", "") or ""), 180) if txt: lines.append(f"{role}: {txt}") lines.append(f"客户: {_truncate(cm, 220)}") lines.append(f"客服: {_truncate(rm, 220)}") body = "\n".join(lines) return await asyncio.to_thread(_send_mail, subject, body)