Skip to content

chore: update version to 7.12.10#750

Merged
idranme merged 13 commits into
mainfrom
dev
Apr 30, 2026
Merged

chore: update version to 7.12.10#750
idranme merged 13 commits into
mainfrom
dev

Conversation

@idranme
Copy link
Copy Markdown
Collaborator

@idranme idranme commented Apr 30, 2026

Summary by Sourcery

为 WebUI 增加注销支持,改进消息 @ 提及和回复处理,优化用户信息获取方式,并更新工具配置和版本。

New Features:

  • 在 WebUI 设置对话框中新增注销操作,带确认提示,并通过 Cookie 实现登出。

Bug Fixes:

  • 通过选择正确的 VIP 信息条目来修正 VIP 状态解析,而不是假定使用列表中的第一个条目。
  • 修复群聊 @ 提及显示文本,在不同的消息编码器中统一使用成员的群名片或昵称。
  • 在过滤时,确保回复消息查找逻辑能包含时间戳相差 1 毫秒的消息。
  • 在解析用户 UID 时,当详细信息不可用时回退到简单的用户信息获取,以避免失败。

Enhancements:

  • 补充用户资料相关 API 方法在服务端拉取和缓存语义方面的行为文档。
  • 将 WebUI 开发服务器的 host 和 port 调整为固定的 localhost 地址和自定义端口。

Build:

  • 在发布工作流中跳过 Electron 二进制下载,并调整依赖安装步骤,包括单独安装 WebUI 前端依赖。

Chores:

  • 将应用版本号提升至 7.12.10。
Original summary in English

Summary by Sourcery

Add WebUI logout support, improve message mention and reply handling, refine user info retrieval, and update tooling configuration and version.

New Features:

  • Add a logout action in the WebUI settings dialog with confirmation and cookie-based sign-out.

Bug Fixes:

  • Correct VIP status parsing by selecting the appropriate VIP info entry instead of assuming the first list item.
  • Fix group @ mention display texts to use the member’s card name or nickname across different message encoders.
  • Ensure replied message lookup includes messages that may differ by one millisecond in timestamp when filtering.
  • Make user UID resolution fall back to simple user info retrieval to avoid failures when detailed info is unavailable.

Enhancements:

  • Document behavior of user profile API methods regarding server fetching and caching semantics.
  • Adjust the WebUI dev server host and port to a fixed localhost address and custom port.

Build:

  • Skip Electron binary download and adjust dependency installation steps in the publish workflow, including installing WebUI frontend dependencies separately.

Chores:

  • Bump application version to 7.12.10.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 30, 2026

Reviewer’s Guide

通过设置对话框引入 WebUI 登出流程;改进 VIP 标记处理和用户信息获取;优化群聊 @ 提及显示名;调整 WebUI 开发服务器和 CI 安装行为;微调消息时间过滤逻辑,并将应用版本升级到 7.12.10。

从设置对话框进行 WebUI 登出交互的时序图

sequenceDiagram
  actor User
  participant SettingsDialog
  participant App
  participant Browser

  User->>SettingsDialog: Click logout button
  SettingsDialog->>SettingsDialog: setShowLogoutConfirm(true)
  SettingsDialog-->>User: Show confirmation modal

  User->>SettingsDialog: Click Confirm logout
  SettingsDialog->>App: onLogout()
  App->>Browser: deleteCookie(webui_token)
  App->>Browser: window.location.reload()
  Browser-->>User: Reloaded page without WebUI session
Loading

优化群聊 @ 提及显示名解析的时序图

sequenceDiagram
  participant Sender
  participant MessageBuilder as MessageBuilder_createSendElements
  participant NTQQUserApi as NTQQUserApi
  participant NTQQGroupApi as NTQQGroupApi

  Sender->>MessageBuilder: Provide segment type mention with user_id
  MessageBuilder->>NTQQUserApi: getUidByUin(memberUin, peerUid)
  NTQQUserApi-->>MessageBuilder: memberUid
  MessageBuilder->>NTQQGroupApi: getGroupMember(peerUid, memberUid)
  NTQQGroupApi-->>MessageBuilder: memberInfo(cardName, nick)
  MessageBuilder->>MessageBuilder: Build displayName = "@" + (cardName or nick)
  MessageBuilder-->>Sender: SendElement.at(..., displayName)
