| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- from typing import Dict, Tuple, Any, List
- # ==========================================
- # 0. Internal Helpers
- # ==========================================
- def _format_slot_summary(availability: Any) -> str:
- if not isinstance(availability, list) or not availability:
- return "No specific time slots recorded."
- items = []
- for day in availability[:4]:
- date = day.get("date", "")
- # Minimalist text for anti-spam
- items.append(f"- {date}")
- return "\n".join(items)
- def _format_currency(amount_in_cents: Any) -> str:
- """将分转换为元,保留两位小数"""
- try:
- # 确保是数字类型
- value = float(amount_in_cents) / 100.0
- return f"{value:.2f}"
- except (ValueError, TypeError):
- return str(amount_in_cents)
- def _get_status_meta(status: str) -> Tuple[str, str, str]:
- if status == 'Available':
- return "READY", "#16a34a", "Available"
- if status == 'Waitlist':
- return "WAIT", "#ca8a04", "Waitlist Only"
- return "NONE", "#dc2626", "No Slots"
- # ==========================================
- # 1. EMAIL TEMPLATES (Anti-Spam Optimized)
- # ==========================================
- def _email_base(content_html: str, app_name: str = "Visafly") -> str:
- """
- Ultra-clean HTML structure.
- Uses standard fonts and minimal CSS to pass through ESP filters.
- """
- return f"""
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Notification</title>
- <style>
- /* Standard fonts only */
- body {{ font-family: Arial, sans-serif; -webkit-font-smoothing: antialiased; font-size: 16px; line-height: 1.5; color: #333333; margin: 0; padding: 0; }}
- .wrapper {{ background-color: #f6f9fc; padding: 20px; }}
- .container {{ max-width: 600px; margin: 0 auto; background-color: #ffffff; border: 1px solid #e1e4e8; border-radius: 4px; }}
- .content {{ padding: 30px; }}
- .footer {{ padding: 20px; text-align: center; font-size: 12px; color: #777777; }}
- .button {{ display: inline-block; padding: 10px 20px; background-color: #0052cc; color: #ffffff !important; text-decoration: none; border-radius: 3px; }}
- .code {{ font-family: monospace; font-size: 24px; font-weight: bold; background: #f4f4f4; padding: 10px; border-radius: 4px; display: inline-block; letter-spacing: 2px; }}
- hr {{ border: 0; border-top: 1px solid #eeeeee; margin: 20px 0; }}
- </style>
- </head>
- <body>
- <div class="wrapper">
- <div class="container">
- <div class="content">
- {content_html}
- <hr>
- <p style="font-size: 13px; color: #888;">
- Regards,<br>
- <strong>{app_name} Team</strong>
- </p>
- </div>
- </div>
- <div class="footer">
- You received this because you are a registered user of {app_name}.<br>
- To manage your notification settings, please visit our website.<br>
- © 2026 {app_name}. Support: contact@visafly.top
- </div>
- </div>
- </body>
- </html>
- """
- def template_email_verification_bind(payload: Dict) -> str:
- # Use neutral language: "Verification code" instead of "URGENT ACTION"
- html = f"""
- <h2 style="font-size: 20px; color: #111;">Email Verification</h2>
- <p>You requested to link this email address to your {payload.get('app_name', 'Visafly')} account.</p>
- <p>Your verification code is:</p>
- <div class="code">{payload.get('code')}</div>
- <p>This code will expire in {payload.get('expiration_time', '10 minutes')}.</p>
- <p>If you did not make this request, you can safely ignore this email.</p>
- """
- return _email_base(html, payload.get('app_name', 'Visafly'))
- def template_email_password_reset(payload: Dict) -> str:
- html = f"""
- <h2 style="font-size: 20px; color: #111;">Password Reset Code</h2>
- <p>We received a request to reset your password. Please enter the following code to continue:</p>
- <div class="code">{payload.get('code')}</div>
- <p>Valid for {payload.get('expiration_time', '10 minutes')}. For security reasons, do not share this code.</p>
- """
- return _email_base(html, payload.get('app_name', 'Visafly'))
- def template_email_login_credentials(payload: Dict) -> str:
- html = f"""
- <h2 style="font-size: 20px; color: #111;">Your Account Credentials</h2>
- <p>Your account has been successfully created. Here are your temporary login details:</p>
- <table style="width: 100%; background: #f9f9f9; padding: 15px; border-radius: 4px;">
- <tr><td><strong>User:</strong></td><td>{payload.get('username')}</td></tr>
- <tr><td><strong>Pass:</strong></td><td><code>{payload.get('password')}</code></td></tr>
- </table>
- <p><a href="{payload.get('login_url')}" class="button">Log in to your account</a></p>
- """
- return _email_base(html, payload.get('app_name', 'Visafly'))
- def template_email_slot_subscription(payload: Dict) -> str:
- # Change "Alert" to "Update" to sound less like spam
- html = f"""
- <h2 style="font-size: 20px; color: #111;">Visa Slot Update</h2>
- <p>This is an automated update regarding your visa slot subscription.</p>
- <div style="border-left: 3px solid #0052cc; padding-left: 15px; margin: 20px 0;">
- <strong>Location:</strong> {payload.get('country')}, {payload.get('city')}<br>
- <strong>Visa Type:</strong> {payload.get('visa_type')}<br>
- <strong>Earliest Date:</strong> <span style="color:#d93025;">{payload.get('earliest_date')}</span>
- </div>
- <p><a href="{payload.get('website')}" class="button">Check Official Website</a></p>
- """
- return _email_base(html)
- def template_email_appointment_confirmation(payload: Dict) -> str:
- """预约成功确认邮件 (Anti-Spam Optimized)"""
- html = f"""
- <h2 style="font-size: 20px; color: #111;">Appointment Confirmed</h2>
- <p>Dear {payload.get('username', 'Customer')},</p>
- <p>We are pleased to inform you that your visa appointment has been successfully booked.</p>
-
- <table style="width: 100%; background: #f9f9f9; padding: 15px; border-radius: 4px; border-collapse: separate; border-spacing: 0 10px; text-align: left;">
- <tr>
- <td style="width: 35%; color: #555;"><strong>Order ID:</strong></td>
- <td><code>{payload.get('order_id', 'N/A')}</code></td>
- </tr>
- <tr>
- <td style="color: #555;"><strong>Location:</strong></td>
- <td>{payload.get('country', '')}, {payload.get('city', '')}</td>
- </tr>
- <tr>
- <td style="color: #555;"><strong>Visa Type:</strong></td>
- <td>{payload.get('visa_type', 'N/A')}</td>
- </tr>
- <tr>
- <td style="color: #555;"><strong>Appointment Date:</strong></td>
- <td><strong style="color: #0052cc;">{payload.get('appointment_date', 'N/A')}</strong></td>
- </tr>
- <tr>
- <td style="color: #555;"><strong>Account Email:</strong></td>
- <td>{payload.get('user_email', 'N/A')}</td>
- </tr>
- </table>
-
- <p>Please ensure you prepare all required documents well in advance of your appointment date. If you have any questions, feel free to contact our support team.</p>
- """
- return _email_base(html, payload.get('app_name', 'Visafly'))
- def template_email_order_event_update(payload: Dict) -> str:
- """
- Template for visa progress updates.
- Focuses on neutral language to ensure inbox delivery.
- """
- order_no = payload.get('order_no', 'N/A')
- summary = payload.get('summary', 'No summary available.')
- # Neutral subject line used in get_email_meta: "Notification: Visa Appointment Update"
-
- html = f"""
- <h2 style="font-size: 20px; color: #111;">Visa Appointment Update</h2>
- <p>We have received a new update regarding your visa process for <strong>Order #{order_no}</strong>.</p>
-
- <div style="border-left: 3px solid #0052cc; padding: 15px; margin: 20px 0; background-color: #f9fafb; border-radius: 0 4px 4px 0;">
- <strong style="color: #555; font-size: 13px; text-transform: uppercase;">Update Summary:</strong><br>
- <p style="margin-top: 5px; color: #333;">{summary}</p>
- </div>
-
- <p>For your security and privacy, please log in to your dashboard to view the full message and any relevant documents.</p>
-
- <p style="margin-top: 30px;">
- <a href="https://visafly.top/dashboard" class="button">Access Dashboard</a>
- </p>
- """
- return _email_base(html, "Visafly")
- # ==========================================
- # 2. WECHAT / TELEGRAM / WHATSAPP (Keeping the styles)
- # ==========================================
- # [Note: These channels don't have spam filters based on HTML structure,
- # so we keep the rich formatting we designed previously.]
- def template_wechat_payment_confirmed(payload: Dict) -> str:
- """支付成功通知模板"""
- amount_decimal = _format_currency(payload.get('amount'))
- return (
- f"# ✅ Payment Success\n"
- f"--- \n"
- f"💰 **Amount:** {amount_decimal} {payload.get('currency')}\n"
- f"🆔 **Order ID:** `{payload.get('order_id')}`\n"
- f"📧 **User Account:** {payload.get('user_email')}\n"
- f"🕒 **Time:** {payload.get('confirmed_at')}\n\n"
- f"Your service has been activated. Please stay tuned for further updates."
- )
-
- def template_wechat_appointment_confirmation(payload: Dict) -> str:
- """预约成功微信通知 (Markdown)"""
- return (
- f"# 🎉 Appointment Confirmed\n"
- f"--- \n"
- f"👤 **User:** {payload.get('username', 'Customer')}\n"
- f"📍 **Location:** {payload.get('country')}, {payload.get('city')}\n"
- f"🛂 **Visa Type:** {payload.get('visa_type')}\n"
- f"📅 **Date:** `{payload.get('appointment_date')}`\n"
- f"🆔 **Order ID:** `{payload.get('order_id')}`\n\n"
- f"Your appointment is successfully booked. Please prepare your documents."
- )
- def template_wechat_slot_snapshot(payload: Dict) -> str:
- emoji, _, status_text = _get_status_meta(payload.get("availability_status"))
- summary = _format_slot_summary(payload.get("availability"))
- return (
- f"# {emoji} {payload.get('country')} {payload.get('city')}\n"
- f"> {status_text} | {payload.get('visa_type')}\n\n"
- f"📅 **Earliest Date:** `{payload.get('earliest_date', 'N/A')}`\n\n"
- f"🔍 **Slots Overview:**\n{summary}\n\n"
- f"🔗 [Book Now]({payload.get('website', '#')})"
- )
- def template_telegram_slot_snapshot(payload: Dict) -> str:
- emoji, _, status_text = _get_status_meta(payload.get("availability_status"))
- summary = _format_slot_summary(payload.get("availability"))
- return (
- f"{emoji} <b>{payload.get('country')}, {payload.get('city')}</b>\n"
- f"<i>{status_text} · {payload.get('visa_type')}</i>\n"
- f"──────────────────\n"
- f"📅 <b>Earliest Date:</b> <code>{payload.get('earliest_date', 'N/A')}</code>\n\n"
- f"📊 <b>Availability:</b>\n{summary}\n"
- f"──────────────────\n"
- f"🔗 <a href='{payload.get('website', '#')}'><b>Official Website ➜</b></a>\n\n"
- )
- def template_telegram_appointment_confirmation(payload: Dict) -> str:
- """预约成功 TG 通知 (HTML)"""
- return (
- f"🎉 <b>Appointment Confirmed</b>\n"
- f"──────────────────\n"
- f"👤 <b>Name:</b> {payload.get('username', 'Customer')}\n"
- f"📍 <b>Location:</b> {payload.get('country')}, {payload.get('city')}\n"
- f"🛂 <b>Visa Type:</b> {payload.get('visa_type')}\n"
- f"📅 <b>Date:</b> <code>{payload.get('appointment_date')}</code>\n"
- f"──────────────────\n"
- f"🆔 <i>Order: {payload.get('order_id')}</i>\n"
- f"📧 <i>Email: {payload.get('user_email')}</i>"
- )
-
- def template_telegram_order_event_update(payload: Dict) -> str:
- summary = payload.get('summary', 'New status update received.')
- return (
- f"🔔 <b>Visa Progress Update</b>\n"
- f"──────────────────\n"
- f"🆔 <b>Order:</b> #{payload.get('order_no')}\n"
- f"📊 <b>Summary:</b>\n<i>{summary}</i>\n"
- f"──────────────────\n"
- f"🔗 <a href='https://visafly.top/dashboard'><b>View Dashboard ➜</b></a>"
- )
- def template_whatsapp_slot_snapshot(payload: Dict) -> str:
- emoji, _, _ = _get_status_meta(payload.get("availability_status"))
- return (
- f"{emoji} *VISA SLOT UPDATE*\n\n"
- f"📍 *Location:* {payload.get('country')}, {payload.get('city')}\n"
- f"🛂 *Type:* {payload.get('visa_type')}\n"
- f"📅 *Earliest:* {payload.get('earliest_date')}\n\n"
- f"🌐 *Book Now:* {payload.get('website')}"
- )
-
- def template_whatsapp_appointment_confirmation(payload: Dict) -> str:
- """预约成功 WhatsApp 通知 (Clean Text)"""
- return (
- f"✅ *APPOINTMENT CONFIRMED*\n\n"
- f"Dear {payload.get('username', 'Customer')},\n"
- f"Your visa appointment has been successfully booked.\n\n"
- f"📍 *Location:* {payload.get('country')}, {payload.get('city')}\n"
- f"🛂 *Visa Type:* {payload.get('visa_type')}\n"
- f"📅 *Date:* {payload.get('appointment_date')}\n"
- f"🆔 *Order ID:* {payload.get('order_id')}\n\n"
- f"Please ensure all required documents are prepared. Thank you for choosing Visafly."
- )
-
- def template_whatsapp_order_event_update(payload: Dict) -> str:
- """
- WhatsApp template for progress updates.
- """
- order_no = payload.get('order_no', 'N/A')
- # If the service pre-generated a message, use it, otherwise build one
- message = payload.get('message')
- if not message:
- summary = payload.get('summary', 'New status update received.')
- message = (
- f"🔔 *VISA PROGRESS UPDATE*\n\n"
- f"Order: #{order_no}\n\n"
- f"Latest Update:\n"
- f"_{summary}_\n\n"
- f"Please log in to your dashboard or check your email for full details."
- )
- return message
- # ==========================================
- # Dispatchers (保持不变)
- # ==========================================
- EMAIL_MAP = {
- "email_verification_for_bind": (template_email_verification_bind, "Verification code for your account"),
- "email_verification_for_reset": (template_email_password_reset, "Password reset code"),
- "login_credentials": (template_email_login_credentials, "Your login credentials"),
- "slot_subscription": (template_email_slot_subscription, "Update: Visa availability change"),
- "appointment_confirmation": (template_email_appointment_confirmation, "Your Visa Appointment Confirmation"),
- "order_event_update": (template_email_order_event_update, "Notification: Visa Appointment Update"),
- }
- WECHAT_MAP = {
- "slot_snapshot": template_wechat_slot_snapshot,
- "payment_user_confirmed": template_wechat_payment_confirmed,
- "appointment_confirmation": template_wechat_appointment_confirmation,
- }
- WHATSAPP_MAP = {
- "slot_subscription": template_whatsapp_slot_snapshot,
- "slot_snapshot": template_whatsapp_slot_snapshot,
- "appointment_confirmation": template_whatsapp_appointment_confirmation,
- "order_event_update": template_whatsapp_order_event_update,
- }
- TELEGRAM_MAP = {
- "slot_snapshot": template_telegram_slot_snapshot,
- "appointment_confirmation": template_telegram_appointment_confirmation,
- "order_event_update": template_telegram_order_event_update,
- }
- def render_email(tid: str, payload: Dict) -> str:
- return EMAIL_MAP[tid][0](payload) if tid in EMAIL_MAP else "Notification from Visafly"
- def get_email_meta(tid: str) -> Tuple[str, str]:
- sender = "donotreply@visafly.top"
- return (sender, EMAIL_MAP[tid][1]) if tid in EMAIL_MAP else (sender, "Notification")
- def render_wechat_markdown(tid: str, payload: Dict) -> str:
- return WECHAT_MAP[tid](payload) if tid in WECHAT_MAP else str(payload)
- def render_whatsapp_text(tid: str, payload: Dict) -> str:
- return WHATSAPP_MAP[tid](payload) if tid in WHATSAPP_MAP else str(payload)
- def render_telegram_html(tid: str, payload: Dict) -> str:
- return TELEGRAM_MAP[tid](payload) if tid in TELEGRAM_MAP else str(payload)
|