Lark Report Collector
自动收集飞书周报,生成文档并通知,适用于团队周报管理。
自动将文件上传并发送至钉钉用户,基于当前会话账号绑定。
openclaw skills install @kenriou/dingtalk-file-send命令、参数、文件名以原文为准
将文件上传至钉钉媒体服务器,并发送给指定用户。
自动检测当前会话绑定的钉钉账号。
✅ 请在以下情况使用此技能:
此技能自动检测当前会话绑定的钉钉账号:
工作原理:
~/.openclaw/openclaw.json 中查找绑定关系agentId → accountId → 钉钉凭证绑定示例:
{
"bindings": [
{
"agentId": "dingtalk-office",
"match": {
"channel": "dingtalk",
"accountId": "office"
}
}
],
"channels": {
"dingtalk": {
"accounts": {
"office": {
"clientId": "your_client_id",
"clientSecret": "xxx",
"robotCode": "your_robot_code"
}
}
}
}
}注意: 在钉钉 API 中,clientId 用于身份验证,等同于 appKey。
1. 从会话上下文中检测当前 agentId
2. 查找绑定关系:agentId → accountId
3. 根据 accountId 从 OpenClaw 配置中读取凭证
4. 获取访问令牌
5. 将文件上传至钉钉媒体服务器
6. 向接收者发送文件消息OPENCLAW_CONFIG=~/.openclaw/openclaw.json
# 从环境变量获取当前 agent ID(由 OpenClaw 运行时设置)
AGENT_ID="${OPENCLAW_AGENT_ID:-dingtalk-office}"
# 从绑定配置中查找 accountId
ACCOUNT_ID=$(jq -r ".bindings[] | select(.agentId == \"$AGENT_ID\") | .match.accountId" $OPENCLAW_CONFIG)
# 备用方案:若未找到绑定,使用 agent ID 的后缀部分
if [ -z "$ACCOUNT_ID" ] || [ "$ACCOUNT_ID" = "null" ]; then
# 提取 accountId(例如 "dingtalk-office" → "office")
ACCOUNT_ID=$(echo "$AGENT_ID" | sed 's/dingtalk-//')
fi
# 读取凭证(clientId 作为 appKey 使用)
APP_KEY=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].clientId" $OPENCLAW_CONFIG)
APP_SECRET=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].clientSecret" $OPENCLAW_CONFIG)
ROBOT_CODE=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].robotCode" $OPENCLAW_CONFIG)
# 验证凭证
if [ "$APP_KEY" = "null" ] || [ "$APP_SECRET" = "null" ] || [ "$ROBOT_CODE" = "null" ]; then
echo "❌ 账号 '$ACCOUNT_ID' 在 OpenClaw 配置中未找到。"
echo "当前 agent: $AGENT_ID"
echo "可用账号: $(jq -r '.channels.dingtalk.accounts | keys | join(", ")' $OPENCLAW_CONFIG)"
exit 1
fiACCESS_TOKEN=$(curl -s -X POST "https://api.dingtalk.com/v1.0/oauth2/accessToken" \
-H "Content-Type: application/json" \
-d "{\"appKey\":\"$APP_KEY\",\"appSecret\":\"$APP_SECRET\"}" | jq -r '.accessToken')
if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then
echo "❌ 获取访问令牌失败。"
exit 1
fiFILE_PATH="$1"
FILE_NAME=$(basename "$FILE_PATH")
UPLOAD_RESULT=$(curl -s -X POST "https://oapi.dingtalk.com/media/upload?access_token=$ACCESS_TOKEN&type=file&robotCode=$ROBOT_CODE" \
-F "media=@$FILE_PATH;filename=$FILE_NAME" \
-H "Expect:")
MEDIA_ID=$(echo "$UPLOAD_RESULT" | jq -r '.media_id')
if [ -z "$MEDIA_ID" ] || [ "$MEDIA_ID" = "null" ]; then
echo "❌ 上传失败: $UPLOAD_RESULT"
exit 1
fiUSER_ID="$2"
FILE_EXT="${FILE_NAME##*.}"
PAYLOAD=$(jq -n \
--arg robotCode "$ROBOT_CODE" \
--arg msgKey "sampleFile" \
--arg mediaId "$MEDIA_ID" \
--arg fileName "$FILE_NAME" \
--arg fileType "$FILE_EXT" \
--arg userId "$USER_ID" \
'{
robotCode: $robotCode,
msgKey: $msgKey,
msgParam: ({
mediaId: $mediaId,
fileName: $fileName,
fileType: $fileType
} | tojson),
userIds: [$userId]
}')
SEND_RESULT=$(curl -s -X POST "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend" \
-H "Content-Type: application/json" \
-H "x-acs-dingtalk-access-token: $ACCESS_TOKEN" \
-d "$PAYLOAD")
PROCESS_KEY=$(echo "$SEND_RESULT" | jq -r '.processQueryKey // empty')
if [ -n "$PROCESS_KEY" ]; then
echo "✅ 文件发送成功!"
echo "ProcessQueryKey: $PROCESS_KEY"
else
echo "❌ 发送失败: $SEND_RESULT"
exit 1
fi# Dingtalk 文件发送技能
## 使用方法send_dingtalk_file.sh <文件路径> <用户ID>
该脚本会自动从当前会话中检测钉钉账户,无需额外配置。
## 脚本逻辑说明#!/bin/bash
set -e
OPENCLAW_CONFIG=~/.openclaw/openclaw.json
if [ ! -f "$OPENCLAW_CONFIG" ]; then
echo "❌ OpenClaw 配置文件未找到: $OPENCLAW_CONFIG"
exit 1
fi
FILE_PATH="$1"
USER_ID="$2"
if [ -z "$FILE_PATH" ] || [ -z "$USER_ID" ]; then
echo "使用方法: $0 <file_path> <user_id>"
exit 1
fi
if [ ! -f "$FILE_PATH" ]; then
echo "❌ 文件不存在: $FILE_PATH"
exit 1
fi
AGENT_ID="${OPENCLAW_AGENT_ID:-dingtalk-office}"
ACCOUNT_ID=$(jq -r ".bindings[] | select(.agentId == \"$AGENT_ID\") | .match.accountId" $OPENCLAW_CONFIG)
if [ -z "$ACCOUNT_ID" ] || [ "$ACCOUNT_ID" = "null" ]; then
ACCOUNT_ID=$(echo "$AGENT_ID" | sed 's/dingtalk-//')
fi
APP_KEY=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].clientId" $OPENCLAW_CONFIG)
APP_SECRET=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].clientSecret" $OPENCLAW_CONFIG)
ROBOT_CODE=$(jq -r ".channels.dingtalk.accounts[\"$ACCOUNT_ID\"].robotCode" $OPENCLAW_CONFIG)
if [ "$APP_KEY" = "null" ] || [ "$APP_SECRET" = "null" ] || [ "$ROBOT_CODE" = "null" ]; then
echo "❌ 账户 '$ACCOUNT_ID' 在 OpenClaw 配置中未找到。"
echo "当前代理: $AGENT_ID"
echo "可用账户: $(jq -r '.channels.dingtalk.accounts | keys | join(", ")' $OPENCLAW_CONFIG)"
exit 1
fi
FILE_NAME=$(basename "$FILE_PATH")
FILE_EXT="${FILE_NAME##*.}"
ACCESS_TOKEN=$(curl -s -X POST "https://api.dingtalk.com/v1.0/oauth2/accessToken" \
-H "Content-Type: application/json" \
-d "{\"appKey\":\"$APP_KEY\",\"appSecret\":\"$APP_SECRET\"}" | jq -r '.accessToken')
if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then
echo "❌ 获取访问令牌失败。"
exit 1
fi
UPLOAD_RESULT=$(curl -s -X POST "https://oapi.dingtalk.com/media/upload?access_token=$ACCESS_TOKEN&type=file&robotCode=$ROBOT_CODE" \
-F "media=@$FILE_PATH;filename=$FILE_NAME" \
-H "Expect:")
MEDIA_ID=$(echo "$UPLOAD_RESULT" | jq -r '.media_id')
if [ -z "$MEDIA_ID" ] || [ "$MEDIA_ID" = "null" ]; then
echo "❌ 上传失败: $UPLOAD_RESULT"
exit 1
fi
PAYLOAD=$(jq -n \
--arg robotCode "$ROBOT_CODE" \
--arg msgKey "sampleFile" \
--arg mediaId "$MEDIA_ID" \
--arg fileName "$FILE_NAME" \
--arg fileType "$FILE_EXT" \
--arg userId "$USER_ID" \
'{robotCode:$robotCode,msgKey:$msgKey,msgParam:({mediaId:$mediaId,fileName:$fileName,fileType:$fileType}|tojson),userIds:[$userId]}')
SEND_RESULT=$(curl -s -X POST "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend" \
-H "Content-Type: application/json" \
-H "x-acs-dingtalk-access-token: $ACCESS_TOKEN" \
-d "$PAYLOAD")
PROCESS_KEY=$(echo "$SEND_RESULT" | jq -r '.processQueryKey // empty')
if [ -n "$PROCESS_KEY" ]; then
echo "✅ 文件发送成功!"
echo "ProcessQueryKey: $PROCESS_KEY"
else
echo "❌ 发送失败: $SEND_RESULT"
exit 1
fi
## 支持的文件类型
| 类型 | 扩展名 | 最大大小 |
|------|--------|----------|
| PDF | `.pdf` | 20 MB |
| 文档 | `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx` | 20 MB |
| 图片 | `.jpg`, `.jpeg`, `.png`, `.gif`, `.bmp` | 10 MB |
| 其他 | `.txt`, `.zip`, `.rar` | 20 MB |
## 环境变量
| 变量 | 描述 | 是否必需 |
|------|------|----------|
| `OPENCLAW_AGENT_ID` | 当前代理 ID(由 OpenClaw 运行时自动设置) | 自动 |
## 响应处理
**成功响应示例:**{
"flowControlledStaffIdList": [],
"invalidStaffIdList": [],
"processQueryKey": "xxx="
}
**检查发送状态:**curl -s -X GET "https://api.dingtalk.com/v1.0/robot/oToMessages/status?processQueryKey=$PROCESS_KEY" \
-H "x-acs-dingtalk-access-token: $ACCESS_TOKEN"
## 注意事项
- **全自动运行** —— 自动识别当前会话账户,无需手动配置
- 上传的媒体文件在钉钉服务器上保存 **30 天** 后过期
- 最大文件大小限制:**20 MB**(普通文件),**10 MB**(图片)
- 访问令牌有效期为 **2 小时**
- 请求频率限制:每应用约 **100 次/秒**
- 支持所有在 OpenClaw 配置中绑定的钉钉账户
## 常见问题
**“账户在 OpenClaw 配置中未找到”**
→ 检查绑定关系:`jq '.bindings[] | select(.agentId == "current-agent-id")' ~/.openclaw/openclaw.json`
→ 验证账户是否存在:`jq '.channels.dingtalk.accounts | keys' ~/.openclaw/openclaw.json`
**“获取访问令牌失败”**
→ 确认 OpenClaw 配置中的 clientId 和 clientSecret 正确且未过期。
**“无效的 media_id”**
→ 文件上传失败或已过期。请重新上传并获取新的 media_id。
**“无效的 userId”**
→ 检查接收方的钉钉用户 ID 格式是否正确。
**文件显示但无法打开**
→ 确保文件本身是有效的(例如,不要将 HTML 文件重命名为 .pdf)。
## 安全说明
- **无硬编码凭证** —— 仅从 OpenClaw 配置文件读取
- **基于会话的账户检测** —— 使用当前代理绑定的账户
- 访问令牌按需生成,不会持久存储
- 凭证仅在调用钉钉 API 时传输,不会离开本地机器已收录 1 个 Skill