20 KiB
| name | description |
|---|---|
| send-complex-message | 在当前微信会话中发送纯文本、引用回复或群聊艾特/@/提及消息,支持用昵称、曾用名、外号或微信 ID 查找群成员。也可按时间、关键词、消息类型和发送人查询当前群聊或私聊最近24小时内的聊天记录。用户要求发送、艾特、引用回复或查找近期历史消息时使用,可在发送后结束当前 Agent 对话。 |
Send Complex Message Skill
描述
本技能是当前微信会话中纯文本、艾特和引用回复的统一发送入口。仅艾特时不需要正文;引用消息必须有正文,也可以附带成员艾特参数。艾特支持指定一个或多个成员,也支持微信原生的 @所有人。
技能脚本位于 scripts/send_complex_message.py。加上 --query-history 时只查询当前会话历史消息;发送模式调用客户端的 /message/send/refermessage 接口。refer_message_id 是可选参数:不传时由客户端调用普通文本消息方法,传入时发送引用消息。
按名称艾特成员时,先查记忆找到对应的微信 ID,再发送消息。@所有人使用客户端协议值 notify@all,不要把 @昵称 或 @所有人 当普通正文拼接。
本技能不带引用 ID 时通过普通文本消息实现原生艾特,支持只艾特而不附加正文。带引用 ID 时,客户端接收 at 并显示艾特名称,但尚未实现引用消息中的原生艾特提醒,不能把引用发送成功表述为已经提醒成员。
触发条件
- 用户要求将一段话分成多条发送。
- 用户要求「引用这条消息回复」「引用我刚才的话说收到」「回复我引用的那条消息」。
- 需要发送微信原生引用回复,而不是在普通文本中复述原文。
- 需要艾特、@、提及某个群成员或多个群成员。
- 用户要求「帮我艾特下 xxx」「@ 一下 xxx」「提一下 xxx 和 yyy」。
- 用户要求「@所有人」「提醒全体成员」「通知群里所有人」。
- 需要在群聊里点名提醒某人。
- 用户要求查询、搜索当前群聊或私聊的近期聊天记录,或需要查找历史消息的
messages.id以便引用,特别注意的是,如果需要查询最近发送的图片、视频等文件,你应该使用 find-recent-chat-media 这个技能,本技能(send-complex-message) 不提供下载文件的能力。 - 其它时候不应该使用本技能
不带艾特的纯文本和引用回复可用于私聊和群聊;只要提供艾特参数,ROBOT_FROM_WX_ID 就必须是群聊 ID。
历史聊天记录查询
使用 --query-history 进入只读查询模式。会话固定取系统注入的 ROBOT_FROM_WX_ID:群聊只能查询该群,私聊只能查询与当前好友的私聊,包含该会话双方的收发消息。查询同时限定 from_wxid 和 is_chat_room,不能按发送人跨群或跨私聊搜索,也不能修改会话环境变量来切换查询对象。
所有时间范围必须落在执行查询时的最近 24 小时内。24 小时是最大回溯范围,不是固定查询时长;可以查询最近半小时、2 小时,或这 24 小时内任意更短的起止区间。超过上限、落在过去更早日期、包含未来或起止倒置的时间范围会报错,不会静默扩大或改写用户指定的范围。
| 查询参数 | 说明 |
|---|---|
--query-history |
必须提供,不能与发送参数或 --ended 同用 |
--hours <小时数> |
最近多少小时,支持小数,0 < hours <= 24,精度为整秒且至少 1 秒;如 0.5 表示最近 30 分钟 |
--start-time <时间> |
起点,支持 Unix 秒或北京时间 YYYY-MM-DD HH:mm[:ss] |
--end-time <时间> |
终点,格式同上;与起点均为包含边界 |
--keyword <关键词> |
对 content 和 display_full_content 做包含匹配;可重复,多个关键词必须全部命中,%、_、反斜杠按字面量匹配 |
--message-type <类型> |
消息类型编号或名称,可重复,多个类型匹配任一即可;省略时查询所有类型 |
--app-msg-type <子类型编号> |
可重复,如 57 引用、6 文件、5 链接;自动限定消息类型为 49,不能与其他消息类型组合 |
--sender-wxid <微信ID> |
只在当前会话内按发送人微信 ID 精确过滤 |
--limit <条数> |
单页条数,默认 50,范围 1..200 |
--offset <偏移量> |
分页偏移量,默认 0,必须非负 |
--hours 与 --start-time/--end-time 互斥。未指定时间参数时默认查询最近 24 小时;只传起点时终点为现在,只传终点时起点为现在减 24 小时。
类型名称支持 text=1、image=3、voice=34、card=42、video=43、emoji=47、location=48、app=49、system=10000、recall=10002,其他类型可直接传数字编号。不同类别的过滤条件同时生效。
查询最近 30 分钟包含“安排”的文本消息:
python3 scripts/send_complex_message.py --query-history --hours 0.5 --keyword '安排' --message-type text
查询最近 6 小时某人发送的引用消息:
python3 scripts/send_complex_message.py --query-history --hours 6 --sender-wxid 'wxid_zhangsan' --app-msg-type 57 --limit 20
按明确起止时间查询(先根据用户指定的区间设置两个 Unix 秒时间戳,且必须在最近 24 小时内):
python3 scripts/send_complex_message.py --query-history --start-time "$START_TIMESTAMP" --end-time "$END_TIMESTAMP" --message-type image --message-type video
查询成功时输出 JSON 对象,包含当前会话、实际 start_time/end_time(Unix 秒)、count、has_more、next_offset 和 messages。消息按 created_at DESC, id DESC 排列,每条包含数据库主键 id、会话及发送人微信 ID、消息类型/子类型、正文、显示内容、撤回标记和发送时间(Unix 秒)。messages: [] 表示没有匹配记录。has_more: true 时可以保留过滤条件,使用返回的 next_offset 作为 --offset 继续查询,不能把单页结果表述为全部记录。
查询不发送微信消息、不输出 ended,也不要求配置客户端端口。需要引用查询结果时,先根据消息内容、发送人和时间确定目标,再单独以返回的 id 调用发送模式。不能绕过脚本直接查询其他会话或更早记录。
发送入参规范
refer_message_id 不全局必填,只在用户明确要求引用时传入,值为 messages.id。仅文本、仅艾特时省略该参数,不查询引用目标,也不从环境变量自动补齐引用 ID。
| 发送方式 | 艾特参数 | refer_message_id |
正文 content |
|---|---|---|---|
| 仅艾特 | 指定成员或 all: true |
不传 | 可省略或为空 |
| 仅文本 | 不传 | 不传 | 必填且不能是纯空白 |
| 仅引用 | 不传 | 传入 messages.id |
必填且不能是纯空白 |
| 引用并艾特 | 指定成员或 all: true |
传入 messages.id |
必填且不能是纯空白 |
不引用时也支持正文加艾特。下方 schema 仅描述发送模式,没有全局必填字段,组合校验由 schema 和脚本共同约束;查询模式使用上方查询参数。
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [],
"properties": {
"mention": {
"type": "string",
"description": "用户给出的成员名称。先查记忆,将找到的微信 ID 填入 mention_wxids;查不到或记忆工具不可用时,用此参数查当前群成员。"
},
"mentions": {
"type": "array",
"items": {
"type": "string"
},
"description": "多个成员名称,用法同 mention。"
},
"mention_wxids": {
"type": "array",
"items": {
"type": "string",
"pattern": "\\S"
},
"description": "用户明确提供或从当前会话工具结果唯一确认的微信 ID。脚本只按微信 ID 校验当前群成员,可与 mention/mentions 混用,不得填入昵称或猜测 ID。"
},
"all": {
"type": "boolean",
"description": "是否 @所有人。用户明确要求 @所有人或通知全体成员时设为 true,不能与 mention/mentions/mention_wxids 同时使用。"
},
"content": {
"type": "string",
"description": "要发送的文本内容。纯文本和引用回复必须提供非空正文;只艾特不附加正文时可以省略或为空字符串。"
},
"refer_message_id": {
"type": "integer",
"minimum": 1,
"maximum": 9223372036854775807,
"description": "可选,仅引用时传入被引用消息的数据库主键 messages.id。仅文本、仅艾特时省略;传入后 content 必须为非空白文本。"
},
"ended": {
"type": "boolean",
"description": "是否结束当前对话。当 Agent 已经完成消息发送、要说的话已说完、要做的事已做完时,设置为 true。"
}
},
"anyOf": [
{
"required": ["content"],
"properties": { "content": { "pattern": "\\S" } }
},
{
"required": ["mention"],
"properties": { "mention": { "pattern": "\\S" } }
},
{
"required": ["mentions"],
"properties": {
"mentions": { "contains": { "type": "string", "pattern": "\\S" } }
}
},
{
"required": ["mention_wxids"],
"properties": {
"mention_wxids": { "minItems": 1 }
}
},
{
"required": ["all"],
"properties": { "all": { "const": true } }
}
],
"dependencies": {
"refer_message_id": {
"required": ["content"],
"properties": { "content": { "minLength": 1, "pattern": "\\S" } }
}
},
"additionalProperties": false
}
对应命令行参数:
--refer-message-id <messages.id>可选,传入后发送引用消息,必须同时提供非空--content--mention <名称>仅在记忆工具不可用或无候选时传入原名称,脚本按当前昵称或备注匹配,可重复传入--mentions <JSON数组>与--mention相同,用于一次传入多个名称--mention-wxid <微信ID>对应mention_wxids中的一个 ID,可重复传入,也可与名称参数混用;只精确匹配微信 ID--all(也支持--mention-all)可选,用于真正 @所有人,不能与任何指定成员参数同用--content <文本内容>纯文本和引用消息必填,单独艾特时可省略或为空--ended可选标志。当 Agent 已完成消息发送、要说的话已说完时传入。
引用消息的选择
- 仅在用户要求引用时选择消息,传给脚本的引用 ID 只使用
messages.id。 - 引用当前用户触发本次对话的消息时,使用环境变量
ROBOT_MESSAGE_ID中的消息主键。 - 用户要求回复他引用的原消息时,使用
ROBOT_REF_MESSAGE_ID中的消息主键;该值为空或0表示没有引用目标,不能改为引用当前消息。 - 引用其他历史消息时,使用本脚本
--query-history,在当前会话最近 24 小时内按时间、关键词、类型或发送人查找,按用户描述确认原消息后取id。不要使用msg_id、client_msg_id或 XML 中的svrid。 - 引用目标不明确时先确认,不能猜测消息 ID。不要因为上下文存在引用消息就自动发送引用回复。
- 脚本接收明确的
--refer-message-id,不会自动选择最近一条消息;仅引用且不指定成员时不需要查询成员表。
成员匹配规则
发送前,脚本会检查当前群聊 ROBOT_FROM_WX_ID 对应的 chat_room_members,只接受 is_leaved 为空或 0 的成员。--all 不查询成员表。
--mention-wxid:只按wechat_id精确匹配;不存在、已退群或只存在于其他群时失败,不回退到昵称匹配。--mention/--mentions:先查当前备注和昵称是否与输入完全相同,找不到再查是否包含输入名称;找到多人就停止。仅在记忆工具不可用或查不到人时使用。如果记忆已经查出多个候选,应请用户明确要艾特谁。
查找要艾特的成员
- 用
search_chat_room_memory查询用户给出的名称。如果用户已经提供微信 ID,或当前会话的工具结果已经确认了微信 ID,直接用--mention-wxid发送。 - 将用户给出的原名称放入
member_names,将原始艾特请求放入query;每次最多查询 10 个名称,超出时分批。只有search_chat_room_memory不可用而search_memory可用时,才使用后者并同样传入member_names和query。 - 检查
name_resolutions中每个名称的candidates。只有一个候选且current_in_room为true时,取其wechat_id。名称记录的is_active: false表示旧称呼,是否退群看current_in_room。查到多人或对方已退群时,停止发送并请用户明确要艾特谁。 - 两个记忆工具都不可用,或某个名称没有候选时,把原名称传给
--mention/--mentions,查当前群成员。仍然找不到或找到多人时,请用户补充名称或微信 ID。返回no_reliable_match: true时,检查每个名称的候选,分别处理未找到和重名的情况。 - 用
--mention-wxid传入查到的微信 ID。脚本会再次检查对方是否还在群内。艾特多人时,保留全部收件人、正文和引用参数,所有人都查清楚后一起发送。
例如用户说“帮我艾特 xxxx,提醒他看群公告”,先调用 search_chat_room_memory:
{"member_names": ["xxxx"], "query": "帮我艾特 xxxx,提醒他看群公告"}
仅当返回唯一且仍在群内的候选,并且其 wechat_id 为 wxid_example 时执行(将示例 ID 替换为实际结果):
python3 scripts/send_complex_message.py --mention-wxid 'wxid_example' --content '请看群公告' --ended
发送执行步骤
- 判断用户需要纯文本、仅艾特、引用回复,还是引用时同时艾特。纯文本和引用回复必须准备非空正文
content;引用时按上面的规则确定refer_message_id。 - 按上面的步骤查找成员,把确认的微信 ID 写入
mention_wxids;记忆工具不可用或查不到人时使用mention/mentions。@所有人时设置all: true并使用--all。 - 在该技能目录执行脚本,例如:
以下指定成员的示例均假设已确认其微信 ID 为 wxid_zhangsan,执行时使用实际 ID。仅艾特某人,不附加正文:
python3 scripts/send_complex_message.py --mention-wxid 'wxid_zhangsan' --ended
仅发送文本,不艾特、不引用:
python3 scripts/send_complex_message.py --content '收到,我来处理' --ended
引用当前用户消息回复:
python3 scripts/send_complex_message.py --refer-message-id "$ROBOT_MESSAGE_ID" --content '收到,我来处理' --ended
回复用户引用的原消息(先确认 ROBOT_REF_MESSAGE_ID 大于 0):
python3 scripts/send_complex_message.py --refer-message-id "$ROBOT_REF_MESSAGE_ID" --content '同意这个安排'
引用已确认的历史消息并附带成员显示(当前客户端不产生原生艾特提醒):
python3 scripts/send_complex_message.py --refer-message-id 12345 --mention-wxid 'wxid_zhangsan' --content '请看一下这个'
艾特群成员并附加正文:
python3 scripts/send_complex_message.py --mention-wxid 'wxid_zhangsan' --content '看一下这个'
用户要求 @所有人时传 --all,不要把“所有人”当成员昵称查询:
python3 scripts/send_complex_message.py --all --content '请大家查看群公告'
当 Agent 认为任务已完成、对话可以结束时,加上 --ended 标志:
python3 scripts/send_complex_message.py --mention-wxid 'wxid_zhangsan' --content '看一下这个' --ended
- 脚本会检查所有指定成员,任何一个人找不到或有重名,整条消息都不发送。如果还没查过记忆,先查记忆;查过仍无法确定是谁,请用户补充信息。
--all时跳过数据库查询,使用at: ["notify@all"]。 - 脚本通过
X-Private-Token请求头传递环境变量ROBOT_CLIENT_PRIVATE_TOKEN,统一调用POST http://127.0.0.1:{ROBOT_WECHAT_CLIENT_PORT}/api/v1/robot/message/send/refermessage。请求体包含to_wxid、content、at;只有引用时才添加refer_message_id。不引用时,客户端直接转入普通文本发送方法;纯文本的at为空数组,仅艾特的content为空字符串。
to_wxid 固定取当前会话 ROBOT_FROM_WX_ID。技能在客户端内执行,无需携带管理后台的机器人实例 query id。
校验规则
ROBOT_FROM_WX_ID必须配置;带艾特时必须以@chatroom结尾。- 没有艾特且正文为空或纯空白时不能发送;空的成员名称、空成员数组和
all: false都不算有效艾特。 - 仅艾特时,
content可直接为空,由客户端根据at生成艾特正文,不添加额外话语。 - 传入
refer_message_id时,必须是 int64 范围内的正整数,引用正文不能是空字符串或纯空白;不引用时直接省略 ID,不能为满足校验而猜测或自动填入消息 ID。 --all必须独占,不能再指定成员。- 每个要艾特的人都必须能在当前群内匹配到未退群成员。
- 如果同一个微信 ID 被多个昵称或 ID 参数命中,只会艾特一次。
依赖安装
- 查询历史记录或指定成员、需要查询数据库时,脚本会自动创建虚拟环境并安装依赖,使用
MYSQL_HOST、MYSQL_PORT、MYSQL_USER、MYSQL_PASSWORD连接ROBOT_CODE对应的机器人数据库;纯文本、仅引用或--all不需要安装数据库依赖。 - 如需手动重新安装,可执行:
python3 scripts/bootstrap.py
ended 行为
- 当
--ended传入且客户端返回业务状态code: 200时,脚本在正常输出末尾追加打印独立一行ended。 ended字符串必须位于输出的最末尾,前面不能跟其他字符。- Agent 检测到输出以
ended结尾时,会自动退出 Agent 循环。
回复要求
- 成功时,脚本输出「文本消息发送成功」「引用消息发送成功」「艾特消息发送成功」或「艾特所有人消息发送成功」,表示消息已通过客户端接口直接发送,不要再重复发送正文。
- HTTP 200 不等于业务成功,必须检查响应中的
code。当前引用接口成功时可能返回data: null,不能因没有消息对象重发。 - 如果传入
--ended,输出末尾会追加ended,Agent 会自动结束对话。 - 失败时不输出
ended。找不到成员时,按上面的步骤查询;仍无法确定是谁就说明原因。引用消息不存在、原消息内容不完整或原发送人不存在时,按客户端错误说明原因;请求超时或发送结果不确定时,不自动重试,以免重复发送。