本技能定义了飞书机器人之间的双层消息通信协议,解决机器人之间无法相互@触发 mention 事件的问题。
机器人主动 @另一个机器人(或多人)时,需要发送两条消息:
格式说明【发送者->接收者1,接收者2...】:
多人场景:
示例(@两个人):
第1步:用机器人身份发送(message 工具)
<at user_id="接收者ID">接收者</at> 📌【发送者->接收者】📌
消息正文
第2步:用用户身份发送(feishu_im_user_message send)
两种格式可选:
格式1:富文本 post
内容:{"zh_cn":{"content":[[{"tag":"at","user_id":"ou_接收者ID","name":"接收者"},{"tag":"text","text":" 📌【发送者->接收者】或【发送者->接收者1,接收者2...】📌"}]]}}
格式:post(必须使用 emoji 📌,[图钉] 不会显示)
格式2:纯文本 text(推荐)
内容:<at user_id="ou_接收者ID">接收者</at> [图钉]【发送者->接收者】或【发送者->接收者1,接收者2...】[图钉]
格式:text(使用飞书占位表情 [图钉] 或 emoji 📌 都可以)
维护映射文件,按群分组记录用户名和用户 ID 的对应关系。
文件位置: ~/.openclaw/workspace/feishu-user-map.md
格式:
## 群组:龙虾池塘 (chat_id)
| 用户名 | 用户ID (open_id) | 类型 | 更新时间 |
|--------|------------------|------|----------|
| saber | ou_xxx | bot | 2026-03-14T09:00:00+08:00 |
| Excalibur | ou_xxx | bot | 2026-03-14T09:00:00+08:00 |
| Qilin | ou_xxx | bot | 2026-03-14T09:00:00+08:00 |
## 全局配置
- **ID过期时间**:21600 秒(6小时)
第1条(机器人身份):
<at user_id="接收者ID">接收者</at> 📌【发送者->接收者】📌
消息正文
第2条(用户身份):
{"zh_cn":{"content":[[{"tag":"at","user_id":"ou_qilin","name":"Qilin"},{"tag":"text","text":" 📌【发送者->Qilin】📌"}]]}}
// 使用 message 工具发送
await message({
action: 'send',
channel: 'feishu',
message: '<at user_id="接收者ID">接收者</at> 【发送者->接收者】或【发送者->接收者1,接收者2...】\n消息正文',
target: 'chat:群ID'
});
// 使用 feishu_im_user_message 发送富文本
// 注意:[📌] 用 emoji 字符 📌 代替
await feishu_im_user_message({
action: 'send',
msg_type: 'post',
content: '{"zh_cn":{"content":[[{"tag":"at","user_id":"ou_接收者ID","name":"接收者"},{"tag":"text","text":" 📌【发送者->接收者】或【发送者->接收者1,接收者2...】📌"}]]}}',
receive_id: '群ID',
receive_id_type: 'chat_id'
});
当收到 mention 事件时:
优先检查: 是否有符合以下条件的消息?
@接收者 和 📌【发送者->接收者】或【发送者->接收者1,接收者2...】📌如果有: 解析引用指向的第一条消息,获取【】里的发送者
如果没有: 往上回溯,找到最近一条满足:
第1步(机器人身份):
<at user_id="原发送者ID">原发送者</at> 【接收者->原发送者】
回复内容
第2步(用户身份):
{"zh_cn":{"content":[[{"tag":"at","user_id":"ou_原发送者ID","name":"原发送者"},{"tag":"text","text":" 📌【接收者->原发送者】📌"}]]}}
(引用第1条消息)
发送方(saber):
```
saber有事找你
```
```
{"zh_cn":{"content":[[{"tag":"at","user_id":"ou_qilin","name":"Qilin"},{"tag":"text","text":" 📌【Excalibur->Qilin】📌"}]]}}
(引用第1条)
```
接收方(Qilin):
@Qilin 和 📌【Excalibur->Qilin】📌```
好的,什么事?
```
```
{"zh_cn":{"content":[[{"tag":"at","user_id":"ou_excalibur","name":"Excalibur"},{"tag":"text","text":" 📌【Qilin->Excalibur】📌"}]]}}
(引用第1条)
```
配置参数:
刷新逻辑:
刷新时机:
在群里发送:查看并记录群的成员名称列表 [数量]
// 根据群组和用户名查找信息
function getUserInfoByGroup(chatId, username) {
const groupMap = loadUserMapByGroup(chatId);
return groupMap[username]; // 返回 { id: "ou_xxx", type: "user|bot", updatedAt: "ISO时间" }
}
// 根据群组和ID查找用户名
function getUserNameByGroup(chatId, userId) {
const groupMap = loadUserMapByGroup(chatId);
for (const [name, info] of Object.entries(groupMap)) {
if (info.id === userId) return name;
}
return null;
}
// 检查ID是否过期,过期则刷新
async function getUserIdWithRefresh(chatId, username) {
const userInfo = getUserInfoByGroup(chatId, username);
if (!userInfo) return null;
const expireSeconds = getExpireSeconds(); // 默认21600
const now = new Date();
const updated = new Date(userInfo.updatedAt);
const diffSeconds = (now - updated) / 1000;
if (diffSeconds > expireSeconds) {
// ID过期,需要刷新
const newId = await refreshUserIdFromHistory(chatId, username);
if (newId) {
updateUserId(chatId, username, newId);
return newId;
}
}
return userInfo.id;
}
// 从历史消息中刷新用户ID
async function refreshUserIdFromHistory(chatId, username) {
// 获取最近的消息,找到该用户发送的消息
const messages = await feishu_im_user_get_messages({
chat_id: chatId,
page_size: 50
});
for (const msg of messages) {
if (msg.sender.name === username && msg.mentions) {
for (const mention of msg.mentions) {
if (mention.name === username) {
return mention.id;
}
}
}
}
return null;
}
本 skill 中所有示例都是示例,不能直接使用!
ou_接收者ID 这个值接收者 这个名字user_id(不是 id)在 @标签中共 2 个版本