Loading

在 UserMixin 中提取 VIP 信息的时序图

sequenceDiagram
  participant UserMixin as UserMixin_getUser
  participant PMHQ as PMHQBase
  participant Misc as Misc_UserInfoBusiness

  UserMixin->>PMHQ: fetch raw user info
  PMHQ-->>UserMixin: info(body.properties)
  UserMixin->>UserMixin: bytes[107] ?
  alt has business bytes
    UserMixin->>Misc: decode(bytes[107])
    Misc-->>UserMixin: business
    UserMixin->>UserMixin: vipInfo = business.body.lists.find(type == 1)
    UserMixin->>UserMixin: isVip = !!vipInfo
    UserMixin->>UserMixin: isYearsVip = !!vipInfo.isYear
    UserMixin->>UserMixin: vipLevel = vipInfo.level or 0
  else no business bytes
    UserMixin->>UserMixin: isVip = false
    UserMixin->>UserMixin: isYearsVip = false
    UserMixin->>UserMixin: vipLevel = 0
  end
  UserMixin-->>PMHQ: Return normalized user data
Loading

更新后的 SettingsDialog props 与 NTQQ 用户 API 的类图

classDiagram
  class SettingsDialogProps {
    +boolean visible
    +function onClose()
    +function onLogout() optional
  }

  class SettingsDialog {
    +SettingsDialogProps props
    -boolean showLogoutConfirm
    +render()
  }

  class App {
    -boolean showSettingsDialog
    +render()
    +handleLogout()
  }

  SettingsDialogProps <.. SettingsDialog : uses
  App o-- SettingsDialog : renders

  class NTQQUserApi {
    +getUidByUin(uin string, peerUid string) Promise~string~
    +fetchUserDetailInfo(uid string) Promise
    +getUserSimpleInfo(uid string, force boolean) Promise~SimpleInfo~
    +getCoreAndBaseInfo(uids string[]) Promise
  }

  class UserMixinResult {
    +string uin
    +string nick
    +string longNick
    +number age
    +number gender
    +string location
    +string avatarUrl
    +string qid
    +string qidName
    +string sign
    +string status
    +boolean isVip
    +boolean isYearsVip
    +number vipLevel
    +string[] labels
    +string school
    +string remark
  }

  class UserMixin {
    +getUser(uid string) Promise~UserMixinResult~
  }

  UserMixinResult <.. UserMixin : returns
  NTQQUserApi <.. UserMixin : uses
Loading

文件级变更

Change Details Files
通过设置对话框支持 WebUI 登出,包括确认弹窗和 token 清理。
  • 扩展 SettingsDialog 的 props,新增可选的 onLogout 回调,并在本地维护登出确认状态。
  • 在设置对话框底部渲染一个登出按钮,点击后打开确认覆盖对话框。
  • 实现一个确认弹窗:确认时触发 onLogout 回调,取消时仅关闭弹窗。
  • 在 App.tsx 中将 SettingsDialog.onLogout 连接到清理 webui_token cookie 并重新加载页面的逻辑。
src/webui/FE/components/common/SettingsDialog.tsx
src/webui/FE/App.tsx
改进用户元数据处理中 VIP 状态和 UID 解析。
  • 从第一个 type === 1 的 business 列表项中推导与 VIP 相关的字段,而不是假设使用索引 0。
  • 在 getUidByUin 中将回退 UID 查询方式改为使用 getUserSimpleInfo,以提升解析的可靠性。
  • 在 fetchUserDetailInfo、getUserSimpleInfo 和 getCoreAndBaseInfo 的 JSDoc 中澄清其服务器访问与缓存语义。
src/main/pmhq/mixins/user.ts
src/ntqqapi/api/user.ts
增强 @ 提及行为,在各消息流水线中使用群成员显示名。
  • 在转换外发群消息时,获取群成员信息,并用成员名片或昵称填充 @ 元素文本。
  • 在 OneBot 11 helper 中,默认从群成员信息推导 @ 显示文本,可被显式的 segment name 覆盖。
  • 在 Satori 消息编码器中,当没有提供显式名称时,使用群成员信息构造 @ 显示文本。
