新增 Microsoft Teams 渠道扩展,支持在 Yuxi 平台中集成 Microsoft Teams 协作平台。 包含以下功能模块: - sdk: Bot Framework SDK 封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - streaming: 流式消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - auth: JWT 认证 - jwks: JWKS 密钥管理 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - session: 会话管理 - state: 状态管理 - runtime: 运行时管理 - actions: 动作处理 - adaptive_card: 自适应卡片 - task_modules: 任务模块 - message_extension: 消息扩展 - proactive: Proactive Messaging - graph: Microsoft Graph API 集成 - graph_teams: Teams 操作 - graph_members: 成员管理 - graph_messages: 消息获取 - graph_thread: 线程管理 - graph_users: 用户管理 - graph_upload: 文件上传 - files: 文件处理 - file_consent: 文件授权 - conversations: 会话存储 - mentions: @提及处理 - threading: 线程管理 - reactions: 表情反应 - polls: 投票功能 - meetings: 会议集成 - feedback: 反馈处理 - sso: 单点登录 - deep_links: 深层链接 - incoming_webhook: 入站 Webhook - localization: 本地化 - user_agent: 用户代理 - sent_message_cache: 消息缓存 - types: 类型定义
284 lines
7.3 KiB
Python
284 lines
7.3 KiB
Python
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
ADAPTIVE_CARD_VERSION = "1.4"
|
||
|
||
|
||
def build_card_base(
|
||
title: str,
|
||
body_items: list[dict] | None = None,
|
||
actions: list[dict] | None = None,
|
||
*,
|
||
fallback_text: str = "",
|
||
) -> dict:
|
||
card: dict[str, Any] = {
|
||
"type": "AdaptiveCard",
|
||
"version": ADAPTIVE_CARD_VERSION,
|
||
}
|
||
|
||
if title:
|
||
card.setdefault("body", [])
|
||
card["body"].append(
|
||
{
|
||
"type": "TextBlock",
|
||
"text": title,
|
||
"weight": "Bolder",
|
||
"size": "Medium",
|
||
"wrap": True,
|
||
}
|
||
)
|
||
|
||
if body_items:
|
||
card.setdefault("body", [])
|
||
card["body"].extend(body_items)
|
||
|
||
if actions:
|
||
card.setdefault("actions", [])
|
||
card["actions"].extend(actions)
|
||
|
||
if fallback_text:
|
||
card["fallbackText"] = fallback_text
|
||
|
||
return card
|
||
|
||
|
||
def build_text_block(text: str, *, weight: str = "Default", size: str = "Default", wrap: bool = True) -> dict:
|
||
return {
|
||
"type": "TextBlock",
|
||
"text": text,
|
||
"weight": weight,
|
||
"size": size,
|
||
"wrap": wrap,
|
||
}
|
||
|
||
|
||
def build_action_button(title: str, data: dict, *, style: str = "default") -> dict:
|
||
return {
|
||
"type": "Action.Submit",
|
||
"title": title,
|
||
"data": data,
|
||
"style": style,
|
||
}
|
||
|
||
|
||
def build_action_url(title: str, url: str) -> dict:
|
||
return {
|
||
"type": "Action.OpenUrl",
|
||
"title": title,
|
||
"url": url,
|
||
}
|
||
|
||
|
||
def build_action_execute(title: str, data: dict, *, verb: str = "", fallback: str = "submit") -> dict:
|
||
return {
|
||
"type": "Action.Execute",
|
||
"title": title,
|
||
"data": data,
|
||
"verb": verb,
|
||
"fallback": fallback,
|
||
}
|
||
|
||
|
||
def build_refresh_properties(refresh_enabled: bool = True, *, user_ids: list[str] | None = None, refresh_interval_s: int | None = None) -> dict:
|
||
refresh: dict = {"action": {"type": "Action.Execute", "verb": "refresh"}}
|
||
if user_ids:
|
||
refresh["userIds"] = user_ids
|
||
if refresh_interval_s:
|
||
refresh["action"]["refreshInterval"] = refresh_interval_s
|
||
return refresh
|
||
|
||
|
||
def build_user_specific_card(base_card: dict, user_id: str, user_body_items: list[dict]) -> dict:
|
||
card = {**base_card}
|
||
card["refresh"] = build_refresh_properties(refresh_enabled=True, user_ids=[user_id])
|
||
return card
|
||
|
||
|
||
def build_hero_card(title: str, *, subtitle: str = "", text: str = "", image_url: str = "", buttons: list[dict] | None = None) -> dict:
|
||
card = {
|
||
"contentType": "application/vnd.microsoft.card.hero",
|
||
"content": {
|
||
"title": title,
|
||
"subtitle": subtitle,
|
||
"text": text,
|
||
"images": [{"url": image_url}] if image_url else [],
|
||
"buttons": buttons or [],
|
||
},
|
||
}
|
||
return card
|
||
|
||
|
||
def build_thumbnail_card(title: str, *, subtitle: str = "", text: str = "", image_url: str = "", buttons: list[dict] | None = None) -> dict:
|
||
card = {
|
||
"contentType": "application/vnd.microsoft.card.thumbnail",
|
||
"content": {
|
||
"title": title,
|
||
"subtitle": subtitle,
|
||
"text": text,
|
||
"images": [{"url": image_url}] if image_url else [],
|
||
"buttons": buttons or [],
|
||
},
|
||
}
|
||
return card
|
||
|
||
|
||
def build_receipt_card(title: str, items: list[dict], *, total: str = "", tax: str = "", buttons: list[dict] | None = None) -> dict:
|
||
card = {
|
||
"contentType": "application/vnd.microsoft.card.receipt",
|
||
"content": {
|
||
"title": title,
|
||
"facts": [],
|
||
"items": items,
|
||
"total": total,
|
||
"tax": tax,
|
||
"buttons": buttons or [],
|
||
},
|
||
}
|
||
return card
|
||
|
||
|
||
def build_receipt_item(title: str, price: str, *, quantity: str = "1", image_url: str = "", subtitle: str = "") -> dict:
|
||
return {
|
||
"title": title,
|
||
"subtitle": subtitle,
|
||
"price": price,
|
||
"quantity": quantity,
|
||
"image": {"url": image_url} if image_url else None,
|
||
}
|
||
|
||
|
||
def build_welcome_card(
|
||
bot_name: str,
|
||
description: str = "",
|
||
*,
|
||
features: list[str] | None = None,
|
||
) -> dict:
|
||
items = [
|
||
build_text_block(f"👋 欢迎使用 **{bot_name}**!", weight="Bolder", size="Large"),
|
||
]
|
||
|
||
if description:
|
||
items.append(build_text_block(description))
|
||
|
||
if features:
|
||
feature_text = "\n".join(f"- {f}" for f in features)
|
||
items.append(build_text_block(feature_text))
|
||
|
||
items.append(build_text_block("发送消息开始对话,或使用交互按钮探索功能。"))
|
||
|
||
return build_card_base(
|
||
title="",
|
||
body_items=items,
|
||
fallback_text=f"欢迎使用 {bot_name}!发送消息开始对话。",
|
||
)
|
||
|
||
|
||
def build_poll_card(
|
||
question: str,
|
||
options: list[str],
|
||
*,
|
||
poll_id: str = "",
|
||
is_multi_select: bool = False,
|
||
) -> dict:
|
||
body_items = [
|
||
build_text_block(question, weight="Bolder", size="Medium"),
|
||
{
|
||
"type": "Input.ChoiceSet",
|
||
"id": "pollChoice",
|
||
"choices": [{"title": opt, "value": opt} for opt in options],
|
||
"style": "expanded",
|
||
"isMultiSelect": is_multi_select,
|
||
},
|
||
]
|
||
|
||
actions = [
|
||
build_action_button("投票", {"action": "vote", "poll_id": poll_id}, style="positive"),
|
||
]
|
||
|
||
return build_card_base(
|
||
title="",
|
||
body_items=body_items,
|
||
actions=actions,
|
||
fallback_text=f"投票: {question}",
|
||
)
|
||
|
||
|
||
def build_confirm_card(
|
||
message: str,
|
||
confirm_text: str = "确认",
|
||
cancel_text: str = "取消",
|
||
*,
|
||
confirm_data: dict | None = None,
|
||
cancel_data: dict | None = None,
|
||
) -> dict:
|
||
body_items = [
|
||
build_text_block(message, size="Medium"),
|
||
]
|
||
|
||
actions = []
|
||
if confirm_text:
|
||
actions.append(build_action_button(confirm_text, confirm_data or {"action": "confirm"}, style="positive"))
|
||
if cancel_text:
|
||
actions.append(build_action_button(cancel_text, cancel_data or {"action": "cancel"}, style="default"))
|
||
|
||
return build_card_base(
|
||
title="",
|
||
body_items=body_items,
|
||
actions=actions,
|
||
fallback_text=message,
|
||
)
|
||
|
||
|
||
def build_demo_card(
|
||
title: str,
|
||
content: str,
|
||
*,
|
||
action_title: str | None = None,
|
||
action_data: dict | None = None,
|
||
action_url: str | None = None,
|
||
) -> dict:
|
||
body_items = [
|
||
build_text_block(content, size="Default"),
|
||
]
|
||
|
||
actions = []
|
||
if action_title and action_data:
|
||
actions.append(build_action_button(action_title, action_data))
|
||
if action_title and action_url:
|
||
actions.append(build_action_url(action_title, action_url))
|
||
|
||
return build_card_base(
|
||
title=title,
|
||
body_items=body_items,
|
||
actions=actions,
|
||
fallback_text=f"{title}: {content}",
|
||
)
|
||
|
||
|
||
def build_attachment(card: dict) -> dict:
|
||
return {
|
||
"contentType": "application/vnd.microsoft.card.adaptive",
|
||
"content": card,
|
||
}
|
||
|
||
|
||
def build_card_activity(
|
||
card: dict,
|
||
*,
|
||
reply_to_id: str | None = None,
|
||
tenant_id: str | None = None,
|
||
) -> dict:
|
||
activity: dict[str, Any] = {
|
||
"type": "message",
|
||
"attachments": [build_attachment(card)],
|
||
}
|
||
|
||
if reply_to_id:
|
||
activity["replyToId"] = reply_to_id
|
||
if tenant_id:
|
||
activity.setdefault("channelData", {})
|
||
activity["channelData"]["tenant"] = {"id": tenant_id}
|
||
|
||
return activity
|