钉钉通讯录技能 负责钉钉通讯录的所有查询操作。本文件为 策略指南 ,仅包含决策逻辑和工作流程。完整 API 请求格式见文末「references/api.md 查阅索引」。 位于本 同级目录的 。 工作流程(每次执行前) 1. 先识别本次任务类型 → 例如:搜索用户、查用户详情、搜索部门、列部门成员、查部门路径、统计员工数 2. 按本次任务校验所需配置 → 通过 读取;仅校验本任务必须项 3. 仅收集缺失配置 → 若缺少某项, 一次性询问用户 所有缺失值,用 写入 4. 获取 Token → 直接调用 5. 执行操作 → 复杂的创建临时文件再执行,简单的直接执行;禁止 heredoc 按任务校验配置(必须先做) - 所有任务通用必需 : 、 - 需要“以当前操作者为起点”或“直接读取本人身份信息”的任务 :必须有 规则:未通过“本次任务配置校验”前,不得进入 API 调用步骤。 凭证禁止在输出中完整打印,确认时仅显示前 4 位 + 所需配置 | 配置键 | 必填 | 说明 | 如何获取 | |---|---|---|---| | | ✅ | 应用 AppKey | 钉钉开放平台 → 应用管理 → 凭证信息 | | | ✅ | 应用 AppSecret | 同上 | | | ❌ | 当前操作用户 的 userId(即运行此技能的人自己),仅在需要以自身为起点查询时才需要 | 管理后…