src/milky/transform/message/outgoing.ts
src/onebot11/helper/createMessage.ts
src/satori/message.ts
调整 WebUI 与 CI 流水线的构建和开发工具配置。
  • 在发布 GitHub Action 中,跳过 Electron 二进制下载,使用带 --no-immutable 的 yarn install,并在 src/webui/FE 中单独执行 npm install。
  • 将 WebUI 的 Vite 开发服务器明确绑定到 127.0.0.1,端口改为 15173(不再使用默认的 5173)。
.github/workflows/publish.yml
src/webui/FE/vite.config.ts
优化消息历史过滤并更新版本元数据。
  • 在获取回复消息上下文时,将 filterMsgToTime 偏移 +1 毫秒并转为字符串,以避免因时间戳相等而漏掉消息。
  • 将导出的应用版本常量更新为 7.12.10,并同步调整分发与实体元数据文件。
src/ntqqapi/api/msg.ts
src/version.ts
package-dist.json
src/ntqqapi/entities.ts
a/doc/用户说明.txt -> b/doc/用户说明.txt

提示与命令

与 Sourcery 交互

  • 触发一次新评审: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub issue: 在某条评审评论下回复,要求 Sourcery 从该评论创建 issue。你也可以直接回复 @sourcery-ai issue 来创建对应 issue。
  • 生成 pull request 标题: 在 pull request 标题中任意位置写入 @sourcery-ai 可随时生成标题。也可以在 pull request 中评论 @sourcery-ai title 以(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文任意位置写入 @sourcery-ai summary,即可在对应位置生成 PR 摘要。也可以评论 @sourcery-ai summary 来在任意时间(重新)生成摘要。
  • 生成 Reviewer’s Guide: 在 pull request 中评论 @sourcery-ai guide,即可在任意时间(重新)生成 Reviewer’s Guide。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve 来标记所有 Sourcery 评论为已解决。如果你已经处理完所有评论且不想再看到它们,这会很有用。
  • 一次性撤销所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss 来撤销所有现有 Sourcery 评审。尤其适用于你想从一个全新的评审开始——别忘了之后再评论 @sourcery-ai review 来触发新评审!

自定义你的体验

访问你的 控制台 以:

  • 启用或禁用评审特性,例如 Sourcery 生成的 pull request 摘要、Reviewer’s Guide 等。
  • 更改评审语言。
  • 添加、删除或编辑自定义评审指令。
  • 调整其他评审设置。

获取帮助

Original review guide in English

Reviewer's Guide

Introduces a WebUI logout flow from the settings dialog, improves VIP flag handling and user info fetching, refines @-mention display names, adjusts WebUI dev server and CI install behavior, tweaks message time filtering, and bumps the app version to 7.12.10.

Sequence diagram for WebUI logout interaction from settings dialog

sequenceDiagram
  actor User
  participant SettingsDialog
  participant App
  participant Browser

  User->>SettingsDialog: Click logout button
  SettingsDialog->>SettingsDialog: setShowLogoutConfirm(true)
  SettingsDialog-->>User: Show confirmation modal

  User->>SettingsDialog: Click Confirm logout
  SettingsDialog->>App: onLogout()
  App->>Browser: deleteCookie(webui_token)
  App->>Browser: window.location.reload()
  Browser-->>User: Reloaded page without WebUI session
Loading

Sequence diagram for improved group @-mention display resolution

sequenceDiagram
  participant Sender
  participant MessageBuilder as MessageBuilder_createSendElements
  participant NTQQUserApi as NTQQUserApi
  participant NTQQGroupApi as NTQQGroupApi

  Sender->>MessageBuilder: Provide segment type mention with user_id
  MessageBuilder->>NTQQUserApi: getUidByUin(memberUin, peerUid)
  NTQQUserApi-->>MessageBuilder: memberUid
  MessageBuilder->>NTQQGroupApi: getGroupMember(peerUid, memberUid)
  NTQQGroupApi-->>MessageBuilder: memberInfo(cardName, nick)
  MessageBuilder->>MessageBuilder: Build displayName = "@" + (cardName or nick)
  MessageBuilder-->>Sender: SendElement.at(..., displayName)
Loading

Sequence diagram for VIP info extraction in UserMixin

sequenceDiagram
  participant UserMixin as UserMixin_getUser
  participant PMHQ as PMHQBase
  participant Misc as Misc_UserInfoBusiness

  UserMixin->>PMHQ: fetch raw user info
  PMHQ-->>UserMixin: info(body.properties)
  UserMixin->>UserMixin: bytes[107] ?
  alt has business bytes
    UserMixin->>Misc: decode(bytes[107])
    Misc-->>UserMixin: business
    UserMixin->>UserMixin: vipInfo = business.body.lists.find(type == 1)
    UserMixin->>UserMixin: isVip = !!vipInfo
    UserMixin->>UserMixin: isYearsVip = !!vipInfo.isYear
    UserMixin->>UserMixin: vipLevel = vipInfo.level or 0
  else no business bytes
    UserMixin->>UserMixin: isVip = false
    UserMixin->>UserMixin: isYearsVip = false
    UserMixin->>UserMixin: vipLevel = 0
  end
  UserMixin-->>PMHQ: Return normalized user data
Loading

Class diagram for updated SettingsDialog props and NTQQ user APIs

classDiagram
  class SettingsDialogProps {
    +boolean visible
    +function onClose()
    +function onLogout() optional
  }

  class SettingsDialog {
    +SettingsDialogProps props
    -boolean showLogoutConfirm
    +render()
  }

  class App {
    -boolean showSettingsDialog
    +render()
    +handleLogout()
  }

  SettingsDialogProps <.. SettingsDialog : uses
  App o-- SettingsDialog : renders

  class NTQQUserApi {
    +getUidByUin(uin string, peerUid string) Promise~string~
    +fetchUserDetailInfo(uid string) Promise
    +getUserSimpleInfo(uid string, force boolean) Promise~SimpleInfo~
    +getCoreAndBaseInfo(uids string[]) Promise
  }

  class UserMixinResult {
    +string uin
    +string nick
    +string longNick
    +number age
    +number gender
    +string location
    +string avatarUrl
    +string qid
    +string qidName
    +string sign
    +string status
    +boolean isVip
    +boolean isYearsVip
    +number vipLevel
    +string[] labels
    +string school
    +string remark
  }

  class UserMixin {
    +getUser(uid string) Promise~UserMixinResult~
  }

  UserMixinResult <.. UserMixin : returns
  NTQQUserApi <.. UserMixin : uses
Loading

File-Level Changes

Change Details Files
Add WebUI logout support via the settings dialog with a confirmation modal and token clearing.
  • Extend SettingsDialog props to accept an optional onLogout callback and track local logout confirmation state.
  • Render a logout button in the settings dialog footer that opens a confirmation overlay dialog.
  • Implement a confirmation modal that on confirm triggers the onLogout callback and on cancel hides the modal.
  • Wire SettingsDialog.onLogout in App.tsx to clear the webui_token cookie and reload the page.
src/webui/FE/components/common/SettingsDialog.tsx
src/webui/FE/App.tsx
Improve user metadata handling for VIP status and UID resolution.
  • Derive VIP-related fields from the first business list entry with type === 1 instead of assuming index 0.
  • Change the fallback UID lookup in getUidByUin to use getUserSimpleInfo for more reliable resolution.
  • Clarify JSDoc comments on fetchUserDetailInfo, getUserSimpleInfo, and getCoreAndBaseInfo regarding server access and caching semantics.
src/main/pmhq/mixins/user.ts
src/ntqqapi/api/user.ts
Enhance @-mention behavior to use group member display names across message pipelines.
  • When transforming outgoing group messages, fetch group member info and populate the @ element text with the member card name or nickname.
  • In the OneBot 11 helper, derive the default @ display text from group member info, overridable by an explicit segment name.
  • In the Satori message encoder, use group member info to build the @ display text when no explicit name is provided.
src/milky/transform/message/outgoing.ts
src/onebot11/helper/createMessage.ts
src/satori/message.ts
Adjust build and dev tooling configuration for the WebUI and CI pipeline.
  • In the publish GitHub Action, skip Electron binary downloads, run yarn install with --no-immutable, and separately npm install in src/webui/FE.
  • Update the WebUI Vite dev server to bind explicitly to 127.0.0.1 and listen on port 15173 instead of the default 5173.
.github/workflows/publish.yml
src/webui/FE/vite.config.ts
Refine message history filtering and bump version metadata.
  • Offset filterMsgToTime by +1 millisecond and cast to string when fetching replied message context to avoid missing messages due to equal timestamps.
  • Update the exported application version constant to 7.12.10 and adjust distribution / entity metadata files accordingly.
src/ntqqapi/api/msg.ts
src/version.ts
package-dist.json
src/ntqqapi/entities.ts
a/doc/用户说明.txt -> b/doc/用户说明.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 5 个问题,并给出了一些整体反馈:

  • SettingsDialog 中,onLogout 是可选的,但登出 UI 始终显示,并且确认按钮在点击时无条件调用它;建议在未提供 onLogout 时隐藏登出按钮,或者在调用时加上保护,以避免无效操作 / 令人困惑的交互体验。
  • transformOutgoingMessagecreateSendElementsMessageEncoder 中构建 @ 提及时新增的 getGroupMember 调用假定请求一定成功且 cardName/nick 一定非空;建议增加失败或数据缺失时的处理,以避免运行时错误,并在群成员信息不可用时仍能保持消息发送功能正常。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `SettingsDialog`, `onLogout` is optional but the logout UI is always shown and the confirm button calls it unconditionally; consider hiding the logout button when `onLogout` is not provided or guarding the call to avoid a no-op/confusing UX.
- The new `getGroupMember` calls when building @-mentions in `transformOutgoingMessage`, `createSendElements`, and `MessageEncoder` assume a successful response and non-empty `cardName`/`nick`; consider handling failures or missing data to avoid runtime errors and preserve message sending when group member info is unavailable.

## Individual Comments

### Comment 1
<location path="src/webui/FE/components/common/SettingsDialog.tsx" line_range="15-18" />
<code_context>
+const SettingsDialog: React.FC<SettingsDialogProps> = ({ visible, onClose, onLogout }) => {
   const { mode, setMode } = useThemeStore()
   const { autoHideSidebarInWebQQ, setAutoHideSidebarInWebQQ, showWebQQFullscreenButton, setShowWebQQFullscreenButton } = useSettingsStore()
+  const [showLogoutConfirm, setShowLogoutConfirm] = React.useState(false)

   if (!visible) return null
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard optional `onLogout` and consider hiding the logout entry when it is not provided.

Since `onLogout` is optional but the UI always renders a logout button and calls it unconditionally, omitting the prop leaves the confirm dialog with a non-functional confirm button. Consider either only rendering the logout button/dialog when `onLogout` is defined, or guarding the handler (e.g. `onClick={() => { onLogout?.(); setShowLogoutConfirm(false); }}`) so the dialog still closes if no callback is provided.
</issue_to_address>

### Comment 2
<location path="src/ntqqapi/api/user.ts" line_range="76" />
<code_context>
         return (await this.ctx.pmhq.invoke('nodeIKernelUixConvertService/getUin', [[uid]])).uinInfo.get(uid)
       },
       async () => {
-        return (await this.fetchUserDetailInfo(uid)).detail.get(uid)?.uin
+        return (await this.getUserSimpleInfo(uid)).uin
       },
     ]
