Skip to content

chore: update version to 7.12.9#749

Merged
idranme merged 5 commits into
mainfrom
dev
Apr 28, 2026
Merged

chore: update version to 7.12.9#749
idranme merged 5 commits into
mainfrom
dev

Conversation

@idranme
Copy link
Copy Markdown
Collaborator

@idranme idranme commented Apr 28, 2026

Summary by Sourcery

简化用户资料获取逻辑,改为依赖 PMHQ 用户信息服务,并在各类 API 中扩展对外暴露的用户元数据,同时提升项目版本。

New Features:

  • 在 go-cqhttp 陌生人信息 API 响应中暴露 VIP 状态、VIP 等级和备注字段。
  • 在基于 PMHQ 服务的用户资料响应中包含更多用户元数据,如学校和备注信息。

Enhancements:

  • 当 NT 用户详情查询失败或缺少群成员常用扩展字段时,回退到 PMHQ 用户信息,以确保数据获取更加健壮。
  • 在共享的用户 mixin 中,从 PMHQ 用户信息中解码并暴露更多与业务和资料相关的用户属性(包括与 VIP 相关的字段)。
  • 更新内部协议定义,以描述用户业务信息,用于解码 PMHQ 响应。

Build:

  • 将项目版本从 7.12.8 升级到 7.12.9。
Original summary in English

Summary by Sourcery

Simplify user profile retrieval to rely on the PMHQ user info service and extend exposed user metadata across APIs while bumping the project version.

New Features:

  • Expose VIP status, VIP level, and remark fields in the go-cqhttp stranger info API response.
  • Include additional user metadata such as school and remark in user profile responses sourced from the PMHQ service.

Enhancements:

  • Fallback to PMHQ user info when NT user detail lookup fails or lacks common extensions for group member info, ensuring more robust data retrieval.
  • Decode and surface additional user business and profile properties (including VIP-related fields) from PMHQ user info in the shared user mixin.
  • Update internal protocol definitions to describe user business information for decoding PMHQ responses.

Build:

  • Bump project version from 7.12.8 to 7.12.9.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 28, 2026

审阅者指南

重构用户资料和群成员信息的获取逻辑,使其更一致地依赖 pmhq.fetchUserInfo 和新增解码的业务字段;为陌生人信息添加与 VIP 相关的元数据;扩展 PMHQ 用户 mixin 和 proto 定义以支持业务/用户元数据,并将项目版本提升到 7.12.9。

更新后的 GetUserProfile API 解析时序图

sequenceDiagram
  participant Client
  participant GetUserProfile
  participant Ctx
  participant PMHQ as PMHQ_fetchUserInfo

  Client->>GetUserProfile: call(payload.user_id)
  GetUserProfile->>Ctx: access pmhq
  GetUserProfile->>PMHQ: fetchUserInfo(user_id)
  PMHQ-->>GetUserProfile: UserInfo
  GetUserProfile->>GetUserProfile: map fields (nick, qid, age, sex, remark, longNick, level, country, city, school)
  GetUserProfile-->>Client: Ok(profile)
Loading

更新后的 GetGroupMemberInfo(带 PMHQ 回退)的时序图

sequenceDiagram
  participant Caller
  participant GetGroupMemberInfo
  participant Ctx
  participant NtUserApi
  participant PMHQ as PMHQ_fetchUserInfo

  Caller->>GetGroupMemberInfo: handle(payload, member)
  GetGroupMemberInfo->>Ctx: access ntUserApi
  GetGroupMemberInfo->>NtUserApi: getUserDetailInfoWithBizInfo(member.uid)
  alt primary detail succeeds
    NtUserApi-->>GetGroupMemberInfo: UserDetailInfo
  else primary fails
    GetGroupMemberInfo->>NtUserApi: fetchUserDetailInfo(member.uid)
    NtUserApi-->>GetGroupMemberInfo: Map<uid, UserDetailInfo>
    GetGroupMemberInfo->>GetGroupMemberInfo: lookup info by member.uid
  end

  alt info.commonExt exists
    GetGroupMemberInfo->>GetGroupMemberInfo: set sex, qq_level, age from info
    alt qq_level is 0
      GetGroupMemberInfo->>PMHQ: fetchUserInfo(user_id)
      PMHQ-->>GetGroupMemberInfo: UserInfo
      GetGroupMemberInfo->>GetGroupMemberInfo: overwrite qq_level from PMHQ
    end
  else no info or no commonExt
    GetGroupMemberInfo->>PMHQ: fetchUserInfo(user_id)
    PMHQ-->>GetGroupMemberInfo: UserInfo
    GetGroupMemberInfo->>GetGroupMemberInfo: set sex, qq_level, age from PMHQ
  end

  GetGroupMemberInfo-->>Caller: OB11GroupMember
Loading

包含 VIP 元数据的 GetStrangerInfo 时序图

sequenceDiagram
  participant Client
  participant GetStrangerInfo
  participant Ctx
  participant PMHQ as PMHQ_fetchUserInfo

  Client->>GetStrangerInfo: call(payload.user_id)
  GetStrangerInfo->>Ctx: access pmhq
  GetStrangerInfo->>PMHQ: fetchUserInfo(user_id)
  PMHQ-->>GetStrangerInfo: UserInfo
  GetStrangerInfo->>GetStrangerInfo: map fields (labels, isVip, isYearsVip, vipLevel, remark, etc.)
  GetStrangerInfo-->>Client: Response with VIP metadata
Loading

更新后的 PMHQ 用户 mixin 与 Misc proto 类型类图

classDiagram
  class PMHQBase {
  }

  class PMHQWithUserMixin {
    +fetchUserInfo(uin: number) UserInfo
  }

  PMHQBase <|-- PMHQWithUserMixin

  class UserInfo {
    +uin: number
    +nick: string
    +level: number
    +age: number
    +sex: number
    +country: string
    +city: string
    +regTime: number
    +birthdayYear: number
    +birthdayMonth: number
    +birthdayDay: number
    +labels: string[]
    +school: string
    +remark: string
    +isVip: boolean
    +isYearsVip: boolean
    +vipLevel: number
  }

  PMHQWithUserMixin --> UserInfo

  class Oidb_FetchUserInfoResp {
    +body: Oidb_FetchUserInfoBody
  }

  class Oidb_FetchUserInfoBody {
    +uin: number
    +properties: Oidb_UserProperties
  }

  class Oidb_UserProperties {
    +numberProperties: NumberProperty[]
    +bytesProperties: BytesProperty[]
  }

  class NumberProperty {
    +key: number
    +value: number
  }

  class BytesProperty {
    +key: number
    +value: Uint8Array
  }

  Oidb_FetchUserInfoResp --> Oidb_FetchUserInfoBody
  Oidb_FetchUserInfoBody --> Oidb_UserProperties
  Oidb_UserProperties --> NumberProperty
  Oidb_UserProperties --> BytesProperty

  class Misc_UserInfoLabel {
    +labels: Misc_UserInfoLabelItem[]
  }

  class Misc_UserInfoLabelItem {
    +content: string
  }

  Misc_UserInfoLabel --> Misc_UserInfoLabelItem

  class Misc_UserInfoBusiness {
    +body: Misc_UserInfoBusinessBody
  }

  class Misc_UserInfoBusinessBody {
    +msg: string
    +lists: Misc_UserInfoBusinessListItem[]
  }

  class Misc_UserInfoBusinessListItem {
    +type: number
    +field2: number
    +isYear: number
    +level: number
    +isPro: number
    +icon1: string
    +icon2: string
  }

  Misc_UserInfoBusiness --> Misc_UserInfoBusinessBody
  Misc_UserInfoBusinessBody --> Misc_UserInfoBusinessListItem

  UserInfo --> Misc_UserInfoBusinessListItem : derives VIP flags from
Loading

文件级变更

