微信发媒体技能
本技能解决了微信渠道发送媒体消息的核心问题:contextToken 持久化。
⚠️ 前提条件:微信渠道必须已登录并处于活动状态。
🔒 安全说明(重要)
为什么需要修改核心文件?
本技能需要修改 ~/.openclaw/extensions/openclaw-weixin/src/messaging/inbound.ts,这是因为:
- 1. contextToken 的生成和存储逻辑在核心扩展中
-
setContextToken() 和
getContextToken() 是内部函数
- 没有公开 API 可以外部调用
- 2. 这是唯一可行的方案
- 微信 API 要求发送消息时必须提供 contextToken
- Token 只在收到用户消息时生成
- 原始设计只缓存在内存,重启即丢失
- 3. 修改是安全可控的
- 只添加了文件读写逻辑,不改变原有功能
- 所有数据存储在用户本地
~/.openclaw/openclaw-weixin/context-tokens/
- 不会外传任何敏感信息
安全最佳实践
CODEBLOCK0
ClawHub 安全警告说明
ClawHub 可能显示 Security: SUSPICIOUS,这是因为:
- - 技能修改了 OpenClaw 核心扩展文件
- 使用了补丁(patch)方式注入
这是误报,本技能:
- - ✅ 不窃取任何数据
- ✅ 不连接外部服务器
- ✅ 所有操作在本地完成
- ✅ 代码完全开源可审计
背景问题
微信渠道的 contextToken(会话上下文令牌)原本只缓存在 gateway 进程内存中,导致:
- - ❌ CLI 命令无法发送媒体消息
- ❌ 自动化脚本无法调用
- ❌ 重启 gateway 后 token 丢失
本技能通过持久化 contextToken 到磁盘文件,解决了这个问题。
使用方式
方法 1:CLI 发送图片
CODEBLOCK1
方法 2:CLI 发送文件
CODEBLOCK2
方法 3:CLI 发送网络图片
CODEBLOCK3
方法 4:Node.js 脚本调用
CODEBLOCK4
方法 5:使用封装脚本
CODEBLOCK5
核心修改
修改文件
INLINECODE5
修改内容
在文件开头添加:
CODEBLOCK6
添加持久化目录和文件路径函数:
CODEBLOCK7
修改 setContextToken 函数(添加磁盘持久化):
CODEBLOCK8
修改 getContextToken 函数(添加磁盘回退):
export function getContextToken(accountId: string, userId: string): string | undefined {
const k = contextTokenKey(accountId, userId);
const val = contextTokenStore.get(k);
logger.debug(
`getContextToken: key=${k} found=${val !== undefined} storeSize=${contextTokenStore.size}`,
);
if (val) return val;
// Fallback to disk
const file = contextTokenFile(accountId, userId);
try {
if (fs.existsSync(file)) {
const data = JSON.parse(fs.readFileSync(file, "utf8"));
logger.debug(`getContextToken: loaded from disk ${file}`);
return data.token;
}
} catch (e) {
logger.warn(`getContextToken: failed to load from disk: ${e}`);
}
return undefined;
}
文件结构
CODEBLOCK10
安装步骤
自动安装(推荐)
CODEBLOCK11
手动安装
- 1. 应用补丁
CODEBLOCK12
- 2. 重启 gateway
CODEBLOCK13
- 3. 发送一条消息触发 token 保存
(任意微信消息即可)
- 4. 验证 token 文件
cat ~/.openclaw/openclaw-weixin/context-tokens/*.json
典型工作流
发送二维码给家人
用户 query 示例:
执行流程:
- 1. 生成二维码图片(
openclaw qr) - 保存二维码到文件
- 调用
openclaw message send --media 发送 - 家人扫码绑定
分享截图
用户 query 示例:
执行流程:
- 1. 确认图片路径
- 确认目标用户
- 发送图片消息
定时发送日报
脚本示例:
#!/bin/bash
# 每天下午 6 点发送日报
IMAGE=/path/to/daily-report.png
openclaw message send \
--channel openclaw-weixin \
--account <account-id> \
--target <user-id> \
--media $IMAGE \
--message "📊 今日日报"
❓ 常见问题 FAQ
Q1: 安装后还是提示 "contextToken is required"?
A: 可能是因为:
- 1. 还没收到过用户消息(token 还没生成)
- gateway 没重启(补丁没生效)
解决:
CODEBLOCK16
Q2: token 文件在哪里?安全吗?
A:
- - 位置: INLINECODE10
- 安全:建议设置权限 INLINECODE11
Q3: 可以发送给多人吗?
A: 可以,循环调用即可:
CODEBLOCK17
Q4: 支持发送视频吗?
A: 支持,但文件大小不能超过 20MB(微信限制)。
Q5: 发送失败怎么排查?
A: 查看日志:
tail -f ~/.openclaw/logs/gateway.log | grep -i weixin
🔧 故障排查
问题 1: 补丁应用失败
症状:
CODEBLOCK19
解决:
CODEBLOCK20
问题 2: gateway 启动失败
症状:
CODEBLOCK21
原因: TypeScript 编译错误
解决:
CODEBLOCK22
问题 3: token 文件为空
症状:
CODEBLOCK23
原因: 还没收到用户消息
解决: 在微信里给机器人发条消息
问题 4: 发送消息超时
症状:
CODEBLOCK24
可能原因:
解决:
- 1. 检查网络连接
- 等待几分钟后重试
- 压缩文件到 20MB 以内
错误处理
| 错误 | 原因 | 解决方案 |
|---|
| INLINECODE12 | 没有收到过用户消息 | 先让用户发一条消息 |
| INLINECODE13 |
图片路径不存在 | 检查文件路径 |
|
media too large | 文件超过 20MB | 压缩文件或换小图 |
|
account not configured | 微信未登录 | 运行
openclaw channels login |
|
patch command not found | 系统没有 patch 工具 | 安装 patch |
|
gateway failed to start | TypeScript 编译错误 | 重新编译扩展 |
技术细节
contextToken 是什么?
INLINECODE19 是微信 API 要求的会话上下文令牌:
- - 每次收到用户消息时生成
- 发送回复时必须原样回传
- 用于关联会话,防止机器人骚扰
为什么需要持久化?
| 方案 | 优点 | 缺点 |
|---|
| 内存缓存(原始) | 安全:重启后失效 | 不便:CLI 无法调用 |
| 磁盘持久化(本技能) |
方便:CLI/脚本都能用 | 需注意文件权限 |
文件格式
CODEBLOCK25
Token 生命周期
- 1. 生成:用户发送消息时
- 存储:内存 + 磁盘双写
- 读取:先查内存,没有再查磁盘
- 过期:建议 30 天清理一次
相关文件
- - 原始实现: INLINECODE20
- 发送逻辑: INLINECODE21
- 媒体上传: INLINECODE22
- Token 存储: INLINECODE23
更新日志
v1.1.0 (2026-03-26)
- - ✅ 添加安全说明章节,解释补丁必要性
- ✅ 添加完整 FAQ 和故障排查指南
- ✅ 补全 scripts 目录(send-image.js, send-file.js)
- ✅ 添加测试脚本 test-token-persistence.sh
- ✅ 优化文档结构和可读性
- ✅ 修复硬编码的 account ID 和 user ID
v1.0.0 (2026-03-23)
- - ✅ 实现 contextToken 磁盘持久化
- ✅ 支持 CLI 发送图片/文件
- ✅ 支持网络图片 URL
- ✅ 添加错误处理和日志
作者
🦆 鸭鸭 (Yaya) - OpenClaw 微信渠道增强
许可证
MIT-0 License (Free to use, modify, and redistribute. No attribution required.)
微信发媒体技能
本技能解决了微信渠道发送媒体消息的核心问题:contextToken 持久化。
⚠️ 前提条件:微信渠道必须已登录并处于活动状态。
🔒 安全说明(重要)
为什么需要修改核心文件?
本技能需要修改 ~/.openclaw/extensions/openclaw-weixin/src/messaging/inbound.ts,这是因为:
- 1. contextToken 的生成和存储逻辑在核心扩展中
- setContextToken() 和 getContextToken() 是内部函数
- 没有公开 API 可以外部调用
- 2. 这是唯一可行的方案
- 微信 API 要求发送消息时必须提供 contextToken
- Token 只在收到用户消息时生成
- 原始设计只缓存在内存,重启即丢失
- 3. 修改是安全可控的
- 只添加了文件读写逻辑,不改变原有功能
- 所有数据存储在用户本地 ~/.openclaw/openclaw-weixin/context-tokens/
- 不会外传任何敏感信息
安全最佳实践
bash
1. 限制 token 文件权限
chmod 600 ~/.openclaw/openclaw-weixin/context-tokens/*.json
2. 定期清理过期 token
find ~/.openclaw/openclaw-weixin/context-tokens/ -mtime +30 -delete
3. 检查 token 文件
cat ~/.openclaw/openclaw-weixin/context-tokens/*.json
ClawHub 安全警告说明
ClawHub 可能显示 Security: SUSPICIOUS,这是因为:
- - 技能修改了 OpenClaw 核心扩展文件
- 使用了补丁(patch)方式注入
这是误报,本技能:
- - ✅ 不窃取任何数据
- ✅ 不连接外部服务器
- ✅ 所有操作在本地完成
- ✅ 代码完全开源可审计
背景问题
微信渠道的 contextToken(会话上下文令牌)原本只缓存在 gateway 进程内存中,导致:
- - ❌ CLI 命令无法发送媒体消息
- ❌ 自动化脚本无法调用
- ❌ 重启 gateway 后 token 丢失
本技能通过持久化 contextToken 到磁盘文件,解决了这个问题。
使用方式
方法 1:CLI 发送图片
bash
openclaw message send \
--channel openclaw-weixin \
--account d72d5b576646-im-bot \
--target o9cq802hhREiOXPlXq_Tgb0MjPTo@im.wechat \
--media /path/to/image.png \
--message 图片说明
方法 2:CLI 发送文件
bash
openclaw message send \
--channel openclaw-weixin \
--account d72d5b576646-im-bot \
--target o9cq802hhREiOXPlXq_Tgb0MjPTo@im.wechat \
--media /path/to/document.pdf \
--message 文件说明
方法 3:CLI 发送网络图片
bash
openclaw message send \
--channel openclaw-weixin \
--account d72d5b576646-im-bot \
--target o9cq802hhREiOXPlXq_Tgb0MjPTo@im.wechat \
--media https://example.com/image.jpg \
--message 网络图片
方法 4:Node.js 脚本调用
javascript
const { execSync } = require(child_process);
function sendWeixinMedia(userId, mediaPath, caption = ) {
const cmd = openclaw message send \\
--channel openclaw-weixin \\
--account \\
--target ${userId} \\
--media ${mediaPath} \\
--message ${caption};
return execSync(cmd, { encoding: utf8 });
}
// 使用示例
sendWeixinMedia(
o9cq802hhREiOXPlXq_Tgb0MjPTo@im.wechat,
/path/to/qr.png,
这是二维码
);
方法 5:使用封装脚本
bash
发送图片
./scripts/send-image.js
[caption]
示例
./scripts/send-image.js o9cq802hhREiOXPlXq_Tgb0MjPTo@im.wechat ./qr.png 扫码绑定
核心修改
修改文件
~/.openclaw/extensions/openclaw-weixin/src/messaging/inbound.ts
修改内容
在文件开头添加:
typescript
import fs from node:fs;
import path from node:path;
添加持久化目录和文件路径函数:
typescript
// Persist context tokens to disk for CLI access
const CONTEXTTOKENDIR = path.join(process.env.HOME!, .openclaw/openclaw-weixin/context-tokens);
try { fs.mkdirSync(CONTEXTTOKENDIR, { recursive: true }); } catch {}
function contextTokenFile(accountId: string, userId: string): string {
const safeId = accountId.replace(/[@:]/g, _);
const safeUser = userId.replace(/[@:]/g, _);
return path.join(CONTEXTTOKENDIR, ${safeId}${safeUser}.json);
}
修改 setContextToken 函数(添加磁盘持久化):
typescript
export function setContextToken(accountId: string, userId: string, token: string): void {
const k = contextTokenKey(accountId, userId);
logger.debug(setContextToken: key=${k});
contextTokenStore.set(k, token);
// Persist to disk
const file = contextTokenFile(accountId, userId);
try {
fs.writeFileSync(file, JSON.stringify({
accountId,
userId,
token,
savedAt: new Date().toISOString()
}, null, 2));
logger.debug(setContextToken: persisted to ${file});
} catch (e) {
logger.warn(setContextToken: failed to persist to disk: ${e});
}
}
修改 getContextToken 函数(添加磁盘回退):
typescript
export function getContextToken(accountId: string, userId: string): string | undefined {
const k = contextTokenKey(accountId, userId);
const val = contextTokenStore.get(k);
logger.debug(
getContextToken: key=${k} found=${val !== undefined} storeSize=${contextTokenStore.size},
);
if (val) return val;
// Fallback to disk
const file = contextTokenFile(accountId, userId);
try {
if (fs.existsSync(file)) {
const data = JSON.parse(fs.readFileSync(file, utf8));
logger.debug(getContextToken: loaded from disk ${file});
return data.token;
}
} catch (e) {
logger.warn(getContextToken: failed to load from disk: ${e});
}
return undefined;
}
文件结构
weixin-send-media/
├── SKILL.md # 本文件 - 完整文档
├── README.md # 快速入门
├── CHANGELOG.md # 更新日志
├── scripts/
│ ├── send-image.js # 发图片脚本
│ ├── send-file.js # 发文件脚本
│ └── export-context-token.js # 导出 token 工具
├── patches/
│ └── inbound.ts.patch # 补丁文件
├── references/
│ └── api-docs.md # API 文档
└── tests/
└── test-token-persistence.sh # 测试脚本
安装步骤
自动安装(推荐)
bash
npx clawhub@latest install weixin-send-media
手动安装
- 1. 应用补丁
bash
cd ~/.openclaw/workspace/skills/weixin-send-media
./install.sh
- 2. 重启 gateway
bash
openclaw gateway restart
- 3. 发送一条消息触发 token 保存
(任意微信消息即可)
- 4. 验证 token 文件
bash
cat ~/.openclaw/openclaw-weixin/context-tokens/*.json
典型工作流
发送二维码给家人
用户 query 示例:
执行流程:
- 1. 生成二维码图片(openclaw qr)
- 保存二维码到文件
- 调用 openclaw message send --media 发送
- 家人