ForcePilot/backend/package/yuxi/channels/adapters/dingding/tools/doc.py
Kris 9c01d480cc feat(dingding): 完整实现钉钉适配器功能模块
- 新增钉钉工具调用审计日志功能
- 新增参数校验装饰器与基础工具集(文档、审批、多维表格)
- 完善事件类型映射与会话ID生成逻辑
- 新增消息去重、访问策略匹配、配置校验与UI提示
- 新增酷应用卡片、目录管理、表情反应支持
- 优化消息发送逻辑,添加401重试机制
- 新增配置化的权限控制与流式输出支持
2026-05-13 16:06:10 +08:00

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