Repository navigation
Conversation
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: undefined-moe <29992205+undefined-moe@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
主动私聊被错误地当成群聊发送:core 的 sendPrivateMessage 只使用
createDirectChannel 返回的 id,channel type 在 MessageEncoder 中丢失,
导致 flush() 里 session.isDirect 恒为 false,最终请求
POST /v2/groups/{user_openid}/messages 而报 invalid request (11255)。
由于群 openid 与用户 openid 格式相同,无法从 id 区分,因此:
- QQBot.sendPrivateMessage 通过 options.direct 显式标记主动私聊;
- QQMessageEncoder 记录该标记,flush() / sendFile() 据此选择单聊接口,
并修正主动发送时使用的 channelId(原逻辑依赖 this.session.channelId)。
Closes satorijs#304
|
说明:本 PR 仅修复 -u(主动私聊),-c / -g(显式指定频道/群组)无法在本层修复 背景 @satorijs/adapter-qq 的群消息编码器在判断「发到用户」还是「发到群」时,只依据会话的 isDirect: // adapters/qq/src/message.ts 而 bot.sendPrivateMessage() 经由 createDirectChannel() 拿到目标 id 后,createDirectChannel() 返回的 type: DIRECT 被 Bot.sendMessage() 丢弃,sendPrivateMessage 本身又是主动发送、不带 session,因此 isDirect 恒为 false,最终错误地调用了群接口,把用户 openid 当成群 openid。 本 PR 通过在 encoder 上引入 directTarget 标志并让 QQBot.sendPrivateMessage() 构造 encoder 时显式置位,使 -u 场景正确路由到 /v2/users/{openid}/messages,同时把寻址统一收敛到构造时传入的 channelId(避免 options.session 浅合并污染目标 id)。 该修复对应 #304。 为什么 -c / -g 无法在 adapter 层修复 Koishi 的 echo 插件对三种参数的实际调用是: // koishijs/plugin-echo bot.sendMessage(channelId, content, referrer?, options?) 的第三个参数是 Referrer,不是 guildId。也就是说:
QQ 的群 openid 与用户 openid 格式完全相同,adapter 无法从 id 字符串反推出目标类型,因此这不是一个“补全判断逻辑”就能解决的问题:信息在 echo → Bot.sendMessage 这一跳就已经丢失,adapter 层没有可依赖的输入。 同理,任何「在 encoder 里加一个 guildTarget 标志」的做法都是无效设计 —— 没有任何调用方会去设置它。 修复 core 需要的改动(本 PR 不含,暂缓) 要正确支持 -c / -g,需要在 @satorijs/core 把「目标会话类型」透传到 encoder。核心是让 Bot.sendMessage 能够接收并传递一个显式的目标类型,而不是依赖 options.session 的副作用。
export interface SendOptions {
async createMessage(channelId: string, content: h.Fragment, referrer?: any, options?: SendOptions) { async sendMessage( 即保持签名不变,由调用方通过 options.channelType / options.guildId 表达意图。
async send(content: h.Fragment) { 注意这里仍需保证 id 不被 options.session.event.channel.id 覆盖,建议改为: channel: {
private get targetIsDirect() {
if (options.user) { 结论 ╭───────────────┬────────────┬───────────────────────────────────────────────────────────────────────╮ -c / -g 的修复依赖 @satorijs/core 增加 SendOptions.channelType / guildId 透传,并同步修改 koishijs/plugin-echo 的调用方式。该改动涉及跨仓库接口约定,本 PR 暂不包含,待接口方案确定后单独提交。 |
|
抱歉,base 分支搞错了:本仓库的开发分支是 |
问题
使用
adapter-qq主动发送单聊消息(如bot.sendPrivateMessage(openid, content)、echo -u @某人)时,用户 openid 被当成群 openid,实际请求了群聊接口:在 Koishi 中的表现是一条无信息的
Error(QQMessageEncoder.send→Proxy.sendMessage),只提示「发生未知错误」。不指定用户时为群聊,正常;指定用户就报错。根因
@satorijs/core的Bot.sendPrivateMessage()只使用了createDirectChannel()返回的id,丢弃了type: DIRECT:而
MessageEncoder.send()在没有options.session时构造的伪 sessionchannel.type不是 DIRECT,于是flush()中的判断恒为假:QQ 群 openid 与用户 openid 格式完全相同,无法从 id 区分,因此只能由调用侧显式传递意图。
修改
adapters/qq/src/bot/index.ts:覆写sendPrivateMessage(),在options上打direct: true标记后再走sendMessage()。adapters/qq/src/message.ts:QQMessageEncoder通过isDirectSendgetter 读取该标记;flush()用isDirectSend || session.isDirect选择单聊/群聊接口,并修正主动发送时使用的channelId(原逻辑依赖this.session.channelId,主动发送时为伪 session);sendFile()同样修正私聊文件上传的目标 id(原逻辑取options.session.userId,主动发送时不存在)。package.json:补上@satorijs/protocol的 peer/dev 依赖(SendOptions来源)。被动回复(
session.send()、ctx.broadcast())路径行为不变。关联
Closes #304
另见 koishijs/koishi#1456、koishijs/koishi#1485、#382(同一根因的不同入口)。
署名
本 PR 的改动由 AI 编码助手 Maki(deepseek/deepseek-v4-flash)在用户 gyf9835 的指导下定位与实现;分析与提交内容由 Maki 生成,请审阅后再合并。