变更 详情 文件
简化 GetUserProfile API,使其使用 pmhq.fetchUserInfo 作为用户资料字段的唯一数据源。
  • 将用于获取用户资料的 ntUserApi.getUserDetailInfoByUin 调用替换为 pmhq.fetchUserInfo
  • pmhq.fetchUserInfo 返回的字段(nick, qid, age, sex, remark, longNick, level, country, city, school)直接映射到 API 响应中。
  • 删除当 level 为 0 时重新拉取等级的回退逻辑,因为现在 pmhq.fetchUserInfo 是主要数据源。
src/milky/api/system.ts
调整群成员信息解析逻辑,在存在 commonExt 的情况下优先使用 ntUserApi 明细,否则回退到 pmhq.fetchUserInfo
  • 移除围绕 ntUserApi 调用和信息转储的调试日志。
  • ntUserApi 返回包含 commonExt 的信息时,从该结构中推导 sex、qq_level(通过 calcQQLevel)和 age,仅当 qq_level 为 0 时才回退到 pmhq.fetchUserInfo
  • ntUserApi 缺少 commonExt 时,从 pmhq 获取用户信息,并用该结果填充 sex、qq_level 和 age。
src/onebot11/action/group/GetGroupMemberInfo.ts
扩展 protobuf/misc 定义,以描述用户业务信息,用于提取 VIP 元数据。
  • 新增 Misc.UserInfoBusiness ProtoMessage,包含嵌套的 body.lists 结构,字段包括 type、field2、isYear、level、isPro、icon1 和 icon2。
src/ntqqapi/proto/misc.ts
在 go-cqhttp 的 GetStrangerInfo 响应中暴露 VIP 与备注元数据。
  • 在 Response 接口中新增 is_vipis_years_vipvip_levelremark 字段。
  • 在构造 GetStrangerInfo 响应时,从 pmhq.fetchUserInfo 结果中填充新增的 VIP 与 remark 字段。
src/onebot11/action/go-cqhttp/GetStrangerInfo.ts
增强 PMHQ 用户 mixin,请求并解析包括备注、学校和 VIP/业务信息在内的更多用户元数据。
  • Oidb.FetchUserInfo 请求中增加额外的属性 key,包括备注(103)、业务列表(107)以及学校(20021)。
  • 使用 Misc.UserInfoBusiness 解码业务字节数据,并从第一条业务列表条目中推导 isVipisYearsVipvipLevel
  • 在返回的用户信息对象上暴露新字段:school、remark、isVip、isYearsVip、vipLevel。
src/main/pmhq/mixins/user.ts
为新版本更新项目元数据。
  • 将导出的版本常量从 7.12.8 提升到 7.12.9。
  • 重新生成或调整 distribution/package 元数据和锁文件条目,以匹配新版本及其依赖。
  • 在不同目录路径之间移动或重命名一个文本文档。
src/version.ts
package-dist.json
yarn.lock
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 摘要。也可以在 Pull Request 中评论 @sourcery-ai summary 来随时(重新)生成摘要。
  • 生成审阅者指南: 在 Pull Request 中评论 @sourcery-ai guide,可随时(重新)生成审阅者指南。
  • 批量解决所有 Sourcery 评论: 在 Pull Request 中评论 @sourcery-ai resolve,以解决所有 Sourcery 评论。如果你已经处理完所有评论且不希望再看到它们,这会很有用。
  • 取消所有 Sourcery 审查: 在 Pull Request 中评论 @sourcery-ai dismiss,以取消所有现有的 Sourcery 审查。若你想基于一次全新的审查重新开始,这尤其有用——别忘了再评论 @sourcery-ai review 以启动新的审查!

自定义你的体验

前往你的 控制面板 来:

  • 启用或禁用审查功能,例如 Sourcery 自动生成的 Pull Request 摘要、审阅者指南等。
  • 修改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

Refactors user profile and group member info retrieval to rely more consistently on pmhq.fetchUserInfo and newly decoded business fields, adds VIP-related metadata to stranger info, extends the PMHQ user mixin and proto definitions to support business/user metadata, and bumps the project version to 7.12.9.

Sequence diagram for updated GetUserProfile API resolution

sequenceDiagram
  participant Client
  participant GetUserProfile
  participant Ctx
  participant PMHQ as PMHQ_fetchUserInfo

  Client->>GetUserProfile: call(payload.user_id)
  GetUserProfile->>Ctx: access pmhq
  GetUserProfile->>PMHQ: fetchUserInfo(user_id)
  PMHQ-->>GetUserProfile: UserInfo
  GetUserProfile->>GetUserProfile: map fields (nick, qid, age, sex, remark, longNick, level, country, city, school)
  GetUserProfile-->>Client: Ok(profile)
Loading

Sequence diagram for updated GetGroupMemberInfo resolution with PMHQ fallback

sequenceDiagram
  participant Caller
  participant GetGroupMemberInfo
  participant Ctx
  participant NtUserApi
  participant PMHQ as PMHQ_fetchUserInfo

  Caller->>GetGroupMemberInfo: handle(payload, member)
  GetGroupMemberInfo->>Ctx: access ntUserApi
  GetGroupMemberInfo->>NtUserApi: getUserDetailInfoWithBizInfo(member.uid)
  alt primary detail succeeds
    NtUserApi-->>GetGroupMemberInfo: UserDetailInfo
  else primary fails
    GetGroupMemberInfo->>NtUserApi: fetchUserDetailInfo(member.uid)
    NtUserApi-->>GetGroupMemberInfo: Map<uid, UserDetailInfo>
    GetGroupMemberInfo->>GetGroupMemberInfo: lookup info by member.uid
  end

  alt info.commonExt exists
    GetGroupMemberInfo->>GetGroupMemberInfo: set sex, qq_level, age from info
    alt qq_level is 0
      GetGroupMemberInfo->>PMHQ: fetchUserInfo(user_id)
      PMHQ-->>GetGroupMemberInfo: UserInfo
      GetGroupMemberInfo->>GetGroupMemberInfo: overwrite qq_level from PMHQ
    end
  else no info or no commonExt
    GetGroupMemberInfo->>PMHQ: fetchUserInfo(user_id)
    PMHQ-->>GetGroupMemberInfo: UserInfo
    GetGroupMemberInfo->>GetGroupMemberInfo: set sex, qq_level, age from PMHQ
  end

  GetGroupMemberInfo-->>Caller: OB11GroupMember
Loading

Sequence diagram for GetStrangerInfo including VIP metadata

sequenceDiagram
  participant Client
  participant GetStrangerInfo
  participant Ctx
  participant PMHQ as PMHQ_fetchUserInfo

  Client->>GetStrangerInfo: call(payload.user_id)
  GetStrangerInfo->>Ctx: access pmhq
  GetStrangerInfo->>PMHQ: fetchUserInfo(user_id)
  PMHQ-->>GetStrangerInfo: UserInfo
  GetStrangerInfo->>GetStrangerInfo: map fields (labels, isVip, isYearsVip, vipLevel, remark, etc.)
  GetStrangerInfo-->>Client: Response with VIP metadata
Loading

Class diagram for updated PMHQ user mixin and Misc proto types

classDiagram
  class PMHQBase {
  }

  class PMHQWithUserMixin {
    +fetchUserInfo(uin: number) UserInfo
  }

  PMHQBase <|-- PMHQWithUserMixin

  class UserInfo {
    +uin: number
    +nick: string
    +level: number
    +age: number
    +sex: number
    +country: string
    +city: string
    +regTime: number
    +birthdayYear: number
    +birthdayMonth: number
    +birthdayDay: number
    +labels: string[]
    +school: string
    +remark: string
    +isVip: boolean
    +isYearsVip: boolean
    +vipLevel: number
  }

  PMHQWithUserMixin --> UserInfo

  class Oidb_FetchUserInfoResp {
    +body: Oidb_FetchUserInfoBody
  }

  class Oidb_FetchUserInfoBody {
    +uin: number
    +properties: Oidb_UserProperties
  }

  class Oidb_UserProperties {
    +numberProperties: NumberProperty[]
    +bytesProperties: BytesProperty[]
  }

  class NumberProperty {
    +key: number
    +value: number
  }

  class BytesProperty {
    +key: number
    +value: Uint8Array
  }

  Oidb_FetchUserInfoResp --> Oidb_FetchUserInfoBody
  Oidb_FetchUserInfoBody --> Oidb_UserProperties
  Oidb_UserProperties --> NumberProperty
  Oidb_UserProperties --> BytesProperty

  class Misc_UserInfoLabel {
    +labels: Misc_UserInfoLabelItem[]
  }

  class Misc_UserInfoLabelItem {
    +content: string
  }

  Misc_UserInfoLabel --> Misc_UserInfoLabelItem

  class Misc_UserInfoBusiness {
    +body: Misc_UserInfoBusinessBody
  }

  class Misc_UserInfoBusinessBody {
    +msg: string
    +lists: Misc_UserInfoBusinessListItem[]
  }

  class Misc_UserInfoBusinessListItem {
    +type: number
    +field2: number
    +isYear: number
    +level: number
    +isPro: number
    +icon1: string
    +icon2: string
  }

  Misc_UserInfoBusiness --> Misc_UserInfoBusinessBody
  Misc_UserInfoBusinessBody --> Misc_UserInfoBusinessListItem

  UserInfo --> Misc_UserInfoBusinessListItem : derives VIP flags from
Loading

File-Level Changes

Change Details Files
Simplify GetUserProfile API to use pmhq.fetchUserInfo as the single data source for user profile fields.
  • Replace ntUserApi.getUserDetailInfoByUin calls with pmhq.fetchUserInfo for user profile retrieval.
  • Map fields from pmhq.fetchUserInfo (nick, qid, age, sex, remark, longNick, level, country, city, school) directly into the API response.
  • Remove fallback logic that re-fetches level when it is zero, since pmhq.fetchUserInfo is now the primary source.
src/milky/api/system.ts
Adjust group member info resolution to prefer ntUserApi details when commonExt is present and fall back to pmhq.fetchUserInfo otherwise.
  • Remove debug logging around ntUserApi calls and info dumping.
  • When ntUserApi returns info with commonExt, derive sex, qq_level (via calcQQLevel), and age from that structure, with qq_level falling back to pmhq.fetchUserInfo only when zero.
  • When ntUserApi lacks commonExt, fetch user info from pmhq and populate sex, qq_level, and age from that result.
src/onebot11/action/group/GetGroupMemberInfo.ts
Extend protobuf/misc definitions to describe user business info for VIP metadata extraction.
  • Add Misc.UserInfoBusiness ProtoMessage with nested body.lists structure including fields for type, field2, isYear, level, isPro, icon1, and icon2.
src/ntqqapi/proto/misc.ts
Expose VIP and remark metadata on the go-cqhttp GetStrangerInfo response.
  • Extend the Response interface with is_vip, is_years_vip, vip_level, and remark fields.
  • Populate the new VIP and remark fields from pmhq.fetchUserInfo results when building the GetStrangerInfo response.
src/onebot11/action/go-cqhttp/GetStrangerInfo.ts
Enhance PMHQ user mixin to request and parse additional user metadata including remark, school, and VIP/business info.
  • Request additional property keys from Oidb.FetchUserInfo, including remark (103), business list (107), and school (20021).
  • Decode business bytes with Misc.UserInfoBusiness and derive isVip, isYearsVip, and vipLevel from the first business list entry.
  • Expose new fields (school, remark, isVip, isYearsVip, vipLevel) on the returned user info object.
src/main/pmhq/mixins/user.ts
Update project metadata for the new release.
  • Bump the exported version constant from 7.12.8 to 7.12.9.
  • Regenerate or adjust distribution/package metadata and lockfile entries to match the new version and dependencies.
  • Move or rename a documentation text file under a different directory path.
src/version.ts
package-dist.json
yarn.lock
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 - 我发现了 3 个问题,并给出了一些总体反馈:

  • GetGroupMemberInfo 中,ret.age 现在直接从 info.simpleInfo.baseInfo.age 赋值,而没有使用 ?? 0 兜底,这改变了之前的行为,可能在期望得到数字的地方出现 undefined;建议保留原有的默认值逻辑。
  • GetUserProfile 中,完全切换为使用 ctx.pmhq.fetchUserInfo 移除了之前基于 ntUserApi 的显式错误处理/Failed 返回路径;建议补充等价的错误处理,这样在获取失败时,调用方能得到结构化的失败结果,而不是未捕获异常。
