dingtalk-todo
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese钉钉待办技能
DingTalk Todo Skill
负责钉钉待办(Todo)的所有操作。本文件为策略指南,仅包含决策逻辑和工作流程。完整 API 请求格式见文末「references/api.md 查阅索引」。
Responsible for all operations of DingTalk Todo. This document is a Strategy Guide that only includes decision logic and workflows. Complete API request formats can be found in the "references/api.md Index" at the end of the document.
工作流程(每次执行前)
Workflow (Before Each Execution)
- 读取配置 → 用一条 命令一次性读取配置文件
grep -E, 所有所需配置键值(配置文件跨会话保留,无需重复询问)~/.dingtalk-skills/config - 仅收集缺失配置 → 若配置文件不存在或缺少某项,一次性询问用户所有缺失的值,不要逐条问
- 持久化 → 将收集到的值写入 文件,后续无需再问
~/.dingtalk-skills/config - 获取/复用 Token → 有效期内复用缓存(缓存 7000 秒,约 2 小时),避免重复请求;遇 401 重新获取
- 执行操作 → 凡是包含变量替换、管道或多行逻辑的命令,再
/tmp/<task>.sh执行。不要把多行命令直接粘到终端里(终端工具会截断),也不要用bash /tmp/<task>.sh语法(heredoc 在工具中同样会被截断导致变量丢失)<<'EOF'
凭证禁止在输出中完整打印,确认时仅显示前 4 位 +****
- Read Configuration → Use a single command to read all required configuration key-value pairs from the configuration file
grep -Eat once (the configuration file is retained across sessions, no need to ask repeatedly)~/.dingtalk-skills/config - Collect Missing Configurations Only → If the configuration file does not exist or is missing certain items, ask the user all missing values at once, do not ask one by one
- Persistence → Write the collected values to the file, no need to ask again in subsequent uses
~/.dingtalk-skills/config - Retrieve/Reuse Token → Reuse the cache within its validity period (cached for 7000 seconds, approximately 2 hours) to avoid repeated requests; retrieve a new token if a 401 error is encountered
- Execute Operation → For any command involving variable substitution, pipes, or multi-line logic, write it to first and then execute it with
/tmp/<task>.sh. Do not directly paste multi-line commands into the terminal (terminal tools will truncate them), and do not use thebash /tmp/<task>.shsyntax (heredoc will also be truncated in tools leading to variable loss)<<'EOF'
Credentials are prohibited from being printed in full in the output; only display the first 4 digits +when confirming****
所需配置
Required Configurations
| 配置键 | 说明 | 如何获取 |
|---|---|---|
| 应用 AppKey | 钉钉开放平台 → 应用管理 → 凭证信息 |
| 应用 AppSecret | 同上 |
| 当前用户的企业员工 ID(userId) | 管理后台 → 通讯录 → 成员管理 → 点击姓名查看(不是手机号、不是 unionId) |
| 当前用户的 unionId | 首次由脚本自动通过 userId 转换获取并写入 |
| Configuration Key | Description | How to Obtain |
|---|---|---|
| App Key | DingTalk Open Platform → Application Management → Credential Information |
| App Secret | Same as above |
| Current user's enterprise employee ID (userId) | Admin Backend → Address Book → Member Management → Click on the name to view (not phone number, not unionId) |
| Current user's unionId | Automatically obtained and written by the script via userId conversion for the first time |
身份标识说明
Identity Identification Description
钉钉有两种用户 ID,不同 API 使用不同的 ID:
| 标识 | 说明 | 如何获取 |
|---|---|---|
| 企业内部员工 ID,最容易获取 | 管理后台 → 通讯录 → 成员管理 → 点击姓名查看;或调用手机号查询 API |
| 跨企业/跨应用唯一 | 通过 userId 调用 API 转换获取 |
- 待办 API 的路径参数 和查询参数
{unionId}均使用 unionIdoperatorId - executorIds / participantIds(指派同事)也使用 unionId
- 因此配置中优先收集 (用户容易拿到),由脚本自动转换为
userIdunionId
DingTalk has two types of user IDs, and different APIs use different IDs:
| Identifier | Description | How to Obtain |
|---|---|---|
| Internal enterprise employee ID, easiest to obtain | Admin Backend → Address Book → Member Management → Click on the name to view; or call the phone number query API |
| Unique across enterprises/applications | Obtained by calling the API to convert from userId |
- The path parameter and query parameter
{unionId}of the Todo API both use unionIdoperatorId - executorIds / participantIds (assigning to colleagues) also use unionId
- Therefore, prioritize collecting (easy for users to obtain) in the configuration, and the script will automatically convert it to
userIdunionId
userId → unionId 转换
userId → unionId Conversion
需要旧版 access_token(与新版不同):
bash
undefinedRequires the old version of access_token (different from the new version):
bash
undefined1. 获取旧版 token
1. 获取旧版 token
OLD_TOKEN=$(curl -s "https://oapi.dingtalk.com/gettoken?appkey=${APP_KEY}&appsecret=${APP_SECRET}" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
OLD_TOKEN=$(curl -s "https://oapi.dingtalk.com/gettoken?appkey=${APP_KEY}&appsecret=${APP_SECRET}" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
2. userId → unionId
2. userId → unionId
UNION_ID=$(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}"}" | grep -o '"unionid":"[^"]*"' | cut -d'"' -f4)
-H 'Content-Type: application/json'
-d "{"userid":"${USER_ID}"}" | grep -o '"unionid":"[^"]*"' | cut -d'"' -f4)
UNION_ID=$(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}"}" | grep -o '"unionid":"[^"]*"' | cut -d'"' -f4)
-H 'Content-Type: application/json'
-d "{"userid":"${USER_ID}"}" | grep -o '"unionid":"[^"]*"' | cut -d'"' -f4)
3. 写入配置文件
3. 写入配置文件
echo "DINGTALK_MY_OPERATOR_ID=$UNION_ID" >> ~/.dingtalk-skills/config
> ⚠️ 注意:返回体中 `result.unionid`(无下划线)有值,`result.union_id`(有下划线)可能为空。echo "DINGTALK_MY_OPERATOR_ID=$UNION_ID" >> ~/.dingtalk-skills/config
> ⚠️ Note: `result.unionid` (without underscore) has a value in the response body, while `result.union_id` (with underscore) may be empty.给同事创建待办时
When Creating a Todo for a Colleague
如果用户要给同事创建待办(指定 executorIds),需要同事的 unionId。向用户询问同事的 userId(管理后台可查),然后用上述方法转换。
If the user wants to create a todo for a colleague (specify executorIds), the colleague's unionId is required. Ask the user for the colleague's userId (available in the admin backend), then convert it using the method above.
执行脚本模板
Execution Script Template
bash
#!/bin/bash
set -e
CONFIG=~/.dingtalk-skills/config
APP_KEY=$(grep '^DINGTALK_APP_KEY=' "$CONFIG" | cut -d= -f2-)
APP_SECRET=$(grep '^DINGTALK_APP_SECRET=' "$CONFIG" | cut -d= -f2-)
USER_ID=$(grep '^DINGTALK_MY_USER_ID=' "$CONFIG" | cut -d= -f2-)bash
#!/bin/bash
set -e
CONFIG=~/.dingtalk-skills/config
APP_KEY=$(grep '^DINGTALK_APP_KEY=' "$CONFIG" | cut -d= -f2-)
APP_SECRET=$(grep '^DINGTALK_APP_SECRET=' "$CONFIG" | cut -d= -f2-)
USER_ID=$(grep '^DINGTALK_MY_USER_ID=' "$CONFIG" | cut -d= -f2-)新版 Token 缓存(用于待办 API)
新版 Token 缓存(用于待办 API)
CACHED_TOKEN=$(grep '^DINGTALK_ACCESS_TOKEN=' "$CONFIG" 2>/dev/null | cut -d= -f2-)
TOKEN_EXPIRY=$(grep '^DINGTALK_TOKEN_EXPIRY=' "$CONFIG" 2>/dev/null | cut -d= -f2-)
NOW=$(date +%s)
if [ -n "$CACHED_TOKEN" ] && [ -n "$TOKEN_EXPIRY" ] && [ "$NOW" -lt "$TOKEN_EXPIRY" ]; then
TOKEN=$CACHED_TOKEN
else
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) sed -i '/^DINGTALK_ACCESS_TOKEN=/d;/^DINGTALK_TOKEN_EXPIRY=/d' "$CONFIG" echo "DINGTALK_ACCESS_TOKEN=$TOKEN" >> "$CONFIG" echo "DINGTALK_TOKEN_EXPIRY=$((NOW + 7000))" >> "$CONFIG" fi
-H 'Content-Type: application/json'
-d "{"appKey":"$APP_KEY","appSecret":"$APP_SECRET"}") TOKEN=$(echo "$RESP" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4) sed -i '/^DINGTALK_ACCESS_TOKEN=/d;/^DINGTALK_TOKEN_EXPIRY=/d' "$CONFIG" echo "DINGTALK_ACCESS_TOKEN=$TOKEN" >> "$CONFIG" echo "DINGTALK_TOKEN_EXPIRY=$((NOW + 7000))" >> "$CONFIG" fi
CACHED_TOKEN=$(grep '^DINGTALK_ACCESS_TOKEN=' "$CONFIG" 2>/dev/null | cut -d= -f2-)
TOKEN_EXPIRY=$(grep '^DINGTALK_TOKEN_EXPIRY=' "$CONFIG" 2>/dev/null | cut -d= -f2-)
NOW=$(date +%s)
if [ -n "$CACHED_TOKEN" ] && [ -n "$TOKEN_EXPIRY" ] && [ "$NOW" -lt "$TOKEN_EXPIRY" ]; then
TOKEN=$CACHED_TOKEN
else
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) sed -i '/^DINGTALK_ACCESS_TOKEN=/d;/^DINGTALK_TOKEN_EXPIRY=/d' "$CONFIG" echo "DINGTALK_ACCESS_TOKEN=$TOKEN" >> "$CONFIG" echo "DINGTALK_TOKEN_EXPIRY=$((NOW + 7000))" >> "$CONFIG" fi
-H 'Content-Type: application/json'
-d "{"appKey":"$APP_KEY","appSecret":"$APP_SECRET"}") TOKEN=$(echo "$RESP" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4) sed -i '/^DINGTALK_ACCESS_TOKEN=/d;/^DINGTALK_TOKEN_EXPIRY=/d' "$CONFIG" echo "DINGTALK_ACCESS_TOKEN=$TOKEN" >> "$CONFIG" echo "DINGTALK_TOKEN_EXPIRY=$((NOW + 7000))" >> "$CONFIG" fi
unionId:优先从配置读取,未存储时自动从 userId 转换并写入
unionId:优先从配置读取,未存储时自动从 userId 转换并写入
UNION_ID=$(grep '^DINGTALK_MY_OPERATOR_ID=' "$CONFIG" 2>/dev/null | cut -d= -f2-)
if [ -z "$UNION_ID" ]; then
OLD_TOKEN=$(curl -s "https://oapi.dingtalk.com/gettoken?appkey=${APP_KEY}&appsecret=${APP_SECRET}" | grep -o '"access_token":"[^"]"' | cut -d'"' -f4)
UNION_ID=$(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}"}" | grep -o '"unionid":"[^"]"' | cut -d'"' -f4) echo "DINGTALK_MY_OPERATOR_ID=$UNION_ID" >> "$CONFIG" fi
-H 'Content-Type: application/json'
-d "{"userid":"${USER_ID}"}" | grep -o '"unionid":"[^"]"' | cut -d'"' -f4) echo "DINGTALK_MY_OPERATOR_ID=$UNION_ID" >> "$CONFIG" fi
UNION_ID=$(grep '^DINGTALK_MY_OPERATOR_ID=' "$CONFIG" 2>/dev/null | cut -d= -f2-)
if [ -z "$UNION_ID" ]; then
OLD_TOKEN=$(curl -s "https://oapi.dingtalk.com/gettoken?appkey=${APP_KEY}&appsecret=${APP_SECRET}" | grep -o '"access_token":"[^"]"' | cut -d'"' -f4)
UNION_ID=$(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}"}" | grep -o '"unionid":"[^"]"' | cut -d'"' -f4) echo "DINGTALK_MY_OPERATOR_ID=$UNION_ID" >> "$CONFIG" fi
-H 'Content-Type: application/json'
-d "{"userid":"${USER_ID}"}" | grep -o '"unionid":"[^"]"' | cut -d'"' -f4) echo "DINGTALK_MY_OPERATOR_ID=$UNION_ID" >> "$CONFIG" fi
在此追加具体 API 调用,例如创建待办:
在此追加具体 API 调用,例如创建待办:
RESULT=$(curl -s -X POST
"https://api.dingtalk.com/v1.0/todo/users/${UNION_ID}/tasks?operatorId=${UNION_ID}"
-H "x-acs-dingtalk-access-token: $TOKEN"
-H 'Content-Type: application/json'
-d "{"subject":"今天完成需求评审"}") echo "$RESULT" TASK_ID=$(echo "$RESULT" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) echo "创建成功,taskId=$TASK_ID"
"https://api.dingtalk.com/v1.0/todo/users/${UNION_ID}/tasks?operatorId=${UNION_ID}"
-H "x-acs-dingtalk-access-token: $TOKEN"
-H 'Content-Type: application/json'
-d "{"subject":"今天完成需求评审"}") echo "$RESULT" TASK_ID=$(echo "$RESULT" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) echo "创建成功,taskId=$TASK_ID"
---
> ⚠️ 通过应用 API 创建的待办显示在钉钉「待办」的 **Teambition 分类**下,不是「个人」分类。
> ⚠️ 通过 API 创建的任务无法在钉钉 UI 里手动删除,只能通过 API 删除。RESULT=$(curl -s -X POST
"https://api.dingtalk.com/v1.0/todo/users/${UNION_ID}/tasks?operatorId=${UNION_ID}"
-H "x-acs-dingtalk-access-token: $TOKEN"
-H 'Content-Type: application/json'
-d "{"subject":"今天完成需求评审"}") echo "$RESULT" TASK_ID=$(echo "$RESULT" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) echo "创建成功,taskId=$TASK_ID"
"https://api.dingtalk.com/v1.0/todo/users/${UNION_ID}/tasks?operatorId=${UNION_ID}"
-H "x-acs-dingtalk-access-token: $TOKEN"
-H 'Content-Type: application/json'
-d "{"subject":"今天完成需求评审"}") echo "$RESULT" TASK_ID=$(echo "$RESULT" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) echo "创建成功,taskId=$TASK_ID"
---
> ⚠️ Todos created via the application API are displayed under the **Teambition category** in DingTalk's "Todo" section, not the "Personal" category.
> ⚠️ Tasks created via the API cannot be manually deleted in the DingTalk UI, only via the API.references/api.md 查阅索引
references/api.md Index
确定好要做什么之后,用以下命令从 中提取对应章节的完整 API 细节(请求格式、参数说明、返回值示例):
references/api.mdbash
undefinedAfter determining what to do, use the following commands to extract the complete API details (request format, parameter description, return value example) of the corresponding section from :
references/api.mdbash
undefined身份标识与 userId ↔ unionId 转换(28 行)
身份标识与 userId ↔ unionId 转换(28 行)
grep -A 28 "^## 身份标识" references/api.md
grep -A 28 "^## 身份标识" references/api.md
创建待办(含所有可选字段)(47 行)
创建待办(含所有可选字段)(47 行)
grep -A 47 "^## 1. 创建待办" references/api.md
grep -A 47 "^## 1. 创建待办" references/api.md
获取待办详情(29 行)
获取待办详情(29 行)
grep -A 29 "^## 2. 获取待办详情" references/api.md
grep -A 29 "^## 2. 获取待办详情" references/api.md
查询待办列表(含分页)(42 行)
查询待办列表(含分页)(42 行)
grep -A 42 "^## 3. 查询待办列表" references/api.md
grep -A 42 "^## 3. 查询待办列表" references/api.md
更新待办(25 行)
更新待办(25 行)
grep -A 25 "^## 4. 更新待办" references/api.md
grep -A 25 "^## 4. 更新待办" references/api.md
删除待办(16 行)
删除待办(16 行)
grep -A 16 "^## 5. 删除待办" references/api.md
grep -A 16 "^## 5. 删除待办" references/api.md
错误码表(9 行)
错误码表(9 行)
grep -A 9 "^## 错误码" references/api.md
grep -A 9 "^## 错误码" references/api.md
所需应用权限(7 行)
所需应用权限(7 行)
grep -A 7 "^## 所需应用权限" references/api.md
undefinedgrep -A 7 "^## 所需应用权限" references/api.md
undefined