# 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}"
)