Conversation
审阅者指南重构用户资料和群成员信息的获取逻辑,使其更一致地依赖 更新后的 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)
更新后的 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
包含 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
更新后的 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
文件级变更
提示与命令与 Sourcery 交互
自定义你的体验前往你的 控制面板 来:
获取帮助Original review guide in EnglishReviewer's GuideRefactors 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 resolutionsequenceDiagram
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)
Sequence diagram for updated GetGroupMemberInfo resolution with PMHQ fallbacksequenceDiagram
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
Sequence diagram for GetStrangerInfo including VIP metadatasequenceDiagram
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
Class diagram for updated PMHQ user mixin and Misc proto typesclassDiagram
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
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
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>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的代码审查。
Original comment in English
Hey - I've found 3 issues, and left some high level feedback:
- In
GetGroupMemberInfo,ret.ageis now assigned directly frominfo.simpleInfo.baseInfo.agewithout a?? 0fallback, which changes previous behavior and may result inundefinedwhere a number is expected; consider keeping the defaulting logic. - In
GetUserProfile, switching entirely toctx.pmhq.fetchUserInforemoves the explicit error handling/Failedresponse path that existed withntUserApi; 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>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) |
There was a problem hiding this comment.
issue: 在调用 calcQQLevel 之前,请处理 info.commonExt 存在但 qqLevel 缺失或格式不正确的情况。
之前的版本会在 info.commonExt?.qqLevel 上短路,所以当 qqLevel 缺失时不会调用 calcQQLevel。现在只要 commonExt 存在就会执行 calcQQLevel(info.commonExt.qqLevel)。如果 qqLevel 可能为 undefined 或结构异常,这可能会抛出异常或产生错误结果。建议恢复保护判断,或者让 calcQQLevel 对 undefined/无效输入更加健壮。
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 |
There was a problem hiding this comment.
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.
| const business = bytes[107] ? Misc.UserInfoBusiness.decode(bytes[107]) : undefined | ||
| return { | ||
| uin: info.body.uin, | ||
| nick: bytes[20002]?.toString() ?? '', |
There was a problem hiding this comment.
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 数据异常时也能安全处理。
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.
Test Report
✅ All tests passed |
Summary by Sourcery
简化用户资料获取逻辑,改为依赖 PMHQ 用户信息服务,并在各类 API 中扩展对外暴露的用户元数据,同时提升项目版本。
New Features:
Enhancements:
Build:
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:
Enhancements:
Build: