ForcePilot/backend/package/yuxi/channel/extensions/feishu/card.py
Kris 5e91bb9985 feat(feishu): 新增飞书渠道完整插件实现
新增飞书渠道插件,包含基础配置、事件处理、消息收发、工具调用、权限管理等完整功能模块,支持WebSocket长连接、卡片消息、流式响应、群管理、审批通知等能力
2026-05-21 10:46:42 +08:00

154 lines
3.3 KiB
Python

from __future__ import annotations
import json
import logging
from yuxi.channel.security.html_guard import sanitize_text_content_preserve_entities
logger = logging.getLogger(__name__)
def build_markdown_card(text: str) -> dict:
return {
"schema": "2.0",
"config": {
"width_mode": "fill",
"enable_forward": True,
},
"body": {
"elements": [
{
"tag": "markdown",
"content": sanitize_text_content_preserve_entities(text),
}
]
},
}
def build_structured_card(
text: str,
*,
title: str | None = None,
template: str = "blue",
note: str | None = None,
) -> dict:
card: dict = {
"schema": "2.0",
"config": {"width_mode": "fill"},
}
if title:
card["header"] = {
"title": {"tag": "plain_text", "content": title},
"template": template,
}
card["body"] = {
"elements": [
{
"tag": "markdown",
"content": sanitize_text_content_preserve_entities(text),
}
]
}
if note:
card["note"] = {
"elements": [
{"tag": "plain_text", "content": note}
]
}
return card
def build_presentation_card(
text: str,
*,
title: str,
template: str = "blue",
actions: list[dict] | None = None,
) -> dict:
card: dict = {
"schema": "2.0",
"config": {"width_mode": "fill"},
"header": {
"title": {"tag": "plain_text", "content": title},
"template": template,
},
"body": {
"elements": [
{"tag": "markdown", "content": sanitize_text_content_preserve_entities(text)},
{"tag": "hr"},
]
},
}
if actions:
card["elements"] = card.get("elements", []) + actions
return card
def build_button(
text: str,
*,
action: str = "",
value: str = "",
button_type: str = "default",
confirm: dict | None = None,
) -> dict:
btn = {
"tag": "button",
"text": {"tag": "plain_text", "content": text},
"type": button_type,
}
if action:
btn["value"] = json.dumps({"action": action, "value": value}, ensure_ascii=False)
if confirm:
btn["confirm"] = confirm
return btn
def build_select_menu(
placeholder: str,
*,
options: list[dict] | None = None,
action: str = "",
) -> dict:
select = {
"tag": "select_static",
"placeholder": {"tag": "plain_text", "content": placeholder},
}
if options:
select["options"] = [
{"text": {"tag": "plain_text", "content": opt.get("text", "")}, "value": opt.get("value", "")}
for opt in options
]
return select
HEADER_TEMPLATE_COLORS = {
"blue": "blue",
"green": "green",
"red": "red",
"orange": "orange",
"purple": "purple",
"indigo": "indigo",
"wathet": "wathet",
"turquoise": "turquoise",
"yellow": "yellow",
"grey": "grey",
"carmine": "carmine",
"violet": "violet",
"lime": "lime",
}
TEMPLATE_TO_PRESENTATION_TONE = {
"blue": "default",
"green": "success",
"red": "danger",
"orange": "warning",
}