</code_context>
<issue_to_address>
**issue:** New `getUidByUin` fallback is stricter and may throw when user info is missing.

The previous fallback used `fetchUserDetailInfo(uid)).detail.get(uid)?.uin`, which safely returned `undefined` when the user detail was missing. `getUserSimpleInfo(uid)` instead does `data.get(uid)!`, which will throw if the user is not in the map (e.g., cache miss or API quirk). If callers expect an optional result, this changes behavior from "undefined" to "exception". Please either make `getUserSimpleInfo` handle a missing entry or access the map safely here and keep returning `undefined` instead of throwing.
</issue_to_address>

### Comment 3
<location path="src/ntqqapi/api/msg.ts" line_range="293" />
<code_context>
         chatInfo: peer,
         filterMsgType: [],
         filterSendersUid,
-        filterMsgToTime: filterMsgTime,
+        filterMsgToTime: String(filterMsgTime + 1),  // 获取到的消息时间可能比 replyMsgTime 多一毫秒
         filterMsgFromTime: filterMsgTime,
         isReverseOrder: true,
</code_context>
<issue_to_address>
**issue (bug_risk):** Inconsistent typing between `filterMsgToTime` and `filterMsgFromTime` may be problematic.

`filterMsgToTime` is now passed as `String(filterMsgTime + 1)` while `filterMsgFromTime` is still a number. If the API expects these fields to share a type, this mismatch could lead to subtle issues. Consider normalizing both fields, e.g. `String(filterMsgTime)` and `String(filterMsgTime + 1)`, or both as numbers, based on the PMHQ/NT API contract.
</issue_to_address>

