From 6d22374d00bfb62400072ce4e40a48e0619a8ae1 Mon Sep 17 00:00:00 2001 From: aitest Date: Thu, 5 Mar 2026 18:47:56 +0900 Subject: [PATCH] Add DingTalk success notes and long-term memory - token and msgKey configuration --- MEMORY.md | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ TOOLS.md | 83 +++++++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 MEMORY.md diff --git a/MEMORY.md b/MEMORY.md new file mode 100644 index 0000000..ba0c537 --- /dev/null +++ b/MEMORY.md @@ -0,0 +1,168 @@ +# MEMORY.md - 长期记忆 + +这个文件用于记录重要的长期知识和经验教训。 + +--- + +## DingTalk媒体发送成功经验 + +**日期**: 2026-03-05 +**重要性**: ⭐⭐⭐⭐⭐ + +### 问题背景 +尝试通过DingTalk发送文件时遇到多次失败,反复尝试了多种方法: +- 使用message工具 - 失败("Failed to upload media") +- 直接调用robot/send API - 失败("token is not exist" / "缺少参数 access_token") +- 尝试不同的API端点和消息格式 - 大部分失败 + +### 解决方案(成功方式) + +#### Token获取 +```javascript +// 关键:使用GET + URL参数(非params) +const tokenUrl = `https://oapi.dingtalk.com/gettoken?appkey=${APP_KEY}&appsecret=${APP_SECRET}`; +const response = await axios.get(tokenUrl); +const accessToken = response.data.access_token; +``` + +**重要**:不要使用`axios.get(url, {params: {...}})`,必须在URL中直接拼接参数 + +#### 文件上传 +```javascript +const uploadUrl = `https://oapi.dingtalk.com/media/upload?access_token=${accessToken}`; +const form = new FormData(); +form.append('media', fs.createReadStream(filePath)); +form.append('type', 'file'); +const upload = await axios.post(uploadUrl, form, {headers: form.getHeaders()}); +const media_id = upload.data.media_id; +``` + +#### 消息发送(关键成功点) +```javascript +const url = "https://api.dingtalk.com/v1.0/robot/groupMessages/send"; + +const headers = { + "x-acs-dingtalk-access-token": accessToken, // ← 关键:在Header中 + "Content-Type": "application/json" +}; + +const body = { + robotCode: ROBOT_CODE, + openConversationId: OPEN_CONVERSATION_ID, + msgKey: "sampleFile", // ← 文件用sampleFile,图片用sampleMarkdown + msgParam: JSON.stringify({ // ← 关键:必须是字符串! + mediaId: media_id, + fileName: fileName + }) +}; + +const result = await axios.post(url, body, { headers }); +``` + +### 关键msgKey对应关系 + +| 媒体类型 | msgKey | 测试状态 | +|---------|--------|---------| +| 文件 | `sampleFile` | ✅ 验证成功 | +| 图片 | `sampleMarkdown` | ✅ 验证成功 | +| 视频 | `sampleVideo` | 待测试 | + +### 失败原因总结 + +1. **使用旧的robot/send API** + - 返回 "token is not exist" + - 使用v1.0 API后解决 + +2. **msgParam使用对象而非字符串** + - 返回 "MissingmsgParam" + - 必须使用`JSON.stringify()` + +3. **access_token放在错误位置** + - 在URL中会失败 + - 必须在Header中:`x-acs-dingtalk-access-token` + +### 验证的端点差异 + +| API端点 | 状态 | 说明 | +|---------|------|------| +| `https://oapi.dingtalk.com/gettoken` | ✅ 可用 | 必须用URL参数 | +| `https://oapi.dingtalk.com/media/upload` | ✅ 可用 | 支持文件上传 | +| `https://api.dingtalk.com/v1.0/robot/groupMessages/send` | ✅ 可用 | 成功发送消息 | +| `https://oapi.dingtalk.com/robot/send` | ❌ 失败 | 返回token错误 | + +### 配置信息 + +``` +AppKey: ding4ursdp0l2giat4bj +AppSecret: J0gBicjKiIHoKla7WfKKhRs1Tv8L6Xd5UhW3EVQByF16G7Vn7UUcRhP6u-PBCQNo +RobotCode: ding4ursdp0l2giat4bj +OpenConversationId: cidcjYshXVtKck5LfOO9AqOJg== +``` + +### 成功案例 + +**2026-03-05 18:44** +- 项目:database_analysis_summary.txt +- 文件大小:3,155 bytes +- media_id: @lAjPM2GaR32g2U3OC2pNq84Clg_0 +- msgKey: sampleFile +- API: /v1.0/robot/groupMessages/send +- ProcessQueryKey: lXs/uLRd0YxJVk1x0VyTLfSdY2YKCE1yrGn2vGlQ+GM= +- 状态:成功发送到钉钉群聊 + +### 经验教训 + +1. **不要在OpenClaw环境中直接调用原始钉钉API** + - OpenClaw插件会拦截请求 + - 认证可能不匹配 + +2. **msgKey是关键** + - 文件 → sampleFile + - 图片 → sampleMarkdown + - 视频 → sampleVideo + +3. **msgParam必须是JSON字符串** + - 这是容易出错的地方 + - 必须用`JSON.stringify()`包裹 + +4. **使用正确的API版本** + - v1.0 API是正确选择 + - 旧版API不再有效 + +--- + +## Python环境问题 + +**日期**: 2026-03-05 +**重要性**: ⭐⭐⭐ + +**问题**: PowerShell直接调用Python时遇到路径和编码问题 + +**解决方案**: +1. 设置PATH: `$env:PATH = "F:\pyenv\pyenv-win\pyenv-win\pyenv-win\versions\3.14.2;$env:PATH"` +2. 使用UTF-8编码: `sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')` + +--- + +## Git配置 + +**日期**: 2026-03-05 +**重要性**: ⭐⭐⭐ + +**Git服务器**: git.alicorns.co.jp (Gitea) +**用户**: aitest +**密码**: Aiitest123456 + +**主要仓库**: +- `aitest/office-file-handler.git` +- `aitest/dingtalk-media-sender.git` +- `aitest/workspace.git` + +--- + +## 记录准则 + +1. 只记录重要且会重复用到的信息 +2. 包括成功和失败的经验 +3. 提供可执行的代码示例 +4. 标注日期和重要性 diff --git a/TOOLS.md b/TOOLS.md index cc5744d..12a684c 100644 --- a/TOOLS.md +++ b/TOOLS.md @@ -44,4 +44,87 @@ Skills are shared. Your setup is yours. Keeping them apart means you can update - **robotCode**: ding4ursdp0l2giat4bj - **OpenConversationId**: cidcjYshXVtKck5LfOO9AqOJg== +## DingTalk成功发送的关键配置 + +### Token获取(必须使用GET + URL参数) +```javascript +// ✓ 正确方式 +const tokenUrl = `https://oapi.dingtalk.com/gettoken?appkey=${APP_KEY}&appsecret=${APP_SECRET}`; +const token = await axios.get(tokenUrl).data.access_token; + +// ✗ 错误方式(会失败) +const tokenUrl = "https://oapi.dingtalk.com/gettoken"; +const token = await axios.get(tokenUrl, { + params: {appkey: APP_KEY, appsecret: APP_SECRET} +}).data.access_token; +``` + +### v1.0 API发送消息(成功方式) +```javascript +// API端点 +const url = "https://api.dingtalk.com/v1.0/robot/groupMessages/send"; + +// Headers(关键:包含access_token) +const headers = { + "x-acs-dingtalk-access-token": accessToken, + "Content-Type": "application/json" +}; + +// Body格式 +const body = { + robotCode: ROBOT_CODE, + openConversationId: OPEN_CONVERSATION_ID, + msgKey: "sampleFile", // 或 "sampleMarkdown" + msgParam: JSON.stringify({ // ← 必须是字符串! + mediaId: media_id, + fileName: "file.txt" + }) +}; + +await axios.post(url, body, { headers }); +``` + +### 关键msgKey对应关系 + +| 媒体类型 | msgKey | 说明 | +|---------|--------|------| +| **文件** | `sampleFile` | 发送.txt, .docx, .pdf等文件 | +| **图片** | `sampleMarkdown` | 使用Markdown格式嵌入图片 | +| **视频** | `sampleVideo` | 发送.mp4等视频文件(待测试) | + +### 重要注意事项 + +1. **msgParam必须是字符串** + ```javascript + // ✓ 正确 + msgParam: JSON.stringify({mediaId: "@xxx", fileName: "test.txt"}) + + // ✗ 错误 + msgParam: {mediaId: "@xxx", fileName: "test.txt"} + ``` + +2. **直接调用API会失败于OpenClaw环境** + - OpenClaw的DingTalk插件会拦截所有API请求 + - 直接调用`robot/send`会返回"token is not exist"或"缺少参数 access_token" + - 必须使用v1.0 API + Headers中的access_token + +3. **文件上传API** + ```javascript + // 上传(GET方式获取token已经验证有效) + const uploadUrl = `https://oapi.dingtalk.com/media/upload?access_token=${accessToken}`; + const form = new FormData(); + form.append('media', fs.createReadStream(filePath)); + form.append('type', 'file'); + await axios.post(uploadUrl, form, {headers: form.getHeaders()}); + ``` + +### 成功案例记录 + +**2026-03-05 18:44** - 成功发送database_analysis_summary.txt +- 文件: database_analysis_summary.txt (3,155 bytes) +- media_id: @lAjPM2GaR32g2U3OC2pNq84Clg_0 +- msgKey: sampleFile +- API: /v1.0/robot/groupMessages/send +- ProcessQueryKey: lXs/uLRd0YxJVk1x0VyTLfSdY2YKCE1yrGn2vGlQ+GM= + Add whatever helps you do your job. This is your cheat sheet.