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 = [