### Comment 4
<location path="src/onebot11/helper/createMessage.ts" line_range="67-68" />
<code_context>
           }
           else if (peer.chatType === ChatType.Group) {
             const uid = await ctx.ntUserApi.getUidByUin(atQQ, peer.peerUid)
-            let display = ''
+            const info = await ctx.ntGroupApi.getGroupMember(peer.peerUid, uid)
+            let display = `@${info.cardName || info.nick}`
             if (segment.data.name) {
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid unnecessary `getGroupMember` call when a custom mention name is provided.

`getGroupMember` is always called even though its result is discarded when `segment.data.name` is set. Consider branching on `segment.data.name` first, and only calling `getGroupMember` in the `else` path so the API is only used when no custom name is provided.
</issue_to_address>

### Comment 5
<location path="src/satori/message.ts" line_range="385-386" />
<code_context>
         this.elements.push(SendElement.at('', '', NT.AtType.All, '@全体成员'))
       } else {
         const uid = await this.ctx.ntUserApi.getUidByUin(attrs.id, this.peer.peerUid)
-        const display = attrs.name ? '@' + attrs.name : ''
+        const info = await this.ctx.ntGroupApi.getGroupMember(this.peer.peerUid, uid)
+        const display = attrs.name ? '@' + attrs.name : `@${info.cardName || info.nick}`
         this.elements.push(SendElement.at(attrs.id, uid, NT.AtType.One, display))
</code_context>
<issue_to_address>
**suggestion (performance):** Similar to OneBot path, `getGroupMember` is done even when `attrs.name` is provided.

Here, as in `createMessage.ts`, `getGroupMember` is always called even though `attrs.name` may already provide the display text. To avoid unnecessary network calls and handle failures more robustly, only fetch the group member when `attrs.name` is absent, and guard the `getGroupMember` result with try/catch or a null-check in case the member cannot be resolved.
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得这次 Review 有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的 Review。
Original comment in English

Hey - I've found 5 issues, and left some high level feedback:

  • In SettingsDialog, onLogout is optional but the logout UI is always shown and the confirm button calls it unconditionally; consider hiding the logout button when onLogout is not provided or guarding the call to avoid a no-op/confusing UX.
  • The new getGroupMember calls when building @-mentions in transformOutgoingMessage, createSendElements, and MessageEncoder assume a successful response and non-empty cardName/nick; consider handling failures or missing data to avoid runtime errors and preserve message sending when group member info is unavailable.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `SettingsDialog`, `onLogout` is optional but the logout UI is always shown and the confirm button calls it unconditionally; consider hiding the logout button when `onLogout` is not provided or guarding the call to avoid a no-op/confusing UX.
- The new `getGroupMember` calls when building @-mentions in `transformOutgoingMessage`, `createSendElements`, and `MessageEncoder` assume a successful response and non-empty `cardName`/`nick`; consider handling failures or missing data to avoid runtime errors and preserve message sending when group member info is unavailable.

## Individual Comments

### Comment 1
<location path="src/webui/FE/components/common/SettingsDialog.tsx" line_range="15-18" />
<code_context>
+const SettingsDialog: React.FC<SettingsDialogProps> = ({ visible, onClose, onLogout }) => {
   const { mode, setMode } = useThemeStore()
   const { autoHideSidebarInWebQQ, setAutoHideSidebarInWebQQ, showWebQQFullscreenButton, setShowWebQQFullscreenButton } = useSettingsStore()
+  const [showLogoutConfirm, setShowLogoutConfirm] = React.useState(false)

   if (!visible) return null
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard optional `onLogout` and consider hiding the logout entry when it is not provided.

Since `onLogout` is optional but the UI always renders a logout button and calls it unconditionally, omitting the prop leaves the confirm dialog with a non-functional confirm button. Consider either only rendering the logout button/dialog when `onLogout` is defined, or guarding the handler (e.g. `onClick={() => { onLogout?.(); setShowLogoutConfirm(false); }}`) so the dialog still closes if no callback is provided.
</issue_to_address>

### Comment 2
<location path="src/ntqqapi/api/user.ts" line_range="76" />
<code_context>
         return (await this.ctx.pmhq.invoke('nodeIKernelUixConvertService/getUin', [[uid]])).uinInfo.get(uid)
       },
       async () => {
-        return (await this.fetchUserDetailInfo(uid)).detail.get(uid)?.uin
+        return (await this.getUserSimpleInfo(uid)).uin
       },
     ]
</code_context>
<issue_to_address>
**issue:** New `getUidByUin` fallback is stricter and may throw when user info is missing.

The previous fallback used `fetchUserDetailInfo(uid)).detail.get(uid)?.uin`, which safely returned `undefined` when the user detail was missing. `getUserSimpleInfo(uid)` instead does `data.get(uid)!`, which will throw if the user is not in the map (e.g., cache miss or API quirk). If callers expect an optional result, this changes behavior from "undefined" to "exception". Please either make `getUserSimpleInfo` handle a missing entry or access the map safely here and keep returning `undefined` instead of throwing.
</issue_to_address>

### Comment 3
<location path="src/ntqqapi/api/msg.ts" line_range="293" />
<code_context>
         chatInfo: peer,
         filterMsgType: [],
         filterSendersUid,
-        filterMsgToTime: filterMsgTime,
+        filterMsgToTime: String(filterMsgTime + 1),  // 获取到的消息时间可能比 replyMsgTime 多一毫秒
         filterMsgFromTime: filterMsgTime,
         isReverseOrder: true,
</code_context>
<issue_to_address>
**issue (bug_risk):** Inconsistent typing between `filterMsgToTime` and `filterMsgFromTime` may be problematic.

`filterMsgToTime` is now passed as `String(filterMsgTime + 1)` while `filterMsgFromTime` is still a number. If the API expects these fields to share a type, this mismatch could lead to subtle issues. Consider normalizing both fields, e.g. `String(filterMsgTime)` and `String(filterMsgTime + 1)`, or both as numbers, based on the PMHQ/NT API contract.
</issue_to_address>

### Comment 4
<location path="src/onebot11/helper/createMessage.ts" line_range="67-68" />
<code_context>
           }
           else if (peer.chatType === ChatType.Group) {
             const uid = await ctx.ntUserApi.getUidByUin(atQQ, peer.peerUid)
-            let display = ''
+            const info = await ctx.ntGroupApi.getGroupMember(peer.peerUid, uid)
+            let display = `@${info.cardName || info.nick}`
             if (segment.data.name) {
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid unnecessary `getGroupMember` call when a custom mention name is provided.

`getGroupMember` is always called even though its result is discarded when `segment.data.name` is set. Consider branching on `segment.data.name` first, and only calling `getGroupMember` in the `else` path so the API is only used when no custom name is provided.
</issue_to_address>

### Comment 5
<location path="src/satori/message.ts" line_range="385-386" />
<code_context>
         this.elements.push(SendElement.at('', '', NT.AtType.All, '@全体成员'))
       } else {
         const uid = await this.ctx.ntUserApi.getUidByUin(attrs.id, this.peer.peerUid)
-        const display = attrs.name ? '@' + attrs.name : ''
+        const info = await this.ctx.ntGroupApi.getGroupMember(this.peer.peerUid, uid)
+        const display = attrs.name ? '@' + attrs.name : `@${info.cardName || info.nick}`
         this.elements.push(SendElement.at(attrs.id, uid, NT.AtType.One, display))
</code_context>
<issue_to_address>
**suggestion (performance):** Similar to OneBot path, `getGroupMember` is done even when `attrs.name` is provided.

Here, as in `createMessage.ts`, `getGroupMember` is always called even though `attrs.name` may already provide the display text. To avoid unnecessary network calls and handle failures more robustly, only fetch the group member when `attrs.name` is absent, and guard the `getGroupMember` result with try/catch or a null-check in case the member cannot be resolved.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +15 to 18
const [showLogoutConfirm, setShowLogoutConfirm] = React.useState(false)

if (!visible) return null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 需要保护可选的 onLogout,并在未提供该回调时考虑隐藏登出入口。

由于 onLogout 是可选的,但 UI 始终渲染登出按钮且在点击时无条件调用它,如果不传这个 prop,则确认对话框中的确认按钮会变成无效操作。建议要么只在 onLogout 已定义时渲染登出按钮 / 对话框,要么在处理函数中加保护(例如 onClick={() => { onLogout?.(); setShowLogoutConfirm(false); }}),这样即使没有回调也能正常关闭对话框。

Original comment in English

issue (bug_risk): Guard optional onLogout and consider hiding the logout entry when it is not provided.

Since onLogout is optional but the UI always renders a logout button and calls it unconditionally, omitting the prop leaves the confirm dialog with a non-functional confirm button. Consider either only rendering the logout button/dialog when onLogout is defined, or guarding the handler (e.g. onClick={() => { onLogout?.(); setShowLogoutConfirm(false); }}) so the dialog still closes if no callback is provided.

Comment thread src/ntqqapi/api/user.ts
return (await this.ctx.pmhq.invoke('nodeIKernelUixConvertService/getUin', [[uid]])).uinInfo.get(uid)
},
async () => {
return (await this.fetchUserDetailInfo(uid)).detail.get(uid)?.uin
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: 新的 getUidByUin 兜底逻辑更严格,在用户信息缺失时可能抛异常。

之前的兜底逻辑使用的是 fetchUserDetailInfo(uid)).detail.get(uid)?.uin,当用户详情缺失时会安全地返回 undefined。而 getUserSimpleInfo(uid) 内部使用的是 data.get(uid)!,如果该用户不在 map 中(例如缓存未命中或 API 行为异常),就会抛出异常。如果调用方预期拿到的是一个可选结果,那么这相当于从“返回 undefined”变成了“抛异常”。请考虑让 getUserSimpleInfo 在条目缺失时安全处理,或者在此处以安全方式访问 map,保持返回 undefined 而不是抛出异常。

Original comment in English

issue: New getUidByUin fallback is stricter and may throw when user info is missing.

The previous fallback used fetchUserDetailInfo(uid)).detail.get(uid)?.uin, which safely returned undefined when the user detail was missing. getUserSimpleInfo(uid) instead does data.get(uid)!, which will throw if the user is not in the map (e.g., cache miss or API quirk). If callers expect an optional result, this changes behavior from "undefined" to "exception". Please either make getUserSimpleInfo handle a missing entry or access the map safely here and keep returning undefined instead of throwing.

