From 7daa8e46c29399c4e9e92b6366aeefd5490baf98 Mon Sep 17 00:00:00 2001
From: bin <632190820@qq.com>
Date: Mon, 9 Mar 2026 15:53:59 +0800
Subject: [PATCH] =?UTF-8?q?build:=20=E6=9B=B4=E6=96=B0=E9=A1=B9=E7=9B=AE?=
=?UTF-8?q?=E7=89=88=E6=9C=AC=E8=87=B30.1.10?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
lzwcai_lark_mcp/lzwcai_lark_mcp/tools.py | 1155 +++++++++++++++++++++-
lzwcai_lark_mcp/pyproject.toml | 2 +-
2 files changed, 1148 insertions(+), 9 deletions(-)
diff --git a/lzwcai_lark_mcp/lzwcai_lark_mcp/tools.py b/lzwcai_lark_mcp/lzwcai_lark_mcp/tools.py
index e6c118c..9d6b433 100644
--- a/lzwcai_lark_mcp/lzwcai_lark_mcp/tools.py
+++ b/lzwcai_lark_mcp/lzwcai_lark_mcp/tools.py
@@ -72,11 +72,11 @@ tools = [
"description": "图片image_key"
}
},
- "required": ["image_key", "person_id"]
+ "required": ["image_key"]
}
),
Tool(
- name="send_asset_confirmation_card",
+ name="send_confirmation_card",
description="发送定制资产确认卡片,入参为user_id、order_number、change_time、asset_list和remark",
inputSchema={
"type": "object",
@@ -105,7 +105,131 @@ tools = [
"description": "反馈确认弹窗内容"
}
},
- "required": ["user_id", "order_number"]
+ "required": ["user_id", "order_number", "asset_list"]
+ }
+ ),
+ Tool(
+ name="send_review_card",
+ description="发送资产复核卡片,入参为user_id、order_number、change_time、asset_list、person_ids和remark",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "user_id": {
+ "type": "string",
+ "description": "消息接收者ID,同时用于卡片内person组件"
+ },
+ "order_number": {
+ "type": "string",
+ "description": "确认单号"
+ },
+ "change_time": {
+ "type": "string",
+ "description": "资产变动发生时间"
+ },
+ "asset_list": {
+ "type": "array",
+ "description": "可选择的资产变动项列表",
+ "items": {
+ "type": "object"
+ }
+ },
+ "person_ids": {
+ "type": "array",
+ "description": "相关人员ID列表",
+ "items": {
+ "type": "string"
+ }
+ },
+ "remark": {
+ "type": "string",
+ "description": "反馈确认弹窗内容"
+ }
+ },
+ "required": ["user_id", "order_number", "asset_list", "person_ids"]
+ }
+ ),
+ Tool(
+ name="send_stranger_card",
+ description="发送陌生人资产变动卡片,入参为user_id、order_number、change_time、asset_list、face_cap、user_ids和remark",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "user_id": {
+ "type": "string",
+ "description": "消息接收者ID"
+ },
+ "order_number": {
+ "type": "string",
+ "description": "确认单号"
+ },
+ "change_time": {
+ "type": "string",
+ "description": "资产变动发生时间"
+ },
+ "asset_list": {
+ "type": "array",
+ "description": "可选择的资产变动项列表",
+ "items": {
+ "type": "object"
+ }
+ },
+ "face_cap": {
+ "type": "string",
+ "description": "抓拍人脸图片的image_key"
+ },
+ "user_ids": {
+ "type": "array",
+ "description": "候选人员ID列表",
+ "items": {
+ "type": "string"
+ }
+ },
+ "remark": {
+ "type": "string",
+ "description": "反馈确认弹窗内容"
+ }
+ },
+ "required": ["user_id", "order_number", "asset_list", "face_cap", "user_ids"]
+ }
+ ),
+ Tool(
+ name="send_feedback_card",
+ description="发送资产变动反馈单,入参为receiver_ids、user_id、order_number、change_time、asset_list和remark",
+ inputSchema={
+ "type": "object",
+ "properties": {
+ "receiver_ids": {
+ "type": "array",
+ "description": "消息接收者ID列表",
+ "items": {
+ "type": "string"
+ }
+ },
+ "user_id": {
+ "type": "string",
+ "description": "卡片内person组件显示的ID"
+ },
+ "order_number": {
+ "type": "string",
+ "description": "确认单号"
+ },
+ "change_time": {
+ "type": "string",
+ "description": "资产变动发生时间"
+ },
+ "asset_list": {
+ "type": "array",
+ "description": "可选择的资产变动项列表",
+ "items": {
+ "type": "object"
+ }
+ },
+ "remark": {
+ "type": "string",
+ "description": "反馈内容,用于回填到输入框"
+ }
+ },
+ "required": ["receiver_ids", "user_id", "order_number", "asset_list"]
}
)
]
@@ -235,6 +359,21 @@ def _resolve_image_source_from_excel(excel_path: str, image_key: str) -> str:
return matched_source
+def _get_user_info(token: str, user_id: str) -> Dict[str, object]:
+ url = f"https://open.feishu.cn/open-apis/contact/v3/users/{user_id}"
+ headers = {"Authorization": f"Bearer {token}"}
+ params = {"user_id_type": "user_id"}
+ try:
+ response = requests.get(url, headers=headers, params=params, timeout=10)
+ if response.status_code == 200:
+ data = response.json()
+ if data.get("code") == 0:
+ return data.get("data", {}).get("user", {})
+ except Exception:
+ pass
+ return {}
+
+
def _normalize_asset_items(value: object) -> List[str]:
if value is None:
return []
@@ -294,7 +433,7 @@ def _build_select_options(items: List[str]) -> list[Dict[str, object]]:
return options
-def send_asset_confirmation_card(
+def send_confirmation_card(
token: str,
user_id: str,
order_number: str,
@@ -308,6 +447,8 @@ def send_asset_confirmation_card(
normalized_order_number = str(order_number).strip()
if not normalized_order_number:
raise ValueError("missing order_number")
+ if not asset_list:
+ raise ValueError("missing asset_list")
normalized_time = str(change_time or "").strip()
if not normalized_time:
normalized_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@@ -368,7 +509,7 @@ def send_asset_confirmation_card(
},
{
"tag": "markdown",
- "content": "**\\*请准确选择关于您的变动项:**",
+ "content": "**请准确选择关于您的变动项:**",
"text_align": "left",
"text_size": "normal",
"margin": "0px 0px 8px 0px"
@@ -561,10 +702,930 @@ def send_asset_confirmation_card(
return message_id
+def send_review_card(
+ token: str,
+ user_id: str,
+ order_number: str,
+ change_time: str,
+ asset_list: object,
+ person_ids: List[str],
+ remark: object = ""
+) -> str:
+ normalized_user_id = str(user_id).strip()
+ if not normalized_user_id:
+ raise ValueError("missing user_id")
+ normalized_order_number = str(order_number).strip()
+ if not normalized_order_number:
+ raise ValueError("missing order_number")
+ if not asset_list:
+ raise ValueError("missing asset_list")
+ if not person_ids:
+ raise ValueError("missing person_ids")
+ normalized_time = str(change_time or "").strip()
+ if not normalized_time:
+ normalized_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ normalized_remark = str(remark or "").strip()
+
+ options = []
+ if isinstance(asset_list, list):
+ for item in asset_list:
+ if isinstance(item, dict):
+ for k, v in item.items():
+ options.append({
+ "text": {"tag": "plain_text", "content": str(k)},
+ "value": str(v)
+ })
+ elif isinstance(item, str):
+ options.append({
+ "text": {"tag": "plain_text", "content": item},
+ "value": item
+ })
+
+ formatted_person_ids = [{"id": normalized_user_id}]
+ if person_ids:
+ for pid in person_ids:
+ pid_str = str(pid).strip()
+ if pid_str and pid_str != normalized_user_id:
+ formatted_person_ids.append({"id": pid_str})
+
+ content = {
+ "schema": "2.0",
+ "config": {
+ "update_multi": True,
+ "style": {
+ "text_size": {
+ "normal_v2": {
+ "default": "normal",
+ "pc": "normal",
+ "mobile": "heading"
+ }
+ }
+ }
+ },
+ "body": {
+ "direction": "vertical",
+ "elements": [
+ {
+ "tag": "form",
+ "elements": [
+ {
+ "tag": "markdown",
+ "content": "**有资产 **被多人选择确认** 或 **24小时内无人选择确认** 导致异常,请与相关人员进行复核。**",
+ "text_align": "left",
+ "text_size": "normal_v2",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "hr",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "markdown",
+ "content": f"**单号:** {normalized_order_number}",
+ "text_align": "left",
+ "text_size": "normal"
+ },
+ {
+ "tag": "column_set",
+ "horizontal_spacing": "8px",
+ "horizontal_align": "left",
+ "columns": [
+ {
+ "tag": "column",
+ "width": "auto",
+ "elements": [
+ {
+ "tag": "markdown",
+ "content": "**相关用户:**",
+ "text_align": "left",
+ "text_size": "normal"
+ }
+ ],
+ "vertical_spacing": "8px",
+ "horizontal_align": "left",
+ "vertical_align": "top"
+ },
+ {
+ "tag": "column",
+ "width": "auto",
+ "elements": [
+ {
+ "tag": "person_list",
+ "persons": formatted_person_ids,
+ "size": "small",
+ "show_name": True,
+ "margin": "0px 0px 0px 0px"
+ }
+ ],
+ "vertical_spacing": "8px",
+ "horizontal_align": "left",
+ "vertical_align": "top"
+ }
+ ],
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "markdown",
+ "content": f"**发生时间:** {normalized_time}",
+ "text_align": "left",
+ "text_size": "normal"
+ },
+ {
+ "tag": "markdown",
+ "content": "**请准确选择关于您的变动项:**",
+ "text_align": "left",
+ "text_size": "normal",
+ "margin": "0px 0px 8px 0px"
+ },
+ {
+ "tag": "multi_select_static",
+ "placeholder": {
+ "tag": "plain_text",
+ "content": "请选择资产变动项"
+ },
+ "options": options,
+ "type": "default",
+ "width": "fill",
+ "required": False,
+ "name": "input_assets",
+ "margin": "0px 0px 16px 0px",
+ "element_id": "cIiptD7Z4hCtAeR5Rb0b"
+ },
+ {
+ "tag": "hr",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "markdown",
+ "content": "**其他说明:**",
+ "text_align": "left",
+ "text_size": "normal_v2",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "input",
+ "placeholder": {
+ "tag": "plain_text",
+ "content": "请输入"
+ },
+ "default_value": "",
+ "width": "fill",
+ "name": "input_remark",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "column_set",
+ "flex_mode": "flow",
+ "horizontal_spacing": "8px",
+ "horizontal_align": "right",
+ "columns": [
+ {
+ "tag": "column",
+ "width": "auto",
+ "elements": [
+ {
+ "tag": "button",
+ "text": {
+ "tag": "plain_text",
+ "content": "确认"
+ },
+ "type": "primary_filled",
+ "width": "default",
+ "size": "medium",
+ "behaviors": [
+ {
+ "type": "callback",
+ "value": {
+ "action": "card.action.trigger",
+ "order_number": normalized_order_number,
+ "user_id": normalized_user_id
+ }
+ }
+ ],
+ "form_action_type": "submit",
+ "name": "confirm_button",
+ "margin": "4px 0px 4px 0px"
+ }
+ ],
+ "vertical_spacing": "8px",
+ "horizontal_align": "left",
+ "vertical_align": "top"
+ },
+ {
+ "tag": "column",
+ "width": "auto",
+ "elements": [
+ {
+ "tag": "button",
+ "text": {
+ "tag": "plain_text",
+ "content": "反馈问题"
+ },
+ "type": "default",
+ "width": "default",
+ "confirm": {
+ "title": {
+ "tag": "plain_text",
+ "content": "反馈误报并废除该确认单吗?"
+ },
+ "text": {
+ "tag": "plain_text",
+ "content": normalized_remark
+ }
+ },
+ "behaviors": [
+ {
+ "type": "callback",
+ "value": {
+ "action": "card.action.trigger",
+ "order_number": normalized_order_number,
+ "user_id": normalized_user_id
+ }
+ }
+ ],
+ "form_action_type": "submit",
+ "name": "feedback_button",
+ "margin": "4px 0px 4px 0px"
+ }
+ ],
+ "vertical_spacing": "8px",
+ "horizontal_align": "left",
+ "vertical_align": "top"
+ }
+ ]
+ }
+ ],
+ "direction": "vertical",
+ "horizontal_align": "left",
+ "vertical_align": "top",
+ "padding": "12px 12px 12px 12px",
+ "margin": "0px 0px 0px 0px",
+ "name": "asset_confirmation_form"
+ },
+ {
+ "tag": "hr",
+ "margin": "0px 0px 0px 0px"
+ }
+ ]
+ },
+ "header": {
+ "title": {
+ "tag": "plain_text",
+ "content": "资产变动单"
+ },
+ "subtitle": {
+ "tag": "plain_text",
+ "content": ""
+ },
+ "text_tag_list": [
+ {
+ "tag": "text_tag",
+ "text": {
+ "tag": "plain_text",
+ "content": "待复核"
+ },
+ "color": "yellow"
+ }
+ ],
+ "template": "yellow",
+ "icon": {
+ "tag": "standard_icon",
+ "token": "googledrive_outlined"
+ },
+ "padding": "12px 8px 12px 8px"
+ }
+ }
+ payload = {
+ "receive_id": normalized_user_id,
+ "msg_type": "interactive",
+ "content": json.dumps(content, ensure_ascii=False)
+ }
+ headers = {
+ "Authorization": f"Bearer {token}",
+ "Content-Type": "application/json"
+ }
+ response = requests.post(
+ "https://open.feishu.cn/open-apis/im/v1/messages",
+ params={"receive_id_type": "user_id"},
+ headers=headers,
+ json=payload,
+ timeout=30
+ )
+ try:
+ data = response.json()
+ except ValueError:
+ data = {"raw": response.text}
+ if not response.ok:
+ raise RuntimeError(f"lark send message http error: {data}")
+ if data.get("code") not in (0, None):
+ raise RuntimeError(f"lark send message failed: {data}")
+ message_id = data.get("data", {}).get("message_id")
+ if not message_id:
+ raise RuntimeError(f"lark send message missing message_id: {data}")
+ return message_id
+
+
+def send_stranger_card(
+ token: str,
+ user_id: str,
+ order_number: str,
+ change_time: str,
+ asset_list: object,
+ face_cap: str,
+ user_ids: List[str],
+ remark: object = ""
+) -> str:
+ normalized_user_id = str(user_id).strip()
+ if not normalized_user_id:
+ raise ValueError("missing user_id")
+ normalized_order_number = str(order_number).strip()
+ if not normalized_order_number:
+ raise ValueError("missing order_number")
+ if not asset_list:
+ raise ValueError("missing asset_list")
+ normalized_face_cap = str(face_cap).strip()
+ if not normalized_face_cap:
+ raise ValueError("missing face_cap")
+ if not user_ids:
+ raise ValueError("missing user_ids")
+ normalized_time = str(change_time or "").strip()
+ if not normalized_time:
+ normalized_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ normalized_remark = str(remark or "").strip()
+
+ options = []
+ if isinstance(asset_list, list):
+ for item in asset_list:
+ if isinstance(item, dict):
+ for k, v in item.items():
+ options.append({
+ "text": {"tag": "plain_text", "content": str(k)},
+ "value": str(v)
+ })
+ elif isinstance(item, str):
+ options.append({
+ "text": {"tag": "plain_text", "content": item},
+ "value": item
+ })
+
+ user_options = []
+ for uid in user_ids:
+ uid_str = str(uid).strip()
+ if uid_str:
+ user_info = _get_user_info(token, uid_str)
+ user_name = user_info.get("name", "")
+ label = user_name if user_name else uid_str
+ user_options.append({
+ "text": {"tag": "plain_text", "content": label},
+ "value": uid_str
+ })
+
+ content = {
+ "schema": "2.0",
+ "config": {
+ "update_multi": True,
+ "style": {
+ "text_size": {
+ "normal_v2": {
+ "default": "normal",
+ "pc": "normal",
+ "mobile": "heading"
+ }
+ }
+ }
+ },
+ "body": {
+ "direction": "vertical",
+ "elements": [
+ {
+ "tag": "form",
+ "elements": [
+ {
+ "tag": "markdown",
+ "content": "**有资产变动,但未识别到对应公司员工,请进行人工处理。**",
+ "text_align": "left",
+ "text_size": "normal_v2",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "hr",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "markdown",
+ "content": "**相关快照:**",
+ "text_align": "left",
+ "text_size": "normal_v2",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "column_set",
+ "horizontal_spacing": "8px",
+ "horizontal_align": "left",
+ "columns": [
+ {
+ "tag": "column",
+ "width": "weighted",
+ "elements": [],
+ "vertical_spacing": "8px",
+ "horizontal_align": "left",
+ "vertical_align": "top",
+ "weight": 1
+ },
+ {
+ "tag": "column",
+ "width": "weighted",
+ "elements": [
+ {
+ "tag": "img",
+ "img_key": normalized_face_cap,
+ "preview": True,
+ "transparent": False,
+ "scale_type": "crop_center",
+ "margin": "0px 0px 0px 0px"
+ }
+ ],
+ "vertical_spacing": "8px",
+ "horizontal_align": "left",
+ "vertical_align": "top",
+ "weight": 2
+ },
+ {
+ "tag": "column",
+ "width": "weighted",
+ "elements": [],
+ "vertical_spacing": "8px",
+ "horizontal_align": "left",
+ "vertical_align": "top",
+ "weight": 1
+ }
+ ],
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "markdown",
+ "content": f"**单号:** {normalized_order_number}",
+ "text_align": "left",
+ "text_size": "normal"
+ },
+ {
+ "tag": "markdown",
+ "content": f"**发生时间:** {normalized_time}",
+ "text_align": "left",
+ "text_size": "normal"
+ },
+ {
+ "tag": "markdown",
+ "content": "**请选择对应人员:**\n(若是游客取走资产,则需要选择相关负责人)",
+ "text_align": "left",
+ "text_size": "normal",
+ "margin": "0px 0px 8px 0px"
+ },
+ {
+ "tag": "multi_select_static",
+ "placeholder": {
+ "tag": "plain_text",
+ "content": "请选择"
+ },
+ "options": user_options,
+ "type": "default",
+ "width": "default",
+ "name": "selected_user_ids",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "markdown",
+ "content": "**请选择对应人员的变动项:**",
+ "text_align": "left",
+ "text_size": "normal",
+ "margin": "0px 0px 8px 0px"
+ },
+ {
+ "tag": "multi_select_static",
+ "placeholder": {
+ "tag": "plain_text",
+ "content": "请选择资产变动项"
+ },
+ "options": options,
+ "type": "default",
+ "width": "fill",
+ "required": False,
+ "name": "input_assets",
+ "margin": "0px 0px 16px 0px",
+ "element_id": "cIiptD7Z4hCtAeR5Rb0b"
+ },
+ {
+ "tag": "hr",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "markdown",
+ "content": "**其他说明:**",
+ "text_align": "left",
+ "text_size": "normal_v2",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "input",
+ "placeholder": {
+ "tag": "plain_text",
+ "content": "请输入"
+ },
+ "default_value": "",
+ "width": "fill",
+ "name": "input_remark",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "column_set",
+ "flex_mode": "flow",
+ "horizontal_spacing": "8px",
+ "horizontal_align": "right",
+ "columns": [
+ {
+ "tag": "column",
+ "width": "auto",
+ "elements": [
+ {
+ "tag": "button",
+ "text": {
+ "tag": "plain_text",
+ "content": "确认"
+ },
+ "type": "primary_filled",
+ "width": "default",
+ "size": "medium",
+ "behaviors": [
+ {
+ "type": "callback",
+ "value": {
+ "action": "card.action.trigger",
+ "order_number": normalized_order_number,
+ "user_id": normalized_user_id
+ }
+ }
+ ],
+ "form_action_type": "submit",
+ "name": "confirm_button",
+ "margin": "4px 0px 4px 0px"
+ }
+ ],
+ "vertical_spacing": "8px",
+ "horizontal_align": "left",
+ "vertical_align": "top"
+ },
+ {
+ "tag": "column",
+ "width": "auto",
+ "elements": [] # Removed feedback button as per card3.txt logic or keep empty? card3.txt shows empty second column
+ ,
+ "vertical_spacing": "8px",
+ "horizontal_align": "left",
+ "vertical_align": "top"
+ }
+ ]
+ }
+ ],
+ "direction": "vertical",
+ "horizontal_align": "left",
+ "vertical_align": "top",
+ "padding": "12px 12px 12px 12px",
+ "margin": "0px 0px 0px 0px",
+ "name": "asset_confirmation_form"
+ },
+ {
+ "tag": "hr",
+ "margin": "0px 0px 0px 0px"
+ }
+ ]
+ },
+ "header": {
+ "title": {
+ "tag": "plain_text",
+ "content": "资产变动单"
+ },
+ "subtitle": {
+ "tag": "plain_text",
+ "content": ""
+ },
+ "text_tag_list": [
+ {
+ "tag": "text_tag",
+ "text": {
+ "tag": "plain_text",
+ "content": "待人工处理"
+ },
+ "color": "red"
+ }
+ ],
+ "template": "purple",
+ "icon": {
+ "tag": "standard_icon",
+ "token": "googledrive_outlined"
+ },
+ "padding": "12px 8px 12px 8px"
+ }
+ }
+ payload = {
+ "receive_id": normalized_user_id,
+ "msg_type": "interactive",
+ "content": json.dumps(content, ensure_ascii=False)
+ }
+ headers = {
+ "Authorization": f"Bearer {token}",
+ "Content-Type": "application/json"
+ }
+ response = requests.post(
+ "https://open.feishu.cn/open-apis/im/v1/messages",
+ params={"receive_id_type": "user_id"},
+ headers=headers,
+ json=payload,
+ timeout=30
+ )
+ try:
+ data = response.json()
+ except ValueError:
+ data = {"raw": response.text}
+ if not response.ok:
+ raise RuntimeError(f"lark send message http error: {data}")
+ if data.get("code") not in (0, None):
+ raise RuntimeError(f"lark send message failed: {data}")
+ message_id = data.get("data", {}).get("message_id")
+ if not message_id:
+ raise RuntimeError(f"lark send message missing message_id: {data}")
+ return message_id
+
+
+def send_feedback_card(
+ token: str,
+ receiver_ids: List[str],
+ user_id: str,
+ order_number: str,
+ change_time: str,
+ asset_list: object,
+ remark: object = ""
+) -> str:
+ if not receiver_ids:
+ raise ValueError("missing receiver_ids")
+ normalized_user_id = str(user_id).strip()
+ if not normalized_user_id:
+ raise ValueError("missing user_id")
+ normalized_order_number = str(order_number).strip()
+ if not normalized_order_number:
+ raise ValueError("missing order_number")
+ if not asset_list:
+ raise ValueError("missing asset_list")
+ normalized_time = str(change_time or "").strip()
+ if not normalized_time:
+ normalized_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ normalized_remark = str(remark or "").strip()
+
+ options = []
+ if isinstance(asset_list, list):
+ for item in asset_list:
+ if isinstance(item, dict):
+ for k, v in item.items():
+ options.append({
+ "text": {"tag": "plain_text", "content": str(k)},
+ "value": str(v)
+ })
+ elif isinstance(item, str):
+ options.append({
+ "text": {"tag": "plain_text", "content": item},
+ "value": item
+ })
+
+ content = {
+ "schema": "2.0",
+ "config": {
+ "update_multi": True,
+ "style": {
+ "text_size": {
+ "normal_v2": {
+ "default": "normal",
+ "pc": "normal",
+ "mobile": "heading"
+ }
+ }
+ }
+ },
+ "body": {
+ "direction": "vertical",
+ "elements": [
+ {
+ "tag": "form",
+ "elements": [
+ {
+ "tag": "markdown",
+ "content": "**有确认单收到问题反馈,请人工确认后进行处理。**",
+ "text_align": "left",
+ "text_size": "normal_v2",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "hr",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "markdown",
+ "content": f"**单号:** {normalized_order_number}",
+ "text_align": "left",
+ "text_size": "normal"
+ },
+ {
+ "tag": "markdown",
+ "content": f"**用户:** ",
+ "text_align": "left",
+ "text_size": "normal"
+ },
+ {
+ "tag": "markdown",
+ "content": f"**发生时间:** {normalized_time}",
+ "text_align": "left",
+ "text_size": "normal"
+ },
+ {
+ "tag": "markdown",
+ "content": "**请校正对应人员的变动项:**\n(没有变动则留空)",
+ "text_align": "left",
+ "text_size": "normal",
+ "margin": "0px 0px 8px 0px"
+ },
+ {
+ "tag": "multi_select_static",
+ "placeholder": {
+ "tag": "plain_text",
+ "content": "请选择资产变动项"
+ },
+ "options": options,
+ "type": "default",
+ "width": "fill",
+ "required": False,
+ "name": "input_assets",
+ "margin": "0px 0px 16px 0px",
+ "element_id": "cIiptD7Z4hCtAeR5Rb0b"
+ },
+ {
+ "tag": "hr",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "markdown",
+ "content": "**其他说明:**",
+ "text_align": "left",
+ "text_size": "normal_v2",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "input",
+ "placeholder": {
+ "tag": "plain_text",
+ "content": "请输入"
+ },
+ "default_value": normalized_remark,
+ "width": "fill",
+ "name": "input_remark",
+ "margin": "0px 0px 0px 0px"
+ },
+ {
+ "tag": "column_set",
+ "flex_mode": "flow",
+ "horizontal_spacing": "8px",
+ "horizontal_align": "right",
+ "columns": [
+ {
+ "tag": "column",
+ "width": "auto",
+ "elements": [
+ {
+ "tag": "button",
+ "text": {
+ "tag": "plain_text",
+ "content": "确认"
+ },
+ "type": "primary_filled",
+ "width": "default",
+ "size": "medium",
+ "behaviors": [
+ {
+ "type": "callback",
+ "value": {
+ "action": "card.action.trigger",
+ "order_number": normalized_order_number,
+ "user_id": normalized_user_id
+ }
+ }
+ ],
+ "form_action_type": "submit",
+ "name": "confirm_button",
+ "margin": "4px 0px 4px 0px"
+ }
+ ],
+ "vertical_spacing": "8px",
+ "horizontal_align": "left",
+ "vertical_align": "top"
+ },
+ {
+ "tag": "column",
+ "width": "auto",
+ "elements": [],
+ "vertical_spacing": "8px",
+ "horizontal_align": "left",
+ "vertical_align": "top"
+ }
+ ]
+ }
+ ],
+ "direction": "vertical",
+ "horizontal_align": "left",
+ "vertical_align": "top",
+ "padding": "12px 12px 12px 12px",
+ "margin": "0px 0px 0px 0px",
+ "name": "asset_confirmation_form"
+ },
+ {
+ "tag": "hr",
+ "margin": "0px 0px 0px 0px"
+ }
+ ]
+ },
+ "header": {
+ "title": {
+ "tag": "plain_text",
+ "content": "资产变动单"
+ },
+ "subtitle": {
+ "tag": "plain_text",
+ "content": ""
+ },
+ "text_tag_list": [
+ {
+ "tag": "text_tag",
+ "text": {
+ "tag": "plain_text",
+ "content": "待人工处理"
+ },
+ "color": "red"
+ }
+ ],
+ "template": "purple",
+ "icon": {
+ "tag": "standard_icon",
+ "token": "googledrive_outlined"
+ },
+ "padding": "12px 8px 12px 8px"
+ }
+ }
+
+ headers = {
+ "Authorization": f"Bearer {token}",
+ "Content-Type": "application/json"
+ }
+
+ message_ids = []
+ for receiver_id in receiver_ids:
+ normalized_receiver_id = str(receiver_id).strip()
+ if not normalized_receiver_id:
+ continue
+
+ payload = {
+ "receive_id": normalized_receiver_id,
+ "msg_type": "interactive",
+ "content": json.dumps(content, ensure_ascii=False)
+ }
+
+ try:
+ response = requests.post(
+ "https://open.feishu.cn/open-apis/im/v1/messages",
+ params={"receive_id_type": "user_id"},
+ headers=headers,
+ json=payload,
+ timeout=30
+ )
+ data = response.json()
+ if response.ok and data.get("code") in (0, None):
+ msg_id = data.get("data", {}).get("message_id")
+ if msg_id:
+ message_ids.append(msg_id)
+ except Exception:
+ pass
+
+ if not message_ids:
+ raise RuntimeError("failed to send message to any receiver")
+
+ return json.dumps(message_ids, ensure_ascii=False)
+
+
def send_card_message(token: str, receiver_id: str, person_id: str, image_key: str) -> str:
utc_now = datetime.now(timezone.utc)
cst_now = utc_now + timedelta(hours=8)
current_time = cst_now.strftime("%Y-%m-%d %H:%M:%S")
+
+ if person_id:
+ person_content = f""
+ else:
+ person_content = "未知用户"
+
content = {
"schema": "2.0",
"config": {
@@ -596,7 +1657,7 @@ def send_card_message(token: str, receiver_id: str, person_id: str, image_key: s
},
{
"tag": "markdown",
- "content": f"\n进入仓库",
+ "content": f"\n{person_content}进入仓库",
"i18n_content": {
"en_us": "Alert details\nMobile client crash rate at 5%"
},
@@ -765,7 +1826,7 @@ async def handle_call_tool(name: str, arguments: Dict[str, object], token: str)
if not isinstance(receiver_ids, list):
raise ValueError("receiver_ids must be a list")
result = send_card_messages(token, receiver_ids, person_id, image_key)
- elif name == "send_asset_confirmation_card":
+ elif name == "send_confirmation_card":
user_id = str(arguments.get("user_id", "")).strip()
order_number = str(arguments.get("order_number", "")).strip()
change_time = str(arguments.get("change_time", "")).strip()
@@ -775,7 +1836,9 @@ async def handle_call_tool(name: str, arguments: Dict[str, object], token: str)
raise ValueError("missing user_id")
if not order_number:
raise ValueError("missing order_number")
- result = send_asset_confirmation_card(
+ if not asset_list:
+ raise ValueError("missing asset_list")
+ result = send_confirmation_card(
token,
user_id,
order_number,
@@ -783,6 +1846,82 @@ async def handle_call_tool(name: str, arguments: Dict[str, object], token: str)
asset_list,
remark
)
+ elif name == "send_review_card":
+ user_id = str(arguments.get("user_id", "")).strip()
+ order_number = str(arguments.get("order_number", "")).strip()
+ change_time = str(arguments.get("change_time", "")).strip()
+ asset_list = arguments.get("asset_list")
+ remark = str(arguments.get("remark", "")).strip()
+ person_ids = arguments.get("person_ids")
+ if not user_id:
+ raise ValueError("missing user_id")
+ if not order_number:
+ raise ValueError("missing order_number")
+ if not asset_list:
+ raise ValueError("missing asset_list")
+ if not person_ids:
+ raise ValueError("missing person_ids")
+ result = send_review_card(
+ token,
+ user_id,
+ order_number,
+ change_time,
+ asset_list,
+ person_ids=person_ids,
+ remark=remark
+ )
+ elif name == "send_stranger_card":
+ user_id = str(arguments.get("user_id", "")).strip()
+ order_number = str(arguments.get("order_number", "")).strip()
+ change_time = str(arguments.get("change_time", "")).strip()
+ asset_list = arguments.get("asset_list")
+ face_cap = str(arguments.get("face_cap", "")).strip()
+ user_ids = arguments.get("user_ids")
+ remark = str(arguments.get("remark", "")).strip()
+ if not user_id:
+ raise ValueError("missing user_id")
+ if not order_number:
+ raise ValueError("missing order_number")
+ if not asset_list:
+ raise ValueError("missing asset_list")
+ if not face_cap:
+ raise ValueError("missing face_cap")
+ if not user_ids:
+ raise ValueError("missing user_ids")
+ result = send_stranger_card(
+ token,
+ user_id,
+ order_number,
+ change_time,
+ asset_list,
+ face_cap,
+ user_ids,
+ remark
+ )
+ elif name == "send_feedback_card":
+ user_id = str(arguments.get("user_id", "")).strip()
+ order_number = str(arguments.get("order_number", "")).strip()
+ change_time = str(arguments.get("change_time", "")).strip()
+ remark = str(arguments.get("remark", "")).strip()
+ asset_list = arguments.get("asset_list")
+ receiver_ids = arguments.get("receiver_ids")
+ if not receiver_ids:
+ raise ValueError("missing receiver_ids")
+ if not user_id:
+ raise ValueError("missing user_id")
+ if not order_number:
+ raise ValueError("missing order_number")
+ if not asset_list:
+ raise ValueError("missing asset_list")
+ result = send_feedback_card(
+ token,
+ receiver_ids,
+ user_id,
+ order_number,
+ change_time,
+ asset_list,
+ remark
+ )
else:
raise ValueError(f"unknown tool name: {name}")
return [TextContent(type="text", text=result)]
diff --git a/lzwcai_lark_mcp/pyproject.toml b/lzwcai_lark_mcp/pyproject.toml
index 472b035..2685f32 100644
--- a/lzwcai_lark_mcp/pyproject.toml
+++ b/lzwcai_lark_mcp/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "lzwcai-lark-mcp"
-version = "0.1.3"
+version = "0.1.10"
description = "Lark MCP server"
requires-python = ">=3.10"
dependencies = [