新增了Zalo用户频道的完整适配器实现,包括: - 基础的适配器初始化与导出结构 - 群组同步与成员获取功能 - 请求限流与退避重试机制 - 健康检查与状态探针 - 消息反应/表情处理工具 - 贴纸缓存与消息去重功能 - 消息ID格式化与追踪 - TTS语音合成支持 - 消息发送权限校验 - 长文本分块发送 - 操作审批流程 - 常量配置与国际化支持 - 图像视觉分析功能 - 贴纸消息处理 - 登录与配置向导 - 群组上下文缓存 - 网关连接管理 - 配置Schema校验 - 状态问题与安全审计 - 内联按钮与交互组件 - 交互式回调分发 - 联系人与群组目录管理 - 富媒体卡片消息支持
201 lines
4.9 KiB
Python
201 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
from .inline_buttons import ButtonGroup
|
|
|
|
|
|
@dataclass
|
|
class CardImage:
|
|
url: str
|
|
aspect_ratio: str = "1:1"
|
|
|
|
def to_dict(self) -> dict[str, str]:
|
|
return {"url": self.url, "aspect_ratio": self.aspect_ratio}
|
|
|
|
|
|
@dataclass
|
|
class CardAction:
|
|
url: str | None = None
|
|
callback_data: str | None = None
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
d: dict[str, Any] = {}
|
|
if self.url:
|
|
d["url"] = self.url
|
|
if self.callback_data:
|
|
d["callback_data"] = self.callback_data
|
|
return d
|
|
|
|
|
|
@dataclass
|
|
class ZaloCard:
|
|
title: str = ""
|
|
description: str = ""
|
|
image: CardImage | None = None
|
|
buttons: ButtonGroup | None = None
|
|
action: CardAction | None = None
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
d: dict[str, Any] = {
|
|
"type": "card",
|
|
}
|
|
if self.title:
|
|
d["title"] = self.title
|
|
if self.description:
|
|
d["description"] = self.description
|
|
if self.image:
|
|
d["image"] = self.image.to_dict()
|
|
if self.buttons:
|
|
d["buttons"] = self.buttons.to_dict()
|
|
if self.action:
|
|
d["action"] = self.action.to_dict()
|
|
return d
|
|
|
|
|
|
@dataclass
|
|
class CardMessage:
|
|
cards: list[ZaloCard] = field(default_factory=list)
|
|
layout: str = "horizontal" # "horizontal", "carousel", "vertical"
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"type": "card_list",
|
|
"layout": self.layout,
|
|
"elements": [c.to_dict() for c in self.cards],
|
|
}
|
|
|
|
|
|
def build_card_carousel(
|
|
conversation_id: str,
|
|
cards: list[ZaloCard],
|
|
) -> dict[str, Any]:
|
|
return build_card_payload(conversation_id, CardMessage(cards=cards, layout="carousel"))
|
|
|
|
|
|
def build_card_payload(
|
|
conversation_id: str,
|
|
cards: CardMessage,
|
|
) -> dict[str, Any]:
|
|
return {
|
|
"conversation_id": conversation_id,
|
|
"message_type": "card",
|
|
"cards": cards.to_dict(),
|
|
}
|
|
|
|
|
|
def simple_card(
|
|
title: str,
|
|
description: str = "",
|
|
image_url: str | None = None,
|
|
action_url: str | None = None,
|
|
) -> ZaloCard:
|
|
card = ZaloCard(title=title, description=description)
|
|
if image_url:
|
|
card.image = CardImage(url=image_url)
|
|
if action_url:
|
|
card.action = CardAction(url=action_url)
|
|
return card
|
|
|
|
|
|
def template_welcome_card(bot_name: str, description: str = "") -> ZaloCard:
|
|
return ZaloCard(
|
|
title=f"Welcome to {bot_name}",
|
|
description=description or "I'm an AI assistant. How can I help you today?",
|
|
)
|
|
|
|
|
|
def template_confirmation_card(
|
|
title: str,
|
|
confirm_label: str = "Confirm",
|
|
cancel_label: str = "Cancel",
|
|
confirm_data: str = "confirm",
|
|
cancel_data: str = "cancel",
|
|
) -> ZaloCard:
|
|
from .inline_buttons import (
|
|
ButtonGroup,
|
|
ButtonStyle,
|
|
callback_button,
|
|
InlineButtonRow,
|
|
)
|
|
|
|
card = ZaloCard(
|
|
title=title,
|
|
description="Please confirm your action.",
|
|
buttons=ButtonGroup(
|
|
rows=[
|
|
InlineButtonRow(
|
|
buttons=[
|
|
callback_button(confirm_label, confirm_data, ButtonStyle.PRIMARY),
|
|
callback_button(cancel_label, cancel_data, ButtonStyle.DEFAULT),
|
|
]
|
|
)
|
|
]
|
|
),
|
|
)
|
|
return card
|
|
|
|
|
|
def template_info_card(
|
|
title: str,
|
|
fields: dict[str, str],
|
|
image_url: str | None = None,
|
|
) -> ZaloCard:
|
|
description_lines = [f"**{k}**: {v}" for k, v in fields.items()]
|
|
card = ZaloCard(
|
|
title=title,
|
|
description="\n".join(description_lines),
|
|
)
|
|
if image_url:
|
|
card.image = CardImage(url=image_url)
|
|
return card
|
|
|
|
|
|
def template_product_card(
|
|
product_name: str,
|
|
price: str,
|
|
description: str = "",
|
|
image_url: str | None = None,
|
|
buy_url: str | None = None,
|
|
) -> ZaloCard:
|
|
from .inline_buttons import (
|
|
ButtonGroup,
|
|
ButtonStyle,
|
|
InlineButtonRow,
|
|
url_button,
|
|
)
|
|
|
|
card = ZaloCard(
|
|
title=product_name,
|
|
description=description or f"Price: {price}",
|
|
)
|
|
if image_url:
|
|
card.image = CardImage(url=image_url)
|
|
if buy_url:
|
|
card.buttons = ButtonGroup(
|
|
rows=[
|
|
InlineButtonRow(
|
|
buttons=[
|
|
url_button("Buy Now", buy_url, ButtonStyle.PRIMARY),
|
|
]
|
|
)
|
|
]
|
|
)
|
|
return card
|
|
|
|
|
|
async def send_card(
|
|
bridge: Any,
|
|
conversation_id: str,
|
|
card: ZaloCard,
|
|
):
|
|
from yuxi.channels.models import DeliveryResult
|
|
|
|
card_msg = CardMessage(cards=[card])
|
|
payload = build_card_payload(conversation_id, card_msg)
|
|
try:
|
|
return await bridge.send_message(payload)
|
|
except Exception as e:
|
|
return DeliveryResult(success=False, error=str(e))
|