- 新增钉钉工具调用审计日志功能 - 新增参数校验装饰器与基础工具集(文档、审批、多维表格) - 完善事件类型映射与会话ID生成逻辑 - 新增消息去重、访问策略匹配、配置校验与UI提示 - 新增酷应用卡片、目录管理、表情反应支持 - 优化消息发送逻辑,添加401重试机制 - 新增配置化的权限控制与流式输出支持
201 lines
7.0 KiB
Python
201 lines
7.0 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
import httpx
|
|
|
|
from ._validate import validate_params
|
|
|
|
if TYPE_CHECKING:
|
|
from yuxi.channels.adapters.dingding.token import DingDingTokenManager
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
DINGTALK_API_BASE = "https://api.dingtalk.com"
|
|
|
|
|
|
@validate_params
|
|
async def list_approval_templates(
|
|
token_manager: DingDingTokenManager,
|
|
*,
|
|
user_id: str = "",
|
|
page_size: int = 50,
|
|
page_number: int = 1,
|
|
http_client: httpx.AsyncClient | None = None,
|
|
) -> dict[str, Any]:
|
|
token = await token_manager.get_token()
|
|
url = f"{DINGTALK_API_BASE}/v1.0/workflow/processes"
|
|
headers = {"x-acs-dingtalk-access-token": token}
|
|
params: dict[str, Any] = {
|
|
"maxResults": min(page_size, 100),
|
|
"nextToken": str(page_number - 1),
|
|
}
|
|
if user_id:
|
|
params["userId"] = user_id
|
|
|
|
try:
|
|
if http_client is not None:
|
|
resp = await http_client.get(url, headers=headers, params=params)
|
|
else:
|
|
async with httpx.AsyncClient(timeout=httpx.Timeout(15)) as client:
|
|
resp = await client.get(url, headers=headers, params=params)
|
|
|
|
if resp.status_code != 200:
|
|
raise RuntimeError(f"获取审批模板列表失败: HTTP {resp.status_code}: {resp.text[:200]}")
|
|
|
|
data = resp.json()
|
|
items = data.get("items", data.get("processList", data.get("list", [])))
|
|
templates = [
|
|
{
|
|
"process_code": item.get("processCode", item.get("code", "")),
|
|
"name": item.get("name", item.get("processName", "")),
|
|
"description": item.get("description", ""),
|
|
"icon_url": item.get("iconUrl", ""),
|
|
"status": item.get("status", ""),
|
|
}
|
|
for item in items
|
|
]
|
|
return {
|
|
"templates": templates,
|
|
"total": data.get("totalCount", len(templates)),
|
|
"has_more": data.get("hasMore", False),
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"[DingDing Approval] list_approval_templates failed: {e}")
|
|
raise RuntimeError(f"获取审批模板列表失败: {e}") from e
|
|
|
|
|
|
@validate_params
|
|
async def create_approval_instance(
|
|
token_manager: DingDingTokenManager,
|
|
process_code: str,
|
|
originator_user_id: str,
|
|
form_component_values: list[dict[str, Any]],
|
|
*,
|
|
dept_id: str = "",
|
|
approvers: list[str] | None = None,
|
|
cc_list: list[str] | None = None,
|
|
http_client: httpx.AsyncClient | None = None,
|
|
) -> dict[str, Any]:
|
|
token = await token_manager.get_token()
|
|
url = f"{DINGTALK_API_BASE}/v1.0/workflow/processInstances"
|
|
headers = {
|
|
"x-acs-dingtalk-access-token": token,
|
|
"Content-Type": "application/json",
|
|
}
|
|
payload: dict[str, Any] = {
|
|
"processCode": process_code,
|
|
"originatorUserId": originator_user_id,
|
|
"formComponentValues": form_component_values,
|
|
}
|
|
if dept_id:
|
|
payload["deptId"] = dept_id
|
|
if approvers:
|
|
payload["approvers"] = approvers
|
|
if cc_list:
|
|
payload["ccList"] = cc_list
|
|
|
|
try:
|
|
if http_client is not None:
|
|
resp = await http_client.post(url, json=payload, headers=headers)
|
|
else:
|
|
async with httpx.AsyncClient(timeout=httpx.Timeout(15)) as client:
|
|
resp = await client.post(url, json=payload, headers=headers)
|
|
|
|
if resp.status_code != 200:
|
|
raise RuntimeError(f"发起审批实例失败: HTTP {resp.status_code}: {resp.text[:200]}")
|
|
|
|
data = resp.json()
|
|
return {
|
|
"instance_id": data.get("instanceId", data.get("processInstanceId", "")),
|
|
"process_code": process_code,
|
|
"originator_user_id": originator_user_id,
|
|
"status": data.get("status", "RUNNING"),
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"[DingDing Approval] create_approval_instance failed: {e}")
|
|
raise RuntimeError(f"发起审批实例失败: {e}") from e
|
|
|
|
|
|
@validate_params
|
|
async def get_approval_instance(
|
|
token_manager: DingDingTokenManager,
|
|
instance_id: str,
|
|
*,
|
|
http_client: httpx.AsyncClient | None = None,
|
|
) -> dict[str, Any]:
|
|
token = await token_manager.get_token()
|
|
url = f"{DINGTALK_API_BASE}/v1.0/workflow/processInstances/{instance_id}"
|
|
headers = {"x-acs-dingtalk-access-token": token}
|
|
|
|
try:
|
|
if http_client is not None:
|
|
resp = await http_client.get(url, headers=headers)
|
|
else:
|
|
async with httpx.AsyncClient(timeout=httpx.Timeout(15)) as client:
|
|
resp = await client.get(url, headers=headers)
|
|
|
|
if resp.status_code != 200:
|
|
raise RuntimeError(f"获取审批实例详情失败: HTTP {resp.status_code}: {resp.text[:200]}")
|
|
|
|
data = resp.json()
|
|
return {
|
|
"instance_id": data.get("instanceId", data.get("processInstanceId", instance_id)),
|
|
"process_code": data.get("processCode", ""),
|
|
"title": data.get("title", data.get("processTitle", "")),
|
|
"originator_user_id": data.get("originatorUserId", ""),
|
|
"status": data.get("status", ""),
|
|
"result": data.get("result", ""),
|
|
"form_values": data.get("formComponentValues", []),
|
|
"create_time": data.get("createTime", ""),
|
|
"finish_time": data.get("finishTime", ""),
|
|
"approvers": data.get("approvers", []),
|
|
"tasks": data.get("tasks", data.get("operationRecords", [])),
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"[DingDing Approval] get_approval_instance failed: {e}")
|
|
raise RuntimeError(f"获取审批实例详情失败: {e}") from e
|
|
|
|
|
|
@validate_params
|
|
async def cancel_approval_instance(
|
|
token_manager: DingDingTokenManager,
|
|
instance_id: str,
|
|
*,
|
|
operate_user_id: str = "",
|
|
remark: str = "",
|
|
http_client: httpx.AsyncClient | None = None,
|
|
) -> dict[str, Any]:
|
|
token = await token_manager.get_token()
|
|
url = f"{DINGTALK_API_BASE}/v1.0/workflow/processInstances/{instance_id}/terminate"
|
|
headers = {
|
|
"x-acs-dingtalk-access-token": token,
|
|
"Content-Type": "application/json",
|
|
}
|
|
payload: dict[str, Any] = {}
|
|
if operate_user_id:
|
|
payload["operateUserId"] = operate_user_id
|
|
if remark:
|
|
payload["remark"] = remark
|
|
|
|
try:
|
|
if http_client is not None:
|
|
resp = await http_client.put(url, json=payload, headers=headers)
|
|
else:
|
|
async with httpx.AsyncClient(timeout=httpx.Timeout(15)) as client:
|
|
resp = await client.put(url, json=payload, headers=headers)
|
|
|
|
if resp.status_code != 200:
|
|
raise RuntimeError(f"撤销审批实例失败: HTTP {resp.status_code}: {resp.text[:200]}")
|
|
|
|
data = resp.json()
|
|
return {
|
|
"instance_id": instance_id,
|
|
"canceled": True,
|
|
"result": data.get("result", True),
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"[DingDing Approval] cancel_approval_instance failed: {e}")
|
|
raise RuntimeError(f"撤销审批实例失败: {e}") from e
|