给 AI Agents 的提示
Please address the comments from this code review:

## Overall Comments
- In `GetGroupMemberInfo`, `ret.age` is now assigned directly from `info.simpleInfo.baseInfo.age` without a `?? 0` fallback, which changes previous behavior and may result in `undefined` where a number is expected; consider keeping the defaulting logic.
- In `GetUserProfile`, switching entirely to `ctx.pmhq.fetchUserInfo` removes the explicit error handling/`Failed` response path that existed with `ntUserApi`; consider adding equivalent error handling so callers get a structured failure instead of an unhandled exception if the fetch fails.

## Individual Comments

### Comment 1
<location path="src/onebot11/action/group/GetGroupMemberInfo.ts" line_range="40" />
<code_context>
-    }
-    if (ret.qq_level === 0) {
-      ret.qq_level = (await this.ctx.pmhq.fetchUserInfo(+payload.user_id)).level
+      ret.qq_level = calcQQLevel(info.commonExt.qqLevel)
+      ret.age = info.simpleInfo.baseInfo.age
+      if (ret.qq_level === 0) {
</code_context>
<issue_to_address>
**issue:** 在调用 `calcQQLevel` 之前,请处理 `info.commonExt` 存在但 `qqLevel` 缺失或格式不正确的情况。

之前的版本会在 `info.commonExt?.qqLevel` 上短路,所以当 `qqLevel` 缺失时不会调用 `calcQQLevel`。现在只要 `commonExt` 存在就会执行 `calcQQLevel(info.commonExt.qqLevel)`。如果 `qqLevel` 可能为 `undefined` 或结构异常,这可能会抛出异常或产生错误结果。建议恢复保护判断,或者让 `calcQQLevel``undefined`/无效输入更加健壮。
</issue_to_address>

### Comment 2
<location path="src/onebot11/action/group/GetGroupMemberInfo.ts" line_range="41" />
<code_context>
-    if (ret.qq_level === 0) {
-      ret.qq_level = (await this.ctx.pmhq.fetchUserInfo(+payload.user_id)).level
+      ret.qq_level = calcQQLevel(info.commonExt.qqLevel)
+      ret.age = info.simpleInfo.baseInfo.age
+      if (ret.qq_level === 0) {
+        ret.qq_level = (await this.ctx.pmhq.fetchUserInfo(+payload.user_id)).level
</code_context>
<issue_to_address>
**issue (bug_risk):** 建议为 `age` 提供一个默认值,以避免向下游传递 `undefined`。

之前通过 `age ?? 0` 可以保证 `ret.age` 一定是数字。现在的新赋值方式下,当 `baseInfo.age` 缺失时,`ret.age` 可能变为 `undefined`,这可能会破坏那些假定年龄为数值的调用方。请恢复一个兜底逻辑(例如 `ret.age = info.simpleInfo.baseInfo.age ?? 0`)以保持原有行为。
</issue_to_address>

### Comment 3
<location path="src/main/pmhq/mixins/user.ts" line_range="38-41" />
<code_context>
       const info = Oidb.FetchUserInfoResp.decode(oidbRespBody)
       const numbers = Object.fromEntries(info.body.properties.numberProperties.map(p => [p.key, p.value]))
       const bytes = Object.fromEntries(info.body.properties.bytesProperties.map(p => [p.key, p.value]))
+      const business = bytes[107] ? Misc.UserInfoBusiness.decode(bytes[107]) : undefined
       return {
         uin: info.body.uin,
</code_context>
<issue_to_address>
**issue (bug_risk):** 请在 `lists` 本身上使用可选链,以避免在其缺失或为空时出现运行时错误。

`business?.body.lists[0]` 仍然假定 `lists` 存在且可索引;如果 `body.lists``undefined` 或不是数组,就会抛异常。请改为使用 `business?.body.lists?.[0]`(其它位置同理),或者先赋值 `const first = business?.body.lists?.[0]`,然后在 `isVip``isYearsVip``vipLevel` 中复用 `first`,以便在 proto 数据异常时也能安全处理。
</issue_to_address>

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

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

  • In GetGroupMemberInfo, ret.age is now assigned directly from info.simpleInfo.baseInfo.age without a ?? 0 fallback, which changes previous behavior and may result in undefined where a number is expected; consider keeping the defaulting logic.
  • In GetUserProfile, switching entirely to ctx.pmhq.fetchUserInfo removes the explicit error handling/Failed response path that existed with ntUserApi; consider adding equivalent error handling so callers get a structured failure instead of an unhandled exception if the fetch fails.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `GetGroupMemberInfo`, `ret.age` is now assigned directly from `info.simpleInfo.baseInfo.age` without a `?? 0` fallback, which changes previous behavior and may result in `undefined` where a number is expected; consider keeping the defaulting logic.
- In `GetUserProfile`, switching entirely to `ctx.pmhq.fetchUserInfo` removes the explicit error handling/`Failed` response path that existed with `ntUserApi`; consider adding equivalent error handling so callers get a structured failure instead of an unhandled exception if the fetch fails.

## Individual Comments

### Comment 1
<location path="src/onebot11/action/group/GetGroupMemberInfo.ts" line_range="40" />
<code_context>
-    }
-    if (ret.qq_level === 0) {
-      ret.qq_level = (await this.ctx.pmhq.fetchUserInfo(+payload.user_id)).level
+      ret.qq_level = calcQQLevel(info.commonExt.qqLevel)
+      ret.age = info.simpleInfo.baseInfo.age
+      if (ret.qq_level === 0) {
</code_context>
<issue_to_address>
**issue:** Handle cases where `info.commonExt` exists but `qqLevel` is missing or malformed before calling `calcQQLevel`.

The previous version short-circuited on `info.commonExt?.qqLevel`, so `calcQQLevel` was never called when `qqLevel` was missing. Now `calcQQLevel(info.commonExt.qqLevel)` runs whenever `commonExt` exists. If `qqLevel` can be `undefined` or have an unexpected shape, this may throw or yield wrong results. Consider reinstating a guard or making `calcQQLevel` robust to `undefined`/invalid input.
</issue_to_address>

### Comment 2
<location path="src/onebot11/action/group/GetGroupMemberInfo.ts" line_range="41" />
<code_context>
-    if (ret.qq_level === 0) {
-      ret.qq_level = (await this.ctx.pmhq.fetchUserInfo(+payload.user_id)).level
+      ret.qq_level = calcQQLevel(info.commonExt.qqLevel)
+      ret.age = info.simpleInfo.baseInfo.age
+      if (ret.qq_level === 0) {
+        ret.qq_level = (await this.ctx.pmhq.fetchUserInfo(+payload.user_id)).level
</code_context>
<issue_to_address>
**issue (bug_risk):** Consider providing a default value for `age` to avoid passing `undefined` downstream.

Previously `ret.age` was guaranteed to be a number via `age ?? 0`. With the new assignment, `ret.age` can become `undefined` when `baseInfo.age` is missing, which may break callers that assume a numeric age. Please restore a fallback (e.g., `ret.age = info.simpleInfo.baseInfo.age ?? 0`) to preserve the prior behavior.
</issue_to_address>

### Comment 3
<location path="src/main/pmhq/mixins/user.ts" line_range="38-41" />
<code_context>
       const info = Oidb.FetchUserInfoResp.decode(oidbRespBody)
       const numbers = Object.fromEntries(info.body.properties.numberProperties.map(p => [p.key, p.value]))
       const bytes = Object.fromEntries(info.body.properties.bytesProperties.map(p => [p.key, p.value]))
+      const business = bytes[107] ? Misc.UserInfoBusiness.decode(bytes[107]) : undefined
       return {
         uin: info.body.uin,
</code_context>
<issue_to_address>
**issue (bug_risk):** Use optional chaining on `lists` itself to avoid possible runtime errors when it is missing or empty.

`business?.body.lists[0]` still assumes `lists` exists and is indexable; if `body.lists` is `undefined` or not an array, this will throw. Please either use `business?.body.lists?.[0]` (and similarly elsewhere) or assign `const first = business?.body.lists?.[0]` and reuse `first` for `isVip`, `isYearsVip`, and `vipLevel` to safely handle malformed proto data.
</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.

}
if (ret.qq_level === 0) {
ret.qq_level = (await this.ctx.pmhq.fetchUserInfo(+payload.user_id)).level
ret.qq_level = calcQQLevel(info.commonExt.qqLevel)
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: 在调用 calcQQLevel 之前,请处理 info.commonExt 存在但 qqLevel 缺失或格式不正确的情况。

之前的版本会在 info.commonExt?.qqLevel 上短路,所以当 qqLevel 缺失时不会调用 calcQQLevel。现在只要 commonExt 存在就会执行 calcQQLevel(info.commonExt.qqLevel)。如果 qqLevel 可能为 undefined 或结构异常,这可能会抛出异常或产生错误结果。建议恢复保护判断,或者让 calcQQLevelundefined/无效输入更加健壮。

Original comment in English

issue: Handle cases where info.commonExt exists but qqLevel is missing or malformed before calling calcQQLevel.

The previous version short-circuited on info.commonExt?.qqLevel, so calcQQLevel was never called when qqLevel was missing. Now calcQQLevel(info.commonExt.qqLevel) runs whenever commonExt exists. If qqLevel can be undefined or have an unexpected shape, this may throw or yield wrong results. Consider reinstating a guard or making calcQQLevel robust to undefined/invalid input.

if (ret.qq_level === 0) {
ret.qq_level = (await this.ctx.pmhq.fetchUserInfo(+payload.user_id)).level
ret.qq_level = calcQQLevel(info.commonExt.qqLevel)
ret.age = info.simpleInfo.baseInfo.age
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): 建议为 age 提供一个默认值,以避免向下游传递 undefined

之前通过 age ?? 0 可以保证 ret.age 一定是数字。现在的新赋值方式下,当 baseInfo.age 缺失时,ret.age 可能变为 undefined,这可能会破坏那些假定年龄为数值的调用方。请恢复一个兜底逻辑(例如 ret.age = info.simpleInfo.baseInfo.age ?? 0)以保持原有行为。

Original comment in English

issue (bug_risk): Consider providing a default value for age to avoid passing undefined downstream.

Previously ret.age was guaranteed to be a number via age ?? 0. With the new assignment, ret.age can become undefined when baseInfo.age is missing, which may break callers that assume a numeric age. Please restore a fallback (e.g., ret.age = info.simpleInfo.baseInfo.age ?? 0) to preserve the prior behavior.

Comment on lines +38 to 41
const business = bytes[107] ? Misc.UserInfoBusiness.decode(bytes[107]) : undefined
return {
uin: info.body.uin,
nick: bytes[20002]?.toString() ?? '',
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): 请在 lists 本身上使用可选链,以避免在其缺失或为空时出现运行时错误。

business?.body.lists[0] 仍然假定 lists 存在且可索引;如果 body.listsundefined 或不是数组,就会抛异常。请改为使用 business?.body.lists?.[0](其它位置同理),或者先赋值 const first = business?.body.lists?.[0],然后在 isVipisYearsVipvipLevel 中复用 first,以便在 proto 数据异常时也能安全处理。

Original comment in English

issue (bug_risk): Use optional chaining on lists itself to avoid possible runtime errors when it is missing or empty.

business?.body.lists[0] still assumes lists exists and is indexable; if body.lists is undefined or not an array, this will throw. Please either use business?.body.lists?.[0] (and similarly elsewhere) or assign const first = business?.body.lists?.[0] and reuse first for isVip, isYearsVip, and vipLevel to safely handle malformed proto data.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 28, 2026

Test Report

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

✅ All tests passed

@idranme idranme merged commit 52ba15f into main Apr 28, 2026
7 of 8 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.

1 participant