实现了 Teams 机器人所需的全功能组件,包括: - 基础命令解析与帮助卡片生成 - 租户验证与访问控制 - 自定义 UA 与媒体工具 - 消息分块、批注处理与会话管理 - 防抖、缓存与配置路由能力 - 投票、配对、审计与运行时状态管理 - TTS 语音合成与卡片构建工具 - 群组管理与权限控制逻辑
143 lines
3.8 KiB
Python
143 lines
3.8 KiB
Python
"""Microsoft Teams Presentation Card Builder。
|
||
|
||
buildMSTeamsPresentationCard,支持 title/text/context/divider/buttons 等
|
||
通用 block 类型转换为 Adaptive Card。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
|
||
def build_presentation_card(
|
||
title: str = "",
|
||
blocks: list[dict[str, Any]] | None = None,
|
||
) -> dict[str, Any]:
|
||
body: list[dict[str, Any]] = []
|
||
|
||
if title:
|
||
body.append(
|
||
{
|
||
"type": "TextBlock",
|
||
"size": "Large",
|
||
"weight": "Bolder",
|
||
"text": title,
|
||
}
|
||
)
|
||
|
||
for block in blocks or []:
|
||
converted = _convert_block(block)
|
||
if converted:
|
||
if isinstance(converted, list):
|
||
body.extend(converted)
|
||
else:
|
||
body.append(converted)
|
||
|
||
return {
|
||
"type": "AdaptiveCard",
|
||
"version": "1.5",
|
||
"body": body,
|
||
}
|
||
|
||
|
||
def _convert_block(block: dict[str, Any]) -> dict[str, Any] | list[dict[str, Any]] | None:
|
||
block_type = block.get("type", "text")
|
||
|
||
if block_type == "text":
|
||
return {
|
||
"type": "TextBlock",
|
||
"text": block.get("text", ""),
|
||
"wrap": block.get("wrap", True),
|
||
"size": block.get("size", "default"),
|
||
"weight": block.get("weight", "default"),
|
||
"isSubtle": block.get("is_subtle", False),
|
||
"spacing": block.get("spacing", "default"),
|
||
}
|
||
|
||
if block_type == "context":
|
||
return {
|
||
"type": "TextBlock",
|
||
"text": block.get("text", ""),
|
||
"wrap": True,
|
||
"isSubtle": True,
|
||
"size": "small",
|
||
"spacing": "small",
|
||
}
|
||
|
||
if block_type == "divider":
|
||
return []
|
||
|
||
if block_type == "section":
|
||
items: list[dict[str, Any]] = []
|
||
title = block.get("title", "")
|
||
if title:
|
||
items.append(
|
||
{
|
||
"type": "TextBlock",
|
||
"text": title,
|
||
"weight": "Bolder",
|
||
"wrap": True,
|
||
}
|
||
)
|
||
text = block.get("text", "")
|
||
if text:
|
||
items.append(
|
||
{
|
||
"type": "TextBlock",
|
||
"text": text,
|
||
"wrap": True,
|
||
}
|
||
)
|
||
return items
|
||
|
||
if block_type == "facts":
|
||
facts = block.get("facts", {})
|
||
return {
|
||
"type": "FactSet",
|
||
"facts": [{"title": k, "value": v} for k, v in facts.items()],
|
||
}
|
||
|
||
if block_type == "image":
|
||
return {
|
||
"type": "Image",
|
||
"url": block.get("url", ""),
|
||
"altText": block.get("alt", ""),
|
||
"size": block.get("size", "auto"),
|
||
}
|
||
|
||
if block_type == "buttons":
|
||
buttons = block.get("buttons", [])
|
||
return {
|
||
"type": "ActionSet",
|
||
"actions": [
|
||
{
|
||
"type": "Action.OpenUrl" if btn.get("url") else "Action.Submit",
|
||
"title": btn.get("title", "Button"),
|
||
"url": btn.get("url", ""),
|
||
"data": btn.get("data", {}),
|
||
"style": btn.get("style", "default"),
|
||
}
|
||
for btn in buttons
|
||
],
|
||
}
|
||
|
||
return None
|
||
|
||
|
||
def add_actions_to_card(
|
||
card: dict[str, Any],
|
||
actions: list[dict[str, Any]],
|
||
) -> dict[str, Any]:
|
||
formatted = [
|
||
{
|
||
"type": "Action.OpenUrl" if a.get("url") else "Action.Submit",
|
||
"title": a.get("title", "Action"),
|
||
"url": a.get("url", ""),
|
||
"data": a.get("data", {}),
|
||
"style": a.get("style", "default"),
|
||
}
|
||
for a in actions
|
||
]
|
||
card["actions"] = formatted
|
||
return card
|