- 新增钉钉工具调用审计日志功能 - 新增参数校验装饰器与基础工具集(文档、审批、多维表格) - 完善事件类型映射与会话ID生成逻辑 - 新增消息去重、访问策略匹配、配置校验与UI提示 - 新增酷应用卡片、目录管理、表情反应支持 - 优化消息发送逻辑,添加401重试机制 - 新增配置化的权限控制与流式输出支持
161 lines
5.2 KiB
Python
161 lines
5.2 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 create_doc(
|
|
token_manager: DingDingTokenManager,
|
|
title: str,
|
|
*,
|
|
content: str = "",
|
|
folder_id: str = "",
|
|
http_client: httpx.AsyncClient | None = None,
|
|
) -> dict[str, Any]:
|
|
token = await token_manager.get_token()
|
|
url = f"{DINGTALK_API_BASE}/v1.0/doc/word/create"
|
|
headers = {
|
|
"x-acs-dingtalk-access-token": token,
|
|
"Content-Type": "application/json",
|
|
}
|
|
payload: dict[str, Any] = {"title": title}
|
|
if content:
|
|
payload["content"] = content
|
|
if folder_id:
|
|
payload["folderId"] = folder_id
|
|
|
|
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 {
|
|
"document_id": data.get("docId", data.get("documentId", "")),
|
|
"title": data.get("title", title),
|
|
"url": data.get("url", data.get("docUrl", "")),
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"[DingDing Doc] create_doc failed: {e}")
|
|
raise RuntimeError(f"创建文档失败: {e}") from e
|
|
|
|
|
|
@validate_params
|
|
async def get_doc_content(
|
|
token_manager: DingDingTokenManager,
|
|
document_id: str,
|
|
*,
|
|
http_client: httpx.AsyncClient | None = None,
|
|
) -> dict[str, Any]:
|
|
token = await token_manager.get_token()
|
|
url = f"{DINGTALK_API_BASE}/v1.0/doc/word/{document_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 {
|
|
"document_id": data.get("docId", data.get("documentId", document_id)),
|
|
"title": data.get("title", ""),
|
|
"content": data.get("content", data.get("body", "")),
|
|
"blocks": data.get("blocks", []),
|
|
"create_time": data.get("createTime", ""),
|
|
"update_time": data.get("updateTime", ""),
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"[DingDing Doc] get_doc_content failed: {e}")
|
|
raise RuntimeError(f"获取文档内容失败: {e}") from e
|
|
|
|
|
|
@validate_params
|
|
async def append_doc_block(
|
|
token_manager: DingDingTokenManager,
|
|
document_id: str,
|
|
content: str,
|
|
*,
|
|
block_type: str = "text",
|
|
http_client: httpx.AsyncClient | None = None,
|
|
) -> dict[str, Any]:
|
|
token = await token_manager.get_token()
|
|
url = f"{DINGTALK_API_BASE}/v1.0/doc/word/{document_id}/blocks"
|
|
headers = {
|
|
"x-acs-dingtalk-access-token": token,
|
|
"Content-Type": "application/json",
|
|
}
|
|
payload: dict[str, Any] = {
|
|
"blockType": block_type,
|
|
"content": content,
|
|
}
|
|
|
|
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 {
|
|
"document_id": document_id,
|
|
"block_id": data.get("blockId", ""),
|
|
"success": True,
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"[DingDing Doc] append_doc_block failed: {e}")
|
|
raise RuntimeError(f"追加文档块失败: {e}") from e
|
|
|
|
|
|
@validate_params
|
|
async def delete_doc(
|
|
token_manager: DingDingTokenManager,
|
|
document_id: str,
|
|
*,
|
|
http_client: httpx.AsyncClient | None = None,
|
|
) -> dict[str, Any]:
|
|
token = await token_manager.get_token()
|
|
url = f"{DINGTALK_API_BASE}/v1.0/doc/word/{document_id}"
|
|
headers = {"x-acs-dingtalk-access-token": token}
|
|
|
|
try:
|
|
if http_client is not None:
|
|
resp = await http_client.delete(url, headers=headers)
|
|
else:
|
|
async with httpx.AsyncClient(timeout=httpx.Timeout(15)) as client:
|
|
resp = await client.delete(url, headers=headers)
|
|
|
|
if resp.status_code != 200:
|
|
raise RuntimeError(f"删除文档失败: HTTP {resp.status_code}: {resp.text[:200]}")
|
|
|
|
return {"document_id": document_id, "deleted": True}
|
|
except Exception as e:
|
|
logger.error(f"[DingDing Doc] delete_doc failed: {e}")
|
|
raise RuntimeError(f"删除文档失败: {e}") from e
|