Comment thread src/ntqqapi/api/msg.ts
chatInfo: peer,
filterMsgType: [],
filterSendersUid,
filterMsgToTime: filterMsgTime,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): filterMsgToTimefilterMsgFromTime 之间类型不一致,可能带来问题。

现在 filterMsgToTime 传的是 String(filterMsgTime + 1),而 filterMsgFromTime 仍然是一个数字。如果 API 预期这两个字段的类型一致,这种不匹配可能会导致一些隐蔽的问题。建议根据 PMHQ/NT API 的约定统一这两个字段的类型,例如都传字符串(String(filterMsgTime)String(filterMsgTime + 1)),或者都传数字。

Original comment in English

issue (bug_risk): Inconsistent typing between filterMsgToTime and filterMsgFromTime may be problematic.

filterMsgToTime is now passed as String(filterMsgTime + 1) while filterMsgFromTime is still a number. If the API expects these fields to share a type, this mismatch could lead to subtle issues. Consider normalizing both fields, e.g. String(filterMsgTime) and String(filterMsgTime + 1), or both as numbers, based on the PMHQ/NT API contract.

Comment thread src/onebot11/helper/createMessage.ts
Comment thread src/satori/message.ts
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 30, 2026

Test Report

Job Status
unit-test ✅ success
e2e-test ✅ success

✅ All tests passed

@idranme idranme merged commit 735b123 into main Apr 30, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants