# app/services/telegram_service.py import httpx from app.core.biz_exception import BizLogicError from app.schemas.telegram import TelegramIn def _parse_slots_summary(availability_data): """ availability: [ { "date": "2025-01-05", "times": [] }, { "date": "2025-01-06", "times": [{"time": "09:30"}] } ] """ if not availability_data: return "No specific slots data." if not isinstance(availability_data, list): return str(availability_data) summaries = [] # 限制显示天数,防止消息过长 display_limit = 4 for day in availability_data[:display_limit]: date_str = day.get("date") times = day.get("times", []) if not times: # 修改点:即使没有具体时间,只要有日期条目,也显示为可用 summaries.append(f"{date_str}") else: # 有具体时间的处理 time_list = [t.get("time", "") for t in times[:5]] time_text = ", ".join(filter(None, time_list)) # 过滤空时间 if len(times) > 5: time_text += f" (+{len(times) - 5})" summaries.append(f"{date_str}: {time_text}") if len(availability_data) > display_limit: summaries.append(f"(+{len(availability_data) - display_limit} more days)") return " | ".join(summaries) def _get_display_meta(slot_snapshot): """ Return dynamic header logic based on status. Goal: Put the most important info in the notification preview. """ date_str = slot_snapshot.get("earliest_date") if slot_snapshot.get("availability_status") == 'Available': # Notification Preview: "🟢 UK London: 15 Jan 2024" emoji = "🟢" # If available, the DATE is the headline headline = f'{slot_snapshot.get("country")}, {slot_snapshot.get("city")}: {date_str}' color = "info" # Green for WeChat elif slot_snapshot.get("availability_status") == 'Waitlist': emoji = "🟡" headline = f'Waitlist: {slot_snapshot.get("country")}, {slot_snapshot.get("city")}' color = "warning" # Orange for WeChat else: emoji = "🔴" headline = f'No Slots: {slot_snapshot.get("country")}, {slot_snapshot.get("city")}' color = "comment" # Grey for WeChat return emoji, headline, color, date_str class TelegramService: @staticmethod async def push_to_telegram(payload: TelegramIn): url = f"https://api.telegram.org/bot{payload.api_token}/sendMessage" body = { "chat_id": payload.chat_id, "text": payload.message, "parse_mode": "HTML", } async with httpx.AsyncClient(timeout=10) as client: resp = await client.post(url, json=body) if resp.status_code != 200: raise BizLogicError( f"Telegram push failed: {resp.status_code}, {resp.text}" ) async def push_slot_snapshot(api_token: str, chat_id: str, slot_snapshot) -> str: emoji, headline, _, date_str = _get_display_meta(slot_snapshot) # 解析详情 slots_summary = _parse_slots_summary(slot_snapshot.get("availability")) # 处理可能的 None 值 website = slot_snapshot.get("website") or "#" # 如果没有网址,给一个空锚点 # 格式化更新时间 checked_at = slot_snapshot.get("snapshot_at", "") if "T" in str(updated_at): checked_at = str(checked_at).replace("T", " ") TEMPLATE = ( "{emoji} {headline}\n" "──────────────────\n" "🛂 Visa: {visa_type}\n" "📅 Earliest: {date_str}\n" "📊 Slots: {slots_summary}\n" "──────────────────\n" "🔗 Book Now ➜\n\n" "🕒 Checked at {checked_at}" ) content = TEMPLATE.format_map({ "emoji": emoji, "headline": headline, "visa_type": slot_snapshot.get("visa_type", "N/A"), "date_str": date_str, "slots_summary": slots_summary, "website": website, "checked_at": checked_at, }) url = f"https://api.telegram.org/bot{api_token}/sendMessage" body = { "chat_id": chat_id, "text": content, "parse_mode": "HTML", } async with httpx.AsyncClient(timeout=10) as client: resp = await client.post(url, json=body) if resp.status_code != 200: raise BizLogicError( f"Telegram push failed: {resp.status_code}, {resp.text}" )