| tr -d '\"')\nDETAIL=$(curl -s -X POST \"https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${OLD_TOKEN}\" \\\n -H 'Content-Type: application/json' \\\n -d \"{\\\"userid\\\":\\\"$TARGET_UID\\\",\\\"language\\\":\\\"zh_CN\\\"}\")\necho \"用户详情: $DETAIL\"\n```\n\n> **Token 失效处理**:dt_helper 仅按时间缓存,无法感知 token 被提前吊销。若 API 返回 `errcode 40001`/`40014`(token 无效/过期),用 `--nocache` 跳过缓存强制重新获取:\n> ```bash\n> OLD_TOKEN=$(bash \"$HELPER\" --old-token --nocache) # 强制重新获取旧版 token\n> NEW_TOKEN=$(bash \"$HELPER\" --token --nocache) # 强制重新获取新版 token\n> ```\n\n## references/api.md 查阅索引\n确定好要做什么之后,用以下命令从 `references/api.md` 中提取对应章节的完整 API 细节(请求格式、参数说明、返回值示例):\n```bash\ngrep -A 30 \"^## 1. 按关键词搜索用户\" references/api.md\ngrep -A 50 \"^## 2. 获取用户完整详情\" references/api.md\ngrep -A 20 \"^## 3. unionId → userId 转换\" references/api.md\ngrep -A 18 \"^## 4. 企业员工总人数\" references/api.md\ngrep -A 25 \"^## 5. 按关键词搜索部门\" references/api.md\ngrep -A 25 \"^## 6. 获取子部门列表\" references/api.md\ngrep -A 20 \"^## 7. 获取子部门 ID 列表\" references/api.md\ngrep -A 25 \"^## 8. 获取部门详情\" references/api.md\ngrep -A 40 \"^## 9. 获取部门成员完整列表\" references/api.md\ngrep -A 18 \"^## 10. 获取部门成员 userId 列表\" references/api.md\ngrep -A 20 \"^## 11. 获取用户所在部门路径\" references/api.md\ngrep -A 12 \"^## 错误码\" references/api.md\ngrep -A 6 \"^## 所需应用权限\" references/api.md\n```\n---","attachment_filenames":["references/api.md","scripts/dt_helper.sh"],"attachments":[{"filename":"references/api.md","content":"# dingtalk-contact API 参考\n\n> 所有接口均已验证可用。\n> `NEW_TOKEN` = 新版 token(`api.dingtalk.com` 用),获取方式 `bash scripts/dt_helper.sh --token`\n> `OLD_TOKEN` = 旧版 token(`oapi.dingtalk.com` 用),获取方式`bash scripts/dt_helper.sh --old-token`\n\n---\n\n## 1. 按关键词搜索用户\n\n```\nPOST https://api.dingtalk.com/v1.0/contact/users/search\nHeader: x-acs-dingtalk-access-token: {NEW_TOKEN}\n```\n\n请求体:\n```json\n{\n \"queryWord\": \"张三\",\n \"offset\": 0,\n \"size\": 20\n}\n```\n\n| 参数 | 类型 | 必填 | 说明 |\n|---|---|---|---|\n| `queryWord` | string | ✅ | 搜索关键词,不可为空 |\n| `offset` | int | ❌ | 分页偏移量,默认 0 |\n| `size` | int | ❌ | 每页数量,最大 20 |\n\n响应:\n```json\n{\n \"hasMore\": false,\n \"totalCount\": 1,\n \"list\": [\"25262904\"]\n}\n```\n\n> ⚠️ `list` 中存放的是 **userId**(字符串),不是 unionId。\n\n---\n\n## 2. 获取用户完整详情\n\n```\nPOST https://oapi.dingtalk.com/topapi/v2/user/get?access_token={OLD_TOKEN}\n```\n\n请求体:\n```json\n{\n \"userid\": \"25262904\",\n \"language\": \"zh_CN\"\n}\n```\n\n响应(`result` 字段):\n```json\n{\n \"userid\": \"25262904\",\n \"unionid\": \"dGJ...\",\n \"name\": \"张三\",\n \"title\": \"高级工程师\",\n \"job_number\": \"EMP001\",\n \"mobile\": \"138xxxx0000\",\n \"state_code\": \"86\",\n \"email\": \"[email protected]\",\n \"org_email\": \"[email protected]\",\n \"dept_id_list\": [932988755, 933257043],\n \"dept_order_list\": [{\"dept_id\": 932988755, \"order\": 0}],\n \"dept_position_list\": [{\"dept_id\": 932988755, \"title\": \"工程师\"}],\n \"role_list\": [{\"id\": 123, \"name\": \"管理员\", \"group_name\": \"组织架构\"}],\n \"hired_date\": 1640000000000,\n \"active\": true,\n \"admin\": false,\n \"boss\": false\n}\n```\n\n> ⚠️ 返回体中 `result.unionid`(无下划线)有值,`result.union_id`(有下划线)可能为空。\n\nerrcode 说明:0=成功;60121=用户不存在;40014=token 无效。\n\n---\n\n## 3. unionId → userId 转换\n\n```\nPOST https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token={OLD_TOKEN}\n```\n\n请求体:\n```json\n{\n \"unionid\": \"dGJ...\"\n}\n```\n\n响应:\n```json\n{\n \"errcode\": 0,\n \"errmsg\": \"ok\",\n \"result\": {\n \"contact_type\": 0,\n \"userid\": \"25262904\"\n }\n}\n```\n\n> `contact_type=0` 企业内部员工;`contact_type=1` 外部联系人。\n\n---\n\n## 4. 企业员工总人数\n\n```\nPOST https://oapi.dingtalk.com/topapi/user/count?access_token={OLD_TOKEN}\n```\n\n请求体:\n```json\n{ \"only_active\": false }\n```\n\n`only_active=true` 仅统计激活员工;`false` 统计全部。\n\n响应:\n```json\n{\n \"errcode\": 0,\n \"errmsg\": \"ok\",\n \"result\": { \"count\": 2192 }\n}\n```\n\n---\n\n## 5. 按关键词搜索部门\n\n```\nPOST https://api.dingtalk.com/v1.0/contact/departments/search\nHeader: x-acs-dingtalk-access-token: {NEW_TOKEN}\n```\n\n请求体:\n```json\n{\n \"queryWord\": \"技术\",\n \"offset\": 0,\n \"size\": 20\n}\n```\n\n响应:\n```json\n{\n \"hasMore\": false,\n \"totalCount\": 2,\n \"list\": [932988755, 933257043]\n}\n```\n\n> `list` 返回部门 ID(整数)。根部门 ID = `1`。\n\n---\n\n## 6. 获取子部门列表\n\n```\nPOST https://oapi.dingtalk.com/topapi/v2/department/listsub?access_token={OLD_TOKEN}\n```\n\n请求体:\n```json\n{ \"dept_id\": 1, \"language\": \"zh_CN\" }\n```\n\n响应(`result` 为数组):\n```json\n[\n {\n \"dept_id\": 932988755,\n \"name\": \"技术部\",\n \"parent_id\": 1,\n \"auto_add_user\": false,\n \"dept_manager_userid_list\": [\"25262904\"]\n }\n]\n```\n\n---\n\n## 7. 获取子部门 ID 列表\n\n```\nPOST https://oapi.dingtalk.com/topapi/v2/department/listsubid?access_token={OLD_TOKEN}\n```\n\n请求体:\n```json\n{ \"dept_id\": 1 }\n```\n\n响应:\n```json\n{\n \"errcode\": 0,\n \"result\": { \"dept_id_list\": [932988755, 933257043, 933035510] }\n}\n```\n\n> 仅返回直接子部门,不递归多层。如需完整树,循环调用此接口。\n\n---\n\n## 8. 获取部门详情\n\n```\nPOST https://oapi.dingtalk.com/topapi/v2/department/get?access_token={OLD_TOKEN}\n```\n\n请求体:\n```json\n{ \"dept_id\": 932988755, \"language\": \"zh_CN\" }\n```\n\n响应(`result`):\n```json\n{\n \"dept_id\": 932988755,\n \"name\": \"技术部\",\n \"parent_id\": 1,\n \"member_count\": 32,\n \"order\": 100,\n \"auto_add_user\": false,\n \"hide_dept\": false\n}\n```\n\n---\n\n## 9. 获取部门成员完整列表\n\n```\nPOST https://oapi.dingtalk.com/topapi/v2/user/list?access_token={OLD_TOKEN}\n```\n\n请求体:\n```json\n{\n \"dept_id\": 932988755,\n \"cursor\": 0,\n \"size\": 100,\n \"order_field\": \"custom\",\n \"contain_access_level\": false,\n \"language\": \"zh_CN\"\n}\n```\n\n| 参数 | 类型 | 必填 | 说明 |\n|---|---|---|---|\n| `dept_id` | int | ✅ | 部门 ID |\n| `cursor` | int | ✅ | 分页游标,首次传 0 |\n| `size` | int | ✅ | 每页数量,最大 100 |\n| `order_field` | string | ❌ | `custom`=自定义排序 |\n\n响应(`result`):\n```json\n{\n \"has_more\": true,\n \"next_cursor\": 100,\n \"list\": [\n {\n \"userid\": \"25262904\",\n \"unionid\": \"dGJ...\",\n \"name\": \"张三\",\n \"title\": \"工程师\",\n \"job_number\": \"EMP001\",\n \"mobile\": \"138xxxx0000\",\n \"dept_id_list\": [932988755],\n \"active\": true\n }\n ]\n}\n```\n\n> 分页:`has_more=true` 时,用 `next_cursor` 作为下次请求的 `cursor`。\n\n---\n\n## 10. 获取部门成员 userId 列表\n\n```\nPOST https://oapi.dingtalk.com/topapi/user/listid?access_token={OLD_TOKEN}\n```\n\n请求体:\n```json\n{ \"dept_id\": 932988755 }\n```\n\n响应:\n```json\n{\n \"errcode\": 0,\n \"result\": { \"userid_list\": [\"25262904\", \"12345678\"] }\n}\n```\n\n---\n\n## 11. 获取用户所在部门路径\n\n```\nPOST https://oapi.dingtalk.com/topapi/v2/department/listparentbyuser?access_token={OLD_TOKEN}\n```\n\n请求体:\n```json\n{ \"userid\": \"25262904\" }\n```\n\n响应:\n```json\n{\n \"errcode\": 0,\n \"result\": {\n \"parent_list\": [\n { \"parent_dept_id_list\": [932988755, 933257043, 1] }\n ]\n }\n}\n```\n\n> `parent_dept_id_list` 从直属部门到根部门(`1`)排列。\n> 用户可能同时属于多个部门,`parent_list` 每项对应一条路径。\n\n---\n\n## 错误码\n\n| errcode | 含义 | 处理建议 |\n|---|---|---|\n| 0 | 成功 | — |\n| 40014 | token 无效或过期 | 删除缓存,重新获取 token |\n| 60003 | 部门 ID 不存在 | 检查 dept_id 是否正确 |\n| 60121 | 用户不存在 | 检查 userId 是否正确 |\n| 60122 | unionId 不存在 | 检查 unionId 是否正确 |\n| 88 | 没有操作权限 | 在钉钉开放平台申请对应接口权限 |\n| 400 | 参数错误 | 检查 queryWord 不为空等参数约束 |\n\n---\n\n## 所需应用权限\n\n| 权限 | 用途 |\n|---|---|\n| `qyapi_addresslist_search` | 按关键词搜索用户/部门 |\n| `Contact.User.Read` | 读取用户详情(企业内部应用通常默认有) |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6904,"content_sha256":"b5c0746af7f7b5e47ac366e496513ccf10bca0a5d5c980ba8a79209cd0136631"},{"filename":"scripts/dt_helper.sh","content":"#!/bin/bash\n# =============================================================================\n# dt_helper.sh — 钉钉开放平台辅助工具\n# 路径: scripts/common/dt_helper.sh\n# 用法: bash scripts/common/dt_helper.sh \u003c命令> [参数]\n# =============================================================================\n\nset -e\n\nCONFIG=\"${DINGTALK_CONFIG:-$HOME/.dingtalk-skills/config}\"\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 帮助信息\n# ─────────────────────────────────────────────────────────────────────────────\nshow_help() {\n cat \u003c\u003c'EOF'\n钉钉开放平台辅助工具 (dt_helper.sh)\n用法: bash scripts/common/dt_helper.sh \u003c命令> [参数]\n\nToken 管理(两种 token 互不兼容,按域名区分):\n --token [--nocache] 获取新版 accessToken(用于 api.dingtalk.com 域名的所有接口)\n 适用:待办、文档、AI 表格等 api.dingtalk.com 域名下所有版本的接口\n 请求头:x-acs-dingtalk-access-token: \u003ctoken>\n 有缓存且未过期则直接返回,否则自动刷新并缓存\n --nocache:跳过缓存,强制重新获取(token 被提前吊销时使用)\n --token-info 查看新版 token 缓存状态(是否有效、剩余有效秒数)\n --clear-token 清除缓存的新版 token(下次 --token 时强制重新获取)\n --old-token [--nocache]\n 获取旧版 access_token(用于 oapi.dingtalk.com 域名的所有接口)\n 适用:群消息/工作通知/userId↔unionId 转换等 oapi.dingtalk.com 接口\n 不适用:api.dingtalk.com 接口(如待办、文档、AI表格)\n ⚠️ 新旧两种 token 互不兼容,混用会导致 401/403\n --nocache:跳过缓存,强制重新获取(token 被提前吊销时使用)\n\n身份转换:\n --to-unionid [userId] 将 userId 转换为 unionId\n 不传参数:转换配置中的 DINGTALK_MY_USER_ID(操作者自身),\n 结果首次自动写入 DINGTALK_MY_OPERATOR_ID\n 传入参数:动态转换指定 userId,仅返回结果,不写入配置\n --to-userid [unionId] 将 unionId 反向转换为 userId(需传入参数)\n\n配置管理:\n --config 查看 ~/.dingtalk-skills/config 中的所有配置项(敏感项脱敏显示)\n --get KEY [KEY...] 获取一个或多个配置项的值(敏感项脱敏显示)\n --set KEY=VALUE 将配置项持久化写入配置文件(已存在则更新,不存在则追加,目录自动创建)\n\n帮助:\n --help, -h 显示此帮助信息\n\n环境变量:\n DINGTALK_CONFIG 覆盖默认配置文件路径(默认 ~/.dingtalk-skills/config)\n\n配置文件:\n ~/.dingtalk-skills/config key=value 格式,存储以下键:\n DINGTALK_APP_KEY 应用 Client ID(AppKey)\n DINGTALK_APP_SECRET 应用 Client Secret(AppSecret)\n DINGTALK_MY_USER_ID 企业员工 ID(userId,管理后台通讯录可查)\n DINGTALK_MY_OPERATOR_ID 操作者 unionId(由 --to-unionid 自动生成)\n DINGTALK_ACCESS_TOKEN 新版 token 缓存\n DINGTALK_TOKEN_EXPIRY 新版 token 过期时间戳(Unix 秒)\n DINGTALK_OLD_TOKEN 旧版 token 缓存\n DINGTALK_OLD_TOKEN_EXPIRY 旧版 token 过期时间戳(Unix 秒)\n\nEOF\n}\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 工具函数\n# ─────────────────────────────────────────────────────────────────────────────\n\n# 从配置文件读取指定键的值\ncfg_get() {\n local key=\"$1\"\n grep \"^${key}=\" \"$CONFIG\" 2>/dev/null | head -1 | cut -d= -f2-\n}\n\n# 写入或更新配置文件中的键值\ncfg_set() {\n local key=\"$1\"\n local value=\"$2\"\n mkdir -p \"$(dirname \"$CONFIG\")\"\n touch \"$CONFIG\"\n if grep -q \"^${key}=\" \"$CONFIG\" 2>/dev/null; then\n sed -i \"s|^${key}=.*|${key}=${value}|\" \"$CONFIG\"\n else\n echo \"${key}=${value}\" >> \"$CONFIG\"\n fi\n}\n\n# 从配置文件删除指定键\ncfg_del() {\n local key=\"$1\"\n sed -i \"/^${key}=/d\" \"$CONFIG\" 2>/dev/null || true\n}\n\n# 确保必须的配置项存在,否则报错退出\nrequire_cfg() {\n local key=\"$1\"\n local val\n val=$(cfg_get \"$key\")\n if [ -z \"$val\" ]; then\n echo \"❌ 缺少配置项 ${key},请先运行: bash scripts/common/dt_helper.sh --set ${key}=\u003c值>\" >&2\n exit 1\n fi\n echo \"$val\"\n}\n\n# ─────────────────────────────────────────────────────────────────────────────\n# Token 管理\n# ─────────────────────────────────────────────────────────────────────────────\n\ncmd_token() {\n local force=\"${1:-}\" app_key app_secret cached expiry now resp token expire_in\n\n app_key=$(require_cfg DINGTALK_APP_KEY)\n app_secret=$(require_cfg DINGTALK_APP_SECRET)\n now=$(date +%s)\n\n if [ \"$force\" != \"--nocache\" ]; then\n cached=$(cfg_get DINGTALK_ACCESS_TOKEN)\n expiry=$(cfg_get DINGTALK_TOKEN_EXPIRY)\n if [ -n \"$cached\" ] && [ -n \"$expiry\" ] && [ \"$now\" -lt \"$expiry\" ]; then\n echo \"$cached\"\n return 0\n fi\n fi\n\n # 过期或无缓存,重新获取\n resp=$(curl -s -X POST \"https://api.dingtalk.com/v1.0/oauth2/accessToken\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\\\"appKey\\\":\\\"${app_key}\\\",\\\"appSecret\\\":\\\"${app_secret}\\\"}\")\n\n token=$(echo \"$resp\" | grep -o '\"accessToken\":\"[^\"]*\"' | cut -d'\"' -f4)\n expire_in=$(echo \"$resp\" | grep -o '\"expireIn\":[0-9]*' | cut -d: -f2)\n\n if [ -z \"$token\" ]; then\n echo \"❌ 获取 token 失败: $resp\" >&2\n exit 1\n fi\n\n cfg_set DINGTALK_ACCESS_TOKEN \"$token\"\n cfg_set DINGTALK_TOKEN_EXPIRY \"$((now + expire_in - 200))\"\n\n echo \"$token\"\n}\n\ncmd_token_info() {\n local cached expiry now remaining\n\n cached=$(cfg_get DINGTALK_ACCESS_TOKEN)\n expiry=$(cfg_get DINGTALK_TOKEN_EXPIRY)\n now=$(date +%s)\n\n if [ -z \"$cached\" ]; then\n echo \"状态: 无缓存(从未获取或已清除)\"\n return 0\n fi\n\n if [ -z \"$expiry\" ] || [ \"$now\" -ge \"$expiry\" ]; then\n echo \"状态: 已过期\"\n echo \"Token: ${cached:0:20}...\"\n else\n remaining=$((expiry - now))\n echo \"状态: 有效\"\n echo \"Token: ${cached:0:20}...\"\n echo \"剩余: ${remaining} 秒(约 $((remaining / 60)) 分钟)\"\n fi\n}\n\ncmd_clear_token() {\n cfg_del DINGTALK_ACCESS_TOKEN\n cfg_del DINGTALK_TOKEN_EXPIRY\n echo \"✅ 新版 Token 缓存已清除\"\n}\n\ncmd_old_token() {\n # 旧版 access_token,用于所有 oapi.dingtalk.com 接口:\n # - 群消息、工作通知、互动卡片(dingtalk-message)\n # - userId ↔ unionId 转换\n # ⚠️ 不可用于 api.dingtalk.com 接口(待办、文档、AI表格等)\n local force=\"${1:-}\" app_key app_secret resp token cached expiry now\n\n app_key=$(require_cfg DINGTALK_APP_KEY)\n app_secret=$(require_cfg DINGTALK_APP_SECRET)\n now=$(date +%s)\n\n if [ \"$force\" != \"--nocache\" ]; then\n cached=$(cfg_get DINGTALK_OLD_TOKEN)\n expiry=$(cfg_get DINGTALK_OLD_TOKEN_EXPIRY)\n if [ -n \"$cached\" ] && [ -n \"$expiry\" ] && [ \"$now\" -lt \"$expiry\" ]; then\n echo \"$cached\"\n return 0\n fi\n fi\n\n resp=$(curl -s \"https://oapi.dingtalk.com/gettoken?appkey=${app_key}&appsecret=${app_secret}\")\n token=$(echo \"$resp\" | grep -o '\"access_token\":\"[^\"]*\"' | cut -d'\"' -f4)\n expires_in=$(echo \"$resp\" | grep -o '\"expires_in\":[0-9]*' | cut -d: -f2)\n\n if [ -z \"$token\" ]; then\n echo \"❌ 获取旧版 token 失败: $resp\" >&2\n exit 1\n fi\n\n cfg_set DINGTALK_OLD_TOKEN \"$token\"\n cfg_set DINGTALK_OLD_TOKEN_EXPIRY \"$((now + expires_in - 200))\"\n\n echo \"$token\"\n}\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 身份转换\n# ─────────────────────────────────────────────────────────────────────────────\n\ncmd_to_unionid() {\n local user_id=\"$1\"\n local is_self=false\n local old_token resp union_id\n\n # 未传参 → 使用配置中的操作者自身 userId,转换结果写入配置\n if [ -z \"$user_id\" ]; then\n user_id=$(require_cfg DINGTALK_MY_USER_ID)\n is_self=true\n fi\n\n old_token=$(cmd_old_token)\n\n resp=$(curl -s -X POST \\\n \"https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${old_token}\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\\\"userid\\\":\\\"${user_id}\\\"}\")\n\n # 注意:使用无下划线的 unionid 字段(有下划线的 union_id 可能为空)\n union_id=$(echo \"$resp\" | grep -o '\"unionid\":\"[^\"]*\"' | head -1 | cut -d'\"' -f4)\n\n if [ -z \"$union_id\" ]; then\n echo \"❌ userId→unionId 转换失败: $resp\" >&2\n exit 1\n fi\n\n # 仅当转换的是操作者自身时,才写入配置(动态转换他人 userId 不写入)\n if \"$is_self\" && [ -z \"$(cfg_get DINGTALK_MY_OPERATOR_ID)\" ]; then\n cfg_set DINGTALK_MY_OPERATOR_ID \"$union_id\"\n echo \"✅ 自身 unionId 已写入配置 DINGTALK_MY_OPERATOR_ID\" >&2\n fi\n\n echo \"$union_id\"\n}\n\ncmd_to_userid() {\n local union_id=\"$1\"\n local old_token resp user_id\n\n if [ -z \"$union_id\" ]; then\n echo \"❌ 请提供 unionId 参数\" >&2\n exit 1\n fi\n\n old_token=$(cmd_old_token)\n\n resp=$(curl -s -X POST \\\n \"https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=${old_token}\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\\\"unionid\\\":\\\"${union_id}\\\"}\")\n\n user_id=$(echo \"$resp\" | grep -o '\"userid\":\"[^\"]*\"' | head -1 | cut -d'\"' -f4)\n\n if [ -z \"$user_id\" ]; then\n echo \"❌ unionId→userId 转换失败: $resp\" >&2\n exit 1\n fi\n\n echo \"$user_id\"\n}\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 配置管理\n# ─────────────────────────────────────────────────────────────────────────────\n\ncmd_config() {\n if [ ! -f \"$CONFIG\" ]; then\n echo \"配置文件不存在: $CONFIG\"\n echo \"使用 --set KEY=VALUE 写入配置项\"\n return 0\n fi\n\n echo \"配置文件: $CONFIG\"\n echo \"─────────────────────────────────\"\n # 脱敏显示 SECRET 和 TOKEN\n while IFS= read -r line; do\n key=\"${line%%=*}\"\n val=\"${line#*=}\"\n case \"$key\" in\n DINGTALK_APP_SECRET|DINGTALK_ACCESS_TOKEN|DINGTALK_OLD_TOKEN)\n echo \"${key}=${val:0:6}***(已脱敏)\"\n ;;\n *)\n echo \"$line\"\n ;;\n esac\n done \u003c \"$CONFIG\"\n}\n\ncmd_get() {\n if [ $# -eq 0 ]; then\n echo \"❌ 请提供至少一个键名,用法: --get KEY [KEY2 ...]\" >&2\n exit 1\n fi\n for key in \"$@\"; do\n val=$(cfg_get \"$key\")\n if [ -z \"$val\" ]; then\n echo \"${key}=(未设置)\"\n else\n case \"$key\" in\n DINGTALK_APP_SECRET|DINGTALK_ACCESS_TOKEN|DINGTALK_OLD_TOKEN)\n echo \"${key}=${val:0:6}***(脱敏)\"\n ;;\n *)\n echo \"${key}=${val}\"\n ;;\n esac\n fi\n done\n}\n\ncmd_set() {\n local kv=\"$1\"\n if [ -z \"$kv\" ] || [[ \"$kv\" != *\"=\"* ]]; then\n echo \"❌ 格式错误,用法: --set KEY=VALUE\" >&2\n exit 1\n fi\n local key=\"${kv%%=*}\"\n local value=\"${kv#*=}\"\n cfg_set \"$key\" \"$value\"\n echo \"✅ 已设置 ${key}\"\n}\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 入口:解析命令\n# ─────────────────────────────────────────────────────────────────────────────\n\nCMD=\"${1:-}\"\n\ncase \"$CMD\" in\n --help|-h|\"\")\n show_help\n ;;\n --token)\n cmd_token \"${2:-}\"\n ;;\n --token-info)\n cmd_token_info\n ;;\n --clear-token)\n cmd_clear_token\n ;;\n --old-token)\n cmd_old_token \"${2:-}\"\n ;;\n --to-unionid)\n cmd_to_unionid \"${2:-}\"\n ;;\n --to-userid)\n cmd_to_userid \"${2:-}\"\n ;;\n --config)\n cmd_config\n ;;\n --get)\n shift\n cmd_get \"$@\"\n ;;\n --set)\n cmd_set \"${2:-}\"\n ;;\n *)\n echo \"❌ 未知命令: $CMD\" >&2\n echo \"运行 --help 查看用法\" >&2\n exit 1\n ;;\nesac\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":13674,"content_sha256":"f00f4756e205eac947bd9156cfd3cc554f1766a12890a5b5d576aabd67000b1e"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"钉钉通讯录技能","type":"text"}]},{"type":"paragraph","content":[{"text":"负责钉钉通讯录的所有查询操作。本文件为","type":"text"},{"text":"策略指南","type":"text","marks":[{"type":"strong"}]},{"text":",仅包含决策逻辑和工作流程。完整 API 请求格式见文末「references/api.md 查阅索引」。","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"dt_helper.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" 位于本 ","type":"text"},{"text":"SKILL.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" 同级目录的 ","type":"text"},{"text":"scripts/dt_helper.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":"。","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"工作流程(每次执行前)","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"先识别本次任务类型","type":"text","marks":[{"type":"strong"}]},{"text":" → 例如:搜索用户、查用户详情、搜索部门、列部门成员、查部门路径、统计员工数","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"按本次任务校验所需配置","type":"text","marks":[{"type":"strong"}]},{"text":" → 通过 ","type":"text"},{"text":"bash scripts/dt_helper.sh --get KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":" 读取;仅校验本任务必须项","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"仅收集缺失配置","type":"text","marks":[{"type":"strong"}]},{"text":" → 若缺少某项,","type":"text"},{"text":"一次性询问用户","type":"text","marks":[{"type":"strong"}]},{"text":"所有缺失值,用 ","type":"text"},{"text":"bash scripts/dt_helper.sh --set KEY=VALUE","type":"text","marks":[{"type":"code_inline"}]},{"text":" 写入","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"获取 Token","type":"text","marks":[{"type":"strong"}]},{"text":" → 直接调用 ","type":"text"},{"text":"bash scripts/dt_helper.sh","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"执行操作","type":"text","marks":[{"type":"strong"}]},{"text":" → 复杂的创建临时文件再执行,简单的直接执行;禁止 heredoc","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"按任务校验配置(必须先做)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"所有任务通用必需","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"DINGTALK_APP_KEY","type":"text","marks":[{"type":"code_inline"}]},{"text":"、","type":"text"},{"text":"DINGTALK_APP_SECRET","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"需要“以当前操作者为起点”或“直接读取本人身份信息”的任务","type":"text","marks":[{"type":"strong"}]},{"text":":必须有 ","type":"text"},{"text":"DINGTALK_MY_USER_ID","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"规则:未通过“本次任务配置校验”前,不得进入 API 调用步骤。","type":"text"}]}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"凭证禁止在输出中完整打印,确认时仅显示前 4 位 + ","type":"text"},{"text":"****","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"所需配置","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"配置键","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"必填","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"说明","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"如何获取","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"DINGTALK_APP_KEY","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"应用 AppKey","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"钉钉开放平台 → 应用管理 → 凭证信息","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"DINGTALK_APP_SECRET","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"应用 AppSecret","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"同上","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"DINGTALK_MY_USER_ID","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"❌","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"当前操作用户","type":"text","marks":[{"type":"strong"}]},{"text":"的 userId(即运行此技能的人自己),仅在需要以自身为起点查询时才需要","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"管理后台 → 通讯录 → 成员管理 → 点击姓名查看","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"身份标识说明","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"标识","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"说明","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"userId","type":"text","marks":[{"type":"code_inline"}]},{"text":"(= ","type":"text"},{"text":"staffId","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"企业内部员工 ID,可通过通过管理后台 -> 通讯录 -> 成员管理 -> 点击姓名查看","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"unionId","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"跨企业/跨应用唯一标识,可通过 ","type":"text"},{"text":"bash scripts/dt_helper.sh --to-unionid \u003cuserid>","type":"text","marks":[{"type":"code_inline"}]},{"text":" 获取","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"执行脚本模板","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"#!/bin/bash\nset -e\nHELPER=\"./scripts/dt_helper.sh\"\nNEW_TOKEN=$(bash \"$HELPER\" --token) # api.dingtalk.com 接口用\nOLD_TOKEN=$(bash \"$HELPER\" --old-token) # oapi.dingtalk.com 接口用\n# USER_ID=$(bash \"$HELPER\" --get DINGTALK_MY_USER_ID) # 以当前操作用户为起点时启用\n\n# 在此追加具体 API 调用,例如按姓名搜索用户并获取详情:\nKEYWORD=\"张三\"\nSEARCH=$(curl -s -X POST https://api.dingtalk.com/v1.0/contact/users/search \\\n -H \"x-acs-dingtalk-access-token: $NEW_TOKEN\" \\\n -H 'Content-Type: application/json' \\\n -d \"{\\\"queryWord\\\":\\\"$KEYWORD\\\",\\\"offset\\\":0,\\\"size\\\":20}\")\necho \"搜索结果: $SEARCH\"\n\nTARGET_UID=$(echo \"$SEARCH\" | grep -o '\"list\":\\[\"[^\"]*\"' | grep -o '\"[^\"]*\"

钉钉通讯录技能 负责钉钉通讯录的所有查询操作。本文件为 策略指南 ,仅包含决策逻辑和工作流程。完整 API 请求格式见文末「references/api.md 查阅索引」。 位于本 同级目录的 。 工作流程(每次执行前) 1. 先识别本次任务类型 → 例如:搜索用户、查用户详情、搜索部门、列部门成员、查部门路径、统计员工数 2. 按本次任务校验所需配置 → 通过 读取;仅校验本任务必须项 3. 仅收集缺失配置 → 若缺少某项, 一次性询问用户 所有缺失值,用 写入 4. 获取 Token → 直接调用 5. 执行操作 → 复杂的创建临时文件再执行,简单的直接执行;禁止 heredoc 按任务校验配置(必须先做) - 所有任务通用必需 : 、 - 需要“以当前操作者为起点”或“直接读取本人身份信息”的任务 :必须有 规则:未通过“本次任务配置校验”前,不得进入 API 调用步骤。 凭证禁止在输出中完整打印,确认时仅显示前 4 位 + 所需配置 | 配置键 | 必填 | 说明 | 如何获取 | |---|---|---|---| | | ✅ | 应用 AppKey | 钉钉开放平台 → 应用管理 → 凭证信息 | | | ✅ | 应用 AppSecret | 同上 | | | ❌ | 当前操作用户 的 userId(即运行此技能的人自己),仅在需要以自身为起点查询时才需要 | 管理后…

| tr -d '\"')\nDETAIL=$(curl -s -X POST \"https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${OLD_TOKEN}\" \\\n -H 'Content-Type: application/json' \\\n -d \"{\\\"userid\\\":\\\"$TARGET_UID\\\",\\\"language\\\":\\\"zh_CN\\\"}\")\necho \"用户详情: $DETAIL\"","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Token 失效处理","type":"text","marks":[{"type":"strong"}]},{"text":":dt_helper 仅按时间缓存,无法感知 token 被提前吊销。若 API 返回 ","type":"text"},{"text":"errcode 40001","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"40014","type":"text","marks":[{"type":"code_inline"}]},{"text":"(token 无效/过期),用 ","type":"text"},{"text":"--nocache","type":"text","marks":[{"type":"code_inline"}]},{"text":" 跳过缓存强制重新获取:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"OLD_TOKEN=$(bash \"$HELPER\" --old-token --nocache) # 强制重新获取旧版 token\nNEW_TOKEN=$(bash \"$HELPER\" --token --nocache) # 强制重新获取新版 token","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"references/api.md 查阅索引","type":"text"}]},{"type":"paragraph","content":[{"text":"确定好要做什么之后,用以下命令从 ","type":"text"},{"text":"references/api.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" 中提取对应章节的完整 API 细节(请求格式、参数说明、返回值示例):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"grep -A 30 \"^## 1. 按关键词搜索用户\" references/api.md\ngrep -A 50 \"^## 2. 获取用户完整详情\" references/api.md\ngrep -A 20 \"^## 3. unionId → userId 转换\" references/api.md\ngrep -A 18 \"^## 4. 企业员工总人数\" references/api.md\ngrep -A 25 \"^## 5. 按关键词搜索部门\" references/api.md\ngrep -A 25 \"^## 6. 获取子部门列表\" references/api.md\ngrep -A 20 \"^## 7. 获取子部门 ID 列表\" references/api.md\ngrep -A 25 \"^## 8. 获取部门详情\" references/api.md\ngrep -A 40 \"^## 9. 获取部门成员完整列表\" references/api.md\ngrep -A 18 \"^## 10. 获取部门成员 userId 列表\" references/api.md\ngrep -A 20 \"^## 11. 获取用户所在部门路径\" references/api.md\ngrep -A 12 \"^## 错误码\" references/api.md\ngrep -A 6 \"^## 所需应用权限\" references/api.md","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"dingtalk-contact","author":"@skillopedia","source":{"stars":72,"repo_name":"dingtalk-skills","origin_url":"https://github.com/breath57/dingtalk-skills/blob/HEAD/.agents/skills/dingtalk-contact/SKILL.md","repo_owner":"breath57","body_sha256":"7e7d225a34d979c17be2eea4c44a0abbe475310034aad236e7a1d6084c71bc3a","cluster_key":"4c6d20b985ddddfc7728182bbd26a0a7ef5cdd958e988ea074e62fee5a803583","clean_bundle":{"format":"clean-skill-bundle-v1","source":"breath57/dingtalk-skills/.agents/skills/dingtalk-contact/SKILL.md","attachments":[{"id":"01a0908e-e814-57ae-b0e2-b7645349cc7a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/01a0908e-e814-57ae-b0e2-b7645349cc7a/attachment.md","path":"references/api.md","size":6904,"sha256":"b5c0746af7f7b5e47ac366e496513ccf10bca0a5d5c980ba8a79209cd0136631","contentType":"text/markdown; charset=utf-8"},{"id":"f005684a-d0e2-5195-935c-ebceeed45413","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f005684a-d0e2-5195-935c-ebceeed45413/attachment.sh","path":"scripts/dt_helper.sh","size":13674,"sha256":"f00f4756e205eac947bd9156cfd3cc554f1766a12890a5b5d576aabd67000b1e","contentType":"application/x-sh; charset=utf-8"}],"bundle_sha256":"d5ee67553eb5cae7e8d575debaea61d52ed17d02b840776272baa8550d7124a8","attachment_count":2,"text_attachments":2,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":".agents/skills/dingtalk-contact/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"general","category_label":"General"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"general","import_tag":"clean-skills-v1","description":"钉钉通讯录与联系人查询。当用户提到\"钉钉通讯录\"、\"查找员工\"、\"搜索用户\"、\"查用户信息\"、\"获取用户详情\"、\"用户手机号\"、\"员工姓名\"、\"员工工号\"、\"查部门\"、\"搜索部门\"、\"部门成员\"、\"部门列表\"、\"部门详情\"、\"子部门\"、\"父部门\"、\"部门路径\"、\"员工总数\"、\"通讯录搜索\"、\"userId 转 unionId\"、\"unionId 转 userId\"、\"dingtalk contact\"、\"dingtalk directory\"、\"find user\"、\"get user info\"、\"department members\"时使用此技能。支持:按关键词搜索用户/部门、获取用户完整信息(姓名/手机/工号/部门/职位/unionId)、获取部门成员列表、获取部门树结构、查询用户所在部门路径、员工总人数统计等全部通讯录操作。"}},"renderedAt":1782980917460}

钉钉通讯录技能 负责钉钉通讯录的所有查询操作。本文件为 策略指南 ,仅包含决策逻辑和工作流程。完整 API 请求格式见文末「references/api.md 查阅索引」。 位于本 同级目录的 。 工作流程(每次执行前) 1. 先识别本次任务类型 → 例如:搜索用户、查用户详情、搜索部门、列部门成员、查部门路径、统计员工数 2. 按本次任务校验所需配置 → 通过 读取;仅校验本任务必须项 3. 仅收集缺失配置 → 若缺少某项, 一次性询问用户 所有缺失值,用 写入 4. 获取 Token → 直接调用 5. 执行操作 → 复杂的创建临时文件再执行,简单的直接执行;禁止 heredoc 按任务校验配置(必须先做) - 所有任务通用必需 : 、 - 需要“以当前操作者为起点”或“直接读取本人身份信息”的任务 :必须有 规则:未通过“本次任务配置校验”前,不得进入 API 调用步骤。 凭证禁止在输出中完整打印,确认时仅显示前 4 位 + 所需配置 | 配置键 | 必填 | 说明 | 如何获取 | |---|---|---|---| | | ✅ | 应用 AppKey | 钉钉开放平台 → 应用管理 → 凭证信息 | | | ✅ | 应用 AppSecret | 同上 | | | ❌ | 当前操作用户 的 userId(即运行此技能的人自己),仅在需要以自身为起点查询时才需要 | 管理后…