本版新增功能
本文记录本 Fork 相对上游 Gensokyo 的主要代码行为。
范围说明:
| 标记 | 含义 |
|---|---|
- | 通用能力,具体可用性取决于调用的 Action 和 QQ API 限制 |
私聊 (C2C) | QQ C2C 单聊 |
q群 (Group Chat) | QQ 群 |
事件
| Handler | 范围 | 行为 |
|---|---|---|
GroupMessageEventHandler | q群 (Group Chat) | 接收 GROUP_MESSAGE_CREATE 全量群消息(含 @Bot 的,其中 @Bot 始终剥离)。 |
GroupMemberAddEventHandler | q群 (Group Chat) | GROUP_MEMBER_ADD 转 notice.group_increase。 |
GroupMemberRemoveEventHandler | q群 (Group Chat) | GROUP_MEMBER_REMOVE 转 notice.group_decrease。 |
GroupJoinRequestEventHandler | q群 (Group Chat) | GROUP_JOIN_REQUEST 转 request.group(sub_type=add,flag 为 join_request_id);需群管理员身份。 |
C2CMsgRejectHandler | 私聊 (C2C) | 用户关闭 C2C 主动消息推送。 |
C2CMsgReceiveHandler | 私聊 (C2C) | 用户开启 C2C 主动消息推送。 |
FriendAddEventHandler / FriendDelEventHandler | 私聊 (C2C) | 好友关系事件(request.friend + notice.friend_add / notice.friend_decrease)。 |
text_intent 只注册写入的 handler。遇到 4014: disallowed intents 时可开启:
suppress_disallowed_intents: true该开关在 Identify 阶段自动屏蔽 GroupMemberAdd/Remove、GroupMessageCreate 等容易被拒绝的 intent。GroupMessageEventHandler 仅当无其他群组 handler 注册时才会被屏蔽;若同时注册了 GroupATMessageEventHandler 等 handler,则保留 intent 并复用。不会自动启用对应 handler。
API
| Action | 范围 | 行为 |
|---|---|---|
get_avatar | - | 获取 QQ 头像直链。 |
get_robot_share_link | - | 获取机器人分享链接(以 notice 事件推送)。 |
send_private_msg_wakeup | 私聊 (C2C) | 发送带 is_wakeup=true 的 C2C 召回消息。 |
put_interaction | - | 回复按钮交互结果。 |
delete_group_msg | q群 (Group Chat) | 撤回指定用户在群内的消息。 |
set_group_card | - | QQ 官方 API 未提供设置群名片接口,明确失败回执(retcode 100),不做假成功。 |
set_group_add_request | q群 (Group Chat) | 入群申请审批:真实调用 QQ v2 审批接口(approve 映射 op,flag 为 join_request_id)。与 [CQ:set_group,action=add_request] 共享实现底层。 |
get_group_info | q群 (Group Chat) | 获取群信息:真实调用 QQ v2 群信息接口,返回群名/简介/成员数。 |
set_group_ban / set_group_whole_ban | q群 (Group Chat) | 群禁言:set_group_ban 按官方增量语义提交(members[{op:add,member_openid,mute_expire_at(RFC3339)}];解禁 op:del + 空 mute_expire_at);set_group_whole_ban 固定失败——官方 POST /restrict_chat_setting 请求体只有 members,无全员禁言写入字段,API 路径返回 retcode=100 + 「官方未提供全员禁言写入字段,仅支持按成员禁言」,不再提交任何请求。与 [CQ:set_group] 共享实现底层。 |
get_group_join_request_list | q群 (Group Chat) | 入群申请列表:请求 cursor(string,优先)/ limit(默认 20、上限 50),next_index(int)为兼容回退;响应 list + next_cursor,保留 join_requests / next_index 兼容别名。返回的 group_id/user_id/flag 可直接回传审批。 |
get_group_bot_state | q群 (Group Chat) | 机器人群内状态:新字段 joined_at(RFC3339 原样)/allow_proactive_msg/recv_msg_setting/member_role 与官方一致;旧字段 join_time(由 joined_at 解析的秒级时间戳)/can_push/push_msg_setting/role 保留为别名。 |
join_approval_strategy_create / _list / _update / _execute / _whitelist / _delete | - | 入群自动审批策略全生命周期管理(对应 QQ v2 策略接口)。 |
get_group_member_list | q群 (Group Chat) | 群成员列表:真实调用 QQ v2 接口,游标分页全量拉取(单页≤30、安全上限 100 页),返回 join_time/role/nickname 真实数据;官方接口不可用时回退 idmap-pro 本地缓存。 |
get_group_member_info | q群 (Group Chat) | 群成员信息:真实调用 QQ v2 接口,失败时回退中性值。 |
set_group_kick | q群 (Group Chat) | 批量移出群成员(单批≤20,user_id/user_ids 二选一、可合并去重),可同步拉黑。与 [CQ:set_group,action=kick] 共享实现底层。 |
get_group_member_blacklist / set_group_member_blacklist | q群 (Group Chat) | 群黑名单查询 / 增删(单批≤20)。 |
get_custom_menu / set_custom_menu | 私聊 (C2C) | C2C 自定义菜单读取 / 设置(覆盖式,仅 C2C 全局生效)。 |
get_panel_list / create_panel / get_panel / set_panel / delete_panel / set_panel_target | - | 指令面板管理(列表/创建/详情/更新/删除/关联对象增删),支持 c2c/group/channel/dm 场景。 |
/metrics | - | Prometheus 文本指标。 |
假数据诚实化(2026-09)
QQ 官方 API 不提供的字段/能力不再伪造假值:
- 发送失败 / 审核中回执的
message_id由固定假值改为0(message_parser.go)。 set_group_card:官方无此接口,由假成功改为明确失败(retcode 100)。get_group_list:group_create_time置 0;逐群拉取真实群名/简介/成员数,get_g_list_delay(毫秒)控制逐群节流。get_status:包收发/断线/丢包统计置 0(官方无对应接口),online/good保持 true,消息收发计数仍为 botstats 真实数据。mark_msg_as_read:官方无已读上报接口,保持空实现仅回执 ok。get_group_member_list:官方路径弱字段level/area/title返回空字符串(本地缓存回退路径仍为 "0")。
消息与 CQ 行为
q群 to_me
范围:q群 (Group Chat)
q群消息事件新增 to_me 字段:
GROUP_AT_MESSAGE_CREATE固定为true。GROUP_MESSAGE_CREATE根据Mentions[].IsYou判断。
非自身 @
范围:q群 (Group Chat)
GROUP_MESSAGE_CREATE 中:
@bot始终从文本中剥离(通过Mentions[].OpenID匹配<@OpenID>/<@!OpenID>),并设置to_me=true。不依赖remove_at配置,因全量群消息中的 @ 只是原文一部分,而非GROUP_AT_MESSAGE_CREATE的专门 @ 事件。@其他人转成[CQ:at,qq=<虚拟ID>],同时建立 idmap 映射。
出站 @
范围:q群 (Group Chat)
出站 [CQ:at,qq=<虚拟ID>] 根据消息类型做不同处理:
- 纯文本消息:通过
resolvePlainTextAtMentions将[CQ:at,qq=数字]替换为@用户名(带空格),用户名来自idmap.GetUserName缓存(入站时自动缓存,10 分钟 TTL)。 - Markdown 消息:通过
ResolveMarkdownAtMentions反查 OpenID,转换为<qqbot-at-user id="OpenID" />,整个messageText(含<qqbot-at-user>标签和文本内容)合并到 Markdown 内容头部,确保文本不丢失。 - 图文混合消息(msg_type=7):同样通过
resolvePlainTextAtMentions转换文本段中的[CQ:at]。覆盖send_group_msg、send_group_msg_raw、send_private_msg的图文混合路径(2026-08 修复,此前漏处理导致 CQ 码原文显示)。 - 图文混合消息走 Markdown 路径(auto_md,msg_type=2):当图文混合触发
auto_md(transmd=true)时,messageText在塞进 Markdown 参数前经过ResolveMarkdownAtMentions转换为<qqbot-at-user>标签。覆盖共用auto_md的send_group_msg、send_group_msg_raw(2026-08 修复,此前auto_md把含[CQ:at]的文本直接塞进 Markdown 参数,导致 QQ 官方 Markdown 渲染把[CQ:at,qq=数字]当纯文本显示,变形为[CO:at,qq=数字])。
纯文本出站: [CQ:at,qq=123213]你好 → @张三 你好
Markdown 出站: [CQ:at,qq=123213]你好 → <qqbot-at-user id="OpenID"/>你好(合并到 markdown 内容头部)
图文混合 Markdown 出站: 图片[CQ:at,qq=123213]你好 → 图片<qqbot-at-user id="OpenID"/>你好is_full_group_message 字段
范围:q群 (Group Chat)
群消息事件新增 is_full_group_message 布尔字段,标识消息是否来自全量(非@)群聊通道:
| 来源 | is_full_group_message | to_me |
|---|---|---|
GROUP_MESSAGE_CREATE(全量群聊) | true | 取决于是否提及机器人 |
GROUP_AT_MESSAGE_CREATE(@机器人) | 不出现(false) | true |
该字段用于区分消息的底层事件来源,与 to_me 字段互补。to_me 表示消息是否 @ 了机器人,is_full_group_message 表示消息是否来自全量监听通道。
Markdown 图片
范围:q群 (Group Chat)
Markdown 内容中的 :
- HTTP(S) URL 保留。
- 本地路径或
file://路径会上传到 QQ CDN 后替换为直链。
[CQ:reply]
范围:-
send_group_msg / send_private_msg 会从文本中提取 [CQ:reply,id=<虚拟消息ID>],尝试通过 idmap 缓存反查真实 QQ message_id,再写入 message_reference 和 msg_id。两个 Handler 均已支持,确保 v2 API 正确识别为回复消息。QQ q群 API 可能接受但不显示引用样式。
REFIDX 引用(v013 新增):引用非机器人发的消息时,message_reference.message_id 必须使用事件 message_scene.ext[] 中的 REFIDX_* 索引(官方 Go SDK 未暴露,Gensokyo 自扩展)。入站时 Processor 从 message_scene.ext[].msg_idx= 提取并存储 消息ID→REFIDX 映射(echo 内存缓存,30min 清理);出站反查时若目标消息关联了 REFIDX 则优先使用,无 REFIDX(机器人自己消息 / 老消息)回退普通消息 ID,MsgID 始终为普通消息 ID。
覆盖路径(按优先级):
- 纯文本路径 — 所有 handler 均支持。
- 图文混合(combo)路径 —
send_private_msg支持;send_group_msg在 2026-07 修复中补齐(含 transmd→markdown 路径)。 - 纯媒体路径(无文本) — 群聊和私聊的 foundItems 循环中,富媒体消息(msg_type=7)在 2026-07 修复中新增 reply 支持。
注意:msg_id 与 event_id 二选一(API 规范),设置 reply 的 MsgID 时会自动清空 EventID。
[CQ:member]
范围:q群 (Group Chat)
GROUP_MEMBER_ADD / GROUP_MEMBER_REMOVE 转 OneBot notice,message 字段附带 [CQ:member]。后端用普通 send_group_msg 回复时带回该 CQ 码,Gensokyo 会重新路由到真实 q群。详见 CQ member。
[CQ:active]
范围:-
[CQ:active] 用于标记出站消息为主动推送模式。支持两种格式:
[CQ:active]— 裸标记,识别为主动推送。[CQ:active,type=xxx,sub_type=yyy]— 带参数,type写入foundItems["active_type"],sub_type写入foundItems["active_sub_type"]。
入站解析时,ProcessCQActive 从文本中移除该 CQ 码并在 foundItems 中记录。出站发送时,send_group_msg 和 send_private_msg 检测到 active 标记后清空缓存的 msg_id 和 event_id,强制走主动推送通道。send_private_msg_wakeup 中也会跳过 active 相关 key 避免误发媒体。
需要 C2C 召回消息时使用 send_private_msg_wakeup。详见 CQ active。
[CQ:wakeup]
范围:私聊 (C2C)
[CQ:wakeup,userid=xxx] 用于在 send_private_msg 出站消息中标记 C2C 互动召回(唤醒)消息:
- 解析:
ProcessCQWakeup从文本中移除 CQ 码,userid写入foundItems["wakeup"];数组消息段"type":"wakeup"(data.userid)与 TRSS 格式同样支持。 - 发送:
send_private_msg检测到wakeup标记后,将目标用户覆盖为 CQ 码指定的用户(虚拟 ID 自动转 OpenID),按召回通道发送(IsWakeup=true、MsgID=""、EventID=""互斥),支持纯文本/图文混合/Markdown/富媒体。 - 发送结果以
notice(notice_type=c2c_wakeup_resp)推送给应用端。 - 群聊消息(
send_group_msg)中的[CQ:wakeup]会被跳过,不发送(召回仅支持 C2C)。
详见 CQ wakeup。
[CQ:keyboard]
范围:q群 (Group Chat) / C2C (私聊)
[CQ:keyboard] 用于在文本消息上附加 QQ 官方内嵌键盘(按钮消息),按钮支持跳转/回调/指令三种行为。语法与 [CQ:markdown] 一致:data 支持 base64 编码或原始 JSON。
- 解析:
ProcessCQKeyboard从文本中提取并移除 CQ 码,解码后的键盘 JSON 存入foundItems["keyboard"];消息段路径(数组/TRSS)同样支持keyboard段(v013 补齐)。 - 发送:
send_group_msg/send_private_msg/send_group_msg_raw文本路径在无 markdown 时解析键盘并附加到MessageToCreate.Keyboard,以msg_type=0文本 + 键盘发送;specify_user_ids虚拟 ID 自动转 OpenID,私聊__USER_ID__占位符替换为实际用户 OpenID。 - 与
[CQ:markdown]同存时:markdown JSON 内嵌的 keyboard 优先;markdown 未内嵌 keyboard 时独立[CQ:keyboard]仍会附加(v013 起支持共存)。
详见 CQ keyboard。
[CQ:remove]
范围:q群 (Group Chat)
出站 CQ 码 [CQ:remove,user_id=虚拟用户ID,msg_id=虚拟消息ID],后端通过 send_group_msg 携带此 CQ 码即可撤回指定的群消息。user_id 和 msg_id 均为虚拟 ID,Gensokyo 内部自动转换为 QQ OpenID / 真实 message_id。纯 CQ 码的消息不会实际发送。详见 CQ remove。
[CQ:card]
范围:q群 (Group Chat)
新增扩展 CQ 码 [CQ:card,title=xxx,desc=xxx,pic=xxx,url=xxx],在群聊中发送图文卡片消息(msg_type=8)。参数顺序无关,title 必填,其余可选。详见 CQ card。
[CQ:input_notify]
范围:私聊 (C2C)
新增扩展 CQ 码 [CQ:input_notify,type=1,second=60],在单聊正文发送前先发送"正在输入"状态通知(msg_type=6)。详见 CQ input_notify。
[CQ:stream]
范围:私聊 (C2C)
新增扩展 CQ 码 [CQ:stream,type:start,qq:<虚拟ID>],支持单聊流式消息。分 start→mid→finish 三阶段逐片发送,实现打字机效果。阶段间通过 stream_msg_id 缓存关联。详见 CQ stream。
按钮权限 ID
范围:-
发送 keyboard 时,会遍历按钮 permission.specify_user_ids,把虚拟数字 ID 转成 QQ OpenID。C2C 场景中,如果 permission.type=0 且指定了用户,会改为 type=2(所有人),避免 QQ API 拒绝。
零值字段
范围:-
action.type=0、permission.type=0、render_data.style=0 会被保留,不再因为 JSON omitempty 丢失。
语音
范围:-
[CQ:record] 支持自动转 SILK v3。WAV/MP3 可走 Go 内部处理;其他格式仍可能需要 ffmpeg。目标音频为 16-bit、单声道、采样率由 record_sampleRate 控制,默认 24000。
入站事件新增字段(2026-09-16)
范围:q群 (Group Chat) / 私聊 (C2C)
botgo/dto/message.go 补齐官方 2026-09-16 新增的消息事件字段,handlers/message_parser.go 将其降级为 CQ 码 / 消息段上报:
| 字段 | 说明 |
|---|---|
message_type | 消息类型:0 文本 / 3 结构化卡片 / 101 并行消息 / 102 聊天记录 / 103 引用消息。 |
ark_data | 结构化卡片数据(prompt/ark_type/ark_name/fields);ark_type 含官方 2026-09-16 新增的 picture。 |
msg_elements | 嵌套消息元素(递归结构,含 msg_idx/content/attachments/ark_data/msg_elements)。 |
voice_wav_url / asr_refer_text | 语音附件 WAV 直链与语音转写文本。 |
MediaResponse.id / raw_url | 富媒体上传响应新增字段。 |
降级形态(字符串路径默认 array: false,追加在图片 CQ 码之后):
[CQ:record,file=<voice_wav_url 或附件 url>,asr=<转义文本>](asr为空时省略)[CQ:ark,ark_type=..,ark_name=..,prompt=..];无ark_data时输出裸[CQ:ark][CQ:reply,msg_idx=..],以及嵌套元素正文(101并行消息 /102聊天记录按正文保留,不静默丢弃)
数组路径(array: true)追加 record / ark / reply / text 段,排在图片段之后。
富媒体上传:5MB 直传上限与自动分片
范围:q群 (Group Chat) / 私聊 (C2C)
官方 file_data 二进制直传上限为 5MB(handlers/upload_helper.go 的 maxDirectFileDataBytes):
- 超过 5MB 的 base64 / 二进制自动改走分片上传(
upload_prepare→ 预签名 PUT →upload_part_finish→ 合并),群聊 / 单聊一致。 - 按类型软限制(图片 20MB / 视频 30MB / 语音 20MB / 文件 200MB)降级为“平台侧降级提示日志”,不再作为直传判据;硬限制 200MB 不变。
错误码提示
范围:-
当 QQ OpenAPI 调用返回错误时,Gensokyo 会在控制台向开发者输出该错误码对应的中文描述和排查建议,方便定位问题。
实现: handlers/qq_error_codes.go
qqErrorCodes:错误码 → 中文描述/排查建议映射表(数据来源:QQ 官方文档)ExtractQQErrorCode(err):从err.Error()中正则提取"code":xxxxxFormatQQError(err):格式化为开发者可读的提示
覆盖 Handler: send_group_msg / send_group_msg_raw / send_private_msg / send_private_msg_wakeup
2026-10-18 补码: 本轮新增 16 个官方错误码——富媒体上传(10000/850018/850019/850026/850027/850031/40093001/40093002)、群聊 v2 撤回(40061001/40062003/40064004/50065001)、取 token(100001/100007/100016);11253 描述更新为官方措辞;10004 双语义(频道 vs 取 token 的"机器人不存在")以注释说明。完整映射见 handlers/qq_error_codes.go。
示例输出:
发送文本群组信息失败: {"code":22009,"message":"频控"}
[QQ API 错误码 22009] 主动消息频控超限。排查建议:降低发送频率或等待配额恢复未识别的错误码会提示查阅官方文档;无错误码的原始错误也会打印,方便调试。
idmap
旧版单库 idmap.db 拆为:
| 文件 | 用途 |
|---|---|
idmap-identity.db | OpenID/rUIN/raw 与内部 vUIN 的身份关系(实际运行)。 |
idmap-msg.db | QQ message_id 与虚拟 message_id 的临时映射。 |
idmap.db | 旧库,仅作为一次性迁移源。 |
可通过
go build -tags map_idmap启用openid-map.db/msgid-map.db多桶架构(v2 重构方案),两者的对外行为完全一致。详见 idmap 文档。
迁移阻塞式(2026-08-01 修复)
StartMigration 已改为阻塞式迁移(此前为 go backgroundMigration() 非阻塞)。迁移完成才返回,确保 main.go 调用点之后才连 WS / 启动 HTTP,避免迁移期间 storeIdentity 双写与 backgroundMigration 迁入并发产生 2 row → 1 OpenID 重复映射。同时 writeBatchToNewDB 追加按 value 委重(逆向条目 uin:row-* 迁入前查正向 uin:<OpenID> 是否已存在,若已有则跳过)。
强制解绑工具(2026-08-01 新增)
迁移异常或历史数据残留导致 2 个虚拟 ID 指向同一 OpenID 时,可用强制解绑工具按 OpenID 直接清理全部映射:
ForceUnbindID(id):Go 内部函数,入参支持OpenID 字符串或虚拟 ID两种形式。OpenID 时直接删正向 + 扫删所有指向同一 OpenID 的逆向条目;虚拟 ID 时先通过逆向条目uin:row-<N>反查 OpenID(新库+旧库回退),再按 OpenID 清理。返回清理条数getid type=18:HTTP APIGET /getid?type=18&id=<OpenID或虚拟ID>,返回{"status":"success","unbound_count":N}UpdateVirtualValue(old, 0)解绑行为修复:newRowValue=0解绑时已同步彻底清理正向 + 重复逆向条目(此前只删单条逆向不删正向,导致解绑失效)
2026-08-01 修复:
ForceUnbindID原仅接受 OpenID 入参,用户传虚拟 ID 时查正向uin:<虚拟ID>找不到直接返回unboundCount=0(表现为getid type=18返回 false)。现已支持双形式入参:纯数字视为虚拟 ID 先反查 OpenID,非纯数字视为 OpenID 直接清理。
解绑后该 OpenID 彻底无映射,下次 storeIdentity 会重新分配唯一虚拟 ID。详见 idmap 文档。
规则:
- 下游默认继续使用内部 vUIN。
- QQ OpenID 身份键为
openid:QQ:<appid>:<openid>。 - q群、私聊 (C2C)、好友事件、群成员事件共用同一 QQ OpenID 身份键。
- message_id 默认保留 1 小时,由
msgid_ttl_seconds控制。
相关配置:
op_userid_type: vuin
msgid_ttl_seconds: 3600lazy_message_id 多段回复 msgseq 去重修复(2026-08-02)
文件: echo/messageidmap.go
lazy_message_id: true 下一条命令触发多段独立回复时,偶发某一段发送失败,QQ API 返回 {"message":"消息被去重,请检查请求msgseq","code":40054005}。
根因: GetLazyMessagesId / GetLazyMessagesIdv2 选中一条 record 后执行 usageCount++,导致同一条命令的多段回复每次调用都选中不同的 record(下次该条 usageCount!=0,选中另一条),两段拿到不同 msg_id。但 QQ API 视两 msg_id 为同一回复链(都回复同一 event),要求 msg_seq 在同一回复链内连续递增。GetMappingSeq 按 msg_id 字符串做 key 存 seq,两段拿到不同 msg_id 时第二段查不到第一段的 seq,seq 与第一段冲突,判去重 40054005。
修复: 移除 GetLazyMessagesId / GetLazyMessagesIdv2 选中后的 usageCount++,让同一回复链的多段回复都拿到同一 msg_id,配合 GetMappingSeq / AddMappingSeq 在该 msg_id 上连续递增 msg_seq。
关联 Issue: Te-River/Gensokyo-NewQQ#19
事件字段
sender.nickname
event.sender.nickname 优先级:
card_nick配置。- QQ API 的
Author.Username。 - 空字符串。
q群 sender.role
范围:q群 (Group Chat)
q群消息优先使用 QQ API 的 member_role:
member_role | sender.role |
|---|---|
owner | owner |
admin | admin |
member | member |
没有 member_role 时,再回退到 master_id 判断。
event_id
已存储 event_id 的入口:
| 事件 | 处理入口 |
|---|---|
INTERACTION_CREATE | ProcessInlineSearch |
C2C_MSG_RECEIVE | process_c2c_switch |
FRIEND_ADD | process_friend |
GROUP_ADD_ROBOT | ProcessGroupAddBot |
GROUP_MSG_RECEIVE | ProcessGroupMsgReceive |
GROUP_MSG_REJECT | ProcessGroupMsgReject |
GROUP_MEMBER_ADD | ProcessGroupMember |
GROUP_JOIN_REQUEST
范围:q群 (Group Chat)
GROUP_JOIN_REQUEST 转 request.group 的字段对齐官方现文档:
apply_at:官方为 RFC3339 字符串,按 RFC3339 解析为秒级时间戳(保留历史数字时间戳兼容)。auto_approved:官方为对象({strategy_id},仅下行事件携带),改为对象透传;新增布尔伴随字段auto_approved_bool(对象是否存在)。
日志与监控
mylog
当前内置日志支持:
- 控制台按级别着色:
log_color_enabled。 - 控制台日志格式:
2006/JAN/02 PM 03:04:05 [INFO ] [module] message。 - 本地文件日志默认关闭,
save_logs: true或启动参数run --local-logger=enable均可启用。 - 文件日志写入
log/下的纯文本文件,文件名使用 UTC 时间。 log_suffix_per_mins控制时间切分,log_max_size_mb控制单文件大小轮转。log_max_age_days控制最大保留天数,log_keep_files控制本地旧日志文件最大保留个数。
慢事件与 /metrics
runWithTimer 会按 log_slow_event_threshold_ms 判定慢事件。/metrics 输出:
gensokyo_uptime_secondsgensokyo_messages_received_totalgensokyo_messages_sent_totalgensokyo_errors_totalgensokyo_slow_events_totalgensokyo_memory_allocated_bytes
Master 指令
范围:进入 HandleFrameworkCommand 的消息事件。
权限:
master_id为空数组时不限制。- 否则要求真实用户 ID 或虚拟用户 ID 命中
master_id。
指令:
| 配置项 | 默认值 | 行为 |
|---|---|---|
status_prefix | /gskstatus | 返回版本、收发计数、慢事件/错误计数、内存、协程数、运行时间。 |
broadcast_prefix | /gskbroadcast | 向 idmap 中的 q群提交广播。 |
me_prefix | /me | 查询当前用户的真实 ID / 虚拟 ID 映射关系(idmaps-pro 模式下显示对应关系)。 |
指令前缀设为 ""、/disabled 或 /disabled... 时视为禁用。
架构
arch_mode 分层架构接入
生产主链已接入 internal/(application/、domain/、infrastructure/)+ adapter/(qq/、onebot/、media/、state/、identity/)分层架构,与 legacy 两路同时装配,按 arch_mode 逐请求切换:
| 配置项 | 默认值 | 可选值 | 行为 |
|---|---|---|---|
arch_mode | new | legacy / shadow / new | new 走新链;shadow 新旧并行、差异仅日志上报、行为仍走 legacy;legacy 全走旧链,一键回退。 |
- 逐请求读取,热重载即时生效;空值/非法值回退
new。 - 六个接缝(出站/入站/action/state/config/media)均保留 legacy 回退:新链未注入或失败时自动落回旧链。
构建
CI 使用静态编译配置 CGO_ENABLED=0,降低 GLIBC 版本依赖。
构建版本由 -ldflags 注入 buildinfo.BuildType 和 buildinfo.BuildSpec,显示为 <buildtype>-<build_spec>,例如 git-2d643f4。未注入时回退为 dev-<UTC 时间戳>.<毫秒>。启动命令 version、OneBot get_version_info 和 /gskstatus 使用同一版本值。
build.ps1 同时支持单平台构建和 -All / -LinuxOnly 多平台构建;build_all.ps1 仅保留为兼容入口。
修复
GetRemoveBotAtGroup误删用户主动发送的 @。- 入站
@bot剥离逻辑改用Mentions[i].OpenID。 ParseAndHandle增加错误日志和 panic 恢复。- 按钮零值字段序列化丢失。
- C2C 按钮指定用户权限导致发送失败。
send_private_msg_wakeup遍历foundItems时未跳过"active"key,导致[CQ:active]标记被误当作媒体类型发送。- Markdown 消息中
[CQ:at]写在文本段(messageText)时未合并到 Markdown 内容,导致 @ 标签丢失。已修复:HandleSendGroupMsg中对messageText也调用ResolveMarkdownAtMentions,将[CQ:at,qq=数字]转换为<qqbot-at-user>后合并到 Markdown 内容头部。 [CQ:video]string 格式缺少base64://和file://正则,视频被当作普通文本发送。已新增base64VideoPattern和localVideoPattern。- 无前缀的 CQ 码(如
[CQ:image,file=filename.png])被收集到unknown_*后静默丢弃。已添加 fallback 处理,作为 URL 媒体尝试发送。 keyMap中"embed"无任何写入路径,移除消除潜在空消息风险。- TRSS 格式(
map[string]interface{})缺少case "reply"和case "avatar"处理,已补充。 - HTTP API 端口 (
http_address) 绑定失败时不再终止进程。Windows 上端口被 Hyper-V/系统保留时,log.Fatalf会杀死整个进程(尽管主 Gin 服务器和 WebSocket 已正常运行),已改为mylog.Printf仅记录错误。 /me命令中不必要的err != nil检查导致 ID 映射失败时误报错误并退出。/me是状态查询命令,映射失败不应阻断执行,已移除多余检查。- 私聊图文混合消息(
imageCount == 1 && messageText != "")对groupReply只做*dto.RichMediaMessage断言:local_image/base64_image等在generatePrivateMessage内已完成上传、返回的是*dto.MessageToCreate,断言失败后直接return "", nil,既未发送也未回执,插件侧表现为WebSocket call api send_msg timeout。已补齐*dto.MessageToCreate兜底断言(与群聊路径一致),并在发送失败时分类处理(22009 / 40034025 / 超时重试)后统一回执,避免客户端超时。 - 入群/退群通知回复被误判为群私聊:
ProcessGroupMember.go生成虚拟 group_id 时未登记消息类型(常规群消息会写echo.AddMsgType(..., "group")与idmap.WriteConfigv2(id, "type", "group")),导致针对GROUP_MEMBER_ADD/REMOVE通知回包的send_group_msg被send_group_msg.go兜底误判为group_private,把群 OpenID 当用户走 C2C 接口报11255 请求的资源不存在(用户/群已注销)。已补上与常规群消息一致的 group 类型登记。 get_group_bot_state字段名与官方不符(join_time/can_push/push_msg_setting/role),按官方字段取值恒空。已改为官方字段joined_at/allow_proactive_msg/recv_msg_setting/member_role,旧字段保留为别名。get_group_join_request_list请求/响应字段与官方不符(next_index→cursor、join_requests→list),且apply_at类型不符(int→RFC3339 字符串)。已对齐官方,旧字段保留为兼容别名。GROUP_JOIN_REQUEST的auto_approved官方为对象而 DTO 为 bool,导致事件解析失败。已改为对象透传,并新增布尔伴随字段auto_approved_bool。
Keyboard 按钮本地图片
范围:q群 (Group Chat), 私聊 (C2C)
按钮 render_data.label 和 render_data.visited_label 中的本地图片路径(如 )现在也会被自动上传到图床并替换为 CDN URL,与 markdown 内容中的图片处理保持一致。
