Conversation
审阅者指南为消息服务新增内联键盘点击 API,增强 AI 群记录发送逻辑,通过基于 msgRandom 关联的方式返回创建出的消息 ID;改进当 senderUin 为 0 时群撤回通知中消息发送方与操作者 UIN 的解析逻辑;并将项目版本号提升至 7.12.11。 通过 msgRandom 解析 SendGroupAiRecord 消息 ID 的时序图sequenceDiagram
actor OneBotClient
participant SendGroupAiRecord
participant Context
participant PMHQ as PMHQMessageMixin
participant OidbSvc as OidbSvcTrpcTcp
participant EventBus
participant Store
OneBotClient->>SendGroupAiRecord: _handle(payload)
SendGroupAiRecord->>Context: access pmhq
SendGroupAiRecord->>PMHQ: getGroupGenerateAiRecord(groupId, character, text, chatType)
PMHQ->>PMHQ: generate msgRandom
PMHQ->>OidbSvc: httpSendPB(OidbSvcTrpcTcp.0x929b_0, encodedBody)
OidbSvc-->>PMHQ: ack
PMHQ-->>SendGroupAiRecord: { msgRandom }
SendGroupAiRecord->>Context: on(nt/message-created, handler)
Note over SendGroupAiRecord,EventBus: handler compares msg.msgRandom with target msgRandom
EventBus-->>Context: nt/message-created(msg)
Context-->>SendGroupAiRecord: invoke handler(msg)
SendGroupAiRecord->>Store: createMsgShortId(msg)
Store-->>SendGroupAiRecord: shortId
SendGroupAiRecord-->>OneBotClient: { message_id: shortId }
更新后的 NodeIKernelMsgService 内联键盘 API 类图classDiagram
class NodeIKernelMsgService {
<<interface>>
setContactLocalTop(peer: Peer, isTop: boolean) Promise~GeneralCallResult~
sendShowInputStatusReq(chatType: ChatType, eventType: number, toUid: string) Promise~GeneralCallResult~
clickInlineKeyboardButton(inlineKeyboardClickInfo: InlineKeyboardClickInfo) Promise~InlineKeyboardClickResult~
}
class InlineKeyboardClickInfo {
+string guildId
+number dmFlag
+string peerId
+ChatType chatType
+string botAppid
+string msgSeq
+string buttonId
+string callback_data
}
class GeneralCallResult {
}
class InlineKeyboardClickResult {
+number status
+string promptText
+number promptType
+number promptIcon
}
NodeIKernelMsgService --> InlineKeyboardClickInfo : uses
NodeIKernelMsgService --> InlineKeyboardClickResult : returns
InlineKeyboardClickResult --|> GeneralCallResult
SendGroupAiRecord 与 PMHQ 消息 mixin 变更的类图classDiagram
class PMHQBase {
}
class MessageMixin {
+getGroupGenerateAiRecord(groupId: number, character: string, text: string, chatType: number) Promise~GetGroupGenerateAiRecordResult~
}
class GetGroupGenerateAiRecordResult {
+number msgRandom
}
class Context {
+PMHQBase pmhq
+Store store
+EventBus on(eventName: string, handler: MessageCreatedHandler) Dispose
}
class Store {
+createMsgShortId(message: Message) number
}
class EventBus {
+on(eventName: string, handler: MessageCreatedHandler) Dispose
}
class Dispose {
+invoke() void
}
class Message {
+string msgRandom
}
class BaseAction~Payload,Response~ {
+Context ctx
}
class SendGroupAiRecord {
+_handle(payload: Payload) Promise~Response~
}
class Payload {
+string group_id
+string character
+string text
+string chat_type
}
class Response {
+number message_id
}
MessageMixin ..|> PMHQBase
Context --> PMHQBase : has
Context --> Store : has
Context --> EventBus : has
SendGroupAiRecord --|> BaseAction
SendGroupAiRecord --> Context : uses
SendGroupAiRecord --> Payload : parameter
SendGroupAiRecord --> Response : returns
PMHQBase <.. MessageMixin : mixin
PMHQBase --> GetGroupGenerateAiRecordResult : returns
Store --> Message : uses
更新后的 OB11GroupRecallNoticeEvent 构造逻辑类图classDiagram
class OB11Entities {
}
class OB11GroupRecallNoticeEvent {
+OB11GroupRecallNoticeEvent(groupId: number, senderUin: number, operatorUin: number, messageId: number)
}
class RevokeElement {
+string operatorUid
+string origMsgSenderUid
}
class NtUserApi {
+getUinByUid(uid: string) Promise~string~
}
class OB11Context {
+NtUserApi ntUserApi
+Logger logger
}
class Logger {
+warn(message: string) void
}
class MessageRecord {
+string peerUid
+string senderUin
}
OB11Entities ..> OB11GroupRecallNoticeEvent : creates
OB11Entities ..> NtUserApi : uses
OB11Entities ..> RevokeElement : uses
OB11Entities ..> MessageRecord : uses
OB11Context --> NtUserApi : has
OB11Context --> Logger : has
文件级变更
提示与命令与 Sourcery 交互
自定义使用体验访问你的 控制面板 以:
获取帮助Original review guide in EnglishReviewer's GuideAdds a new inline keyboard click API to the message service, enhances AI group record sending to return the created message ID by correlating on msgRandom, improves group recall notice sender/operator resolution when senderUin is zero, and bumps the project version to 7.12.11. Sequence diagram for SendGroupAiRecord message ID resolution via msgRandomsequenceDiagram
actor OneBotClient
participant SendGroupAiRecord
participant Context
participant PMHQ as PMHQMessageMixin
participant OidbSvc as OidbSvcTrpcTcp
participant EventBus
participant Store
OneBotClient->>SendGroupAiRecord: _handle(payload)
SendGroupAiRecord->>Context: access pmhq
SendGroupAiRecord->>PMHQ: getGroupGenerateAiRecord(groupId, character, text, chatType)
PMHQ->>PMHQ: generate msgRandom
PMHQ->>OidbSvc: httpSendPB(OidbSvcTrpcTcp.0x929b_0, encodedBody)
OidbSvc-->>PMHQ: ack
PMHQ-->>SendGroupAiRecord: { msgRandom }
SendGroupAiRecord->>Context: on(nt/message-created, handler)
Note over SendGroupAiRecord,EventBus: handler compares msg.msgRandom with target msgRandom
EventBus-->>Context: nt/message-created(msg)
Context-->>SendGroupAiRecord: invoke handler(msg)
SendGroupAiRecord->>Store: createMsgShortId(msg)
Store-->>SendGroupAiRecord: shortId
SendGroupAiRecord-->>OneBotClient: { message_id: shortId }
Class diagram for updated NodeIKernelMsgService inline keyboard APIclassDiagram
class NodeIKernelMsgService {
<<interface>>
setContactLocalTop(peer: Peer, isTop: boolean) Promise~GeneralCallResult~
sendShowInputStatusReq(chatType: ChatType, eventType: number, toUid: string) Promise~GeneralCallResult~
clickInlineKeyboardButton(inlineKeyboardClickInfo: InlineKeyboardClickInfo) Promise~InlineKeyboardClickResult~
}
class InlineKeyboardClickInfo {
+string guildId
+number dmFlag
+string peerId
+ChatType chatType
+string botAppid
+string msgSeq
+string buttonId
+string callback_data
}
class GeneralCallResult {
}
class InlineKeyboardClickResult {
+number status
+string promptText
+number promptType
+number promptIcon
}
NodeIKernelMsgService --> InlineKeyboardClickInfo : uses
NodeIKernelMsgService --> InlineKeyboardClickResult : returns
InlineKeyboardClickResult --|> GeneralCallResult
Class diagram for SendGroupAiRecord and PMHQ message mixin changesclassDiagram
class PMHQBase {
}
class MessageMixin {
+getGroupGenerateAiRecord(groupId: number, character: string, text: string, chatType: number) Promise~GetGroupGenerateAiRecordResult~
}
class GetGroupGenerateAiRecordResult {
+number msgRandom
}
class Context {
+PMHQBase pmhq
+Store store
+EventBus on(eventName: string, handler: MessageCreatedHandler) Dispose
}
class Store {
+createMsgShortId(message: Message) number
}
class EventBus {
+on(eventName: string, handler: MessageCreatedHandler) Dispose
}
class Dispose {
+invoke() void
}
class Message {
+string msgRandom
}
class BaseAction~Payload,Response~ {
+Context ctx
}
class SendGroupAiRecord {
+_handle(payload: Payload) Promise~Response~
}
class Payload {
+string group_id
+string character
+string text
+string chat_type
}
class Response {
+number message_id
}
MessageMixin ..|> PMHQBase
Context --> PMHQBase : has
Context --> Store : has
Context --> EventBus : has
SendGroupAiRecord --|> BaseAction
SendGroupAiRecord --> Context : uses
SendGroupAiRecord --> Payload : parameter
SendGroupAiRecord --> Response : returns
PMHQBase <.. MessageMixin : mixin
PMHQBase --> GetGroupGenerateAiRecordResult : returns
Store --> Message : uses
Class diagram for updated OB11GroupRecallNoticeEvent creation logicclassDiagram
class OB11Entities {
}
class OB11GroupRecallNoticeEvent {
+OB11GroupRecallNoticeEvent(groupId: number, senderUin: number, operatorUin: number, messageId: number)
}
class RevokeElement {
+string operatorUid
+string origMsgSenderUid
}
class NtUserApi {
+getUinByUid(uid: string) Promise~string~
}
class OB11Context {
+NtUserApi ntUserApi
+Logger logger
}
class Logger {
+warn(message: string) void
}
class MessageRecord {
+string peerUid
+string senderUin
}
OB11Entities ..> OB11GroupRecallNoticeEvent : creates
OB11Entities ..> NtUserApi : uses
OB11Entities ..> RevokeElement : uses
OB11Entities ..> MessageRecord : uses
OB11Context --> NtUserApi : has
OB11Context --> Logger : has
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了 1 个问题,并给出了一些整体性的反馈:
- 在
SendGroupAiRecord._handle中,如果目标msgRandom对应的nt/message-created事件从未触发,那么返回的 promise 将永远不会被解决;建议添加超时或兜底逻辑,确保在超时后能 resolve/reject,并且始终正确清理事件监听器。 - 在
SendGroupAiRecord中使用Promise.withResolvers依赖于相对较新的运行时支持;如果这段代码需要在较老的环境中运行,建议包一层显式的new Promise来避免兼容性问题。 - 在
OB11Entities中更新后的撤回处理逻辑里,之前对假值msg.senderUin(!msg.senderUin)的分支被移除了;如果senderUin在某些情况下可能为空或为 undefined,可能需要保留对应场景的处理或日志记录,以避免生成无效的Number(senderUin)。
给 AI Agent 的提示
Please address the comments from this code review:
## Overall Comments
- 在 `SendGroupAiRecord._handle` 中,如果目标 `msgRandom` 对应的 `nt/message-created` 事件从未触发,那么返回的 promise 将永远不会被解决;建议添加超时或兜底逻辑,确保在超时后能 resolve/reject,并且始终正确清理事件监听器。
- 在 `SendGroupAiRecord` 中使用 `Promise.withResolvers` 依赖于相对较新的运行时支持;如果这段代码需要在较老的环境中运行,建议包一层显式的 `new Promise` 来避免兼容性问题。
- 在 `OB11Entities` 中更新后的撤回处理逻辑里,之前对假值 `msg.senderUin`(`!msg.senderUin`)的分支被移除了;如果 `senderUin` 在某些情况下可能为空或为 undefined,可能需要保留对应场景的处理或日志记录,以避免生成无效的 `Number(senderUin)`。
## Individual Comments
### Comment 1
<location path="src/onebot11/action/llbot/msg/SendGroupAiRecord.ts" line_range="26-35" />
<code_context>
- await this.ctx.pmhq.getGroupGenerateAiRecord(+payload.group_id, payload.character, payload.text, +payload.chat_type)
- return { message_id: 0 }
+ const res = await this.ctx.pmhq.getGroupGenerateAiRecord(+payload.group_id, payload.character, payload.text, +payload.chat_type)
+ const targetMsgRandom = res.msgRandom.toString()
+ const { promise, resolve } = Promise.withResolvers<Response>()
+ const dispose = this.ctx.on('nt/message-created', (msg) => {
+ if (msg.msgRandom === targetMsgRandom) {
+ dispose()
+ const shortId = this.ctx.store.createMsgShortId(msg)
+ resolve({ message_id: shortId })
+ }
+ })
+ return promise
}
}
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 如果对应的 `nt/message-created` 事件未被触发,这个 promise 可能永远不会被 settle。
如果 AI 记录消息从未被创建,或者事件被错过(例如网络问题、服务错误、进程重启),这个 promise 将既不会 resolve 也不会 reject,使得该操作无限期地挂起。建议添加超时和/或显式的错误路径(例如在 N 秒后 reject 并清理监听器),这样调用方可以处理失败,而不是一直被挂住。
</issue_to_address>帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进评审质量。
Original comment in English
Hey - I've found 1 issue, and left some high level feedback:
- In
SendGroupAiRecord._handle, the returned promise will never resolve if thent/message-createdevent with the targetmsgRandomis not emitted; consider adding a timeout or fallback to resolve/reject and ensure the event listener is always cleaned up. - The use of
Promise.withResolversinSendGroupAiRecordrelies on relatively new runtime support; if this code must run in older environments, consider an explicitnew Promisewrapper to avoid compatibility issues. - In the updated recall handling in
OB11Entities, the previous branch for a falsymsg.senderUin(!msg.senderUin) was removed; ifsenderUincan be empty/undefined in some cases, you may want to preserve handling or logging for that scenario to avoid producing an invalidNumber(senderUin).
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `SendGroupAiRecord._handle`, the returned promise will never resolve if the `nt/message-created` event with the target `msgRandom` is not emitted; consider adding a timeout or fallback to resolve/reject and ensure the event listener is always cleaned up.
- The use of `Promise.withResolvers` in `SendGroupAiRecord` relies on relatively new runtime support; if this code must run in older environments, consider an explicit `new Promise` wrapper to avoid compatibility issues.
- In the updated recall handling in `OB11Entities`, the previous branch for a falsy `msg.senderUin` (`!msg.senderUin`) was removed; if `senderUin` can be empty/undefined in some cases, you may want to preserve handling or logging for that scenario to avoid producing an invalid `Number(senderUin)`.
## Individual Comments
### Comment 1
<location path="src/onebot11/action/llbot/msg/SendGroupAiRecord.ts" line_range="26-35" />
<code_context>
- await this.ctx.pmhq.getGroupGenerateAiRecord(+payload.group_id, payload.character, payload.text, +payload.chat_type)
- return { message_id: 0 }
+ const res = await this.ctx.pmhq.getGroupGenerateAiRecord(+payload.group_id, payload.character, payload.text, +payload.chat_type)
+ const targetMsgRandom = res.msgRandom.toString()
+ const { promise, resolve } = Promise.withResolvers<Response>()
+ const dispose = this.ctx.on('nt/message-created', (msg) => {
+ if (msg.msgRandom === targetMsgRandom) {
+ dispose()
+ const shortId = this.ctx.store.createMsgShortId(msg)
+ resolve({ message_id: shortId })
+ }
+ })
+ return promise
}
}
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The promise may never settle if the corresponding `nt/message-created` event is not emitted.
If the AI record message is never created or the event is missed (e.g., network issue, service error, process restart), this promise will never resolve or reject, leaving the action hanging indefinitely. Consider adding a timeout and/or explicit error path (e.g., reject after N seconds and dispose the listener) so callers can handle failures instead of hanging forever.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const targetMsgRandom = res.msgRandom.toString() | ||
| const { promise, resolve } = Promise.withResolvers<Response>() | ||
| const dispose = this.ctx.on('nt/message-created', (msg) => { | ||
| if (msg.msgRandom === targetMsgRandom) { | ||
| dispose() | ||
| const shortId = this.ctx.store.createMsgShortId(msg) | ||
| resolve({ message_id: shortId }) | ||
| } | ||
| }) | ||
| return promise |
There was a problem hiding this comment.
suggestion (bug_risk): 如果对应的 nt/message-created 事件未被触发,这个 promise 可能永远不会被 settle。
如果 AI 记录消息从未被创建,或者事件被错过(例如网络问题、服务错误、进程重启),这个 promise 将既不会 resolve 也不会 reject,使得该操作无限期地挂起。建议添加超时和/或显式的错误路径(例如在 N 秒后 reject 并清理监听器),这样调用方可以处理失败,而不是一直被挂住。
Original comment in English
suggestion (bug_risk): The promise may never settle if the corresponding nt/message-created event is not emitted.
If the AI record message is never created or the event is missed (e.g., network issue, service error, process restart), this promise will never resolve or reject, leaving the action hanging indefinitely. Consider adding a timeout and/or explicit error path (e.g., reject after N seconds and dispose the listener) so callers can handle failures instead of hanging forever.
Test Report
✅ All tests passed |
Summary by Sourcery
为行内键盘按钮点击添加支持,改进 AI 群聊记录发送以返回实际消息 ID,并修复群撤回事件中的发送者识别问题。
New Features:
Bug Fixes:
Enhancements:
Chores:
Original summary in English
Summary by Sourcery
Add support for inline keyboard button clicks, improve AI group record sending to return actual message IDs, and fix sender identification for group recall events.
New Features:
Bug Fixes:
Enhancements:
Chores: