
Dingtalk Document
- 1.7k installs
- 97 repo stars
- Updated June 26, 2026
- breath57/dingtalk-skills
dingtalk-document manages DingTalk wikis and documents through dt_helper.sh with per-task config validation.
About
The dingtalk-document skill handles DingTalk wiki workspaces and document nodes with a strategy-first workflow before any API call. Core concepts include workspaces with workspaceId and rootNodeId, FILE or FOLDER nodes, docKey or dentryUuid identifiers for content APIs, and operatorId unionId requirements via scripts/dt_helper.sh. Each task validates only required config keys such as DINGTALK_APP_KEY, APP_SECRET, MY_USER_ID, and MY_OPERATOR_ID, collecting missing values once before execution. Multi-line commands must run from /tmp scripts because terminal tools truncate heredocs. references/api.md sections are grepped per operation for create, read, write, delete, and member management. Token cache uses --nocache on 401 responses. Credentials are never printed in full. Use when users mention DingTalk docs, knowledge bases, or dingtalk doc operations.
- Validates per-task DingTalk config before any wiki or document API call.
- Uses dt_helper.sh for token, unionId conversion, and cached credential access.
- Maps workspace, node, docKey, and operatorId concepts for read and write flows.
- Pulls API details from references/api.md via targeted grep sections.
- Runs multi-step shell via /tmp scripts to avoid terminal truncation issues.
Dingtalk Document by the numbers
- 1,654 all-time installs (skills.sh)
- +17 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #124 of 688 Office & Documents skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
dingtalk-document capabilities & compatibility
- Capabilities
- workspace listing · document crud · member permissions · credential helper
- Use cases
- documentation · orchestration
What dingtalk-document says it does
规则:未通过“本次任务配置校验”前,不得进入 API 调用步骤。
npx skills add https://github.com/breath57/dingtalk-skills --skill dingtalk-documentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.7k |
|---|---|
| repo stars | ★ 97 |
| Security audit | 2 / 3 scanners passed |
| Last updated | June 26, 2026 |
| Repository | breath57/dingtalk-skills ↗ |
How do I create, read, or update DingTalk knowledge base documents from an agent safely?
Operate DingTalk knowledge bases and documents: list workspaces, create files, read or write content, and manage members via dt_helper.sh and API references.
Who is it for?
Teams automating DingTalk document and knowledge base tasks via CLI helpers.
Skip if: Non-DingTalk chat or calendar workflows outside document APIs.
When should I use this skill?
User mentions DingTalk docs, knowledge base, dingtalk doc, or document member management.
What you get
Validated credentials plus executed wiki or document API operations with redacted secrets.
- api integration scripts
- wiki workspace list responses
- authenticated api call patterns
By the numbers
- Uses api.dingtalk.com v2.0 wiki workspace endpoints with default maxResults of 20 per page
- All documented interfaces marked as verified usable in skill readme
Files
钉钉文档技能
负责钉钉知识库和文档的所有操作。本文件为策略指南,仅包含决策逻辑和工作流程。完整 API 请求格式见文末「references/api.md 查阅索引」。
dt_helper.sh位于本SKILL.md同级目录的scripts/dt_helper.sh。
核心概念
- 知识库(Workspace):文档容器,有
workspaceId和rootNodeId - 节点(Node):文件或文件夹,
type为FILE或FOLDER - 文档标识(用于 `/v1.0/doc/suites/documents/{id}`):可用
docKey或dentryUuid - 创建文档响应会返回:
docKey、dentryUuid、nodeId - 其中
docKey/dentryUuid可用于读写正文;nodeId用于删除和文档管理类接口 wiki/nodes返回的nodeId实际上是dentryUuid,可直接用于正文读写- operatorId:所有接口必须的 unionId 参数,通过
bash scripts/dt_helper.sh --to-unionid自动转换
工作流程(每次执行前)
1. 先识别本次任务类型 → 例如:列知识库、读文档、写文档、创建文档、成员管理 2. 按本次任务校验所需配置 → 通过 bash scripts/dt_helper.sh --get KEY 读取;仅校验本任务必须项 3. 仅收集缺失配置 → 若缺少某项,一次性询问用户所有缺失值,用 bash scripts/dt_helper.sh --set KEY=VALUE 写入 4. 获取 Token / operatorId → 直接调用 bash scripts/dt_helper.sh,token 获取与缓存细节无需关心 5. 执行操作 → 凡是包含变量替换、管道或多行逻辑的命令,写入 /tmp/<task>.sh 再 bash /tmp/<task>.sh 执行。不要把多行命令直接粘到终端里(终端工具会截断),也不要用 <<'EOF' 语法(heredoc 在工具中同样会被截断导致变量丢失)
按任务校验配置(必须先做)
- 所有任务通用必需:
DINGTALK_APP_KEY、DINGTALK_APP_SECRET、DINGTALK_MY_USER_ID - 涉及任何文档/知识库 API 调用:必须有
DINGTALK_MY_OPERATOR_ID(若缺失,先用bash scripts/dt_helper.sh --to-unionid自动转换并写回) - 创建/读取/写入/删除/成员管理:除上述通用项外,无额外固定配置键;
workspaceId/nodeId/docKey属于任务参数,运行时从用户输入或 API 响应中获取
规则:未通过“本次任务配置校验”前,不得进入 API 调用步骤。
凭证禁止在输出中完整打印,确认时仅显示前 4 位 + ****所需配置
| 配置键 | 必填 | 说明 | 如何获取 |
|---|---|---|---|
DINGTALK_APP_KEY | ✅ | 应用 AppKey | 钉钉开放平台 → 应用管理 → 凭证信息 |
DINGTALK_APP_SECRET | ✅ | 应用 AppSecret | 同上 |
DINGTALK_MY_USER_ID | ✅ | 当前用户的企业员工 ID(userId) | 管理后台 → 通讯录 → 成员管理 → 点击姓名查看 |
DINGTALK_MY_OPERATOR_ID | ✅ | 当前用户的 unionId(operatorId) | 首次由 bash scripts/dt_helper.sh --to-unionid 自动转换并写入 |
身份标识说明
| 标识 | 说明 |
|---|---|
userId(= staffId) | 企业内部员工 ID,可通过管理后台 -> 通讯录 -> 成员管理 -> 点击姓名查看 |
unionId | 跨企业/跨应用唯一标识,可通过 bash scripts/dt_helper.sh --to-unionid <userid> 获取 |
执行脚本模板
#!/bin/bash
set -e
HELPER="./scripts/dt_helper.sh"
NEW_TOKEN=$(bash "$HELPER" --token)
OPERATOR_ID=$(bash "$HELPER" --get DINGTALK_MY_OPERATOR_ID)
# 在此追加具体 API 调用,例如查询知识库列表:
WORKSPACES=$(curl -s -X GET "https://api.dingtalk.com/v2.0/wiki/workspaces?operatorId=${OPERATOR_ID}&maxResults=20" \
-H "x-acs-dingtalk-access-token: $NEW_TOKEN")
echo "知识库列表: $WORKSPACES"Token 失效处理:dt_helper 仅按时间缓存,无法感知 token 被提前吊销。若 API 返回 401(token 无效/过期),用 --nocache 跳过缓存强制重新获取:```bash
NEW_TOKEN=$(bash "$HELPER" --token --nocache)
```
references/api.md 查阅索引
确定好要做什么之后,用以下命令从 references/api.md 中提取对应章节的完整 API 细节(请求格式、参数说明、返回值示例):
grep -A 30 "^## 1. 查询知识库列表" references/api.md
grep -A 10 "^## 2. 查询知识库信息" references/api.md
grep -A 35 "^## 3. 查询节点列表" references/api.md
grep -A 10 "^## 4. 查询单个节点" references/api.md
grep -A 15 "^## 5. 通过 URL 查询节点" references/api.md
grep -A 28 "^## 6. 创建文档" references/api.md
grep -A 10 "^## 7. 删除文档" references/api.md
grep -A 30 "^## 8. 读取文档内容" references/api.md
grep -A 15 "^## 9. 覆盖写入文档内容" references/api.md
grep -A 12 "^## 10. 追加文本到段落" references/api.md
grep -A 18 "^## 11. 添加文档成员" references/api.md
grep -A 12 "^## 12. 更新文档成员权限" references/api.md
grep -A 10 "^## 13. 移除文档成员" references/api.md
grep -A 10 "^## 错误码" references/api.md
grep -A 10 "^## 所需应用权限" references/api.mddingtalk-document API 参考
所有接口均已验证可用。
NEW_TOKEN= 新版 token(api.dingtalk.com用),获取方式bash scripts/dt_helper.sh --token
OPERATOR_ID= 用户 unionId,获取方式bash scripts/dt_helper.sh --get DINGTALK_MY_OPERATOR_ID
⚠️ 重要:所有接口均需传operatorId(unionId),缺少则返回MissingoperatorId错误。
---
1. 查询知识库列表
GET https://api.dingtalk.com/v2.0/wiki/workspaces?operatorId={OPERATOR_ID}&maxResults=20&nextToken=
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
operatorId | string | ✅ | 用户 unionId |
maxResults | int | ❌ | 每页数量,默认 20 |
nextToken | string | ❌ | 分页令牌,首次为空 |
响应:
{
"workspaces": [
{
"workspaceId": "QXvd5SLN2AxOQz0Z",
"name": "团队知识库",
"description": "...",
"rootNodeId": "P0MALyR8kl3qpB7qTkM1xn3mW3bzYmDO",
"type": "TEAM",
"url": "https://alidocs.dingtalk.com/i/spaces/.../overview",
"createTime": "2024-01-01T00:00Z",
"modifiedTime": "2024-06-01T00:00Z"
}
],
"nextToken": "..."
}翻页:nextToken 非空时传入下次请求继续获取。---
2. 查询知识库信息
GET https://api.dingtalk.com/v2.0/wiki/workspaces/{workspaceId}?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}响应:单个 workspace 对象(结构同列表项)
---
3. 查询节点列表
GET https://api.dingtalk.com/v2.0/wiki/nodes?parentNodeId={nodeId}&operatorId={OPERATOR_ID}&maxResults=50&nextToken=
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
parentNodeId | string | ✅ | 父节点 ID,传知识库的 rootNodeId 可列出顶层内容 |
operatorId | string | ✅ | 用户 unionId |
maxResults | int | ❌ | 每页数量,默认 20 |
nextToken | string | ❌ | 分页令牌 |
响应:
{
"nodes": [
{
"nodeId": "LeBq413JAw31yaz1fB0BBdLGWDOnGvpb",
"name": "使用文档.adoc",
"type": "FILE",
"category": "ALIDOC",
"extension": "adoc",
"workspaceId": "QXvd5SnBnzmZdZ0Z",
"url": "https://alidocs.dingtalk.com/i/nodes/...",
"createTime": "2026-03-04T16:58Z",
"modifiedTime": "2026-03-04T17:51Z"
}
],
"nextToken": "..."
}type:FILE(文档/文件)|FOLDER(文件夹)
---
4. 查询单个节点
GET https://api.dingtalk.com/v2.0/wiki/nodes/{nodeId}?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}响应:{ "node": { nodeId, name, type, category, workspaceId, url, ... } }
---
5. 通过 URL 查询节点
POST https://api.dingtalk.com/v2.0/wiki/nodes/queryByUrl?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}请求体:
{
"url": "https://alidocs.dingtalk.com/i/nodes/<nodeId>",
"operatorId": "{OPERATOR_ID}"
}响应:与 GET 单个节点相同的 node 结构。
---
6. 创建文档
POST https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}请求体:
{
"operatorId": "{OPERATOR_ID}",
"docType": "DOC",
"name": "文档标题"
}| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
operatorId | string | ✅ | 用户 unionId |
docType | string | ✅ | 固定填 "DOC"(ALIDOC 富文本格式) |
name | string | ✅ | 文档标题 |
响应:
{
"dentryUuid": "aaa",
"nodeId": "xxx",
"docKey": "yyy",
"workspaceId": "zzz",
"url": "https://..."
}重要:
-docKey/dentryUuid:用于/v1.0/doc/suites/documents/{id}内容读写
-nodeId:用于/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}删除/管理
---
7. 删除文档
DELETE https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}workspaceId和nodeId均使用创建文档响应中的值。成功返回200 {}。
---
8. 读取文档内容(Block 结构)
GET https://api.dingtalk.com/v1.0/doc/suites/documents/{docKey}/blocks?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}docKey 的来源:
- 通过 wiki nodes 接口查到的nodeId本质是dentryUuid,可直接用于正文读写
- 通过创建文档接口新建的文档:可使用响应中的docKey或dentryUuid
- 创建响应中的nodeId(通常出现在/docs/<nodeId>链接中)不能直接用于正文读写
响应:
{
"result": {
"data": [
{ "blockType": "heading", "heading": { "level": "heading-2", "text": "快速开始" }, "index": 0, "id": "xxx" },
{ "blockType": "paragraph", "paragraph": { "text": "正文内容..." }, "index": 1, "id": "yyy" },
{ "blockType": "table", "table": { "colSize": 2, "rowSize": 10 }, "index": 2, "id": "zzz" },
{ "blockType": "unknown", "index": 3, "id": "aaa", "unknown": {} }
]
},
"success": true
}blockType 枚举:heading、paragraph、unorderedList、orderedList、table、blockquote、unknown(代码块/图片等)
---
9. 覆盖写入文档内容
POST https://api.dingtalk.com/v1.0/doc/suites/documents/{docKey}/overwriteContent?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}⚠️operatorId必须同时作为 query param 和请求体字段传入,缺一会报Missingcontent错误。
请求体:
{
"operatorId": "{OPERATOR_ID}",
"content": "# 新标题\n\n新的正文内容,支持 Markdown 格式。",
"contentType": "markdown"
}⚠️ 此操作全量覆盖文档内容,不可撤销。
---
10. 追加文本到段落
POST https://api.dingtalk.com/v1.0/doc/suites/documents/{docKey}/blocks/{blockId}/paragraph/appendText
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}请求体:
{ "operatorId": "{OPERATOR_ID}", "text": "追加的文字" }---
11. 添加文档成员
POST https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}/members
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}请求体:
{
"operatorId": "{OPERATOR_ID}",
"members": [
{ "id": "<userId>", "roleType": "viewer" }
]
}| 参数 | 说明 |
|---|---|
id | 用户 userId(注意:这里用 userId,不是 unionId) |
roleType | viewer(只读) |
---
12. 更新文档成员权限
PUT https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}/members/{memberId}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}请求体:
{ "operatorId": "{OPERATOR_ID}", "roleType": "viewer" }---
13. 移除文档成员
DELETE https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}/members/{memberId}?operatorId={OPERATOR_ID}
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}---
14. 搜索文档(用于找回可读写 ID)
GET https://api.dingtalk.com/v1.0/doc/docs?operatorId={OPERATOR_ID}&workspaceId={workspaceId}&keyword={keyword}&maxResults=20&nextToken=
Header: x-acs-dingtalk-access-token: {NEW_TOKEN}说明:
- 需要权限:
Document.WorkspaceDocument.Read - 返回结果中的
docs[].nodeBO.nodeId即可用于/v1.0/doc/suites/documents/{id}的读写(该值是dentryUuid风格 ID)
示例响应(节选):
{
"docs": [
{
"nodeBO": {
"name": "测试创建.adoc",
"nodeId": "np9zOoBVBYwB2OZBIn4y0G1vW1DK0g6l",
"url": "https://alidocs.dingtalk.com/i/nodes/np9zOoBVBYwB2OZBIn4y0G1vW1DK0g6l"
},
"workspaceBO": {
"workspaceId": "QXvd5SLN2AxOQz0Z"
}
}
],
"hasMore": false
}---
错误码
| HTTP状态码 | 错误码 | 说明 | 处理建议 |
|---|---|---|---|
| 400 | MissingoperatorId | operatorId 未传 | 补充 operatorId(unionId) |
| 400 | paramError | 参数类型错误 | operatorId 必须是 unionId,不是 userId |
| 403 | Forbidden.AccessDenied.AccessTokenPermissionDenied | 应用缺少权限 | 错误中有 requiredScopes,开通对应权限 |
| 404 | InvalidAction.NotFound | 接口路径不存在 | 检查版本号(v1.0/v2.0)和路径 |
| 429 | — | QPS 限流 | 1 秒后重试 |
---
所需应用权限
| 功能 | 权限 scope |
|---|---|
| 查询知识库/节点 | Wiki.Node.Read |
| 读取文档正文 | Storage.File.Read |
| 写入文档正文 | Storage.File.Write |
| 创建/删除文档 | Storage.File.Write |
| 查询用户 unionId | Contact.User.Read |
#!/bin/bash
# =============================================================================
# dt_helper.sh — 钉钉开放平台辅助工具
# 路径: scripts/common/dt_helper.sh
# 用法: bash scripts/common/dt_helper.sh <命令> [参数]
# =============================================================================
set -e
CONFIG="${DINGTALK_CONFIG:-$HOME/.dingtalk-skills/config}"
# ─────────────────────────────────────────────────────────────────────────────
# 帮助信息
# ─────────────────────────────────────────────────────────────────────────────
show_help() {
cat <<'EOF'
钉钉开放平台辅助工具 (dt_helper.sh)
用法: bash scripts/common/dt_helper.sh <命令> [参数]
Token 管理(两种 token 互不兼容,按域名区分):
--token [--nocache] 获取新版 accessToken(用于 api.dingtalk.com 域名的所有接口)
适用:待办、文档、AI 表格等 api.dingtalk.com 域名下所有版本的接口
请求头:x-acs-dingtalk-access-token: <token>
有缓存且未过期则直接返回,否则自动刷新并缓存
--nocache:跳过缓存,强制重新获取(token 被提前吊销时使用)
--token-info 查看新版 token 缓存状态(是否有效、剩余有效秒数)
--clear-token 清除缓存的新版 token(下次 --token 时强制重新获取)
--old-token [--nocache]
获取旧版 access_token(用于 oapi.dingtalk.com 域名的所有接口)
适用:群消息/工作通知/userId↔unionId 转换等 oapi.dingtalk.com 接口
不适用:api.dingtalk.com 接口(如待办、文档、AI表格)
⚠️ 新旧两种 token 互不兼容,混用会导致 401/403
--nocache:跳过缓存,强制重新获取(token 被提前吊销时使用)
身份转换:
--to-unionid [userId] 将 userId 转换为 unionId
不传参数:转换配置中的 DINGTALK_MY_USER_ID(操作者自身),
结果首次自动写入 DINGTALK_MY_OPERATOR_ID
传入参数:动态转换指定 userId,仅返回结果,不写入配置
--to-userid [unionId] 将 unionId 反向转换为 userId(需传入参数)
配置管理:
--config 查看 ~/.dingtalk-skills/config 中的所有配置项(敏感项脱敏显示)
--get KEY [KEY...] 获取一个或多个配置项的值(敏感项脱敏显示)
--set KEY=VALUE 将配置项持久化写入配置文件(已存在则更新,不存在则追加,目录自动创建)
帮助:
--help, -h 显示此帮助信息
环境变量:
DINGTALK_CONFIG 覆盖默认配置文件路径(默认 ~/.dingtalk-skills/config)
配置文件:
~/.dingtalk-skills/config key=value 格式,存储以下键:
DINGTALK_APP_KEY 应用 Client ID(AppKey)
DINGTALK_APP_SECRET 应用 Client Secret(AppSecret)
DINGTALK_MY_USER_ID 企业员工 ID(userId,管理后台通讯录可查)
DINGTALK_MY_OPERATOR_ID 操作者 unionId(由 --to-unionid 自动生成)
DINGTALK_ACCESS_TOKEN 新版 token 缓存
DINGTALK_TOKEN_EXPIRY 新版 token 过期时间戳(Unix 秒)
DINGTALK_OLD_TOKEN 旧版 token 缓存
DINGTALK_OLD_TOKEN_EXPIRY 旧版 token 过期时间戳(Unix 秒)
EOF
}
# ─────────────────────────────────────────────────────────────────────────────
# 工具函数
# ─────────────────────────────────────────────────────────────────────────────
# 从配置文件读取指定键的值
cfg_get() {
local key="$1"
grep "^${key}=" "$CONFIG" 2>/dev/null | head -1 | cut -d= -f2-
}
# 写入或更新配置文件中的键值
cfg_set() {
local key="$1"
local value="$2"
mkdir -p "$(dirname "$CONFIG")"
touch "$CONFIG"
if grep -q "^${key}=" "$CONFIG" 2>/dev/null; then
sed -i "s|^${key}=.*|${key}=${value}|" "$CONFIG"
else
echo "${key}=${value}" >> "$CONFIG"
fi
}
# 从配置文件删除指定键
cfg_del() {
local key="$1"
sed -i "/^${key}=/d" "$CONFIG" 2>/dev/null || true
}
# 确保必须的配置项存在,否则报错退出
require_cfg() {
local key="$1"
local val
val=$(cfg_get "$key")
if [ -z "$val" ]; then
echo "❌ 缺少配置项 ${key},请先运行: bash scripts/common/dt_helper.sh --set ${key}=<值>" >&2
exit 1
fi
echo "$val"
}
# ─────────────────────────────────────────────────────────────────────────────
# Token 管理
# ─────────────────────────────────────────────────────────────────────────────
cmd_token() {
local force="${1:-}" app_key app_secret cached expiry now resp token expire_in
app_key=$(require_cfg DINGTALK_APP_KEY)
app_secret=$(require_cfg DINGTALK_APP_SECRET)
now=$(date +%s)
if [ "$force" != "--nocache" ]; then
cached=$(cfg_get DINGTALK_ACCESS_TOKEN)
expiry=$(cfg_get DINGTALK_TOKEN_EXPIRY)
if [ -n "$cached" ] && [ -n "$expiry" ] && [ "$now" -lt "$expiry" ]; then
echo "$cached"
return 0
fi
fi
# 过期或无缓存,重新获取
resp=$(curl -s -X POST "https://api.dingtalk.com/v1.0/oauth2/accessToken" \
-H "Content-Type: application/json" \
-d "{\"appKey\":\"${app_key}\",\"appSecret\":\"${app_secret}\"}")
token=$(echo "$resp" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
expire_in=$(echo "$resp" | grep -o '"expireIn":[0-9]*' | cut -d: -f2)
if [ -z "$token" ]; then
echo "❌ 获取 token 失败: $resp" >&2
exit 1
fi
cfg_set DINGTALK_ACCESS_TOKEN "$token"
cfg_set DINGTALK_TOKEN_EXPIRY "$((now + expire_in - 200))"
echo "$token"
}
cmd_token_info() {
local cached expiry now remaining
cached=$(cfg_get DINGTALK_ACCESS_TOKEN)
expiry=$(cfg_get DINGTALK_TOKEN_EXPIRY)
now=$(date +%s)
if [ -z "$cached" ]; then
echo "状态: 无缓存(从未获取或已清除)"
return 0
fi
if [ -z "$expiry" ] || [ "$now" -ge "$expiry" ]; then
echo "状态: 已过期"
echo "Token: ${cached:0:20}..."
else
remaining=$((expiry - now))
echo "状态: 有效"
echo "Token: ${cached:0:20}..."
echo "剩余: ${remaining} 秒(约 $((remaining / 60)) 分钟)"
fi
}
cmd_clear_token() {
cfg_del DINGTALK_ACCESS_TOKEN
cfg_del DINGTALK_TOKEN_EXPIRY
echo "✅ 新版 Token 缓存已清除"
}
cmd_old_token() {
# 旧版 access_token,用于所有 oapi.dingtalk.com 接口:
# - 群消息、工作通知、互动卡片(dingtalk-message)
# - userId ↔ unionId 转换
# ⚠️ 不可用于 api.dingtalk.com 接口(待办、文档、AI表格等)
local force="${1:-}" app_key app_secret resp token cached expiry now
app_key=$(require_cfg DINGTALK_APP_KEY)
app_secret=$(require_cfg DINGTALK_APP_SECRET)
now=$(date +%s)
if [ "$force" != "--nocache" ]; then
cached=$(cfg_get DINGTALK_OLD_TOKEN)
expiry=$(cfg_get DINGTALK_OLD_TOKEN_EXPIRY)
if [ -n "$cached" ] && [ -n "$expiry" ] && [ "$now" -lt "$expiry" ]; then
echo "$cached"
return 0
fi
fi
resp=$(curl -s "https://oapi.dingtalk.com/gettoken?appkey=${app_key}&appsecret=${app_secret}")
token=$(echo "$resp" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
expires_in=$(echo "$resp" | grep -o '"expires_in":[0-9]*' | cut -d: -f2)
if [ -z "$token" ]; then
echo "❌ 获取旧版 token 失败: $resp" >&2
exit 1
fi
cfg_set DINGTALK_OLD_TOKEN "$token"
cfg_set DINGTALK_OLD_TOKEN_EXPIRY "$((now + expires_in - 200))"
echo "$token"
}
# ─────────────────────────────────────────────────────────────────────────────
# 身份转换
# ─────────────────────────────────────────────────────────────────────────────
cmd_to_unionid() {
local user_id="$1"
local is_self=false
local old_token resp union_id
# 未传参 → 使用配置中的操作者自身 userId,转换结果写入配置
if [ -z "$user_id" ]; then
user_id=$(require_cfg DINGTALK_MY_USER_ID)
is_self=true
fi
old_token=$(cmd_old_token)
resp=$(curl -s -X POST \
"https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${old_token}" \
-H "Content-Type: application/json" \
-d "{\"userid\":\"${user_id}\"}")
# 注意:使用无下划线的 unionid 字段(有下划线的 union_id 可能为空)
union_id=$(echo "$resp" | grep -o '"unionid":"[^"]*"' | head -1 | cut -d'"' -f4)
if [ -z "$union_id" ]; then
echo "❌ userId→unionId 转换失败: $resp" >&2
exit 1
fi
# 仅当转换的是操作者自身时,才写入配置(动态转换他人 userId 不写入)
if "$is_self" && [ -z "$(cfg_get DINGTALK_MY_OPERATOR_ID)" ]; then
cfg_set DINGTALK_MY_OPERATOR_ID "$union_id"
echo "✅ 自身 unionId 已写入配置 DINGTALK_MY_OPERATOR_ID" >&2
fi
echo "$union_id"
}
cmd_to_userid() {
local union_id="$1"
local old_token resp user_id
if [ -z "$union_id" ]; then
echo "❌ 请提供 unionId 参数" >&2
exit 1
fi
old_token=$(cmd_old_token)
resp=$(curl -s -X POST \
"https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=${old_token}" \
-H "Content-Type: application/json" \
-d "{\"unionid\":\"${union_id}\"}")
user_id=$(echo "$resp" | grep -o '"userid":"[^"]*"' | head -1 | cut -d'"' -f4)
if [ -z "$user_id" ]; then
echo "❌ unionId→userId 转换失败: $resp" >&2
exit 1
fi
echo "$user_id"
}
# ─────────────────────────────────────────────────────────────────────────────
# 配置管理
# ─────────────────────────────────────────────────────────────────────────────
cmd_config() {
if [ ! -f "$CONFIG" ]; then
echo "配置文件不存在: $CONFIG"
echo "使用 --set KEY=VALUE 写入配置项"
return 0
fi
echo "配置文件: $CONFIG"
echo "─────────────────────────────────"
# 脱敏显示 SECRET 和 TOKEN
while IFS= read -r line; do
key="${line%%=*}"
val="${line#*=}"
case "$key" in
DINGTALK_APP_SECRET|DINGTALK_ACCESS_TOKEN|DINGTALK_OLD_TOKEN)
echo "${key}=${val:0:6}***(已脱敏)"
;;
*)
echo "$line"
;;
esac
done < "$CONFIG"
}
cmd_get() {
if [ $# -eq 0 ]; then
echo "❌ 请提供至少一个键名,用法: --get KEY [KEY2 ...]" >&2
exit 1
fi
for key in "$@"; do
val=$(cfg_get "$key")
if [ -z "$val" ]; then
echo "${key}=(未设置)"
else
case "$key" in
DINGTALK_APP_SECRET|DINGTALK_ACCESS_TOKEN|DINGTALK_OLD_TOKEN)
echo "${key}=${val:0:6}***(脱敏)"
;;
*)
echo "${key}=${val}"
;;
esac
fi
done
}
cmd_set() {
local kv="$1"
if [ -z "$kv" ] || [[ "$kv" != *"="* ]]; then
echo "❌ 格式错误,用法: --set KEY=VALUE" >&2
exit 1
fi
local key="${kv%%=*}"
local value="${kv#*=}"
cfg_set "$key" "$value"
echo "✅ 已设置 ${key}"
}
# ─────────────────────────────────────────────────────────────────────────────
# 入口:解析命令
# ─────────────────────────────────────────────────────────────────────────────
CMD="${1:-}"
case "$CMD" in
--help|-h|"")
show_help
;;
--token)
cmd_token "${2:-}"
;;
--token-info)
cmd_token_info
;;
--clear-token)
cmd_clear_token
;;
--old-token)
cmd_old_token "${2:-}"
;;
--to-unionid)
cmd_to_unionid "${2:-}"
;;
--to-userid)
cmd_to_userid "${2:-}"
;;
--config)
cmd_config
;;
--get)
shift
cmd_get "$@"
;;
--set)
cmd_set "${2:-}"
;;
*)
echo "❌ 未知命令: $CMD" >&2
echo "运行 --help 查看用法" >&2
exit 1
;;
esac
Related skills
How it compares
Use for DingTalk wiki and document APIs; use other enterprise doc skills for Notion, Confluence, or Google Workspace integrations.
FAQ
What is operatorId?
The unionId required on document APIs, obtained via dt_helper.sh --to-unionid.
Can API run before config validation?
No. Complete per-task config checks before any document API call.
How are API details loaded?
Grep the matching section from references/api.md for the requested operation.
Is Dingtalk Document safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.